AWS CDK CodePipeline Example with GitHub Integration
Introduction
In this post, we will explore how to create an AWS CodePipeline using AWS CDK. The pipeline includes integration with GitHub as the source, CodeBuild for building the application, and CloudFormation for deploying the resources. Additionally, we’ll configure a custom S3 bucket for storing pipeline artifacts.
Prerequisites
Before proceeding, ensure you have the following:
- AWS CDK installed and configured.
- A GitHub repository with the application code and a
buildspec.ymlfile. - A GitHub OAuth token stored in AWS Secrets Manager.
- Proper IAM roles configured for CodePipeline and CodeBuild.
Key Components
The pipeline has three stages:
- Source Stage: Pulls code from a GitHub repository.
- Build Stage: Uses CodeBuild to build the application.
- Deploy Stage: Deploys resources using CloudFormation.
Code Breakdown
Below is a detailed explanation of the CDK code used to create the pipeline.
1. Asset Bucket
An S3 bucket is created to store pipeline artifacts. The bucket has a lifecycle rule to delete files after 7 days.
const assetBucket = new s3.Bucket(this, '<construct id>', {
bucketName: '<bucketName>',
lifecycleRules: [
{
id: 'DeleteAfter7Days',
enabled: true,
expiration: cdk.Duration.days(7),
},
],
});
2. IAM Role
The CodePipeline uses an existing IAM role specified by its ARN.
const codepipelineRole = iam.Role.fromRoleArn(this, '<construct id>', '<CODE_PIPELINE_ROLE_ARN>', {
mutable: false,
});
3. Build Project
A CodeBuild project is configured to build the application. The project uses Amazon Linux 2 as the build environment.
const buildProject = new codebuild.PipelineProject(this, '<construct id>', {
projectName: '<projectName>',
role: codepipelineRole,
queuedTimeout: cdk.Duration.hours(0.5),
timeout: cdk.Duration.hours(0.5),
buildSpec: codebuild.BuildSpec.fromSourceFilename('buildspec.yml'),
environment: {
buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
computeType: codebuild.ComputeType.SMALL,
environmentVariables: {
XX: {
type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
value: '<value>',
},
},
},
});
Specifies the build specification file, buildspec.yml, which contains the build commands and configurations for the CodeBuild project. This ensures that the build process is defined and version-controlled alongside your application code.
4. Pipeline Definition
The pipeline is configured with three stages: Source, Build, and Deploy. It uses the previously defined S3 bucket and IAM role.
const pipeline = new codepipeline.Pipeline(this, '<construct id>', {
pipelineName: '<pipelineName>',
artifactBucket: assetBucket,
role: codepipelineRole,
pipelineType: codepipeline.PipelineType.V1,
});
5. Source Stage
The source stage pulls code from a GitHub repository using a webhook trigger. The GitHub OAuth token is securely retrieved from AWS Secrets Manager.
const sourceAction = new codepipeline_actions.GitHubSourceAction({
actionName: 'GitHubSource',
owner: '<GitHub account user>',
repo: '<GitHub repository>',
branch: '<branch>',
oauthToken: cdk.SecretValue.secretsManager('github-token', {
jsonField: 'oauthToken',
}),
output: sourceOutput,
trigger: codepipeline_actions.GitHubTrigger.WEBHOOK,
runOrder: 1,
});
Explanation:
cdk.SecretValue.secretsManager: This method securely fetches a secret stored in AWS Secrets Manager.'github-token': This is the name of the secret in Secrets Manager. The secret must already exist and contain the necessary OAuth token.jsonField: 'oauthToken': If the secret is a JSON object, this specifies the field from which to retrieve the value. In this case, it fetches theoauthTokenfield.
This approach ensures that sensitive credentials like OAuth tokens are not hardcoded in the application, enhancing security.
6. Build Stage
The build stage executes the CodeBuild project defined earlier.
const buildAction = new codepipeline_actions.CodeBuildAction({
actionName: 'Build',
project: buildProject,
input: sourceOutput,
outputs: [buildOutput],
role: codepipelineRole,
runOrder: 2,
});
7. Deploy Stage
The deploy stage uses a CloudFormation action to create or update the stack.
const deployAction = new codepipeline_actions.CloudFormationCreateUpdateStackAction({
actionName: 'CloudFormation-CreateUpdateStack',
stackName: '<stackName>',
adminPermissions: true,
templatePath: buildOutput.atPath('cloudformation-template.yaml'),
deploymentRole: codepipelineRole,
replaceOnFailure: true,
role: codepipelineRole,
runOrder: 3,
});
Specifies the location of the cloudformation-template.yaml in the build artifact output. This template defines the resources to be deployed or updated during the deployment stage.
8. Adding Stages to the Pipeline
Finally, the stages are added to the pipeline.
pipeline.addStage({
stageName: 'Source',
actions: [sourceAction],
});
pipeline.addStage({
stageName: 'Build',
actions: [buildAction],
});
pipeline.addStage({
stageName: 'Deploy',
actions: [deployAction],
});
Complete code
import * as cdk from 'aws-cdk-lib';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import * as codepipeline from 'aws-cdk-lib/aws-codepipeline';
import * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3 from 'aws-cdk-lib/aws-s3';
import type { Construct } from 'constructs';
export class CodePipelineStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
/** asset bucket */
const assetBucket = new s3.Bucket(this, `<construct id>`, {
bucketName: `<bucketName>`,
lifecycleRules: [
{
id: 'DeleteAfter7Days',
enabled: true,
expiration: cdk.Duration.days(7),
},
],
});
const codepipelineRole = iam.Role.fromRoleArn(this, `<construct id>`, '<CODE_PIPELINE_ROLE_ARN>', {
mutable: false,
});
const buildProject = new codebuild.PipelineProject(this, `<construct id>`, {
projectName: `<projectName>`,
role: codepipelineRole,
queuedTimeout: cdk.Duration.hours(0.5),
timeout: cdk.Duration.hours(0.5),
buildSpec: codebuild.BuildSpec.fromSourceFilename('buildspec.yml'),
environment: {
buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
computeType: codebuild.ComputeType.SMALL,
environmentVariables: {
XX: {
type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
value: '<value>',
},
},
},
});
const pipeline = new codepipeline.Pipeline(this, `<construct id>`, {
pipelineName: `<pipelineName>`,
artifactBucket: assetBucket,
role: codepipelineRole,
pipelineType: codepipeline.PipelineType.V1,
});
const sourceOutput = new codepipeline.Artifact();
const buildOutput = new codepipeline.Artifact();
const sourceAction = new codepipeline_actions.GitHubSourceAction({
actionName: 'GitHubSource',
owner: '<GitHub account user>',
repo: '<GitHub repository>',
branch: '<branch>',
oauthToken: cdk.SecretValue.secretsManager('github-token', {
jsonField: 'oauthToken',
}),
output: sourceOutput,
trigger: codepipeline_actions.GitHubTrigger.WEBHOOK,
runOrder: 1,
});
const buildAction = new codepipeline_actions.CodeBuildAction({
actionName: 'Build',
project: buildProject,
input: sourceOutput,
outputs: [buildOutput],
role: codepipelineRole,
runOrder: 2,
});
const deployAction = new codepipeline_actions.CloudFormationCreateUpdateStackAction({
actionName: 'CloudFormation-CreateUpdateStack',
stackName: `<stackName>`,
adminPermissions: true,
templatePath: buildOutput.atPath(`cloudformation-template.yaml`),
deploymentRole: codepipelineRole,
replaceOnFailure: true,
role: codepipelineRole,
runOrder: 3,
});
pipeline.addStage({
stageName: 'Source',
actions: [sourceAction],
});
pipeline.addStage({
stageName: 'Build',
actions: [buildAction],
});
pipeline.addStage({
stageName: 'Deploy',
actions: [deployAction],
});
}
}
Conclusion
This example demonstrates how to build a robust CI/CD pipeline using AWS CDK. The integration with GitHub allows automatic triggers for every commit, while the deployment via CloudFormation ensures your infrastructure is always up-to-date.
With this pipeline, you can streamline the deployment process and focus more on building features for your application.