Terraform pushing Cloudformation stacks. How to make a string list from parameters

1/22/2020

I'm fighting with wired case. I need to push cloudformation stacks dynamically parameterized with terraform.

My resource looks like this.

resource "aws_cloudformation_stack" "eks-single-az" {
  count = length(var.single_az_node_groups)
  name = "eks-${var.cluster_name}-${var.single_az_node_groups[count.index].name}"

  template_body = <<EOF
Description: "eks-${var.cluster_name}-${var.single_az_node_groups[count.index].name}"

Resources:
  ASG:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: "eks-${var.cluster_name}-${var.single_az_node_groups[count.index].name}"
      VPCZoneIdentifier: ["${var.private_subnet_ids[count.index]}"]
      MinSize: "${lookup(var.single_az_node_groups[count.index], "asg_min", "0")}"
      MaxSize: "${lookup(var.single_az_node_groups[count.index], "asg_max", "10")}"
      HealthCheckType: EC2
      TargetGroupARNs: [] < - here is error. 
      MixedInstancesPolicy:
        InstancesDistribution:
          OnDemandBaseCapacity: "0"
          OnDemandPercentageAboveBaseCapacity: "${lookup(var.single_az_node_groups[count.index], "on_demand_percentage", "0")}"
        LaunchTemplate:
          LaunchTemplateSpecification:
            LaunchTemplateId: "${aws_launch_template.eks-single-az[count.index].id}"
            Version: "${aws_launch_template.eks-single-az[count.index].latest_version}"
          Overrides:
            - 
              InstanceType: m5.large
      Tags:
        - Key: "Name"
          Value: "eks-${var.cluster_name}-${var.single_az_node_groups[count.index].name}"
          PropagateAtLaunch: true
        - Key: "kubernetes.io/cluster/${var.cluster_name}"
          Value: "owned"
          PropagateAtLaunch: true
        - Key: "k8s.io/cluster-autoscaler/enabled"
          Value: "true"
          PropagateAtLaunch: true
        - Key: "k8s.io/cluster-autoscaler/${var.cluster_name}"
          Value: "true"
          PropagateAtLaunch: true
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MinSuccessfulInstancesPercent: 80
        MinInstancesInService: "${lookup(data.external.desired_capacity.result, "eks-${var.cluster_name}-${var.single_az_node_groups[count.index].name}", "0")}"
        PauseTime: PT4M
        SuspendProcesses:
          - HealthCheck
          - ReplaceUnhealthy
          - AZRebalance
          - AlarmNotification
          - ScheduledActions
        WaitOnResourceSignals: true
  EOF

depends_on = [
  aws_launch_template.eks-single-az
]

}

I need to put target groups arn from list containing json objects:

single_az_node_groups = [
  {
    "name"          : "workload-az1",
    "instance_type" : "t2.micro",
    "asg_min"       : "1",
    "asg_max"       : "7",
    "target_group_arns" : "arnA, arnB, arnC"
  },
  ...
]

I tried everything. Problem is that i tried many terraform functions and all the time terraform is addding some double-quotes which cloudformation does not support or terraform won't process the template_body becuase of missing quotes..
Do you know meybe some sneaky trick how to achive that ?

-- Drupi
amazon-web-services
aws-cloudformation-custom-resource
kubernetes
terraform

1 Answer

1/23/2020

When building strings that represent serialized data structures, it's much easier to use Terraform's built-in serialization functions to construct the result, rather than trying to produce a valid string using string templates.

In this case, we can use jsonencode to construct a JSON string representing the template_body from a Terraform object value, which then allows using all of the Terraform language expression features to build it:

  template_body = jsonencode({
    Description: "eks-${var.cluster_name}-${var.single_az_node_groups[count.index].name}",

    Resources: {
      ASG: {
        Type: "AWS::AutoScaling::AutoScalingGroup",
        Properties: {
          AutoScalingGroupName: "eks-${var.cluster_name}-${var.single_az_node_groups[count.index].name}",
          VPCZoneIdentifier: [var.private_subnet_ids[count.index]],
          MinSize: lookup(var.single_az_node_groups[count.index], "asg_min", "0"),
          MaxSize: lookup(var.single_az_node_groups[count.index], "asg_max", "10"),
          HealthCheckType: "EC2",
          TargetGroupArns: flatten([
            for g in local.single_az_node_groups : [
              split(", ", g.target_group_arns)
            ]
          ]),
          # etc, etc
        },
      },
    },
  })

As you can see above, by using jsonencode for the entire data structure we can then use Terraform expression operators to build the values. For TargetGroupArns in the above example I used the flatten function along with a for expression to transform the nested local.single_az_node_groups data structure into a flat list of target group ARN strings.


CloudFormation supports both JSON and YAML, and Terraform also has a yamlencode function that you could potentially use instead of jsonencode here. I chose jsonencode both because yamlencode is currently marked as experimental (the exact YAML formatting it produces may change in a later release) and because Terraform has special support for JSON formatting in the plan output where it can show a structural diff of the data structure inside, rather than a string-based diff.

-- Martin Atkins
Source: StackOverflow