3
votes

With terraform 0.12, there is a templatefile function but I haven't figured out the syntax for passing it a non-trivial map as the second argument and using the result to be executed remotely as the newly created instance's provisioning step.

Here's the gist of what I'm trying to do, although it doesn't parse properly because one can't just create a local variable within the resource block named scriptstr.

While I'm really trying to get the output of the templatefile call to be executed on the remote side, once the provisioner can ssh to the machine, I've so far gone down the path of trying to get the templatefile call output written to a local file via the local-exec provisioner. Probably easy, I just haven't found the documentation or examples to understand the syntax necessary. TIA

resource "aws_instance" "server" {
  count = "${var.servers}"

  ami           = "${local.ami}"
  instance_type = "${var.instance_type}"
  key_name      = "${local.key_name}"

  subnet_id              = "${element(aws_subnet.consul.*.id, count.index)}"
  iam_instance_profile   = "${aws_iam_instance_profile.consul-join.name}"
  vpc_security_group_ids = ["${aws_security_group.consul.id}"]

  ebs_block_device {
    device_name = "/dev/sda1"
    volume_size = 2
  }

  tags = "${map(
    "Name", "${var.namespace}-server-${count.index}",
    var.consul_join_tag_key, var.consul_join_tag_value
  )}"

  scriptstr = templatefile("${path.module}/templates/consul.sh.tpl",
      {
        consul_version = "${local.consul_version}"

        config = <<EOF
         "bootstrap_expect": ${var.servers},
         "node_name": "${var.namespace}-server-${count.index}",
         "retry_join": ["provider=aws tag_key=${var.consul_join_tag_key} tag_value=${var.consul_join_tag_value}"],
         "server": true
        EOF
      })

  provisioner "local-exec" {
    command = "echo ${scriptstr} > ${var.namespace}-server-${count.index}.init.sh"
  }

  provisioner "remote-exec" {
    script = "${var.namespace}-server-${count.index}.init.sh"

    connection {
      type     = "ssh"
      user     = "clear"
      private_key = file("${local.private_key_file}")
    }
  }
}
1
Why would you do that there rather than in a local? I have no idea if it would work as is in a local either but confused why you'd try your current approach.ydaetskcoR
Guess I don't know how to create a local within a resource. The global local would not have access to the resource's count.index.WeakPointer

1 Answers

5
votes

In your question I can see that the higher-level problem you seem to be trying to solve here is creating a pool of HashiCorp Consul servers and then, once they are all booted up, to tell them about each other so that they can form a cluster.

Provisioners are essentially a "last resort" in Terraform, provided out of pragmatism because sometimes logging in to a host and running commands on it is the only way to get a job done. An alternative available in this case is to instead pass the information from Terraform to the server via the aws_instance user_data argument, which will then allow the servers to boot up and form a cluster immediately, rather than being delayed until Terraform is able to connect via SSH.

Either way, I'd generally prefer to have the main body of the script I intend to run already included in the AMI so that Terraform can just run it with some arguments, since that then reduces the problem to just templating the invocation of that script rather than the whole script:

  provisioner "remote-exec" {
    inline = ["/usr/local/bin/init-consul --expect='${var.servers}' etc, etc"]

    connection {
      type     = "ssh"
      user     = "clear"
      private_key = file("${local.private_key_file}")
    }
  }

However, if templating an entire script is what you want or need to do, I'd upload it first using the file provisioner and then run it, like this:

  provisioner "file" {
    destination = "/tmp/consul.sh"
    content = templatefile("${path.module}/templates/consul.sh.tpl", {
        consul_version = "${local.consul_version}"

        config = <<EOF
         "bootstrap_expect": ${var.servers},
         "node_name": "${var.namespace}-server-${count.index}",
         "retry_join": ["provider=aws tag_key=${var.consul_join_tag_key} tag_value=${var.consul_join_tag_value}"],
         "server": true
        EOF
    })
  }

  provisioner "remote-exec" {
    inline = ["sh /tmp/consul.sh"]
  }