<p>The <code>cloudinit</code> provider only knows how to <em>encode</em> multipart MIME documents to pass to cloud init; there is no mechanism for <em>parsing</em> to modify and then re-encode.</p>
<p>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 <code>cloudinit_config</code> data source, like this:</p>
<pre><code>locals {
  core_config = tolist([
    {
      content_type = &quot;text/x-shellscript&quot;
      content      = file(&quot;${path.module}/scripts/core_script1.sh&quot;)
    },
    {
      content_type = &quot;text/x-shellscript&quot;
      content      = file(&quot;${path.module}/scripts/core_script2.sh&quot;)
    },
  ])
  custom_config = tolist([
    {
      content_type = &quot;text/x-shellscript&quot;
      content      = file(&quot;${path.module}/scripts/custom_script.sh&quot;)
    },
  ])
}

data &quot;cloudinit_config&quot; &quot;core&quot; {
  dynamic &quot;part&quot; {
    for_each = local.core_config
    content {
      content_type = part.value.content_type
      content      = part.value.content
    }
  }
}

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

resource &quot;aws_instance&quot; &quot;vm1&quot; {
  instance_type = &quot;t2.small&quot;
  ami           = data.aws_ami.ubuntu_ami.image_id
  
  # ...

  user_data = data.cloudinit_config.core.rendered
}

resource &quot;aws_instance&quot; &quot;vm2&quot; {
  instance_type = &quot;t2.small&quot;
  ami           = data.aws_ami.ubuntu_ami.image_id
  
  # ...

  user_data = data.cloudinit_config.custom.rendered
}
</code></pre>
<p>The above is using <a href="https://www.terraform.io/docs/configuration/expressions.html#dynamic-blocks" rel="nofollow noreferrer"><code>dynamic</code> blocks</a> to generate a dynamic number of <code>part</code> 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 <code>part</code> blocks automatically.</p>
<p><strong>Note:</strong> the <code>hashicorp/template</code> provider has been deprecated and continues to be available only for backward-compatibility. For that reason, I wrote the above to use <a href="https://registry.terraform.io/providers/hashicorp/cloudinit/latest" rel="nofollow noreferrer">the <code>hashicorp/cloudinit</code> provider</a> instead. Its <a href="https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs/data-sources/cloudinit_config" rel="nofollow noreferrer"><code>cloudinit_config</code> data source</a> is based on what was formerly <code>template_cloudinit_config</code>.</p>

CC BY-SA 4.0
2020-11-23T22:38:38.323Z
2020-11-23T22:38:38.323Z
Martin Atkins
<p>I recently discovered <code>template_cloudinit_config</code> 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 <code>template_cloudinit_config</code> that contains a number of shell scripts that should get run on every machine.</p>
<p>Then I'd like to declare a 2nd <code>template_cloudinit_config</code> 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.</p>
<p>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 <a href="https://www.terraform.io/docs/provisioners/" rel="nofollow noreferrer">Terraform says they should be used as a last resort</a>.</p>
<pre><code>data &quot;template_cloudinit_config&quot; &quot;core_config&quot; {
  part {
    content_type = &quot;text/x-shellscript&quot;
    content      = file(&quot;scripts/core_script1.sh&quot;)
  }

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

data &quot;template_cloudinit_config&quot; &quot;custom_config&quot; {
  part {
    content     = &quot;${data.template_cloudinit_config.core_config.rendered}&quot;
  }

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

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

  user_data = &quot;${data.template_cloudinit_config.core_config.rendered}&quot;
}

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

  user_data = &quot;${data.template_cloudinit_config.custom_config.rendered}&quot;
}
</code></pre>

<terraform><amazon-ec2>
Can you inherit a template_cloudinit_config in another template_cloudinit_config object in Terraform?
ION28
# Can you inherit a template_cloudinit_config in another template_cloudinit_config object in Terraform?

(Asked by: username_0 on 2020-11-22)

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}"
}

**Tags:** terraform, amazon-ec2


---

## 1 answers

### Answer by username_1 on 2020-11-23. Score: 2 | Accepted answer

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.


---

