Passing JSON literally inside a string literal

Hi,

I’m working with the Azure virtual machine extension resource and struggling to pass the JSON object to SETTINGS.

Data comes from a .auto.tfvar.json file and in that file I have the following block:

//stuff above
"extensions" : [
              {
                "name" : "myCustomScript1-0-vm2",
                "publisher" : "Microsoft.Compute",
                "type" : "CustomScriptExtension",
                "type_handler_version" : "1.10",
                "auto_upgrade_minor_version" : "true",
                "settings" : {
                  "commandToExecute" : "powershell.exe -encodedCommand 'SoMeEnCoDeDsTrInG'"
                },
                "purpose" : "example extension 1-0"
              },...<repeat>...

I use nested for loops in a local{} block to iterate over this data in the resources main.tf:

locals {
  #vm_object = var.virtual_machine_object
  
  ext_config = flatten([
    for compute_key, compute in var.compute_data : [
      for node_key, node in compute.node_config : [
        for ext_key, ext in node.extensions : {
          id                         = "${compute_key}.${node_key}.${ext_key}"
          settings                   = ext.settings
          test                       = <<EOT  ${ext.settings} EOT
          # other key/value pairs...
        }
      ]
    ]
  ])
}

Ignoring the test attribute for the minute, the block above works in that the output returns what I want:

{
  // Stuff above
  "publisher" = "Microsoft.Compute"
  "settings" = {
        "commandToExecute" = "powershell.exe -encodedCommand 'SoMeEnCoDeDsTrInG'"
  }
  "type" = "CustomScriptExtension"
}

According to the documentation, I need to get the contents of settings, as JSON, into the string literal <<SETTINGS my_settings_json_block_here SETTINGS.

I’ve tried a few things: settings = each.value.settings, the test attribute in the for block above, and the following being another (which is basically the same as test):

resource "azurerm_virtual_machine_extension" "pool" {
for_each = {
    for resource in local.ext_config :
    resource.id => resource
  }
type                       = each.value.type
# Other key/value pairs

settings = <<SETTINGS
    ${each.value.settings}
SETTINGS

With the above, during plan I am getting the error:

Error: Invalid template interpolation value

  on ..\azurerm_virtual_machine_extension\main.tf line 60, in resource "azurerm_virtual_machine_extension" "pool":
  59: 
  60:     ${each.value.settings}
  61: 
    |----------------
    | each.value.settings is object with 1 attribute "commandToExecute"

Cannot include the given value in a string template: string required.

Wrapping the string template (${each.value.settings} is quotes doesn’t help, giving the same string required error.

I’ve looked at jsonencode and jsondecode, but I don’t think these will help as it is already JSON and I want JSON.

I’m running out of ideas, so if anyone can give me any pointers, I’d be most grateful.

T.I.A

If I understand this document properly, I basically want to stop TF doing “Expression Mapping”:

When a JSON string is encountered in a location where arbitrary expressions are expected, its value is first parsed as a string template and then it is evaluated to produce the final result.

From what I understand of your use case, it seems that jsonencode is likely to be what you’re looking for here. each.value.settings is an object type (as the diagnostic shows), but the attribute should be a string containing a JSON representation of an object. That’s what jsonencode does.

Have you tried settings = jsonencode(each.value.settings)? If not, please give it a shot and let me know what happens.

Thank you @alisdair, that’s just the ticket. You’re a lifesaver.

resource "azurerm_virtual_machine_extension" "pool" {
for_each = {
    for resource in local.ext_config :
    resource.id => resource
  }

type     = each.value.type
# other attributes...

settings = jsonencode(each.value.settings)

}
1 Like