Terraform cloud-init dynamic command block

Hi All,
We tried to configure a cloud init template in terraform to render a list of command bases on a map like:

variable "dirs" {
    type = map
    default = {
        /var/logs/test = "root",
        /test = "root",
        /app = "jboss"
    }
}

And have this dynamic block:

runcmd:
  - [ mkdir, -p, /var/logs/test ]
  - [ chown, -R, jboss, /var/logs/test]
....

Is it possible to obtain this block with a map?

Thanks,
Marcello

Hi @cello86,

I understand your question as how to construct a list which has two entries for each element of this map, each of which is itself a list of arguments to run.

I think the following expression would achieve that:

locals {
  dir_commands = concat([
    for path, owner in var.dirs : [
      ["mkdir", "-p", path],
      ["chown", "-R", owner, path],
    ]
  ]...)
}

The above works in two steps:

  1. The for expression first produces a list of lists, where the top-level list has one element per element of your map and each contains two elements representing the two comments.
  2. The concat function takes all of the first-level lists as individual arguments (due to using the ... expansion symbol at the end of its argument) and so removes that first level of lists so all of the commands for all of the paths are together in a single list.

With this, I would expect local.dir_commands to have the following value:

[
  ["mkdir", "-p", "/app"],
  ["chown", "-R", "jboss", "/app"],
  ["mkdir", "-p", "/test"],
  ["chown", "-R", "root", "/test"],
  ["mkdir", "-p", "/var/logs/test"],
  ["chown", "-R", "root", "/var/logs/test"],
]

You can then use that as part of a call to yamlencode to produce the full cloud-init YAML document:

locals {
  cloudinit_config = <<-EOT
    #!cloud-config
    ${yamlencode({
      runcmd = local.dir_commands
      # ...
    })}
  EOT
}

Hi @apparentlymart
I tried the configuration but I have a different format of the commands:

# terraform state show data.template_cloudinit_config.fe_rhsm_registration
# data.template_cloudinit_config.fe_rhsm_registration:
data "template_cloudinit_config" "fe_rhsm_registration" {
    base64_encode = true
    gzip          = true
    id            = "2503000772"

    part {
        content      = <<-EOT
              #cloud-config
              ssh_pwauth: true
              cloud_config_modules:
                - runcmd
              cloud_final_modules:
                - scripts-user
              disable_root: false
              runcmd:
                - [ /usr/bin/sed, -i, -e, 's@^RHSM_HOSTGROUP=.*@RHSM_HOSTGROUP=rhel-9-dev/rhel-9-dev-publicitrp@g',  /etc/rhsm/rhsm.env ]
                - [ /usr/bin/sed, -i, -e, 's@^RHSM_ACTIVATION_KEY=.*@RHSM_ACTIVATION_KEY=rhel-9-terraform@g',  /etc/rhsm/rhsm.env ]
            "runcmd":
            - - "mkdir"
              - "-p"
              - "/data/external/test/"
            - - "chown"
              - "-R"
              - "root:root"
              - "/data/external/test/"
            - - "mkdir"
              - "-p"
              - "/data/sites/finecobank/test/"
            - - "chown"
              - "-R"
              - "jboss:jboss"
              - "/data/sites/finecobank/test/"
            - - "mkdir"
              - "-p"
              - "/var/logs/test/"
            - - "chown"
              - "-R"
              - "root:root"
              - "/var/logs/test/"

        EOT
        content_type = "text/cloud-config"
    }
}

Is ti possible to have the elements into the square brackets?

Thanks,
marcello

Hi @cello86,

The format that yamlencode generates has equivalent meaning, but for it to work you need to generate the entire body using yamlencode like I showed in my example. That will then make the whole result use the same formatting.

It is much harder to generate valid YAML using string concatenation (which is what template interpolation does) and so I would recommend against trying it. Using yamlencode and accepting the formatting decisions it makes is the robust answer that will guarantee to always generate valid YAML syntax.

Hi @apparentlymart,
I noticed this issue reported that provides some examples similar to my case.

I mixed replace replace and yamlencode:

${replace(yamlencode({
      runcmd = local.dir_commands
    }), "\"","")}

Hi @cello86,

Indeed it is possible to use string replacement to try to reformat the result of yamlencode, but then it is no longer guaranteed that the result will always be valid YAML and so you may see strange misbehavior when you update this module in future if it starts generating something invalid.

Cloud-init has a spec-compliant YAML parser and so what yamlencode generates is already sufficient for successfully using cloud-init. There is no reason to try to reformat that output in any other way.