4

I want to create few EC2 instances and grab their private_ips into a ec2.ini style file for further use with Ansible.

resource "local_file" "ec2_id" {
  count = var.instance_count
  content  = "${aws_instance.instance[count.index].private_ip} ansible_ssh_user=ec2-user\n"
  filename = "ec2.ini"
}

This always prints the private_ip of latest EC2 instance created.

Any idea how to solve this.

Update:-

data "template_file" "hehe" {
  count = var.instance_count
  template = "${element(aws_instance.instance.*.private_ip, count.index)} ansible_ssh_user=ec2-user subnetmask=${element(split("/", data.aws_subnet.selected-subnet-id.cidr_block),1)}\n"
}


resource "local_file" "ec2_id" {
  count = var.instance_count
  content  = "${element(data.template_file.hehe.*.rendered, count.index)}"
  filename = "ec2.ini"
}

does not work. gives me the last created instance private_ip.

Biplab
  • 233
  • 4
  • 15
  • Does this answer your question? [using count.index in terraform?](https://stackoverflow.com/questions/50301523/using-count-index-in-terraform) – Amit Baranes Apr 24 '20 at 11:20

1 Answers1

14

When you use count inside a resource you are asking Terraform to create multiple instances of that resource. However, in your case you didn't include count.index in the filename argument and so all of your instances are competing to overwrite the same filename ec2.ini, and so only one of them can "win".

It sounds like your goal is to create only one file that contains all of the IP addresses. This is very close to one of the examples that are in the Terraform String Templates documentation at the time I write this, which we can adapt to your goal like this:

resource "local_file" "ec2_iini" {
  filename = "ec2.ini"
  content = <<-EOT
    %{ for ip in aws_instance.instance.*.private_ip ~}
    ${ip} ansible_ssh_user=ec2-user
    %{ endfor ~}
  EOT
}

In the above example the local_file resource itself does not have count set, because our goal is to create only one file. Instead, we use Terraform's template for directive to repeat a string template once per instance, gathering the result as a single string which local_file can then use as its content argument.

I used the "heredoc" style of string literal here because I think it makes the for directive easier to read by splitting it over multiple lines. The - in <<-EOT causes Terraform to look at all of the lines between the opening <<-EOT and the closing EOT and find the smallest number of leading spaces those lines have in common, which it will then strip off when rendering. That means that you can have the template indented in your configuration but avoid those indentations appearing in the rendered string, which should look something like this:

10.1.0.34 ansible_ssh_user=ec2-user
10.1.0.5 ansible_ssh_user=ec2-user
10.1.0.92 ansible_ssh_user=ec2-user

The ~ markers on the end of the two %{ ... } directives instruct Terraform's template engine to ignore the newline and whitespace after them, so we can wrap the template over multiple lines without introducing additional newlines into the result. The only line of the template that generates a trailing newline here is the middle line containing the IP address interpolation and the ansible_ssh_user portion, so the result ends up having only one newline per entry as seems to be intended here.

Martin Atkins
  • 62,420
  • 8
  • 120
  • 138
  • This was it!! Thank you Martin – Biplab Apr 24 '20 at 17:34
  • Hi Martin, is there a way I can grab the index from the for loop. I want to have a hosts file like below [cluster] host0 ansible_host=10.1.0.34 controller=True host1 ansible_host=10.1.0.5 controller=True host2 ansible_host=10.1.0.92 controller=True so the variable here is the "host" – Biplab Jun 25 '20 at 05:26
  • I got this working by using below code:- `locals { hosts = { host0 = aws_instance.instance[0].private_ip host1 = aws_instance.instance[1].private_ip host2 = aws_instance.instance[2].private_ip } } resource "local_file" "hosts" { content = <<-EOT [cluster] %{ for host, ip in local.hosts ~} ${host} ansible_host=${ip} controller=True %{ endfor ~} [cluster:vars] EOT filename = "./playbook/roles/ansible_rhel/templates/hosts" }` – Biplab Jun 25 '20 at 10:40