Setup CI/CD pipeline

Now you have source code in AWS CodeCommit and empty Amazon ECR repository, you can setup AWS CodePipeline to automatically build container image with your application and push it to Amazon ECR.

AWS CodePipeline is a fully managed continuous delivery service that helps you automate your release pipelines for fast and reliable application and infrastructure updates. CodePipeline automates the build, test, and deploy phases of your release process every time there is a code change, based on the release model you define.

Open NorthwindCdk.sln in Visual Studio.

Add following Nuget packages to the project:

  • Amazon.CDK.AWS.CodeBuild
  • Amazon.CDK.AWS.CodePipeline
  • Amazon.CDK.AWS.CodePipeline.Actions

Nuget Nuget Nuget

Open NorthwindCdkStack.cs.

Add following using statements:

using Amazon.CDK.AWS.CodeBuild;
using Amazon.CDK.AWS.CodePipeline;
using Amazon.CDK.AWS.CodePipeline.Actions;
using System.Collections.Generic;

Please add the following changes at the end of NorthwindCdkStack constructor:

namespace NorthwindCdk
{
    public class NorthwindCdkStack : Stack
    {
        internal NorthwindCdkStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
        {
            .......

            // CodeBuild and CodePipeline

            var buildProject = new PipelineProject(this, "BuildProject", new PipelineProjectProps 
            {
                Vpc = vpc,
                Description = "Build project for the Northwind application",
                Environment = new BuildEnvironment 
                { 
                    BuildImage = LinuxBuildImage.STANDARD_3_0,
                    Privileged = true
                },
                EnvironmentVariables = new Dictionary<string, IBuildEnvironmentVariable>()
                {
                    { "AWS_DEFAULT_REGION", new BuildEnvironmentVariable { Type = BuildEnvironmentVariableType.PLAINTEXT, Value = this.Region} },
                    { "AWS_ACCOUNT_ID", new BuildEnvironmentVariable { Type = BuildEnvironmentVariableType.PLAINTEXT, Value = this.Account} },
                    { "IMAGE_TAG", new BuildEnvironmentVariable { Type = BuildEnvironmentVariableType.PLAINTEXT, Value = "Latest"} },
                    { "IMAGE_REPO_NAME", new BuildEnvironmentVariable { Type = BuildEnvironmentVariableType.PLAINTEXT, Value = containerRegistry.RepositoryName} }
                }
            });

            containerRegistry.GrantPullPush(buildProject);

            var sourceOutput = new Artifact_("Source");

            var sourceAction = new CodeCommitSourceAction(new CodeCommitSourceActionProps
            {
                ActionName = "Source",
                Branch = "master",
                Repository = codeRepository,
                Output = sourceOutput
            });

            var buildAction = new CodeBuildAction(new CodeBuildActionProps
            {
                ActionName = "Build",
                Project = buildProject,
                Input = sourceOutput
            });

            var pipeline = new Pipeline(this, "Pipeline", new PipelineProps
            {
                PipelineName = "NorthwindPipeline",
                Stages = new Amazon.CDK.AWS.CodePipeline.StageProps[]
                {
                    new  Amazon.CDK.AWS.CodePipeline.StageProps
                    {
                        StageName = "Source",
                        Actions = new IAction[]
                        {
                            sourceAction
                        }
                    },
                    new Amazon.CDK.AWS.CodePipeline.StageProps
                    {
                        StageName = "Build",
                        Actions = new IAction[]
                        {
                            buildAction
                        }
                    }
                }
            });

        }
    }
}

The code above is quite long and it does the following:

  • Creates build project and passes environment variables required by buildspec.yml.
  • Grants build project push/pull rights to be able to push containers to Amazon ECR
containerRegistry.GrantPullPush(buildProject);
  • Creates Source build action that gets sources from AWS CodeCommit repository
var sourceAction = new CodeCommitSourceAction(new CodeCommitSourceActionProps
{
    ActionName = "Source",
    Branch = "master",
    Repository = codeRepository,
    Output = sourceOutput
});
  • Creates Build build action that triggers AWS CodeBuild.
var buildAction = new CodeBuildAction(new CodeBuildActionProps
{
    ActionName = "Build",
    Project = buildProject,
    Input = sourceOutput
});
  • Creates AWS CodePipeline with these two actions

Save changes and compile the project.

Run cdk diff to take a look at the changes that are going to be deployed.

cdk diff

The output should look like this:

Stack NorthwindCdkStack
IAM Statement Changes
┌───┬─────────────────────────────────────────┬────────┬───────────────────────────────────────────────────┬───────────────────────────────────────────────────┬────────────────────────┐
│   │ Resource                                │ Effect │ Action                                            │ Principal                                         │ Condition              │
├───┼─────────────────────────────────────────┼────────┼───────────────────────────────────────────────────┼───────────────────────────────────────────────────┼────────────────────────┤
│ + │ ${BuildProject.Arn}                     │ Allow  │ codebuild:BatchGetBuilds                          │ AWS:${Pipeline/Build/Build/CodePipelineActionRole │                        │
│   │                                         │        │ codebuild:StartBuild                              │ }                                                 │                        │
│   │                                         │        │ codebuild:StopBuild                               │                                                   │                        │
├───┼─────────────────────────────────────────┼────────┼───────────────────────────────────────────────────┼───────────────────────────────────────────────────┼────────────────────────┤
│ + │ ${BuildProject/Role.Arn}                │ Allow  │ sts:AssumeRole                                    │ Service:codebuild.amazonaws.com                   │                        │
├───┼─────────────────────────────────────────┼────────┼───────────────────────────────────────────────────┼───────────────────────────────────────────────────┼────────────────────────┤
│ + │ ${NorthwindCodeRepository.Arn}          │ Allow  │ codecommit:CancelUploadArchive                    │ AWS:${Pipeline/Source/Source/CodePipelineActionRo │                        │
│   │                                         │        │ codecommit:GetBranch                              │ le}                                               │                        │
│   │                                         │        │ codecommit:GetCommit                              │                                                   │                        │
│   │                                         │        │ codecommit:GetUploadArchiveStatus                 │                                                   │                        │
│   │                                         │        │ codecommit:UploadArchive                          │                                                   │                        │
├───┼─────────────────────────────────────────┼────────┼───────────────────────────────────────────────────┼───────────────────────────────────────────────────┼────────────────────────┤
│ + │ ${NorthwindContainerRegistry.Arn}       │ Allow  │ ecr:BatchCheckLayerAvailability                   │ AWS:${BuildProject/Role}                          │                        │
│   │                                         │        │ ecr:BatchGetImage                                 │                                                   │                        │
│   │                                         │        │ ecr:GetDownloadUrlForLayer                        │                                                   │                        │
│ + │ ${NorthwindContainerRegistry.Arn}       │ Allow  │ ecr:CompleteLayerUpload                           │ AWS:${BuildProject/Role}                          │                        │
│   │                                         │        │ ecr:InitiateLayerUpload                           │                                                   │                        │
│   │                                         │        │ ecr:PutImage                                      │                                                   │                        │
│   │                                         │        │ ecr:UploadLayerPart                               │                                                   │                        │
├───┼─────────────────────────────────────────┼────────┼───────────────────────────────────────────────────┼───────────────────────────────────────────────────┼────────────────────────┤
│ 
│  ....................
│ 
└───┴─────────────────────────────────────────┴────────┴───────────────────────────────────────────────────┴───────────────────────────────────────────────────┴────────────────────────┘
Security Group Changes
┌───┬───────────────────────────────────────┬─────┬────────────┬─────────────────┐
│   │ Group                                 │ Dir │ Protocol   │ Peer            │
├───┼───────────────────────────────────────┼─────┼────────────┼─────────────────┤
│ + │ ${BuildProject/SecurityGroup.GroupId} │ Out │ Everything │ Everyone (IPv4) │
└───┴───────────────────────────────────────┴─────┴────────────┴─────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[+] AWS::Events::Rule NorthwindCodeRepository/NorthwindCdkStackPipeline9B6AE2E1EventRule NorthwindCodeRepositoryNorthwindCdkStackPipeline9B6AE2E1EventRule1713EBCE
[+] AWS::IAM::Role BuildProject/Role BuildProjectRoleAA92C755
[+] AWS::IAM::Policy BuildProject/Role/DefaultPolicy BuildProjectRoleDefaultPolicy3E9F248C
[+] AWS::EC2::SecurityGroup BuildProject/SecurityGroup BuildProjectSecurityGroupE1E38A88
[+] AWS::CodeBuild::Project BuildProject BuildProject097C5DB7
[+] AWS::IAM::Policy BuildProject/PolicyDocument BuildProjectPolicyDocument17F414D3
[+] AWS::KMS::Key Pipeline/ArtifactsBucketEncryptionKey PipelineArtifactsBucketEncryptionKey01D58D69
[+] AWS::S3::Bucket Pipeline/ArtifactsBucket PipelineArtifactsBucket22248F97
[+] AWS::KMS::Alias Pipeline/ArtifactsBucketEncryptionKeyAlias PipelineArtifactsBucketEncryptionKeyAlias5C510EEE
[+] AWS::IAM::Role Pipeline/Role PipelineRoleD68726F7
[+] AWS::IAM::Policy Pipeline/Role/DefaultPolicy PipelineRoleDefaultPolicyC7A05455
[+] AWS::CodePipeline::Pipeline Pipeline PipelineC660917D
[+] AWS::IAM::Role Pipeline/Source/Source/CodePipelineActionRole PipelineSourceCodePipelineActionRoleC6F9E7F5
[+] AWS::IAM::Policy Pipeline/Source/Source/CodePipelineActionRole/DefaultPolicy PipelineSourceCodePipelineActionRoleDefaultPolicy2D565925
[+] AWS::IAM::Role Pipeline/EventsRole PipelineEventsRole46BEEA7C
[+] AWS::IAM::Policy Pipeline/EventsRole/DefaultPolicy PipelineEventsRoleDefaultPolicyFF4FCCE0
[+] AWS::IAM::Role Pipeline/Build/Build/CodePipelineActionRole PipelineBuildCodePipelineActionRoleD77A08E6
[+] AWS::IAM::Policy Pipeline/Build/Build/CodePipelineActionRole/DefaultPolicy PipelineBuildCodePipelineActionRoleDefaultPolicyC9CB73F8

As you can see there are 18 new resources that will be created and a bunch of IAM roles.

Next deploy the updates using cdk deploy command.

cdk deploy

First it will show you long list of IAM statement changes as AWS CodePipeline need to access Amazon S3, AWS KMS, AWS CodeCommit and AWS CodeBuild. Please confirm by choosing Y.

Once it’s deployed, you will see the following output:

NorthwindCdkStack: deploying...
NorthwindCdkStack: creating CloudFormation changeset...
 0/20 | 10:56:45 AM | CREATE_IN_PROGRESS   | AWS::IAM::Role                        | Pipeline/EventsRole (PipelineEventsRole46BEEA7C)

  ..........

 16/20 | 10:59:00 AM | CREATE_IN_PROGRESS   | AWS::IAM::Policy                      | Pipeline/EventsRole/DefaultPolicy (PipelineEventsRoleDefaultPolicyFF4FCCE0) Resource creation Initiated
 16/20 | 10:59:08 AM | CREATE_IN_PROGRESS   | AWS::KMS::Alias                       | Pipeline/ArtifactsBucketEncryptionKeyAlias (PipelineArtifactsBucketEncryptionKeyAlias5C510EEE) Resource creation Initiated
 17/20 | 10:59:08 AM | CREATE_COMPLETE      | AWS::KMS::Alias                       | Pipeline/ArtifactsBucketEncryptionKeyAlias (PipelineArtifactsBucketEncryptionKeyAlias5C510EEE)
 18/20 | 10:59:15 AM | CREATE_COMPLETE      | AWS::IAM::Policy                      | Pipeline/EventsRole/DefaultPolicy (PipelineEventsRoleDefaultPolicyFF4FCCE0)
 18/20 Currently in progress: NorthwindCodeRepositoryNorthwindCdkStackPipeline9B6AE2E1EventRule1713EBCE
 19/20 | 11:00:01 AM | CREATE_COMPLETE      | AWS::Events::Rule                     | NorthwindCodeRepository/NorthwindCdkStackPipeline9B6AE2E1EventRule (NorthwindCodeRepositoryNorthwindCdkStackPipeline9B6AE2E1EventRule1713EBCE)

 ✅  NorthwindCdkStack

Outputs:
NorthwindCdkStack.ContainerRegistry = 404486542784.dkr.ecr.eu-west-1.amazonaws.com/northwind
NorthwindCdkStack.CodeRepository = https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/Northwind
NorthwindCdkStack.PostgreSQLEndpointAddress = northwind-postgresql.cluster-cyhlwzws5aoz.eu-west-1.rds.amazonaws.com

Stack ARN:
arn:aws:cloudformation:eu-west-1:404486542784:stack/NorthwindCdkStack/f78fb1c0-8ed1-11ea-8a83-0a0af0d573f8

Once stack is deployed, go to AWS Console, open CodePipeline and check that it is created.

CodePipeline

Once build is succeeded, check that container image is pushed to Amazon ECR. This container image will be used to run the application in Amazon ECS.

ECR