I have a requirement to generate around ten YAML files and use kubernetes_manifest
resource to apply them. Most of the content in the YAML is constant, only a few parameters change. Some YAML files have a repetitive ports section, in the below example I have http
and https
under the ports section. In some cases, I have three http
, https
and sql
. Based on the inputs I should be able to generate a yaml file out of the template file. The below code works fine when I have only one section under ports like only http
. I have confusion as to how to loop the ports section. should i create a map of map?
I am using Terraform v1.2.2. I request you to help me correct my issue or suggest an alternate idea/solution achieve my goal
Expected File after generation
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: test
namespace: test
spec:
hosts:
- 'api.facebook.com'
ports:
- name: http
number: 8080
protocol: TCP
- name: https
number: 443
protocol: TCP
resolution: NONE
Template file
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: ${service_entry}
namespace: ${namespace}
spec:
hosts:
- ${jsonencode(hosts)}
ports:
%{ for name, number, protocol in service_entry ~}
- name: ${name}
number: ${number}
protocol: ${protocol}
%{ endfor ~}
resolution: ${resolution}
main.tf
resource "kubernetes_manifest" "service-entry" {
for_each = var.service_entry
manifest = yamldecode(templatefile("${path.module}/templates/service_entry.yaml.tpl", {
service_entry_name = each.value.service_entry
namespace = each.value.namespace
hosts = each.value.hosts
name = each.value.name
number = each.value.number
protocol = each.value.protocol
resolution = each.value.resolution
}))
}
varibles.tf
variable "service_entry" {
type = map(object({
service_entry = string
namespace = string
hosts = string
name = list(string)
number = list(string)
protocol = list(string)
resolution = string
}))
default = {}
}
tfvars
Below tfvars should generate two yaml files. the first YAML would have two sections under ports http
https
and the second YAML would have three sections under ports http
https
and sql
service_entry = {
app1 = {
service_entry = "test"
namespace = "test"
hosts = "api.facebook.com"
resolution = "NONE"
name = ["http", "https"]
number = ["8080", "443"]
protocol = ["TCP", "TCP"]
},
app2 = {
service_entry = "example"
namespace = "example"
hosts = "api.facebook.com"
resolution = "NONE"
name = ["http", "https", "sql"]
number = ["8080", "443", "5432"]
protocol = ["TCP","TCP","TCP"]
}
}
Current error
Error: Error in function call
│
│ on ../../modules/service_entry/main.tf line 3, in resource "kubernetes_manifest" "service-entry":
│ 3: manifest = yamldecode(templatefile("${path.module}/templates/service_entry.yaml.tpl", {
│ 4: service_entry_name = each.value.service_entry_name
│ 5: namespace = each.value.namespace
│ 6: hosts = each.value.hosts
│ 7: name = each.value.name
│ 8: number = each.value.number
│ 9: protocol = each.value.protocol
│ 10: resolution = each.value.resolution
│ 11: }))
│ ├────────────────
│ │ each.value.hosts will be known only after apply
│ │ each.value.namespace will be known only after apply
│ │ each.value.name will be known only after apply
│ │ each.value.number will be known only after apply
│ │ each.value.protocol will be known only after apply
│ │ each.value.resolution will be known only after apply
│ │ each.value.service_entry_name will be known only after apply
│ │ path.module is "../../modules/service_entry"
│
│ Call to function "templatefile" failed:
│ ../../modules/service_entry/templates/service_entry.yaml.tpl:11,32-33:
│ Invalid 'for' directive; For directive requires 'in' keyword after names.,
│ and 1 other diagnostic(s).
╵
ERRO[0005] 1 error occurred:
* exit status 1
The easiest way to work with templates, in your case yaml, is to wrap everything in yamlencode
. So your service_entry.yaml.tpl
can be:
${yamlencode(
{
apiVersion = "networking.istio.io/v1beta1"
kind = "ServiceEntry"
metadata = {
name = service_entry_name
namespace = namespace
}
spec = {
"hosts" = [hosts]
ports = [ for idx in range(length(name)):
{
name: name[idx]
number: number[idx]
protocol: protocol[idx]
}
]
}
}
)}
This generates valid yaml
file and you don't have to fight against the strange templating syntax.