Template File Function If Statement Syntax

Hi Hashicorp Community!

I seem to be getting some weird results when testing the exit code with $? in my templatefile using the templatefile function in my user data when launching an ec2 instance.

In short I am trying this: %{ if “$${?}” != 0 } do this %{ endif }

Is this correct? I ask because when the user script is run it is running “do this” incorrectly because the result of $? is 0 but the above if statement seems to think it is not.

I have checked the cloud init output and set the script to debug and can see the value of $? being 0.

Any help here would be much appreciated. Thanks in advance!

Could you share a little code snippet as your explanation reads ambiguous
Are you using the templatefile() function within the user_data script oder do you generate the user_data using the templatefile() function or do you use use some variables when defining user_data?

Actually the directive used within the user_data definition is created before the user_data is executed. So it only can evaluated commands executed within the script at runtime.

Thanks for the response tbugfinder and sure here is the code snippet.

Yes, I am using the templatefile() function with variables defining the user_data.

In my main.tf it which is a module, it has this:

resource "aws_instance" "instance" {

ami                         = data.aws_ami.ubuntu.id
instance_type               = var.instance_type
key_name                    = "${var.env}-key"
subnet_id                   = var.subnet
iam_instance_profile        = aws_iam_instance_profile.instance.name
associate_public_ip_address = false
root_block_device {
volume_size = 50

user_data     = templatefile("${path.module}/scripts/user_data.tpl", {
env = var.env
ansible_playbooks = var.ansible_playbooks
ansible_default_variables = local.ansible_default_variables
efs_mounts = { efs_mounts = { for key, value in var.efs_mounts : key => merge(value, data.aws_efs_mount_target.by_id[key]) }}

user_data.tpl has this:

inspec exec ./inspec/test

%{ if "$${?}" != 0 }
curl -X POST --data-urlencode "payload={\"channel\": \"#somechannel\", \"username\": \"bot\", \"text\": \"hello\", \"icon_emoji\": \":someemoji:\"}" https://slack.webhook
%%{ endif }

As mentioned the above if statement is triggered even when the test passes and the exit code is 0. I have confirmed this by set -x and checking the cloud init output log.

Hi @akrypto!

I think your question suggests a small misunderstanding of which components are responsible for evaluating which parts of your configuration here, and in what order.

The most important thing to understand is that the templatefile call is rendering your template before assigning the result to user_data, and so the data sent to the remote system is the result of rendering the template, not the template itself.

You seem to be trying to use Terraform’s template language to access data decided only by a shell eventually running the script which was generated by the template. That data isn’t visible to the templatefile function and all of the template directives will have already been interpreted and thus removed by the time a shell is running these commands.

Because you used the escaping syntax $${, Terraform understands "$${?}" as the literal string ${?}, and so your condition is asking if that string is different from zero, which is always true because a string can never be equal to a number in the Terraform language. Therefore the result always includes that curl line; the resulting script would always be the following:

inspec exec ./inspec/test

curl -X POST --data-urlencode "payload={\"channel\": \"#somechannel\", \"username\": \"bot\", \"text\": \"hello\", \"icon_emoji\": \":someemoji:\"}" https://slack.webhook

If you want to make a dynamic decision at runtime in your script you will need to write that condition in the language of whichever shell you are using. Assuming this is a Unix-style shell, that might look something like this (but I’m no shell expert, so please refer to the relevant docs for your shell for more detail and other approaches):

inspec exec ./inspec/test
if [ "$?" -ne "0" ]; then
  curl -X POST --data-urlencode "payload={\"channel\": \"#somechannel\", \"username\": \"bot\", \"text\": \"hello\", \"icon_emoji\": \":someemoji:\"}" https://slack.webhook

Note that this doesn’t include any Terraform template syntax at all anymore, so there’s no need to use templatefile; you can instead include this file directly with the file function and avoid the need to escape any sequences that might normally confuse Terraform’s template file parser.

Hi @apparentlymart

Thanks for the detailed response, much appreciated and although it makes sense I am required to use the templatefile function as I need to pass variables in as shown in my code in previous post. The file function does not allow this and the terraform templatsyntax does not evaluate $? correctly and as you mentioned interprets it as the literal string.

Hi @akrypto,

The template snippet you shared didn’t include any real template interpolations but now I look back at your templatefile call I can see you are passing in four template variables, so I guess your real user_data.tpl has more in it than you showed.

There’s no harm in continuing to use templatefile here if other parts of the file include template interpolation syntax. The main point of my answer was that you must use shell if syntax for any conditions that rely on data that will only be known when the script is running; templatefile can only affect the source code of the script itself.

Hi @apparentlymart

Thanks I seem to have resolved my issue, I was under the misunderstanding that I was required to use terraform templating language. I understand now that I am able to use any shell scripting language and mix with template interpolation syntax.

Thank you for all your help!