1

I recently discovered template_cloudinit_config within Terraform, and I'm hoping I can utilize it to specify something like the code below. Basically my goal is to declare a core template_cloudinit_config that contains a number of shell scripts that should get run on every machine.

Then I'd like to declare a 2nd template_cloudinit_config that, without duplicating code, lets me essentially inherit the core set of scripts that should get applied to every instance AND define an additional script to run.

That way from a compute perspective, I could easily create specialized instances that have custom scripts applied to them, but still inherit a number of basic configurations. I know I could make this work with remote-exec provisioners, but I am trying to avoid them since Terraform says they should be used as a last resort.

data "template_cloudinit_config" "core_config" {
  part {
    content_type = "text/x-shellscript"
    content      = file("scripts/core_script1.sh")
  }

  part {
    content_type = "text/x-shellscript"
    content      = file("scripts/core_script2.sh")
  }
}

data "template_cloudinit_config" "custom_config" {
  part {
    content     = "${data.template_cloudinit_config.core_config.rendered}"
  }

  part {
    content_type = "text/x-shellscript"
    content      = file("scripts/custom_script.sh")
  }
}

resource "aws_instance" "vm1" {
  instance_type = "t2.small"
  ami           = data.aws_ami.ubuntu_ami.image_id
  
  # Other variables that declare subnet, key, etc

  user_data = "${data.template_cloudinit_config.core_config.rendered}"
}

resource "aws_instance" "vm2" {
  instance_type = "t2.small"
  ami           = data.aws_ami.ubuntu_ami.image_id
  
  # Other variables that declare subnet, key, etc

  user_data = "${data.template_cloudinit_config.custom_config.rendered}"
}
2

The cloudinit provider only knows how to encode multipart MIME documents to pass to cloud init; there is no mechanism for parsing to modify and then re-encode.

However, a different way to frame your problem is of constructing a sequence of part objects in a local value and then passing them all together to the cloudinit_config data source, like this:

locals {
  core_config = tolist([
    {
      content_type = "text/x-shellscript"
      content      = file("${path.module}/scripts/core_script1.sh")
    },
    {
      content_type = "text/x-shellscript"
      content      = file("${path.module}/scripts/core_script2.sh")
    },
  ])
  custom_config = tolist([
    {
      content_type = "text/x-shellscript"
      content      = file("${path.module}/scripts/custom_script.sh")
    },
  ])
}

data "cloudinit_config" "core" {
  dynamic "part" {
    for_each = local.core_config
    content {
      content_type = part.value.content_type
      content      = part.value.content
    }
  }
}

data "cloudinit_config" "custom" {
  dynamic "part" {
    for_each = concat(
      local.core_config,
      local.custom_config,
    )
    content {
      content_type = part.value.content_type
      content      = part.value.content
    }
  }
}

resource "aws_instance" "vm1" {
  instance_type = "t2.small"
  ami           = data.aws_ami.ubuntu_ami.image_id
  
  # ...

  user_data = data.cloudinit_config.core.rendered
}

resource "aws_instance" "vm2" {
  instance_type = "t2.small"
  ami           = data.aws_ami.ubuntu_ami.image_id
  
  # ...

  user_data = data.cloudinit_config.custom.rendered
}

The above is using dynamic blocks to generate a dynamic number of part blocks based on the number of elements in a list, which means you can focus on making sure the lists have the necessary items, using whatever dynamic expressions you need to decide that, and have Terraform project that into zero or more part blocks automatically.

Note: the hashicorp/template provider has been deprecated and continues to be available only for backward-compatibility. For that reason, I wrote the above to use the hashicorp/cloudinit provider instead. Its cloudinit_config data source is based on what was formerly template_cloudinit_config.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.