Introduction
HCP Terraform supports execution of runs in the cloud, without the need for the customer to install and manage an agent. Due to security reasons, the execution happens under regular user named tfc-user and sudo is not installed. This means apt can not be used to add software.
The proper way of dealing with these restrictions, when necessary, is building your own agent image, with all that is needed, already baked in.
During development, a workaround can be used however. Local exec resource can be used to install software in the same path the terraform executes in. This is described on the following page:
https://developer.hashicorp.com/terraform/cloud-docs/run/install-software#installing-additional-tools
What sometimes becomes a problem, is a need for this freshly installed software to be found by other tools. Programs are located on GNU/Linux Operating System via PATH environment variable. This variable can not be appended with a new directory, however it does contain the following:
/home/tfc-agent/.tfc-agent/component/terraform/runs/<run_id>/.local/bin
Even though this directory doesn't exist, it can be created and used to access software installed by local-exec between multiple executions.
Here is an example code that installs npm:
resource "null_resource" "install_npm" {
# Force the install to happen on every single run.
triggers = {
always_run = "${timestamp()}"
}
# Use the local-exec provisioned to install npm and then node
provisioner "local-exec" {
command = <<-EOH
wget https://nodejs.org/dist/v20.15.1/node-v20.15.1-linux-x64.tar.xz
tar -xpf node-v20.15.1-linux-x64.tar.xz
mkdir -p ../.local
ln -s ../config/node-v20.15.1-linux-x64/bin ../.local/
../.local/bin/npm version
EOH
}
}
resource "null_resource" "show_version" {
# Force the command to execute on every single run.
triggers = {
always_run = "${timestamp()}"
}
# Confirm npm is installed, can be executed and is accessible by python3
provisioner "local-exec" {
command = <<-EOH
which npm
npm version
python3 -c'import shutil; print(shutil.which("npm"))'
EOH
}
depends_on = [null_resource.install_npm]
}
# This is commented because it will error out without the actual source code being supplied at path
#module "lambda" {
# source = "terraform-aws-modules/lambda/aws"
# version = "7.7.0"
# function_name = "nodejs_lambda"
# runtime = "nodejs18.x"
# handler = "main.lambda_handler"
# source_path = "${path.module}/code"
# depends_on = [null_resource.show_version]
#}
Without a symbolic link being created in .local, the module execution fails to find npm. The same method can be used with any executable. This approach is often discouraged because it is error prone. In suited hands however, it can increase the speed of initial development.