Terraform: No way to escape a dollar in template variable.

Created on 2 Aug 2015  ·  9Comments  ·  Source: hashicorp/terraform

Terraform 0.6.1

In a template variable section, I need to define a variable like this:

resource "template_file" "foo_template" {
    filename = "foo.yaml"
    vars     = {
        TF_FOO = "bar-${baz}"
    }
}

That is - literally, where I have ${TF_FOO} in my foo.yaml, I want it replaced, literally with bar-${baz}

Now this obviously doesn't work, because terraform treats ${baz} as a variable and rightly says it doesn't exist.

So then I tried

TF_FOO="bar-$${baz}"

No luck - this gets written into the template as ... literally bar-$${baz}

So then I tried

TF_FOO="bar-\${baz}"

No luck - terraform treats this as a variable again.

There is no way to get terraform to write a literal ${baz} as a variable replacement in a terraform file.

bug core

All 9 comments

Simple example:

dummy.tf

resource "template_file" "foo" {
  filename = "./foo.yaml"
  vars = {
    bar = "bar-$${baz}"
  }
}

bar.yaml

hello ${bar}

terraform apply

template_file.foo: Creating...
  filename: "" => "./foo.yaml"
  rendered: "" => "<computed>"
  vars.#:   "" => "1"
  vars.bar: "" => "bar-$${baz}"
template_file.foo: Creation complete

vars.bar is incorrect as per documentation

terraform.tfstate

{
    "version": 1,
    "serial": 1,
    ...
            "resources": {
                "template_file.foo": {
    ...
                            "rendered": "hello bar-$${baz}\n\n",
    ...
}

rendered is incorrect

Horrible workaround for now:

    bar = "bar-${replace("%{baz}", "%", "$")}"

Hi @gtmtech! I've just reproduced this on the master branch using the following files:

main.tf:

resource "template_file" "foo" {
  filename = "./foo.yaml"

  vars = {
    bar = "bar-$${baz}"
  }
}

output "out" {
    value = "${template_file.foo.rendered}"
}

foo.yaml:

hello ${bar}

Applying then gives:

$ terraform apply
template_file.foo: Creating...
  filename: "" => "./foo.yaml"
  rendered: "" => "<computed>"
  vars.#:   "" => "1"
  vars.bar: "" => "bar-$${baz}"
template_file.foo: Creation complete

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

Outputs:

  out = hello bar-$${baz}

I'll investigate further and try to get this fixed.

PR is open.

I had following scripts inside ‘neo4j\templates\user-data-slave.tpl”.
But this didn’t work because of ‘$’ character. It didn’t execute the code itself.
So I replaced with ‘$$’ as per terraform interpolation doc. But it copied as empty values.

# Neo4j backup scripts settings
echo 'Neo4j backup scripts settings'
mkdir -p /opt/neo4j-scripts
tee /opt/neo4j-scripts/neo4j_backup_to_s3.sh <<EOL
#Initialization the params
TODAY=$(date +%Y-%m-%d-%H-%M-%S)
NEO4J_SERVER=$1
NEO4J_BACKUP_BINARY=neo4j-backup
NEO4J_BACKUP_PORT=$2 # Default port 6362
NEO4J_HOSTNAME=$(hostname) #ip-10-0-2-1
NEO4j_BACKUP_DESTINATION_FOLDER=/tmp/neo4j-backup-tmp
BACKUP_TAR_FOLDER=/tmp
BACKUP_FILE_NAME=$NEO4J_HOSTNAME.tar
BACKUP_S3_FOLDER=daily/$TODAY
AWS_BUCKET=$3 #s3://bucket-us-east-1
<----- script for neo4j backup upload.   This also contains $ linux variable--->
EOL

So finally fixed as below code. It may be dirty. But working. I am using 0.8.8. Still escaping of '$' with '$$' is not working.

# Neo4j backup scripts settings
echo 'Neo4j backup scripts settings'
mkdir -p /opt/neo4j-scripts
tee /opt/neo4j-scripts/neo4j_backup_to_s3.sh <<EOL
#Initialization the params
TODAY=<dollar>(date +%Y-%m-%d-%H-%M-%S)
NEO4J_SERVER=<dollar>1
NEO4J_BACKUP_BINARY=neo4j-backup
NEO4J_BACKUP_PORT=<dollar>2 # Default port 6362
NEO4J_HOSTNAME=<dollar>(hostname) #ip-10-0-2-1
NEO4j_BACKUP_DESTINATION_FOLDER=/tmp/neo4j-backup-tmp
BACKUP_TAR_FOLDER=/tmp
BACKUP_FILE_NAME=<dollar>NEO4J_HOSTNAME.tar
BACKUP_S3_FOLDER=daily/<dollar>TODAY
AWS_BUCKET=<dollar>3 #s3://bucket-us-east-1
<----- script for neo4j backup upload.   This also contains <dollar> linux variable--->
EOL
sed -i 's/<dollar>/$$/g' /opt/neo4j-scripts/neo4j_backup_to_s3.sh

This is still not working properly in 0.11

  • run/main.tf
resource "template_file" "init_file" {
  template = "${file("${path.cwd}/templates/dsiprouter.tpl")}"

  vars = {
    giturl = "${var.giturl}"
    gitbranch = "${var.gitbranch}"
  }
}

resource "local_file" "foo" {
    content = "${template_file.init_file.rendered}"
    filename = "${path.module}/init.sh"
}
  • run/variables.tf
variable "giturl" {
  default = "https://github.com/dOpensource/dsiprouter.git"
}
variable "gitbranch" {
  default = "master"
}
  • run/output.tf
output "template" {
  value = "${template_file.init_file.rendered}"
}
  • templates/dsiprouter.tf
#!/usr/bin/env bash

BUILD_VERSION="${gitbranch}"
BUILD_DIR="/opt/dsiprouter"

cmdExists() {
    if command -v "$$1" > /dev/null 2>&1; then
        return 0
    else
        return 1
    fi
}

if cmdExists "yum"; then
    yum install -y git curl
elif cmdExists "apt"; then
    apt-get install -y git curl
    if [ $$? -ne 0 ]; then
        if ! grep -q -E '(ftp|deb)\.debian.org/debian' /etc/apt/sources.list /etc/apt/sources.list.d/* 2>/dev/null; then
            # add debian main repo
            [ -e /etc/debian_version ] && CODENAME=$$(cat /etc/os-release | grep '^VERSION=' | cut -d '(' -f 2 | cut -d ')' -f 1)
            echo "deb http://ftp.debian.org/debian $${CODENAME-stable} main contrib non-free" >>/etc/apt/sources.list
            apt-get update -y
            apt-get install -y debian-keyring debian-archive-keyring
        fi
        apt-get install -y git curl
    fi
fi

git clone --depth 1 ${giturl} -b $${BUILD_VERSION} $${BUILD_DIR}
cd $${BUILD_DIR}
./dsiprouter.sh install -rtpengine -servernat

exit $$?

Reproducing Command:

terraform apply -auto-approve ./run/

Expected Output:

Escaped dollar sign literals are output as ONE dollar sign

Actual Output:

Outputs:

template = #!/usr/bin/env bash

BUILD_VERSION="master"
BUILD_DIR="/opt/dsiprouter"

cmdExists() {
    if command -v "$$1" > /dev/null 2>&1; then
        return 0
    else
        return 1
    fi
}

if cmdExists "yum"; then
    yum install -y git curl
elif cmdExists "apt"; then
    apt-get install -y git curl
    if [ $$? -ne 0 ]; then
        if ! grep -q -E '(ftp|deb)\.debian.org/debian' /etc/apt/sources.list /etc/apt/sources.list.d/* 2>/dev/null; then
            # add debian main repo
            [ -e /etc/debian_version ] && CODENAME=$$(cat /etc/os-release | grep '^VERSION=' | cut -d '(' -f 2 | cut -d ')' -f 1)
            echo "deb http://ftp.debian.org/debian ${CODENAME-stable} main contrib non-free" >>/etc/apt/sources.list
            apt-get update -y
            apt-get install -y debian-keyring debian-archive-keyring
        fi
        apt-get install -y git curl
    fi
fi

git clone --depth 1 https://github.com/dOpensource/dsiprouter.git -b ${BUILD_VERSION} ${BUILD_DIR}
cd ${BUILD_DIR}
./dsiprouter.sh install -rtpengine -servernat

exit $$?

The parser seems to be grabbing a few but not all use cases in the template file.
Looking at my previous comment you can see that the following shell variables are correctly escaped:

  • CODENAME
  • BUILD_VERSION
  • BUILD_DIR

While the rest of them are not correctly being escaped

It's still broken.^W^W^W My apologies it works well in my use case, i missed init call

$ terraform --version
Terraform v0.11.13
+ provider.google v2.2.0
+ provider.google-beta v2.2.0
+ provider.template v2.1.1

I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

Was this page helpful?
0 / 5 - 0 ratings