Hi,
I have a aws instance resource defined like this:
resource "aws_instance" "amazon-linux" {
count = length(var.instance_names)
provisioner "file" {
source = "ansible/"
destination = "/home/ec2-user"
}
}
How do I make the provisioner run only when there is an instance named “foo”?
Thanks
-T
Hi @gamename1,
There is no direct way to get that done as you described it here. I can think of two alternatives that could work, though:
My preferred option would be to send those files up to the instance via user_data
and have software such as cloud-init retrieve it and install it, which can then avoid the need for Terraform to SSH into the instance at all. Whether this would be tenable for your use-case will depend on how large the contents of that “ansible” directory are, because there’s a 16kB size limit on user_data
. But assuming it’s a relatively small set of configuration files, you could perhaps build a cloud-init configuration like this:
locals {
ansible_cloudinit = yamlencode({
write_files = [
for p in fileset("${path.module}/ansible", "**") : {
encoding = "b64"
content = filebase64("${path.module}/ansible/${p}")
owner = "ec2-user:ec2-user"
path = "/home/ec2-user/${p}"
}
]
})
}
(I based this on the Writing out arbitrary files example.)
With that defined, you could then set the user_data
conditionally based on the current instance name:
user_data = var.instance_names[count.index] == "foo" ? local.ansible_cloudinit : null
If your file packet is too big to send in this way, or if there are other blockers for that technique, then as a last resort I’d consider moving the provisioner out into a separate resource that is itself conditional on the instance names. This would be marginally easier if you can use for_each
instead of count
because then you can use each.key
as the filtering criteria:
resource "aws_instance" "amazon_linux" {
for_each = toset(var.instance_names)
# ...
}
resource "null_resource" "amazon_linux_ansible" {
for_each = {
for k, v in aws_instance.amazon_linux : k => v
if k == "foo"
}
provisioner "file" {
source = "${path.module}/ansible/"
destination = "/home/ec2-user"
}
}
Here I used a conditional for
expression as a concise way to filter out all but the foo
element. If a particular instantiation of this module doesn’t have a foo
element at all then the null_resource
will have zero instances and thus nothing to provision. If there is a foo
element then it’ll have one instance and thus the provisioner in there will execute once.
The usual tradeoffs with null_resource
apply here, including:
- You’ll need to put a
connection
block in the other resource that refers to attributes of each.value
, like each.value.private_ip
, to match with the corresponding EC2 instance.
- If you expect to be taking actions that will replace the instance in future, you should include something in the
triggers
that will force Terraform to replace the null_resource
instance in that case too. In this case I think each.value.id
would be good enough, because the id will change if you ever replace the EC2 instance.