How do I read values of a variable of type Map passed from terraform to powershell userdata script?

Viewed 1808

I need to pass the variable of type map from terraform to powershell userdata script and be able to access the key value pairs of the map in the powershell script. Thank you

userdata.tf

data "template_file" "user_data" {
  template = "${file("${path.module}/init.ps1")}"
  vars = {
    environment = var.env
    # I want to pass the values as shown below
    hostnames   = {"dev":"devhost","test":"testhost","prod":"prodhost"}
  }
}

init.ps1

$hostnames = "${hostnames}"
$environment = "${environment}"

if ($environment -eq "dev"){
   # print the value of the dev key in the hostname map here
}
2 Answers

The template_file data source is discouraged.

Note In Terraform 0.12 and later, the templatefile function offers a built-in mechanism for rendering a template from a file. Use that function instead, unless you are using Terraform 0.11 or earlier.

The templatefile function is preferred which is why my solution uses it instead.

In either case, only map(string) is supported for template vars. The values must be strings. JSON can encode arbitrary tree structures, including your map of hostnames as strings.

In your terraform code, encode your hostnames to JSON with jsonencode.

userdata.tf:

locals {
  user_data = templatefile("${path.module}/init.ps1" ,{
    environment = var.env
    # I want to pass the values as shown below
    hostnames   = jsonencode({"dev":"devhost","test":"testhost","prod":"prodhost"})
  })
}

In your PowerShell, decode your hostnames from JSON with the ConvertFrom-Json cmdlet.

init.ps1:

$hostnames = '${hostnames}' | ConvertFrom-Json
$environment = "${environment}"

if ($environment -eq "dev"){
   # print the value of the dev key in the hostname map here
}

Update: As noted in the comments, -AsHashtable won't necessarily work as it was added in PowerShell 6.0. Windows 10 and Windows Server 2016 include PowerShell 5.1. If you have maps with case-only differences in keys ({"name" = "foo" ; "Name" = "bar"}) then you will need to install PowerShell 6.0 or later and use ConvertFrom-Json -AsHashtable.

In order to include a collection value in a template result you must decide how you want to represent it as a string, because template results are always strings.

PowerShell supports JSON encoding via the ConvertFrom-Json cmdlet, so a JSON string might be a good candidate, although it presents some challenges because you must ensure that the JSON string is written into the result as a valid PowerShell expression, which means we must also apply PowerShell escaping.

Putting that all together, you can adjust the template like this:

$hostnames = '${replace(jsonencode(hostnames), "'", "''")}' | ConvertFrom-Json
$environment = '${replace(environment, "'", "''")}'

if ($environment -eq "dev"){
   Write-Output $hostnames["dev"]
}

The jsonencode function produces a JSON-encoded version of the given value. The above then passes that result to replace so that any ' characters in the result will be escaped as '', which then allows placing the entire result in single quotes ' to ensure valid PowerShell syntax.

The result of rendering the template would be something like this:

$hostnames = '{"dev":"devhost","test":"testhost","prod":"prodhost"}' | ConvertFrom-Json -AsHashtable
$environment = 'dev'

if ($environment -eq "dev"){
   Write-Output $hostnames["dev"]
}

You seem to be using Terraform 0.12, so you should use the templatefile function instead of the template_file data source. The function is better because it can accept values of any type, whereas the data source can only accept string values (because it is designed for Terraform 0.11).

To use templatefile, find the place where you were previously referring to data.template_file.user_data and use the templatefile function there instead:

templatefile("${path.module}/init.ps1", {
  environment = var.env
  hostnames   = {"dev":"devhost","test":"testhost","prod":"prodhost"}
})

You can then remove the data "template_file" "user_data" block, because this templatefile function call replaces it.

Related