0
votes

I am experimenting with CloudFormation to automate creation of resources. In the following setup, I am trying to breakdown creation of a load balancing target group away from the main script into a seperate script. I know that we can reference external script with a "Transform" section, but the main script flow needs the created target group's ARN to proceed. May I know if there is a way to pass back values from external scripts? Thanks

(Create_Cluster.yaml) Main script

    AWSTemplateFormatVersion: 2010-09-09
    Description: Create ECS cluster, task definition and service

    Transform:
      Name: 'AWS::Include'
      Parameters:
        Location: s3://MyAmazonS3BucketName/Create_Target_Group.yaml

    Resources:
      TestECSCluster:
        Type: 'AWS::ECS::Cluster'
        Properties: {}

      ECSTaskExecutionRole:
        Type: 'AWS::IAM::Role'
        Properties:
          AssumeRolePolicyDocument:
            Version: 2012-10-17
            Statement:
              - Sid: ''
                Effect: Allow
                Principal:
                  Service: ecs-tasks.amazonaws.com
                Action: 'sts:AssumeRole'
          ManagedPolicyArns:
            - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'

      PostCodeECSTaskDefinition:
        Type: 'AWS::ECS::TaskDefinition'
        Properties:
          ExecutionRoleArn: !GetAtt 
            - ECSTaskExecutionRole
            - Arn
          ContainerDefinitions:
            - Name: PostCode
              Image: 'nginxdemos/hello:latest'
              Essential: true
              PortMappings:
                - HostPort: 80
                  Protocol: tcp
                  ContainerPort: 80
          RequiresCompatibilities:
            - FARGATE
          NetworkMode: awsvpc
          Cpu: '256'
          Memory: '512'
          Family: ecs-demo

      PostCodeECSService:
        Type: 'AWS::ECS::Service'
        Properties:
          Cluster: TestCluster
          DeploymentController: 
            Type: CODE_DEPLOY
          DesiredCount: 1
          HealthCheckGracePeriodSeconds: 300
          LaunchType: FARGATE
          LoadBalancers: 
            - ContainerName: PostCode
              ContainerPort: 80
              TargetGroupArn: <HOW TO GET THE ARN OF THE TARGET GROUP CREATED IN EXTERNAL SCRIPT?>
          SchedulingStrategy: REPLICA
          ServiceName: TestService
          TaskDefinition: PostCodeECSTaskDefinition

(Create_Target.yaml) Script to create the target group

    AWSTemplateFormatVersion: 2010-09-09
    Description: Create target group
    Resources:
      TestTargetGroup01:
        Type: AWS::ElasticLoadBalancingV2::TargetGroup
        Properties:
          Name: TestTargetGroup01
          Protocol: HTTP
          Port: 80
          TargetType: instance
          VpcId: vpc-b830c6c5

==============================
Update (6 Apr 2021)
==============================

I got it working after some study. You would need to put the part that you want to externalize in a seperate stack (nested stack), and call it in the main stack. In the nested stack, add an "Output" section to return any value; In the main stack, get the return value from the nested stack with the "GetAtt" function.

      ...
      TargetGroupArn: !GetAtt
        - TargetGroup01Stack
        - Outputs.TargetGroupARN
      ...

Here I used a nested stack instead of the Transform section, as it can have its own "Output" section.
Reference:
https://aws.amazon.com/premiumsupport/knowledge-center/cloudformation-nested-stacks-values/

The difference between a nested stack and transform is discussed here:
https://acloud.guru/forums/aws-cda-2018/discussion/-LYOIsyqQdHckIbop4XP/difference_between_nested_stac#:~:text=2%20Answers&text=Nested%20stacks%20create%20a%20distinctly,master%20template's%20parameters%2C%20conditions%20etc

(Create_ECS_Cluster.yaml) Creates the main stack, and calls nested stack inside as a step

    AWSTemplateFormatVersion: '2010-09-09'
    Description: Create ECS cluster, task definition and service
    Resources:
      TargetGroup01Stack:
        Type: 'AWS::CloudFormation::Stack'
        Properties:
          TemplateURL: 'https://mys3bucket.s3.amazonaws.com/Create_Target_Group.yaml'

      TestSecurityGroup:
        Type: 'AWS::EC2::SecurityGroup'
        Properties:
          GroupDescription: Security group for ec2 access
          VpcId: vpc-b830c6c5
          SecurityGroupIngress:
            - IpProtocol: tcp
              FromPort: 80
              ToPort: 80
              CidrIp: 0.0.0.0/0
            - IpProtocol: tcp
              FromPort: 8080
              ToPort: 8080
              CidrIp: 0.0.0.0/0
            - IpProtocol: tcp
              FromPort: 22
              ToPort: 22
              CidrIp: 0.0.0.0/0

      TestALB:
        Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
        Properties:
          Scheme: internet-facing
          SecurityGroups:
            - !Ref TestSecurityGroup
          Subnets:
            - subnet-e80ed48e
            - subnet-7c3ba772
          Tags:
            - Key: Group
              Value: Example
          Type: application
          IpAddressType: ipv4

      TestALBListener:
        Type: 'AWS::ElasticLoadBalancingV2::Listener'
        Properties:
          DefaultActions:
            - Type: forward
              ForwardConfig:
                TargetGroups:
                  - TargetGroupArn: !GetAtt
                    - TargetGroup01Stack
                    - Outputs.TargetGroupARN
          LoadBalancerArn: !Ref TestALB
          Port: 80
          Protocol: HTTP

      TestECSCluster:
        Type: 'AWS::ECS::Cluster'
        Properties: {}

      ECSTaskExecutionRole:
        Type: 'AWS::IAM::Role'
        Properties:
          AssumeRolePolicyDocument:
            Version: 2012-10-17
            Statement:
              - Sid: ''
                Effect: Allow
                Principal:
                  Service: ecs-tasks.amazonaws.com
                Action: 'sts:AssumeRole'
          ManagedPolicyArns:
            - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'

      PostCodeECSTaskDefinition:
        Type: 'AWS::ECS::TaskDefinition'
        Properties:
          ExecutionRoleArn: !GetAtt 
            - ECSTaskExecutionRole
            - Arn
          ContainerDefinitions:
            - Name: PostCode
              Image: patrick888/postcode:latest
              Essential: true
              PortMappings:
                - HostPort: 80
                  Protocol: tcp
                  ContainerPort: 80
              LogConfiguration:
                LogDriver: awslogs
                Options:
                  awslogs-group: awslogs-ecs
                  awslogs-region: us-east-1
                  awslogs-stream-prefix: PostCodeECSService
          RequiresCompatibilities:
            - FARGATE
          NetworkMode: awsvpc
          Cpu: '256'
          Memory: '512'
          Family: ecs-demo

      PostCodeECSService:
        Type: 'AWS::ECS::Service'
        Properties:
          Cluster: !Ref TestECSCluster
          DeploymentController: 
            Type: CODE_DEPLOY
          DesiredCount: 1
          HealthCheckGracePeriodSeconds: 300
          LaunchType: FARGATE
          LoadBalancers: 
            - ContainerName: PostCode
              ContainerPort: 80
              TargetGroupArn: !GetAtt
                - TargetGroup01Stack
                - Outputs.TargetGroupARN
          NetworkConfiguration:
            AwsVpcConfiguration:
              AssignPublicIp: ENABLED
              SecurityGroups:
                - !Ref TestSecurityGroup
              Subnets:
                - subnet-e80ed48e
                - subnet-7c3ba772
          SchedulingStrategy: REPLICA
          ServiceName: TestService
          TaskDefinition: !Ref PostCodeECSTaskDefinition
        DependsOn: TestALBListener

(Create_Target_Group.yaml) Creates a nested stack for creating target group

    AWSTemplateFormatVersion: 2010-09-09
    Description: Create target group
    Resources:
      TestTargetGroup01:
        Type: AWS::ElasticLoadBalancingV2::TargetGroup
        Properties:
          Name: TestTargetGroup01
          Protocol: HTTP
          Port: 80
          TargetType: ip
          VpcId: vpc-b830c6c5
          HealthCheckPath: /healthcheck.html
    Outputs:
      TargetGroupARN:
        Value: !Ref TestTargetGroup01       
        Description: The target group ARN
1
Are you sure that this is a valid use of the AWS::Include? In other words, does this template otherwise work? For instance, can you remove entire PostCodeECSService and verify that it deploys?Marcin
I got the setup working using a nested stack. I read from documentation that "Transfrom" is like a macro where you want to run some reusable routines. For my scenario, it seems nested stack is the solution as what I want to achieve is to spread out resource creations, hence each of those are actually an individual stack. Besides, "Transform" does not return values (please correct if I am wrong, thanks).Patrick C.
Nested setup is better. But as a side note, you could refer to resources from "Transform" in your main template.Marcin

1 Answers

0
votes

Got the setup working by using a nested stack, and return values in the nested stack's "Output" section. Please refer to above for details, thanks.