Creating a CloudFormation template for CodePipeline

We will start by creating a file and calling it helloworld-codepipeline-cf-template.py.

We will start the script with our boilerplates, as shown in the following code:

"""Generating CloudFormation template.""" 
 
from awacs.aws import ( 
    Allow, 
    Policy, 
    Principal, 
    Statement, 
) 
from awacs.sts import AssumeRole 
from troposphere import ( 
    Ref, 
    GetAtt, 
    Template, 
) 
from troposphere.codepipeline import ( 
    Actions, 
    ActionTypeID, 
    ArtifactStore, 
    InputArtifacts, 
    OutputArtifacts, 
    Pipeline, 
    Stages 
) 
from troposphere.iam import Role 
from troposphere.iam import Policy as IAMPolicy 
 
from troposphere.s3 import Bucket, VersioningConfiguration 
 
t = Template() 
 
t.add_description("Effective DevOps in AWS: Helloworld Pipeline") 

The first resource we will create is the S3 bucket that the pipeline will use to store all the artifacts produced by each stage. We will also turn on versioning on that bucket:

t.add_resource(Bucket( 
    "S3Bucket", 
    VersioningConfiguration=VersioningConfiguration( 
        Status="Enabled", 
    ) 
)) 

We will now create the IAM roles needed:

  1. The first role we are going to define will be for the CodePipeline service:
t.add_resource(Role( 
    "PipelineRole", 
    AssumeRolePolicyDocument=Policy( 
        Statement=[ 
            Statement( 
                Effect=Allow, 
                Action=[AssumeRole], 
                Principal=Principal("Service", ["codepipeline.amazonaws.com"]) 
            ) 
        ] 
    ), 
    Path="/", 
    Policies=[ 
        IAMPolicy( 
            PolicyName="HelloworldCodePipeline", 
            PolicyDocument={ 
                "Statement": [ 
                    {"Effect": "Allow", "Action": "cloudformation:*", "Resource": "*"}, 
                    {"Effect": "Allow", "Action": "codebuild:*", "Resource": "*"}, 
                    {"Effect": "Allow", "Action": "codepipeline:*", "Resource": "*"}, 
                    {"Effect": "Allow", "Action": "ecr:*", "Resource": "*"}, 
                    {"Effect": "Allow", "Action": "ecs:*", "Resource": "*"}, 
                    {"Effect": "Allow", "Action": "iam:*", "Resource": "*"}, 
                    {"Effect": "Allow", "Action": "s3:*", "Resource": "*"}, 
                ], 
            } 
        ), 
    ] 
)) 
  1. The second role will be used by the deploy stages to perform CloudFormation changes:
t.add_resource(Role( 
    "CloudFormationHelloworldRole", 
    RoleName="CloudFormationHelloworldRole", 
    Path="/", 
    AssumeRolePolicyDocument=Policy( 
        Statement=[ 
            Statement( 
                Effect=Allow, 
                Action=[AssumeRole], 
                Principal=Principal( 
                    "Service", ["cloudformation.amazonaws.com"]) 
            ), 
        ] 
    ), 
    Policies=[ 
        IAMPolicy( 
            PolicyName="HelloworldCloudFormation", 
            PolicyDocument={ 
                "Statement": [ 
                    {"Effect": "Allow", "Action": "cloudformation:*", "Resource": "*"}, 
                    {"Effect": "Allow", "Action": "ecr:*", "Resource": "*"}, 
                    {"Effect": "Allow", "Action": "ecs:*", "Resource": "*"}, 
                    {"Effect": "Allow", "Action": "iam:*", "Resource": "*"}, 
                ], 
            } 
        ), 
    ] 
)) 
  1. We can now create our pipeline resource. We will first configure its name and specify the role Amazon Resource Name (ARN) of the role we just created:
t.add_resource(Pipeline( 
    "HelloWorldPipeline", 
    RoleArn=GetAtt("PipelineRole", "Arn"), 
  1. After this, we will reference the S3 bucket created earlier so that we have a place to store the different artifacts produced through the pipeline execution:
    ArtifactStore=ArtifactStore( 
        Type="S3", 
        Location=Ref("S3Bucket") 
    ), 
  1. We will now define each stage of the pipeline. The CloudFormation structure reflects what we did previously using the web interface. Each stage has a unique name and is composed of actions. Each action is defined by a name, a category, a configuration, and, optionally, input and output artifacts.

Our first stage will be the GitHub stage:

    Stages=[
Stages(
Name="Source",
Actions=[
Actions(
Name="Source",
ActionTypeId=ActionTypeID(
Category="Source",
Owner="ThirdParty",
Version="1",
Provider="GitHub"
),
Configuration={
"Owner": "ToBeConfiguredLater",
"Repo": "ToBeConfiguredLater",
"Branch": "ToBeConfiguredLater",
"OAuthToken": "ToBeConfiguredLater"
},
OutputArtifacts=[
OutputArtifacts(
Name="App"
)
],
)
]
),
  1. We will create a first artifact called App with the content of the repository. In order to avoid hardcoding any

Our next step will be to configure our build. As mentioned, we will simply call out to the CodeBuild stack we spawned up in the last section. Note we will store the output artifact under the name BuildOutput, meaning that we now have two artifacts, the App one and BuildOutput, which contains the tag.json file produced by CodeBuild:

        Stages(
Name="Build",
Actions=[
Actions(
Name="Container",
ActionTypeId=ActionTypeID(
Category="Build",
Owner="AWS",
Version="1",
Provider="CodeBuild"
),
Configuration={
"ProjectName": "HelloWorldContainer",
},
InputArtifacts=[
InputArtifacts(
Name="App"
)
],
OutputArtifacts=[
OutputArtifacts(
Name="BuildOutput"
)
],
)
]
),
  1. We will now create our staging deployment. Unlike before, we won't use CodeDeploy, but directly update our CloudFormation template. In order to accomplish that, we will need to provide the location of the template to the configuration of our action. Since we added it to our helloworld GitHub repository, we can reference it with the help of the App artifact. Our template is present under <directory root>/templates/helloworld-ecs-service-cf.template, which in turns means for CodePipeline App::templates/helloworld-ecs-service-cf.template.

The next trick in configuring our CloudFormation action relies on the fact that we can override the parameters provided for the stack. CloudFormation provides a couple of functions to help with dynamic parameters. You can read more about those at http://amzn.to/2kTgIUJ. We will rely on a particular one, Fn::GetParam. This function returns a value from a key-value pair file present in an artifact. This is where we take advantage of the file we created in CodeBuild as it will contain a JSON string, like { "tag": "<latest git commit sha>" }:

        Stages( 
            Name="Staging", 
            Actions=[ 
                Actions( 
                    Name="Deploy", 
                    ActionTypeId=ActionTypeID( 
                        Category="Deploy", 
                        Owner="AWS", 
                        Version="1", 
                        Provider="CloudFormation" 
                    ), 
                    Configuration={ 
                        "ChangeSetName": "Deploy", 
                        "ActionMode": "CREATE_UPDATE", 
                        "StackName": "helloworld-ecs-staging-service", 
                        "Capabilities": "CAPABILITY_NAMED_IAM", 
                        "TemplatePath": "App::templates/helloworld-ecs-service-cf.template", 
                        "RoleArn": GetAtt("CloudFormationHelloworldRole", "Arn"), 
                        "ParameterOverrides": """{"Tag" : { "Fn::GetParam" : [ "BuildOutput", "build.json", "tag" ] } }""" 
                    }, 
                    InputArtifacts=[ 
                        InputArtifacts( 
                            Name="App", 
                        ), 
                        InputArtifacts( 
                            Name="BuildOutput" 
                        ) 
                    ], 
                ) 
            ] 
        ), 
  1. After the staging deployment completes, we will request a manual approval, as follows:
        Stages( 
            Name="Approval", 
            Actions=[ 
                Actions( 
                    Name="Approval", 
                    ActionTypeId=ActionTypeID( 
                        Category="Approval", 
                        Owner="AWS", 
                        Version="1", 
                        Provider="Manual" 
                    ), 
                    Configuration={}, 
                    InputArtifacts=[], 
                ) 
            ] 
        ), 
  1. Finally, we will create a last stage to run the production deployment. The code is exactly the same as for staging except for the name of the stage and the stack targeted by our configuration:
        Stages( 
            Name="Production", 
            Actions=[ 
                Actions( 
                    Name="Deploy", 
                    ActionTypeId=ActionTypeID( 
                        Category="Deploy", 
                        Owner="AWS", 
                        Version="1", 
                        Provider="CloudFormation" 
                    ), 
                    Configuration={ 
                        "ChangeSetName": "Deploy", 
                        "ActionMode": "CREATE_UPDATE", 
                        "StackName": "helloworld-ecs-production-service", 
                        "Capabilities": "CAPABILITY_NAMED_IAM", 
                        "TemplatePath": "App::templates/helloworld-ecs-service-cf.template", 
                        "RoleArn": GetAtt("CloudFormationHelloworldRole", "Arn"), 
                        "ParameterOverrides": """{"Tag" : { "Fn::GetParam" : [ "BuildOutput", "build.json", "tag" ] } }""" 
                    }, 
                    InputArtifacts=[ 
                        InputArtifacts( 
                            Name="App", 
                        ), 
                        InputArtifacts( 
                            Name="BuildOutput" 
                        ) 
                    ], 
                ) 
            ] 
        ) 
    ], 
)) 

Our pipeline resource is now created; we can conclude the creation of our script by printing out our template:

print(t.to_json()) 

The script is ready to be used. It should look like this: http://bit.ly/2w3oVHw.

We can now create our pipeline.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset