Using variables within strings in local-exec provisioner

I have the following null_resource:

resource "null_resource" "kubespray" {
  provisioner "local-exec" {
    command = <<-EOT
      if [ ! -d ~/kubespray ]; then
        git clone https://github.com/kubernetes-sigs/kubespray.git ~/kubespray
        sudo apt-get autoremove -y
        sudo apt-get update
        sudo apt-get install python3-pip -y
        sudo -H pip3 install -r ~/kubespray/requirements.txt
      fi

      if [ -d ~/kubespray/inventory/$KI]; then
        mv ~/kubespray/inventory/$KI ~/kubespray/inventory/$KI.$(date "+%Y%m%d-%H%M%S")
      fi

      cp -r ~/kubespray/inventory/sample ~/kubespray/inventory/$KI
      cd ~/kubespray/inventory
      perl -i -pe's/# kubectl_localhost: false/kubectl_localhost: true/g' $KI/group_vars/k8s-cluster/k8s-cluster.yml
      perl -i -pe's/# kubeconfig_localhost: false/kubeconfig_localhost: true/g' $KI/group_vars/k8s-cluster/k8s-cluster.yml
      perl -i -pe's/kube_version: v[0-9].[0-9][0-9].[0-9]/kube_version: v$KV/g' $KI/group_vars/k8s-cluster/k8s-cluster.yml
    EOT

    environment = {
      KI = var.kubespray_inventory
      KV = var.kubernetes_version
    }
  }

  depends_on = [
    vsphere_virtual_machine.standalone
  ]
}

I would like $KV to be expanded within the last perl call, however I suspect that using variable from the environment section in this context is nuanced, as the code stands $KV gets substituted into the command as an empty variable, can someone please advise as to ow I can work around this.

Thanks

Option A:

v${var.kubernetes_version}/g

Option B:
Try using double quotes instead of single quotes.

Thanks, I’ll try this.

It looks like you have three different languages nested here, all of which have an interpolation syntax which uses similar characters but an entirely different source for the data:

  • Terraform’s own template language in strings uses ${ ... } for interpolation, referring to symbols from the current Terraform module.
  • Unix shells typically use $... for interpolation, except in single quotes or when escaped, referring to environment variables.
  • Perl’s regular expression replace syntax allows $... for interpolation into the result value, referring to values in the current Perl scope.

The resulting confusion is why I typically recommend avoiding using one language to generate another, and instead to keep all of these things separate. In your case, you could do that by moving this complicated shell script out into a separate .sh file which you run from command, and ideally also moving the three perl statements into a separate .pl file which you can then run from your .sh file. Something like this:

# Terraform configuration
resource "null_resource" "kubespray" {
  provisioner "local-exec" {
    command = "cd '${path.module}' && bash kubespray.sh"

    environment = {
      KI = var.kubespray_inventory
      KV = var.kubernetes_version
    }
  }
}
# kubespray.sh

if [ ! -d ~/kubespray ]; then
git clone https://github.com/kubernetes-sigs/kubespray.git ~/kubespray
sudo apt-get autoremove -y
sudo apt-get update
sudo apt-get install python3-pip -y
sudo -H pip3 install -r ~/kubespray/requirements.txt
fi

if [ -d ~/kubespray/inventory/"$KI"]; then
mv ~/kubespray/inventory/"$KI" ~/kubespray/inventory/"$KI.$(date "+%Y%m%d-%H%M%S")"
fi

cp -r ~/kubespray/inventory/sample ~/kubespray/inventory/"$KI"

perl kubespray.pl
# kubespray.pl

# (my perl is rusty and I've not tested this, but hopefully
# it's a good enough approximation of your one-liners.)

my $KV = $ENV{'KV'};
my $KI = $ENV{'KI'};

open my $fh, '<', "$KI/group_vars/k8s-cluster/k8s-cluster.yml" or die "Can't open file $!";
my $content = do { local $/; <$fh> };

$content =~ s/# kubectl_localhost: false/kubectl_localhost: true/g;
$content =~ s/# kubeconfig_localhost: false/kubeconfig_localhost: true/g;
$content =~ s/kube_version: v[0-9].[0-9][0-9].[0-9]/kube_version: v$KV/g;

open my $fh, '>', "$KI/group_vars/k8s-cluster/k8s-cluster.yml" or die "Can't open file $!";
print $fh $content;

Through the exercise of de-embedding these I think I found the cause of your problem along the way: your last perl call has the following argument:

-pe's/kube_version: v[0-9].[0-9][0-9].[0-9]/kube_version: v$KV/g'

Here the $KV sequence is inside single quotes ' and so the shell will just treat it literally. Therefore Perl sees literally $KV and so thinks you intend to interpolate a Perl variable called $KV, which doesn’t exist and so (because you aren’t in use strict mode) evaluates to an empty string.

You could potentially address that while keeping this nested pile by taking the $KV part outside of the ' quotes so that the shell can evaluate it, but I think this situation is a pretty good example of why not to use one programming language to generate another programming language, and instead to let each layer be a separate static program that accepts input from the previous one.