Automate CodeCommit and CodePipeline in AWS CloudFormation

Amazon Web Services (AWS) recently announced the integration of AWS CodeCommit with AWS CodePipeline. This means you can now use CodeCommit as a version-control repository as part of your pipelines! AWS describes how to manucodepipeline_codecommitally configure this integration at Simple Pipeline Walkthrough (AWS CodeCommit Repository).

One of the biggest benefits of using CodeCommit is its seamless integration with other AWS services including IAM.

After describing how to create and connect to a new CodeCommit repository, in this blog post, I’ll explain how to fully automate the provisioning of all of the AWS resources in CloudFormation to achieve Continuous Delivery. This includes CodeCommit, CodePipeline, CodeDeploy and IAM using a sample application provided by AWS.

Create a CodeCommit Repository

To create a new CodeCommit version-control repository, go to your AWS console and select CodeCommit under Developer Tools. Click the Create new repository button, enter a unique repository name and a description and click Create repository. Next, you will connect to the repository.

create_codecommit_repo

Connect to the CodeCommit Repository

There are a couple of ways to connect to an existing CodeCommit repository. One is via https and the other ssh. We’re going to focus on the connecting via SSH in this post. For additional information, see Connect to an AWS CodeCommit Repository. The instructions in this section are based on CodeCommit Setup from Andrew Puch.

The first thing you’ll do is create an IAM user. You do not need to create a password or access keys at this time. To do this, go to the AWS IAM console, then the Users section and create a new user. Once you’ve created the new user, open a terminal and type the following. NOTE: These instructions are designed for Mac/Linux environments.

cd $HOME/.ssh
ssh-keygen

When prompted after typing ssh-keygen, use a name like codecommit_rsa and leave all fields blank and just hit enter

cat codecommit_rsa.pub

Go to IAM, select the user you previously created, click on the Security Credentials tab and click the Upload SSH public key button. Copy the contents of the codecommit_rsa.pub to the text box in the Upload SSH public key section and save.

Go back to your terminal and type:

touch config
chmod 600 config
sudo vim config

The YOUR_SSH_KEY_ID_FROM_IAM variable below is the row value for the SSH Key Id that you created when uploading the public key. You will replace this placeholder and brackets with the value for the SSH Key Id from the Security Credentials tab for the IAM user you’d previously created.

Host git-codecommit.*.amazonaws.com
  User [YOUR_SSH_KEY_ID_FROM_IAM]
  IdentityFile ~/.ssh/codecommit_rsa

To verify your SSH connection works, type:

ssh git-codecommit.us-east-1.amazonaws.com

To clone a local copy of the CodeCommit repo you just created, type something like (your region and/or repo name may be different):

git clone ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/codecommit-demo

You’ll be using this local copy of your CodeCommit repo later.

Manually Integrate CodeCommit with CodePipeline

Follow the instructions for manually integrating CodeCommit with CodePipeline as described in Simple Pipeline Walkthrough (AWS CodeCommit Repository). Name the pipeline CodeCommitPipeline.

Create a CloudFormation Template

In this solution, the CloudFormation template is composed of several components. It includes launching EC2 instances and installing the CodeDeploy agent on the instances, provisioning a CodeDeploy application and DeploymentGroup, creating an IAM role that defines a policy for all resources used in the solution including CodeCommit, and provisioning the CodePipeline pipeline stages and action – including the inclusion of CodeCommit.

codepipeline_cc_arch

Launch EC2 instances and install CodeDeploy agent

To launch the EC2 instances that are used by CodeDeploy to deploy the sample application, I’m using a CloudFormation template provided by AWS as part of a nested stack. I’m passing in the name of my EC2 Key Pair along with the tag that CodeDeploy will use to run a deployment against EC2 instances labeled with this tag.

    "CodeDeployEC2InstancesStack":{
      "Type":"AWS::CloudFormation::Stack",
      "Properties":{
        "TemplateURL":"http://s3.amazonaws.com/aws-codedeploy-us-east-1/templates/latest/CodeDeploy_SampleCF_Template.json",
        "TimeoutInMinutes":"60",
        "Parameters":{
          "TagValue":{
            "Ref":"TagValue"
          },
          "KeyPairName":{
            "Ref":"EC2KeyPairName"
          }
        }
      }
    },

Create CodeDeploy Application and Deployment Group

In the snippet below, I’m creating a simple CodeDeploy application along with a DeploymentGroup. I’m passing in the location of the S3 bucket and key that hosts the sample application provided by AWS. You can find the sample application at AWS CodeDeploy Resource Kit. In my template, I’m entering aws-codedeploy-us-east-1 for the S3Bucket parameter and samples/latest/SampleApp_Linux.zip for the S3Key parameter. It translates to this location in S3: s3://aws-codedeploy-us-east-1/samples/latest/SampleApp_Linux.zip. Finally, the stack from the CloudFormation template that provisions the EC2 instances for CodeDeploy provides CodeDeployTrustRoleARN as an output that I use in defining the permissions for my CodeDeploy DeploymentGroup.

    "MyApplication":{
      "Type":"AWS::CodeDeploy::Application",
      "DependsOn":"CodeDeployEC2InstancesStack"
    },
    "MyDeploymentGroup":{
      "Type":"AWS::CodeDeploy::DeploymentGroup",
      "DependsOn":"MyApplication",
      "Properties":{
        "ApplicationName":{
          "Ref":"MyApplication"
        },
        "Deployment":{
          "Description":"First time",
          "IgnoreApplicationStopFailures":"true",
          "Revision":{
            "RevisionType":"S3",
            "S3Location":{
              "Bucket":{
                "Ref":"S3Bucket"
              },
              "BundleType":"Zip",
              "Key":{
                "Ref":"S3Key"
              }
            }
          }
        },
        "Ec2TagFilters":[
          {
            "Key":{
              "Ref":"TagKey"
            },
            "Value":{
              "Ref":"TagValue"
            },
            "Type":"KEY_AND_VALUE"
          }
        ],
        "ServiceRoleArn":{
          "Fn::GetAtt":[
            "CodeDeployEC2InstancesStack",
            "Outputs.CodeDeployTrustRoleARN"
          ]
        }
      }
    },

Create an IAM role and policy to include CodeCommit

In previous posts on CodePipeline, I’d relied on the fact that, by default, AWS has created an AWS-CodePipeline-Service role in IAM. This is, frankly, a lazy and error-prone way of getting the proper permissions assigned to my AWS resources. The reason it’s error prone is because anyone else using the template might have modified the permissions of this built-in IAM role. Because the CodeCommit integration is new, I needed to add the CodeCommit permissions to my IAM policy so I decided to create a new IAM role on the fly as part of my CloudFormation template. This provides the added benefit of assuming nothing else had been previously created in the user’s AWS account.

Below, I’ve included the relevant IAM policy snippet that provides CodePipeline access to certain CodeCommit actions.

"PolicyName":"codepipeline-service",
  "PolicyDocument":{
    "Statement":[
      {
        "Action":[
          "codecommit:GetBranch",
          "codecommit:GetCommit",
          "codecommit:UploadArchive",
          "codecommit:GetUploadArchiveStatus",
          "codecommit:CancelUploadArchive"
        ],
          "Resource":"*",
          "Effect":"Allow"
      },

 

Create a pipeline in CodePipeline using CloudFormation

To get the configuration from the pipeline you manually created in the “Manually Integrate CodeCommit with CodePipeline” step from above, go to your AWS CLI and type:

aws codepipeline get-pipeline --name CodeCommitPipeline > pipeline.json

You will use the contents of the pipeline.json in a latter step.

Below, you can see that I’m creating the initial part of the pipeline in CloudFormation. I’m referring to the IAM role that I created previously in the template. This uses the AWS::CodePipeline::Pipeline CloudFormation resource.

  "CodePipelineStack":{
      "Type":"AWS::CodePipeline::Pipeline",
      "Properties":{
        "RoleArn":{
          "Fn::Join":[
            "",
            [
              "arn:aws:iam::",
              {
                "Ref":"AWS::AccountId"
              },
              ":role/",
              {
                "Ref":"CodePipelineRole"
              }
            ]
          ]
        },

Source Stage for CodeCommit

I got the contents for the stages and actions by copying the contents from the pipeline.json that I’d created above and pasted them into my CloudFormation template in the CodePipeline resource section. After copying the contents, I updated the template to use title case vs. camel case for some of the attribute names in order to conform to the CloudFormation DSL.

For the CodePipeline Source stage and action of this CloudFormation template, I’m referring to the CodeCommit Provider as my Source category. There are no input artifacts and the OutputArtifacts is defined as MyApp. This is used in all downstream stages as part of each action’s InputArtifacts.

        "Stages":[
          {
            "Name":"Source",
            "Actions":[
              {
                "InputArtifacts":[

                ],
                "Name":"Source",
                "ActionTypeId":{
                  "Category":"Source",
                  "Owner":"AWS",
                  "Version":"1",
                  "Provider":"CodeCommit"
                },
                "OutputArtifacts":[
                  {
                    "Name":"MyApp"
                  }
                ],
                "Configuration":{
                  "BranchName":{
                    "Ref":"RepositoryBranch"
                  },
                  "RepositoryName":{
                    "Ref":"RepositoryName"
                  }
                },
                "RunOrder":1
              }
            ]
          },

Beta Stage for CodeDeploy

The beta stage refers to the CodeDeploy DeploymentGroup and Application that were created in previously-defined resources in this CloudFormation template. In the Configuration for this action, I’m referring to these previously-defined references using the Ref intrinsic function in CloudFormation.

          {
            "Name":"Beta",
            "Actions":[
              {
                "InputArtifacts":[
                  {
                    "Name":"MyApp"
                  }
                ],
                "Name":"DemoFleet",
                "ActionTypeId":{
                  "Category":"Deploy",
                  "Owner":"AWS",
                  "Version":"1",
                  "Provider":"CodeDeploy"
                },
                "OutputArtifacts":[

                ],
                "Configuration":{
                  "ApplicationName":{
                    "Ref":"MyApplication"
                  },
                  "DeploymentGroupName":{
                    "Ref":"MyDeploymentGroup"
                  }
                },
                "RunOrder":1
              }
            ]
          }

Store the Pipeline Artifact

You can store the artifact that’s transitioned through the actions in CodePipeline using any S3 bucket for which you have permissions. The template I provide as part of this sample solution dynamically generates a bucket name that should work for anyone’s AWS account as it uses the bucket that AWS CodePipeline defines which refers to the user’s current region and account id.

     ],
        "ArtifactStore":{
          "Type":"S3",
          "Location":{
            "Fn::Join":[
              "",
              [
                "codepipeline-",
                {
                  "Ref":"AWS::Region"
                },
                "-",
                {
                  "Ref":"AWS::AccountId"
                }
              ]
            ]
          }
        }
      }
    }

Launch the Stack

To launch the CloudFormation stack, simply click the button below to launch the template from the CloudFormation console in your AWS account. You’ll need to enter values for the following parameters: Stack name, EC2 KeyPair NameCodeCommit Repository Name, CodeCommit Repository Branch and, optionally, Tag Value for CodeDeploy EC2 instances. You can keep the default values for the other parameters.

codepipeline_cc_cfn

To launch the same stack from your AWS CLI, type the following (while modifying the same values described above):

aws cloudformation create-stack --stack-name CodePipelineCodeCommitStack 
--template-url https://s3.amazonaws.com/stelligent-training-public/public/codepipeline/codepipeline-codecommit.json 
--region us-east-1 --disable-rollback --capabilities="CAPABILITY_IAM" 
--parameters ParameterKey=EC2KeyPairName,ParameterValue=YOUREC2KEYPAIR 
ParameterKey=TagValue,ParameterValue=EC2TAG4CODEDEPLOY 
ParameterKey=RepositoryName,ParameterValue=codecommit-demo 
ParameterKey=RepositoryBranch,ParameterValue=master

Access the Application

Once the CloudFormation stacks have successfully completed, go to CodeDeploy and select Deployments. For example, if you’re in the us-east-1 region, the URL might look like: https://console.aws.amazon.com/codedeploy/home?region=us-east-1#/deployments. Click on the link for the Deployment Id of the deployment you just launched from CloudFormation. Then, click on the link for the Instance Id. From the EC2 instance, copy the Public DNS value and paste into your browser and hit enter. You should see a page like the one below.

codedeploy_deployment

Commit Changes to CodeCommit

Make some visual changes to the code and commit these changes to your CodeCommit repository to see these changes get deployed through your pipeline. You perform these actions from the directory where you cloned a local version of your CodeCommit repo (in the directory created by your git clone command). Some example command-line operations are shown below.

git commit -am "change color to dark orange"
git push

Once these changes have been committed, CodePipeline will discover the changes made to your CodeCommit repo and initiate a new pipeline. After the pipeline is successfully completed, follow the same instructions for launching the application from your browser.

codedeploy_deployment-new

Troubleshooting

In this section, I describe how to fix some of the common errors that you might experience.

After you’ve included CodeCommit as a Source provider in the Source action of a Source stage and run the pipeline, you might get an “Insufficient permissions” error in CodePipeline – like the one you see below.

codepipeline_codecommit_error

To fix this, make sure that the IAM role you’re using has the proper permissions to the appropriate codecommit.* actions in the IAM policy for the role. In the example, I’ve done this by defining the IAM role in CloudFormation and then assigning this role to the pipeline in CodePipeline.

Another error you might see when launching the example stack is when the S3 bucket does not exist or your user does not have the proper permissions to the bucket. Unfortunately, if this happens, all you’ll see is an “Internal Error” in the Source action/stage like the one below.

codepipeline_codecommit_s3_error

Lastly, if you use the default CodeCommit repository name and you’ve not created a repo with the same name or have not matched the CloudFormation parameter value with your CodeCommit repository name, you’ll see an error like this:

codepipeline_codecommit_repo_error

Sample Code

The code for the examples demonstrated in this post are located at https://github.com/stelligent/stelligent_commons/blob/master/cloudformation/codecommit/codepipeline-codecommit.json. Let us know if you have any comments or questions @stelligent or @paulduvall.

Stelligent is hiring! Do you enjoy working on complex problems like figuring out ways to automate all the things as part of a deployment pipeline? Do you believe in the “everything-as-code” mantra? If your skills and interests lie at the intersection of DevOps automation and the AWS cloud, check out the careers page on our website.

Resources

Leave a Reply