Is there a way to pass map variables via templatefile function bash script?

I’m receiving the following error:

Error: Error in Function call
var.app_version is map of string with 5 elements.

Have the following my var.tf file

variable "app_version" {
   type = map
   description = "Application Software version"
    default = {
      "Application_one" = "1.0"
      "Application_two" = "6.0"
       "Application_three" = "1.0"
        "Application_four" = "8.0"
        "Application_five" = "1.5.8"
 }
}

My main.tl has

....
...
user_data = templatefile("test-array.tpl", { my_array = "${var.app_versio}" })
...
...

My bash script test-array.tpl has

....
....
echo ${my_array }[*]}

Pleas advise, thanks

Michael

Hi @mmauney,

Unfortunately you have left out the most important part of the error message which explains what actually failed when running the function, but I’m guessing it was an error saying that var.app_version is not suitable for interpolation because it isn’t a string.

The important thing to remember with a configuration like this is that you are using a template to generate a bash script, not to run a bash script. Therefore you need to think about how to tell Terraform which shell commands to generate so that when Bash does eventually run the script it will run the commands you intend; Bash itself won’t have access to the template variables at runtime.


One approach then would be to use Terraform’s template syntax to generate one command for each element of the array:

%{ for v in my_array ~}
echo "${v}"
%{ endfor ~}

With the default variable value you showed, this would generate a bash script like this:

echo "1.5.8"
echo "8.0"
echo "1.0"
echo "1.0"
echo "6.0"

(By the way: the variable you’ve declared is a map rather than a list or array, so it’s a bit confusing to name the variable my_array but I preserved the name from your question just so you could try it with the rest of your code as you already wrote it.)


Another approach would be to try to get Terraform to generate the syntax that Bash expects for declaring a bash array, which you could then use with the Bash for command. The Bash array syntax is a bit awkward to generate totally robustly with Terraform, but I think something like this will work as your template:

declare -ra my_bash_array=(${
  join(" ", [
    for s in my_array : "'${replace(s, "'", "'\\''")}'"
  ])
})

for s in "$${my_bash_array[@]}"; do
  echo "$${s}"
done

This one is more complicated so I’ll try to explain it in smaller parts:

  • replace(s, "'", "'\\''") is trying to deal with the possibility that a string might contain a literal ' character, which Bash expects us to mark with an escape character. This interpolation sequence is already in '' quotes, so if s was "that's" then that item would be rendered as 'that'\''s' in the bash script, so that Bash can parse it correctly to recover the original string.
  • The [ for s in my_array : ...] expression is generating a new list based on your given list, by applying the same transformation (as described in the previous point) to each item.
  • join(" ", ...) takes an array of strings and transforms it into a single string with all of the items separated by a single space each.
  • Everything discussed so far is in a ${ ... } sequence so that Terraform will include it after the equals sign of a bash array declaration, to generate a list of elements for the Bash array.
  • In the bash for command that follows, we can refer to my_bash_array to get elements from the bash version of the array. However, in both $${my_bash_array[@]} and $${s} the extra $ on the front is there to tell Terraform to produce a literal ${ sequence for Bash to parse, rather than understanding the following as a Terraform template interpolation.

With all of that said then, the result of rendering this template should be something like the following:

declare -ra my_bash_array=('1.5.8' '8.0' '1.0' '1.0' '6.0')

for s in "${my_bash_array[@]}"; do
  echo "${s}"
done

I don’t know if I got that Bash syntax exactly right, since I don’t routinely write complex Bash scripts like this, but hopefully if I didn’t get it right you can see the general idea and adjust it as needed.

Hello,

Thank you for your quick response. After reading your response, I realize that I may no be using the Terraform varaibale.tf configuration file to assign data to the variables and then pass the variables to the bash script to configure the machines and to deploy the applications in the wrong way. Before trying the variable as map, I used a variable type string and passed each variable through the templatefile function. For example:
variable.tf
variable “Application_one” {
type = string
default = “1.0”
}


variable “Application_five” {
type = string
default = “1.5.8”
}
and the main.tf
resource “app_server_1” {


user_data = templatefile("/apps/appl.f.tpl",{ Application_one" = “{var.Application_one}") ... ... } ... ... resource "app_server_5" { ... ... user_data = templatefile("/apps/appl.five.tpl",{ Application_five" = "{var.Application_one}”)


}
The Goal is to use the resource in a for_each using the one bash.tpl file for all the applications variables.

I’m trying to streamline and consolidate the terraform configuration file and the bash script.
I’m also trying to make any variables data change only in the variables.tf file
The other Terraform configuration files and the bash script should be static.

Please let me know if you have any questions or concerns.
Thanks again for your support.

Michael