Nathan Sowatskey
Nathan Sowatskey

Reputation: 169

How to pass a list of AZs from Terraform to a CloudFormation template?

I have a CloudFormation template that defines this parameter:

        "AvailabilityZones" : {
            "Description" : "List of Availability Zones used ... ",
            "Type" : "List<AWS::EC2::AvailabilityZone::Name>"
        },

I invoke the CF template like this:

resource "aws_cloudformation_stack" "xxx" {

  name = "xxx-stack"

  template_body = file("../cloudformation/xxx_CloudFormation_template_2.1.1.json")

  parameters = {
    VpcId                = aws_vpc.xxxvpc.id
    SubnetCidrBlock      = var.aws_vpc_cidr 
    AvailabilityZones    = var.aws_azs
    InstanceType         = "m5.4xlarge"
    ExternalNet          = "0.0.0.0/0"
    ....
  }

  on_failure = "DELETE"
}

Where:

variable "aws_azs" {
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

I get the error:

Inappropriate value for attribute "parameters": element "AvailabilityZones": string required.

I have tried many variations on this theme, but I can't see how to pass a list of AZs to the CF template from TF.

I am also more than a little puzzled by the assertion that a string is required in the error message, as the parameter type in the CF template is defined as a list.

Any ideas please?

Upvotes: 1

Views: 960

Answers (2)

Niels
Niels

Reputation: 77

To pass a list of values from Terraform to CloudFormation you will need to join them as a comma separated list.

TL;DR

resource "aws_cloudformation_stack" "this" {
  ...
  parameters = {
    AvailabilityZones = join(",", ["us-east-1a", "us-east-1b", "us-east-1c"])
  }

}

Explanation

To pass lists of things to a CloudFormation template you will have to either pass a native construct or a comma separated list of values.

https://aws.amazon.com/premiumsupport/knowledge-center/multiple-values-list-parameter-cli/

Consider we have a CloudFormation template cfn-template.yml that requires us to pass a list of AZs as parameter:

AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  AvailabilityZones:
    Description: List of Availability Zones used ...
    Type: "List<AWS::EC2::AvailabilityZone::Name>"
Resources:
  NullResource: # CF needs at least 1 resource, so we hand it a dummy
    Type: "AWS::CloudFormation::WaitConditionHandle"
Outputs:
  PassedAvailabilityZones:
    Description: The AZs passed into the cfn template from Terraform.
    Value: !Join [ ",", !Ref AvailabilityZones ]

In your terraform we will need to create a comma separated string of these values and pass them in the parameter block.

variable "aws_azs" {
  type    = list(string)
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

resource "aws_cloudformation_stack" "this" {
  name = "test-pass-azs"

  parameters = {
    AvailabilityZones = join(",", var.aws_azs)
  }

  template_body = file("./cfn-template.yml")
}

terraform {
  required_version = "~> 1.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

Upvotes: 1

Daniel Hornik
Daniel Hornik

Reputation: 2531

I would prefer to use the templatefile function instead of the file you are using in the template_body field. You can pass variables there and fill the template with passed variables with nice template engine. You can use variables, functions, statements and loops there.

Example is here https://www.terraform.io/docs/language/functions/templatefile.html

Your example

resource "aws_cloudformation_stack" "xxx" {

  ....

  template_body = templatefile("../cloudformation/xxx_CloudFormation_template_2.1.1.json", {
 aws_azs = var. aws_azs
})

  ....
}

and your cf file:

 "AvailabilityZones" : {
            "Description" : "List of Availability Zones used ${join(',', aws_azs)} ",
            "Type" : "List<AWS::EC2::AvailabilityZone::Name>"
        },

Upvotes: 1

Related Questions