How can I account for count.index in a rendered script?

I can run the following from main.tf

		inline = [
			"sudo /etc/bootstrap.sh --vault-token ${var.certificate} --vault-address ${var.vault_uri} --secret-manager-type vault --vault-skip-verify --force-download-certs --vault-cert-sans ${var.agent_ips[count.index]} --component agent"
		]

I have migrated all inline commands to functions in platform-setup.sh script. All work except this one as I’m having difficulty rendering agent_ips[count.index].

locals {
        platform_setup = templatefile("${path.module}/files/platform-setup.sh", {
                certificate = var.certificate
                vault_uri = var.vault_uri
                user = var.user
                agent_ips = var.agent_ips
                }
        )
}
                inline = [
                        "set -x",
                        "bash /home/${var.user}/platform-setup.sh --configure_agent_vault
                ]
configure_agent_vault () {
  sudo /etc/bootstrap.sh --vault-token ${certificate} --vault-address ${vault_uri} --secret-manager-type vault --vault-skip-verify --force-download-certs --component agent --vault-cert-sans {agent_ips[count.index]}
}
1 Like

I think I’m having a similar question; wondering what the “right” approach for this type of thing is:

resource "local_file" "ssh" {
  count = 4
  content = <<-DOC
    #!/bin/bash
    exec ssh -o "UserKnownHostsFile /dev/null" -o StrictHostKeyChecking=no ubuntu@${libvirt_domain.workers.${count.index}.network_interface.0.addresses.0}
DOC
  filename = "./ssh-worker-${count.index}.sh"
  file_permission = "0755"
}

The nested variable doesn’t work. Also I’m interested in nicer ways to arrange convenience for a user to be able to ssh to the newly created VM instances.

Hi @nick.piper,

I think a key thing to notice about your original approach is that, although it may not have been obvious from the way Terraform provisioner configuration is shaped, Terraform was processing the template multiple times with one render for each of the resource instances, and thus a different count.index value each time.

To get the same effect for your separate templatefile call, then, will require calling templatefile once for each index, producing a list of strings containing different versions of the script.

Since you didn’t include the full example of the resource containing that inline argument I’m going to just assume for the sake of example that you had something like count = length(var.agent_ips) in that resource, and so continue under that assumption:

locals {
  platform_setup_scripts = [
    for agent_ip in var.agent_ips : templatefile("${path.module}/files/platform-setup.sh", {
      certificate = var.certificate
      vault_uri   = var.vault_uri
      user        = var.user
      agent_ip    = agent_ip
    })
  ]
}

resource "example" "example" {
  count = length(var.agent_ips)

  # ...

  provisioner "remote-exec" {
    inline = local.platform_setup_scripts[count.index]
  }
}

The above uses a for expression to call templatefile once for each element of var.agent_ips, thus producing a separate rendered result for each element.

Then inside the provisioner configuration we can look up the appropriate rendered script in local.platform_setup_scripts using count.index, because the for expression here guarantees that the indices of the rendered scripts will correlate with the indices of var.agent_ips.

With all of this said, please note that provisioners are a last resort and so I’d recommend using another mechanism to run this script if there’s a suitable mechanism available for whatever platform you’re using. The features described in Passing data into virtual machines and other compute resources might be appropriate for this situation, if your intent is to have this script run on initial boot.

Thank you for your advice. Excellent, as usual!

I worked around the issue as follows.

locals.tf remains as above

then in main.tf I retained ${var.agent_ips[count.index]}.

                inline = [
                        "set -x",
                        "bash /home/${var.user}/platform-setup.sh --configure_agent_vault ${var.agent_ips[count.index]}"
                ]

And in platform-setup.sh:

......
......
configure_agent_vault () {
  sudo /etc/bootstrap.sh --vault-token ${certificate} --vault-address ${vault_uri} --secret-manager-type vault --vault-skip-verify --force-download-certs --vault-cert-sans $agent_ips --component agent
}
......
......
while [[ $# -gt 0 ]]; do
  option="$1"
  case $option in
......
......
    --configure_agent_vault)
      agent_ips=$2
      configure_agent_vault
    ;;
......
......
  esac
  shift
done