Cicd For Cdk

29 Jan 2020

CI/CD for AWS CDK

I’ve written before about the AWS CDK but today I am writing about my experiences creating a CI/CD pipeline for my CDK work. The first thing I came across was that my code needed to be in github instead of our private bitbucket to make this work easier. Below is the code for my pipeline where I can just pass in the org, repo, stack name, and oauth secret arn. Walking through it the first thing I do is setup getting the oauth token for the read only user in our github org. Then I create the pipeline project and add the ability for its role to get the secret value. After that its just creating the three actions: get the github source, run the build, update the cloudformation stack.

from aws_cdk import (
    core,
    aws_codepipeline as codepipeline,
    aws_codepipeline_actions as codepipeline_actions,
    aws_codebuild as codebuild,
    aws_cloudformation as cloudformation,
    aws_iam as iam,
    aws_s3 as s3,
    aws_secretsmanager as secretsmanager
)

class Cicd(core.Construct):
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

    def pipeline(self,owner,repo,stack_name,oauth_token_arn,branch='master'):
        oauth = secretsmanager.Secret.from_secret_arn(self,repo+'-secret',secret_arn=oauth_token_arn)
        oauth_token = oauth.secret_value
        project = codebuild.PipelineProject(self,repo,build_spec=codebuild.BuildSpec.from_source_filename('buildspec.yaml'),project_name=repo,
                                                environment=codebuild.BuildEnvironment(privileged=True))
        project.add_to_role_policy(iam.PolicyStatement(actions=['secretsmanager:GetSecretValue'],resources=[oauth_token_arn]))
        github_source = codepipeline.Artifact('github-source')
        deploy_artifacts = codepipeline.Artifact('cfn_templates')
        github_source_action = codepipeline_actions.GitHubSourceAction(oauth_token=oauth_token,output=github_source,owner=owner,repo=repo,branch=branch,action_name='clone')
        code_build_action = codepipeline_actions.CodeBuildAction(action_name='build',input=github_source,outputs=[deploy_artifacts],project=project)
        update_stack_action = codepipeline_actions.CloudFormationCreateUpdateStackAction(action_name='deploy',template_path=deploy_artifacts.at_path('cdk.out/'+stack_name+'.template.json'),admin_permissions=True,stack_name=stack_name,
                                                                                          capabilities=[cloudformation.CloudFormationCapabilities.NAMED_IAM])
        pipeline = codepipeline.Pipeline(self,repo+'-pipeline',stages=[codepipeline.StageProps(actions=[github_source_action],stage_name='Source'),
                                                               codepipeline.StageProps(actions=[code_build_action],stage_name='Build'),
                                                               codepipeline.StageProps(actions=[update_stack_action],stage_name='Deploy')])
        return pipeline

The code build action requires a buildspec.yaml file to let it know what to do during the build. Below I have pasted ours with some redactions denoted with << and >>. Since the common repo is also kept private I had to modify the requirements.txt to get the repo using an https url.

version: '0.2'
env:
  secrets-manager:
    oauth_token: <<secret_name>>
phases:
  install:
    commands:
      - npm i -g aws-cdk
      - cdk --version
  build:
    commands:
      - . .env/bin/activate
      - sed -i s%git@github.com:<<function_org>>/<<function_repo>>%https://<<user>>:${oauth_token}@github.com/<<function_org>>>/<<function_repo>>%g requirements.txt
      - pip3 install -r requirements.txt
      - cdk synth
artifacts:
  files:
    - 'cdk.out/*'
  name: cfn_templates