Terraform: How to run remote-exec more than once?

Alex Cohen picture Alex Cohen · Aug 22, 2016 · Viewed 12.8k times · Source

I have noticed that terraform will only run "file", "remote-exec" or "local-exec" on resources once. Once a resource is provisioned if the commands in a "remote-exec" are changed or a file from the provisioner "file" is changed then terraform will not make any changes to the instance. So how to I get terraform to run provisioner "file", "remote-exec" or "local-exec" everytime I run a terraform apply?

For more details:

Often I have had a resource provisioned partially due to an error from "remote-exec" causes terraform to stop (mostly due to me entering in the wrong commands while I'm writing my script). Running terraform again after this will cause the resource previously created to be destroyed and force terraform to create a new resource from scratch. This is also the only way I can run "remote-exec" twice on a resource... by creating it over from scratch.

This is really a drawback to terraform as opposed to ansible, which can do the same exact job as terraform except that it is totally idempotent. When using Ansible with tasks such as "ec2", "shell" and "copy" I can achieve the same tasks as terraform only each of those tasks will be idempotent. Ansible will automatically recognise when it doesn't need to make changes, where it does and because of this it can pick up where a failed ansible-playbook left off without destroying everything and starting from scratch. Terraform lacks this feature.

For reference here is a simple terraform resource block for an ec2 instance that uses both "remote-exec" and "file" provisioners:

resource "aws_instance" "test" {

count = ${var.amt}
ami = "ami-2d39803a"
instance_type = "t2.micro"
key_name = "ansible_aws"
tags {
  name = "test${count.index}"
}

#creates ssh connection to consul servers
connection {
  user = "ubuntu"
  private_key="${file("/home/ubuntu/.ssh/id_rsa")}"
  agent = true
  timeout = "3m"
} 

provisioner "remote-exec" {
  inline = [<<EOF

    sudo apt-get update
    sudo apt-get install curl unzip
    echo hi

  EOF
  ]
}

#copying a file over
provisioner "file" {
  source = "scripts/test.txt"
  destination = "/path/to/file/test.txt"
}

}

Answer

Chris Holmes picture Chris Holmes · Dec 24, 2016

Came across this thread in my searches and eventually found a solution:

resource "null_resource" "ansible" {

  triggers {
    key = "${uuid()}"
  }

  provisioner "local-exec" {
  command = "ansible-playbook -i /usr/local/bin/terraform-inventory -u ubuntu playbook.yml --private-key=/home/user/.ssh/aws_user.pem -u ubuntu"
  }
}

You can use uuid(), which is unique to every terraform run, to trigger a null resource or provisioner.