I want to set up a Terraform module that assigns a policy to an Azure resource according to Terraforms policy assignment example.
In order to assign the allowed locations policy, I want to pass the list of allowed locations as a list of strings from the variables.tf file to the main.tf, where the assignment is executed.
#Allowed Locations Policy Assignment
resource "azurerm_policy_assignment" "allowedlocations" {
name = "allowed-locations"
scope = var.scope_allowedlocations
policy_definition_id = var.policy_allowedlocations.id
description = "This policy enables you to restrict the locations."
display_name = "Allowed Locations"
parameters = <<PARAMETERS
{
"allowedLocations": {
"value": ${var.listofallowedlocations}
}
}
PARAMETERS
}
# Scope of the Allowed Locations policy
variable "scope_allowedlocations" {
description = "The scope of the allowed locations assignment."
default = "Subscription"
}
# Scope of the Allowed Locations policy
variable "policy_allowedlocations" {
description = "The allowed locations policy (created by the policy-define module)."
default = "default"
}
# List of the Allowed Locations
variable "listofallowedlocations" {
type = list(string)
description = "The allowed locations list."
default = [ "West Europe", "North Europe", "East US" ]
}
Executing with terraform plan
leads to the following error:
Error: Invalid template interpolation value
on modules/policy-assign/main.tf line 16, in resource "azurerm_policy_assignment" "allowedlocations":
12:
13:
14:
15:
16: "value": ${var.listofallowedlocations}
17:
18:
19:
|----------------
| var.listofallowedlocations is list of string with 3 elements
Cannot include the given value in a string template: string required.
Thus, I don't know how to exactly pass the list from the variables file to the PARAMETERS section of the policy assignment resource. In Terraforms policy assignment example the list is directly inline coded in the PARAMETERS section and it works. But there is no passing of variables...:
parameters = <<PARAMETERS
{
"allowedLocations": {
"value": [ "West Europe" ]
}
}
PARAMETERS
When you are interpolating a value into a string that value must itself be convertible to string, or else Terraform cannot join the parts together to produce a single string result.
There are a few different alternatives here, with different tradeoffs.
The option I personally would choose here is to not use the <<PARAMETERS
syntax at all and to just build that entire value using jsonencode
:
parameters = jsonencode({
allowedLocations = {
value = var.listofallowedlocations
}
})
This avoids the need for your configuration to deal with any JSON syntax issues at all, and (subjectively) therefore makes the intent clearer and future maintenence easier.
In any situation where the result is a single valid JSON value, I would always choose to use jsonencode
rather than the template language. I'm including the other options below for completeness in case a future reader is trying to include a collection value into a string template that isn't producing JSON.
A second option is to write an expression to tell Terraform a way to convert your list value into a string value in a suitable format. In your case you wanted JSON and so jsonencode
again would probably be the most suitable choice:
parameters = <<PARAMETERS
{
"allowedLocations": {
"value": ${jsonencode(var.listofallowedlocations)}
}
}
PARAMETERS
In other non-JSON situations, when the result is a simple list of strings, the join
function can be useful to just concatenate all of the strings together with a fixed delimiter.
Any of Terraform's functions that produce a single string as a result is a candidate here. The ones under "String Functions" and "Encoding Functions" are the most likely choices.
Finally, for situations where the mapping from the collection value to the resulting string is something custom that no standard function can handle, you can use the template repetition syntax:
parameters = <<CONFIG
%{ for port in var.ports ~}
listen 127.0.0.1:${port}
%{ endfor ~}
CONFIG
In this case, Terraform will evaluate the body of the repetition construct once for each element in var.ports
and concatenate all of the results together in order to produce the result. You can generate all sorts of textual output formats using this approach, though if the template gets particularly complicated it could be better to factor it out into a separate file and use the templatefile
function to evaluate it.