Search code examples
amazon-web-servicesterraformterraform-provider-awsaws-ssm

terraform template to build dynamic SSM automation


running Terraform 1.7.4

for each row in arg map my SSM automation needs

{
     "description": "Invoke Lambda Function ${key}",
     "name": "InvokeLambdaFunction${key}",
     "action": "aws:invokeLambdaFunction",
     "inputs": {
         "FunctionName": "arn:aws:lambda:us-east-1:${account_number}:function:hello",
         "InvocationType": "RequestResponse",
         "Payload": "{\"key1\": \"${account_number}\", \"key2\": \"<first value from arg_map>\", \"key3\": \"<second value from arg_map>\"}"
     }
}

main.tf

provider "aws" {
  region = "us-east-1"  # Set your desired region here
}
    
variable "account_number" {
  default = "123456789"
}
    
variable "arg_map" {
  type = map(list(string))
  default = {
    "key" = ["a", "b"]
    "key" = ["d", "e"]
    "key" = ["g", "h"]
    "key" = ["j", "k"]
  }
}
    
resource "aws_ssm_document" "sync_epv2asm" {
  name          = "sync_epv2asm"
  document_type = "Automation"
 
  content = templatefile("${path.module}/ssm_document_template.tftpl", {
    account_number = var.account_number
    arg_map        = var.arg_map
  })
}

ssm_document_template.tftpl

{
  "schemaVersion": "0.3",
  "description": "My description.",
  "mainSteps": [
    % for key, values in arg_map:
     {
        "description": "Invoke Lambda Function ${key}",
        "name": "InvokeLambdaFunction${key}",
        "action": "aws:invokeLambdaFunction",
        "inputs": {
          "FunctionName": "arn:aws:lambda:us-east-1:${account_number}:function:hello",
          "InvocationType": "RequestResponse",
          "Payload": "{\"key1\": \"${account_number}\", \"key2\": \"${values[0]}\", \"key3\": \"${values[1]}\"}"
        }
    }% if not loop.last %,
    % endif
    % endfor
  ]
}

running terraform apply

│ Error: Invalid function argument │ │ on main.tf line 23, in resource "aws_ssm_document" "sync_epv2asm": │ 23: content = templatefile("${path.module}/ssm_document_template.tftpl", { │ 24: account_number = var.account_number │ 25: arg_map = var.arg_map │ 26: }) │ ├──────────────── │ │ while calling templatefile(path, vars) │ │ var.arg_map is a map of list of string │ │ Invalid value for "vars" parameter: vars map does not contain key "key", referenced at ./ssm_document_template.tftpl:7,44-47.

googling around is not finding anything. I even tried some of the AI to see if they and identify my problem.

some of the things on the internet were showing "{}" the template around the "%". that did not change my error.

any thoughts on why I cannot get the template to work?


Solution

  • Since you are trying to create a JSON document, templatefile is almost never enough by itself to achieve that. Based on the documentation for templatefile, you can also use the built-in jsonencode function with the template. It should look something like the following:

    ${jsonencode({
      "schemaVersion": "0.3",
      "description": "My description.",
      "mainSteps": [
        for key, values in arg_map:
        {
          "description": "Invoke Lambda Function ${key}",
          "name": "InvokeLambdaFunction${key}",
          "action": "aws:invokeLambdaFunction",
          "inputs": {
            "FunctionName": "arn:aws:lambda:us-east-1:${account_number}:function:hello:$LATEST",
            "InvocationType": "RequestResponse",
            "Payload": "{\"key1\": \"${account_number}\", \"key2\": \"${values[0]}\", \"key3\": \"${values[1]}\"}"
          }
        }
      ]
    })}
    

    The plan output shows the result like this:

      # aws_ssm_document.sync_epv2asm will be created
      + resource "aws_ssm_document" "sync_epv2asm" {
          + arn              = (known after apply)
          + content          = jsonencode(
                {
                  + description   = "My description."
                  + mainSteps     = [
                      + {
                          + action      = "aws:invokeLambdaFunction"
                          + description = "Invoke Lambda Function key1"
                          + inputs      = {
                              + FunctionName   = "arn:aws:lambda:us-east-1:123456789:function:hello:$LATEST"
                              + InvocationType = "RequestResponse"
                              + Payload        = jsonencode(
                                    {
                                      + key1 = "123456789"
                                      + key2 = "a"
                                      + key3 = "b"
                                    }
                                )
                            }
                          + name        = "InvokeLambdaFunctionkey1"
                        },
                      + {
                          + action      = "aws:invokeLambdaFunction"
                          + description = "Invoke Lambda Function key2"
                          + inputs      = {
                              + FunctionName   = "arn:aws:lambda:us-east-1:123456789:function:hello:$LATEST"
                              + InvocationType = "RequestResponse"
                              + Payload        = jsonencode(
                                    {
                                      + key1 = "123456789"
                                      + key2 = "d"
                                      + key3 = "e"
                                    }
                                )
                            }
                          + name        = "InvokeLambdaFunctionkey2"
                        },
                    ]
                  + schemaVersion = "0.3"
                }
            )
          + created_date     = (known after apply)
          + default_version  = (known after apply)
          + description      = (known after apply)
          + document_format  = "JSON"
          + document_type    = "Automation"
          + document_version = (known after apply)
          + hash             = (known after apply)
          + hash_type        = (known after apply)
          + id               = (known after apply)
          + latest_version   = (known after apply)
          + name             = "sync_epv2asm"
          + owner            = (known after apply)
          + parameter        = (known after apply)
          + platform_types   = (known after apply)
          + schema_version   = (known after apply)
          + status           = (known after apply)
          + tags_all         = (known after apply)
        }
    

    I've trimmed the example to use only two keys, but this should work for any number of keys.

    NOTE: You also have to append the Lambda version to the Lambda ARN, either using $LATEST or a version number, otherwise, the SSM document will throw an error:

    Error: creating SSM Document (sync_epv2asm): InvalidDocumentContent: Input arn:aws:lambda:us-east-1:123456789:function:hello failed to match regex defined in document: (arn:aws)?(-[a-z]+)?(-[a-z]+)?(:lambda:)?([a-z]{2}-[a-z]+(-[a-z]+)?-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-]+)(:($LATEST|[a-zA-Z0-9-]+))?.

    Apply output:

    Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
      + create
    
    Terraform will perform the following actions:
    
      # aws_ssm_document.sync_epv2asm will be created
      + resource "aws_ssm_document" "sync_epv2asm" {
          + arn              = (known after apply)
          + content          = jsonencode(
                {
                  + description   = "My description."
                  + mainSteps     = [
                      + {
                          + action      = "aws:invokeLambdaFunction"
                          + description = "Invoke Lambda Function key1"
                          + inputs      = {
                              + FunctionName   = "arn:aws:lambda:us-east-1:123456789:function:hello:$LATEST"
                              + InvocationType = "RequestResponse"
                              + Payload        = jsonencode(
                                    {
                                      + key1 = "123456789"
                                      + key2 = "a"
                                      + key3 = "b"
                                    }
                                )
                            }
                          + name        = "InvokeLambdaFunctionkey1"
                        },
                      + {
                          + action      = "aws:invokeLambdaFunction"
                          + description = "Invoke Lambda Function key2"
                          + inputs      = {
                              + FunctionName   = "arn:aws:lambda:us-east-1:123456789:function:hello:$LATEST"
                              + InvocationType = "RequestResponse"
                              + Payload        = jsonencode(
                                    {
                                      + key1 = "123456789"
                                      + key2 = "d"
                                      + key3 = "e"
                                    }
                                )
                            }
                          + name        = "InvokeLambdaFunctionkey2"
                        },
                    ]
                  + schemaVersion = "0.3"
                }
            )
          + created_date     = (known after apply)
          + default_version  = (known after apply)
          + description      = (known after apply)
          + document_format  = "JSON"
          + document_type    = "Automation"
          + document_version = (known after apply)
          + hash             = (known after apply)
          + hash_type        = (known after apply)
          + id               = (known after apply)
          + latest_version   = (known after apply)
          + name             = "sync_epv2asm"
          + owner            = (known after apply)
          + parameter        = (known after apply)
          + platform_types   = (known after apply)
          + schema_version   = (known after apply)
          + status           = (known after apply)
          + tags_all         = (known after apply)
        }
    
    Plan: 1 to add, 0 to change, 0 to destroy.
    
    Do you want to perform these actions?
      Terraform will perform the actions described above.
      Only 'yes' will be accepted to approve.
    
      Enter a value: yes
    
    aws_ssm_document.sync_epv2asm: Creating...
    aws_ssm_document.sync_epv2asm: Creation complete after 0s [id=sync_epv2asm]
    
    Apply complete! Resources: 1 added, 0 changed, 0 destroyed.