Terraform: post-configuration by calling remote-exec script with parameters

If you are creating a VM resource and must run a Bash script as part of the initialization, that can be done within Terraform using the remote-exec provisioner and its ability to execute scripts via ssh.

If you need to send arguments to this script, there is a standard pattern described in the official documentation for first getting the script to the remote system with a file provisioner, then calling remote-exec with inline commands where you can specify arguments.

Arguments can be added to the inline commands by pulling from any local, var, or data as shown below.

locals {
  params = [ "a", "b", "c"]
  params_for_inline_command = join(" ",params)
}

resource "vsphere_virtual_machine" "vm" {
  # ...

  # ssh connection values, typically pulled from var
  connection {
    type = "ssh"
    agent = "false"
    host = "myhost"
    user = var.user
    password = var.password
  }

  # copies file from local directory to remote directory
  provisioner "file" {
    source      = "script.sh"
    destination = "/tmp/script.sh"
  }

  # executes the following commands remotely
  provisioner "remote-exec" {
    inline = [
      "chmod +x /tmp/script.sh",
      "/tmp/script.sh ${local.params_for_inline_command}",
    ]
  }
}

Take note that if your ssh connection is made as non-root, sudo may not be  password-less.  If your script needs a password for sudo, then you’ll need to echo that to the script and use “sudo -S”.

  provisioner "remote-exec" {
    inline = [
      "chmod +x /tmp/script.sh",
      "echo ${var.password} | sudo -S /tmp/script.sh ${local.params_for_inline_command}",
    ]
  }

If passing the parameters to the script on the ‘inline’ command becomes to lengthy or complex, you can use another strategy which is to construct the script from a template and have Terraform inject the parameters directly into the script.

Following up on the example above, we can pass the local variables (both array and string) into the Terraform templatefile function.

  # make script from template
  provisioner "file" {
    destination = "/tmp/script.sh"
    content = templatefile(
      "${path.module}/script.sh.tpl",
      { 
        "params": local.params
        "params_for_inline_command" : local.params_for_inline_command
      }
    )
  }

And with a “script.sh.tpl” that looks like below.

#!/bin/bash

# will be replaced by Terraform templating
default_args="${params_for_inline_command}"

# double dollar sign is escape so Terraform does not replace
final_args="$${@:-$default_args}"
echo "final_args = $final_args"

for myarg in $final_args; do
  echo "arg is $myarg"
done

echo "Illustrating a way to inject Terraform list variables via templating"
%{ for param in params ~}
echo "list item param is ${param}"
%{ endfor ~}

The resulting file placed unto the remote host will look like:

#!/bin/bash

# will be replaced by Terraform templating
default_args="a b c"

# double dollar sign is escape so Terraform does not replace
final_args="${@:-$default_args}"
echo "final_args = $final_args"

for myarg in $final_args; do
  echo "arg is $myarg"
done

echo "Illustrating a way to inject Terraform list variables via templating"
echo "list item param is a"
echo "list item param is b"
echo "list item param is c"

This allows you to insert either default values or unroll sets and arrays of complex variables into the script.

Here is a link to my github templatefile test.

And here is a link to a more advanced scenario where a script is used for data disk initialization of a vsphere vm.

 

REFERENCES

terraform, remote-exec with parameters

github, write rendered template to local file