Having issues with passing array to a local_exec provisioner

I am currently running a ansible-playbook via a local-exec within an aws_instance resource. The terraform apply results in an error" “Error: Cycle: aws_instance.server[2], aws_instance.server[1], aws_instance.server[0]” I’m trying to figure out the best way to get passing the array via the extra-vars. The provisioner line looks like:

provisioner “local-exec” {
command = “ansible-playbook -e ‘hostname=server-master-{count.index + 1} node_type=master environ={var.environ} elastic_masters={ aws_instance.server.*.private_dns)}' -u ec2-user -i '{self.private_ip},’ --private-key ‘~/.ssh/id_rsa’ provis.yml”
}

Hi @rmattier,

Unfortunately the forum has misinterpreted your example as a math equation and thus it is quite hard to read (if you’d like to edit it, you can use the <> icon in the toolbar to explicitly mark it as code) but it looks like your provisioner command contains a reference like aws_instance.server.*.private_dns.

Based on the error message, I’m guessing that this provisioner block is inside the resource "aws_instance" "server" block, and therefore it’s failing because that makes the resource depend on itself.

For situations where you have a set of mutually-dependent instances like this, the ideal way to proceed is to have the software in the instances discover each other via some out-of-band mechanism. If the “elastic” there means ElasticSearch, then a common option is the EC2 Discovery plugin, which can use EC2 instance tagging and/or EC2 security group membership to allow the servers to automatically find one another, even if servers are added/removed later.

However, if you do need to do this with Terraform alone, one way to achieve it would be to use a separate null_resource block. The null_resource resource type exists as a last resort to allow provisioners to be separated from the main object they are provisioning, like this:

resource "aws_instance" "server" {
  count = 3

  # (then all of your existing aws_instance arguments,
  # but _not_ the provisioner or connection blocks)
}

resource "null_resource" "server_provisioning" {
  count = length(aws_instance.server)

  triggers = {
    hostname = aws_instance.server[count.index].private_dns
  }

  connection {
    host = ws_instance.server[count.index].private_ip
    # (and any other auth-related settings needed to
    # access these servers)
  }

  provisioner "local-exec" {
    command = "ansible-playbook ... elastic_masters=${join(",", aws_instance.server[*].private_dns)} ..."
  }
}

As usual with provisioners, the provisioner step will be run only when the null_resource instances are first created, and so this triggers argument ensures that Terraform will plan to re-create these objects if their hostnames change, and will thus re-run the provisioning step.

1 Like