├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE.txt ├── README.md ├── app.py ├── cdk.json ├── cdk_pipelines_multi_branch ├── __init__.py ├── cicd │ ├── __init__.py │ ├── aspects │ │ └── key_rotation_aspect.py │ ├── cdk_pipelines_multi_branch_stack.py │ ├── code │ │ ├── create_branch.py │ │ └── destroy_branch.py │ ├── constructs │ │ └── standard_bucket.py │ └── iam_stack.py └── src │ ├── __init__.py │ ├── application_stage.py │ ├── lambda │ └── lambda-handler.py │ └── s3trigger │ ├── __init__.py │ └── s3trigger_stack.py ├── config.ini ├── diagrams ├── architecture.drawio └── architecture.png ├── initial-deploy.sh ├── requirements-dev.txt ├── requirements.txt └── source.bat /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | package-lock.json 3 | __pycache__ 4 | .pytest_cache 5 | .env 6 | .venv 7 | *.egg-info 8 | 9 | # CDK asset staging directory 10 | .cdk.staging 11 | cdk.out 12 | .idea 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All changes to this project will be documented in this file. 3 | 4 | ## 2022-05-25 5 | 6 | ### Changed 7 | - Upgrade to CDK Version 2 8 | ### Added 9 | 10 | ### Fixed -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | This product includes software from the Boto3 project (Apache License) 2 | https://github.com/boto/boto3 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Using AWS CDK Pipelines and AWS Lambda for multi-branch pipeline management and infrastructure deployment. 2 | 3 | This project shows how to use the [AWS CDK Pipelines module](https://docs.aws.amazon.com/cdk/api/latest/docs/pipelines-readme.html) to follow a Gitflow development model Software development teams often follow a strict branching strategy during the 4 | development lifecycle of a solution. It is common for newly created branches to need their own isolated 5 | copy of infrastructure resources in order to develop new features. 6 | 7 | [CDK Pipelines](https://docs.aws.amazon.com/cdk/api/latest/docs/pipelines-readme.html) is a construct library module for painless continuous delivery of AWS CDK applications. 8 | CDK Pipelines are self-updating: if you add application stages or stacks, the pipeline automatically 9 | reconfigures itself to deploy those new stages and/or stacks. 10 | 11 | The following solution creates a new AWS CDK Pipeline within a development account for every new 12 | branch created in the source repository (AWS CodeCommit). When a branch is deleted, the pipeline and 13 | all related resources are destroyed from the account as well. This GitFlow model for infrastructure 14 | provisioning allows developers to work independently from each other, concurrently, even in the same 15 | stack of the application. 16 | 17 | 18 | ## Overview of the solution 19 | 20 | ![Architecture diagram](./diagrams/architecture.png) 21 | 22 | ## Prerequisites 23 | Before setting up this project, you should have the following prerequisites: 24 | * An [AWS account](https://signin.aws.amazon.com/signin?redirect_uri=https%3A%2F%2Fportal.aws.amazon.com%2Fbilling%2Fsignup%2Fresume&client_id=signup) 25 | * [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html#getting_started_install) installed 26 | * Python3 installed 27 | 28 | ## Initial setup 29 | [Create a new AWS CodeCommit repository](https://docs.aws.amazon.com/codecommit/latest/userguide/how-to-create-repository.html) in the AWS Account and region where you want to deploy 30 | the pipeline and upload the source code from this repository. In the config.ini file, change the 31 | repository_name and region variables accordingly. 32 | 33 | Make sure to set up a fresh python environment. Install the dependencies: 34 | 35 | `pip install -r requirements.txt` 36 | 37 | Run the initial-deploy.sh script to bootstrap the development and production environments and to 38 | deploy the default pipeline. You’ll be asked to provide the following parameters: (1) Development 39 | account ID, (2) Development account AWS profile name (3) Production account ID, (4) Production 40 | account AWS profile name. 41 | 42 | `sh ./initial-deploy.sh --dev_account_id -- 43 | dev_profile_name --prod_account_id --prod_profile_name ` 45 | 46 | ## How to use 47 | 48 | [Lambda S3 trigger project](https://github.com/aws-samples/aws-cdk-examples/tree/master/python/lambda-s3-trigger) from AWS CDK Samples is used as infrastructure resources to demonstrate 49 | this solution. The content is placed inside the *src* directory and is deployed by the pipeline. Replace the content of this repository with your infrastructure code. Use [CDK Constructs](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html) to combine your infrastructure code into one stack and reference this in the application stage inside *src/application_stage.py*. 50 | 51 | ### Create a feature branch 52 | 53 | On your machine’s local copy of the repository, create a new feature branch using the git commands 54 | below. Replace user-feature-123 with a unique name for your feature branch. Note: this feature branch name must comply with the [AWS CodePipeline naming restrictions](https://docs.aws.amazon.com/codepipeline/latest/userguide/limits.html#:~:text=Pipeline%20names%20cannot%20exceed%20100,letters%20A%20through%20Z%2C%20inclusive.) for it will be used to name a unique 55 | pipeline later in this walkthrough. 56 | 57 | ``` 58 | # Create the feature branch 59 | git checkout -b user-feature-123 60 | git push origin user-feature-123 61 | ``` 62 | 63 | The first AWS Lambda function will deploy the CodeBuild project which then deployes the feature 64 | pipeline. This can take a few minutes. You can log into the AWS Console and see the CodeBuild project 65 | running under AWS CodeBuild. After the build is successfully finished, you can see the deployed feature pipeline under AWS 66 | CodePipelines. 67 | 68 | ### Destroy a feature branch 69 | There are two common ways for removing feature branches. The first one is related to a pull request, 70 | also known as a “PR”, which occurs when merging a feature branch back into the default branch. Once it 71 | is merged, the feature branch will be automatically closed. The second way is to delete the feature 72 | branch explicitly by running the below git commands. 73 | 74 | ``` 75 | # delete branch local 76 | git branch -d user-feature-123 77 | 78 | # delete branch remote 79 | git push origin --delete user-feature-123 80 | ``` 81 | 82 | 83 | ## Security 84 | 85 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 86 | 87 | ## License 88 | 89 | This library is licensed under the MIT-0 License. See the LICENSE file. 90 | 91 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import configparser 3 | import os 4 | 5 | import aws_cdk as cdk 6 | import boto3 7 | import cdk_nag 8 | 9 | from cdk_pipelines_multi_branch.cicd.cdk_pipelines_multi_branch_stack import CdkPipelinesMultiBranchStack 10 | 11 | app = cdk.App() 12 | 13 | # retrieve configuration variables 14 | global_config = configparser.ConfigParser() 15 | global_config.read('config.ini') 16 | region = global_config.get('general', 'region') 17 | codebuild_prefix = global_config.get('general', 'codebuild_project_name_prefix') 18 | repository_name = global_config.get('general', 'repository_name') 19 | current_branch = os.environ['BRANCH'] 20 | 21 | # retrieve the default branch by the CodeCommit repository 22 | codecommit_client = boto3.client('codecommit', region_name=region) 23 | repository = codecommit_client.get_repository( 24 | repositoryName=repository_name 25 | ) 26 | default_branch = repository['repositoryMetadata']['defaultBranch'] 27 | 28 | config = { 29 | 'dev_account_id': os.environ['DEV_ACCOUNT_ID'], 30 | 'branch': current_branch, 31 | 'default_branch': default_branch, 32 | 'region': region, 33 | 'codebuild_prefix': codebuild_prefix, 34 | 'repository_name': repository_name 35 | } 36 | 37 | # Only the default branch resources will be deployed to the production environment. 38 | if current_branch == default_branch: 39 | config['prod_account_id'] = os.environ['PROD_ACCOUNT_ID'] 40 | 41 | CdkPipelinesMultiBranchStack( 42 | app, 43 | f"cdk-pipelines-multi-branch-{current_branch}", 44 | config, 45 | env=cdk.Environment(account=config['dev_account_id'], region=region) 46 | ) 47 | 48 | cdk.Aspects.of(app).add(cdk_nag.AwsSolutionsChecks()) 49 | 50 | app.synth() 51 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "python3 app.py", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "requirements*.txt", 11 | "source.bat", 12 | "**/__init__.py", 13 | "python/__pycache__", 14 | "tests" 15 | ] 16 | }, 17 | "context": { 18 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 19 | "@aws-cdk/core:stackRelativeExports": true, 20 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 21 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 22 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 23 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 24 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 25 | "@aws-cdk/core:checkSecretUsage": true, 26 | "@aws-cdk/aws-iam:minimizePolicies": true, 27 | "@aws-cdk/core:target-partitions": [ 28 | "aws", 29 | "aws-cn" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-branch-cdk-pipelines/dac457d22679e28435b2224b328ac858ce0fb0ea/cdk_pipelines_multi_branch/__init__.py -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/cicd/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-branch-cdk-pipelines/dac457d22679e28435b2224b328ac858ce0fb0ea/cdk_pipelines_multi_branch/cicd/__init__.py -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/cicd/aspects/key_rotation_aspect.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | import aws_cdk.aws_kms as kms 3 | import jsii 4 | 5 | 6 | @jsii.implements(cdk.IAspect) 7 | class KeyRotationAspect: 8 | 9 | def visit(self, node): 10 | if isinstance(node, kms.CfnKey): 11 | node.enable_key_rotation = True 12 | -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/cicd/cdk_pipelines_multi_branch_stack.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from aws_cdk import ( 4 | Stack, aws_codepipeline_actions, Aspects, RemovalPolicy 5 | ) 6 | from aws_cdk.aws_codecommit import Repository 7 | from aws_cdk.aws_events_targets import LambdaFunction 8 | from aws_cdk.aws_iam import PolicyStatement 9 | from aws_cdk.aws_lambda import Function, Runtime, Code 10 | from aws_cdk.aws_s3 import BucketEncryption 11 | from aws_cdk.pipelines import CodePipeline, CodeBuildStep, CodePipelineSource, ManualApprovalStep 12 | from cdk_nag import NagSuppressions, NagPackSuppression 13 | from constructs import Construct 14 | 15 | from cdk_pipelines_multi_branch.cicd.aspects.key_rotation_aspect import KeyRotationAspect 16 | from .constructs.standard_bucket import S3Construct 17 | from .iam_stack import IAMPipelineStack 18 | from ..src.application_stage import MainStage as Application 19 | 20 | 21 | class CdkPipelinesMultiBranchStack(Stack): 22 | 23 | def __init__(self, scope: Construct, construct_id: str, config: object, **kwargs) -> None: 24 | super().__init__(scope, construct_id, **kwargs) 25 | 26 | this_dir = path.dirname(__file__) 27 | 28 | codebuild_prefix = config['codebuild_prefix'] 29 | region = config['region'] 30 | repo_name = config['repository_name'] 31 | branch = config['branch'] 32 | default_branch = config['default_branch'] 33 | dev_account_id = config['dev_account_id'] 34 | prod_account_id = config['prod_account_id'] if branch == default_branch else dev_account_id 35 | 36 | repo = Repository.from_repository_name(self, 'ImportedRepo', repo_name) 37 | 38 | pipeline = CodePipeline( 39 | self, 40 | f"Pipeline-{branch}", 41 | pipeline_name=f"CICDPipeline-{branch}", 42 | cross_account_keys=True, 43 | synth=CodeBuildStep( 44 | 'Synth', 45 | input=CodePipelineSource.code_commit( 46 | repository=repo, 47 | trigger=aws_codepipeline_actions.CodeCommitTrigger.POLL, 48 | branch=branch 49 | ), 50 | env={ 51 | 'BRANCH': branch, 52 | 'DEV_ACCOUNT_ID': dev_account_id, 53 | 'PROD_ACCOUNT_ID': prod_account_id 54 | }, 55 | install_commands=[ 56 | 'gem install cfn-nag', 57 | 'npm install -g aws-cdk', 58 | 'pip install -r requirements.txt', 59 | 'export LC_ALL="en_US.UTF-8"', 60 | 'locale-gen en_US en_US.UTF-8', 61 | 'dpkg-reconfigure locales' 62 | ], 63 | commands=[ 64 | f'cdk synth', 65 | f'npx cdk synth cdk-pipelines-multi-branch-{branch}/DEV/InfraStack-{branch} > infra_stack.yaml', 66 | 'cfn_nag_scan --input-path infra_stack.yaml' 67 | ], 68 | role_policy_statements=[ 69 | PolicyStatement( 70 | actions=[ 71 | 'codecommit:GetRepository' 72 | ], 73 | resources=[ 74 | f'arn:aws:codecommit:{region}:{dev_account_id}:{repo_name}' 75 | ]) 76 | ] 77 | )) 78 | 79 | Aspects.of(self).add(KeyRotationAspect()) 80 | 81 | dev_stage_name = 'DEV' 82 | dev_stage = Application(self, dev_stage_name, branch, env={'account': dev_account_id, 'region': region}) 83 | pipeline.add_stage(dev_stage) 84 | 85 | if branch == default_branch: 86 | # Prod stage 87 | pipeline.add_stage(Application(self, 'PROD', branch, env={'account': prod_account_id, 'region': region}), 88 | pre=[ManualApprovalStep('ManualApproval', comment='Pre-prod manual approval')]) 89 | 90 | # Artifact bucket for feature AWS CodeBuild projects 91 | args = dict( 92 | encryption=BucketEncryption.KMS_MANAGED, 93 | removal_policy=RemovalPolicy.DESTROY, 94 | auto_delete_objects=True 95 | ) 96 | artifact_bucket = S3Construct(self, 'BranchArtifacts', args).bucket 97 | 98 | # AWS Lambda and AWS CodeBuild projects' IAM Roles. 99 | iam_stack = IAMPipelineStack( 100 | self, 101 | 'IAMPipeline', 102 | account=dev_account_id, 103 | region=region, 104 | repo_name=repo_name, 105 | artifact_bucket_arn=artifact_bucket.bucket_arn, 106 | codebuild_prefix=codebuild_prefix) 107 | 108 | # AWS Lambda function triggered upon branch creation 109 | create_branch_func = Function( 110 | self, 111 | 'LambdaTriggerCreateBranch', 112 | runtime=Runtime.PYTHON_3_9, 113 | function_name='LambdaTriggerCreateBranch', 114 | handler='create_branch.handler', 115 | code=Code.from_asset(path.join(this_dir, 'code')), 116 | environment={ 117 | "ACCOUNT_ID": dev_account_id, 118 | "CODE_BUILD_ROLE_ARN": iam_stack.code_build_role.role_arn, 119 | "ARTIFACT_BUCKET": artifact_bucket.bucket_name, 120 | "CODEBUILD_NAME_PREFIX": codebuild_prefix 121 | }, 122 | role=iam_stack.create_branch_role) 123 | 124 | # Configure AWS CodeCommit to trigger the Lambda function when new branch is created 125 | repo.on_reference_created( 126 | 'BranchCreateTrigger', 127 | description="AWS CodeCommit reference created event.", 128 | target=LambdaFunction(create_branch_func)) 129 | 130 | # AWS Lambda function triggered upon branch deletion 131 | destroy_branch_func = Function( 132 | self, 133 | 'LambdaTriggerDestroyBranch', 134 | runtime=Runtime.PYTHON_3_9, 135 | function_name='LambdaTriggerDestroyBranch', 136 | handler='destroy_branch.handler', 137 | role=iam_stack.delete_branch_role, 138 | environment={ 139 | "ACCOUNT_ID": dev_account_id, 140 | "CODE_BUILD_ROLE_ARN": iam_stack.code_build_role.role_arn, 141 | "ARTIFACT_BUCKET": artifact_bucket.bucket_name, 142 | "CODEBUILD_NAME_PREFIX": codebuild_prefix, 143 | "DEV_STAGE_NAME": f'{dev_stage_name}-{dev_stage.main_stack_name}' 144 | }, 145 | code=Code.from_asset(path.join(this_dir, 146 | 'code'))) 147 | 148 | # Configure AWS CodeCommit to trigger the Lambda function when a branch is deleted 149 | repo.on_reference_deleted( 150 | 'BranchDeleteTrigger', 151 | description="AWS CodeCommit reference deleted event.", 152 | target=LambdaFunction(destroy_branch_func)) 153 | 154 | # CDK Nag supressions 155 | NagSuppressions.add_stack_suppressions(self, [ 156 | NagPackSuppression( 157 | id='AwsSolutions-IAM5', 158 | reason='Wildcard permissions for CDK Pipelines resources are allowed.' 159 | ) 160 | ], True) 161 | 162 | NagSuppressions.add_stack_suppressions(self, [ 163 | NagPackSuppression( 164 | id='AwsSolutions-KMS5', 165 | reason='Fault positive: KMS Key rotation enabled using Aspects.' 166 | ) 167 | ], True) 168 | 169 | NagSuppressions.add_stack_suppressions(self, [ 170 | NagPackSuppression( 171 | id='AwsSolutions-S1', 172 | reason='Server Access Logging not required.' 173 | ) 174 | ], True) 175 | 176 | NagSuppressions.add_stack_suppressions(self, [ 177 | NagPackSuppression( 178 | id='AwsSolutions-IAM4', 179 | reason='Lambda can use AWS Lambda managed policy' 180 | ) 181 | ], True) 182 | -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/cicd/code/create_branch.py: -------------------------------------------------------------------------------- 1 | """ 2 | Lambda function code used to create a CodeBuild project which deploys the CDK pipeline stack for the branch. 3 | """ 4 | import logging 5 | import os 6 | 7 | import boto3 8 | 9 | logger = logging.getLogger() 10 | logger.setLevel(logging.INFO) 11 | 12 | client = boto3.client('codebuild') 13 | region = os.environ['AWS_REGION'] 14 | account_id = os.environ['ACCOUNT_ID'] 15 | role_arn = os.environ['CODE_BUILD_ROLE_ARN'] 16 | artifact_bucket_name = os.environ['ARTIFACT_BUCKET'] 17 | codebuild_name_prefix = os.environ['CODEBUILD_NAME_PREFIX'] 18 | 19 | 20 | def generate_build_spec(branch: str): 21 | """Generates the build spec file used for the CodeBuild project""" 22 | return f"""version: 0.2 23 | env: 24 | variables: 25 | BRANCH: {branch} 26 | DEV_ACCOUNT_ID: {account_id} 27 | PROD_ACCOUNT_ID: {account_id} 28 | REGION: {region} 29 | phases: 30 | pre_build: 31 | commands: 32 | - npm install -g aws-cdk && pip install -r requirements.txt 33 | build: 34 | commands: 35 | - cdk synth 36 | - cdk deploy --require-approval=never 37 | artifacts: 38 | files: 39 | - '**/*'""" 40 | 41 | 42 | def handler(event, context): 43 | """Lambda function handler""" 44 | logger.info(event) 45 | 46 | reference_type = event['detail']['referenceType'] 47 | 48 | try: 49 | if reference_type == 'branch': 50 | branch = event['detail']['referenceName'] 51 | repo_name = event['detail']['repositoryName'] 52 | 53 | client.create_project( 54 | name=f'{codebuild_name_prefix}-{branch}-create', 55 | description="Build project to deploy branch pipeline", 56 | source={ 57 | 'type': 'CODECOMMIT', 58 | 'location': f'https://git-codecommit.{region}.amazonaws.com/v1/repos/{repo_name}', 59 | 'buildspec': generate_build_spec(branch) 60 | }, 61 | sourceVersion=f'refs/heads/{branch}', 62 | artifacts={ 63 | 'type': 'S3', 64 | 'location': artifact_bucket_name, 65 | 'path': f'{branch}', 66 | 'packaging': 'NONE', 67 | 'artifactIdentifier': 'BranchBuildArtifact' 68 | }, 69 | environment={ 70 | 'type': 'LINUX_CONTAINER', 71 | 'image': 'aws/codebuild/standard:6.0', 72 | 'computeType': 'BUILD_GENERAL1_SMALL' 73 | }, 74 | serviceRole=role_arn 75 | ) 76 | 77 | client.start_build( 78 | projectName=f'CodeBuild-{branch}-create' 79 | ) 80 | except Exception as e: 81 | logger.error(e) 82 | -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/cicd/code/destroy_branch.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | import boto3 5 | 6 | logger = logging.getLogger() 7 | logger.setLevel(logging.INFO) 8 | 9 | client = boto3.client('codebuild') 10 | region = os.environ['AWS_REGION'] 11 | role_arn = os.environ['CODE_BUILD_ROLE_ARN'] 12 | account_id = os.environ['ACCOUNT_ID'] 13 | artifact_bucket_name = os.environ['ARTIFACT_BUCKET'] 14 | codebuild_name_prefix = os.environ['CODEBUILD_NAME_PREFIX'] 15 | dev_stage_name = os.environ['DEV_STAGE_NAME'] 16 | 17 | 18 | def generate_build_spec(branch): 19 | return f"""version: 0.2 20 | env: 21 | variables: 22 | BRANCH: {branch} 23 | DEV_ACCOUNT_ID: {account_id} 24 | PROD_ACCOUNT_ID: {account_id} 25 | REGION: {region} 26 | phases: 27 | pre_build: 28 | commands: 29 | - npm install -g aws-cdk && pip install -r requirements.txt 30 | build: 31 | commands: 32 | - cdk destroy cdk-pipelines-multi-branch-{branch} --force 33 | - aws cloudformation delete-stack --stack-name {dev_stage_name}-{branch} 34 | - aws s3 rm s3://{artifact_bucket_name}/{branch} --recursive""" 35 | 36 | 37 | def handler(event, context): 38 | logger.info(event) 39 | reference_type = event['detail']['referenceType'] 40 | 41 | try: 42 | if reference_type == 'branch': 43 | branch = event['detail']['referenceName'] 44 | client.create_project( 45 | name=f'{codebuild_name_prefix}-{branch}-destroy', 46 | description="Build project to destroy branch resources", 47 | source={ 48 | 'type': 'S3', 49 | 'location': f'{artifact_bucket_name}/{branch}/CodeBuild-{branch}-create/', 50 | 'buildspec': generate_build_spec(branch) 51 | }, 52 | artifacts={ 53 | 'type': 'NO_ARTIFACTS' 54 | }, 55 | environment={ 56 | 'type': 'LINUX_CONTAINER', 57 | 'image': 'aws/codebuild/standard:6.0', 58 | 'computeType': 'BUILD_GENERAL1_SMALL' 59 | }, 60 | serviceRole=role_arn 61 | ) 62 | 63 | client.start_build( 64 | projectName=f'CodeBuild-{branch}-destroy' 65 | ) 66 | 67 | client.delete_project( 68 | name=f'CodeBuild-{branch}-destroy' 69 | ) 70 | 71 | client.delete_project( 72 | name=f'CodeBuild-{branch}-create' 73 | ) 74 | except Exception as e: 75 | logger.error(e) 76 | -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/cicd/constructs/standard_bucket.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import App, RemovalPolicy 2 | from aws_cdk.aws_iam import PolicyStatement, Effect, ServicePrincipal, AnyPrincipal 3 | from aws_cdk.aws_kms import Key 4 | from aws_cdk.aws_s3 import Bucket, BucketEncryption, BlockPublicAccess 5 | from constructs import Construct 6 | 7 | 8 | class S3Construct(Construct): 9 | def __init__(self, app: App, id: str, bucket_args: dict, **kwargs): 10 | super().__init__(app, id, **kwargs) 11 | 12 | # kms key 13 | if not bucket_args['encryption']: 14 | bucket_key = Key(self, f"{id}Key", 15 | description=f"Key used for {id} template", 16 | alias=f"{id}Bucket", 17 | enable_key_rotation=True, 18 | removal_policy=RemovalPolicy.RETAIN) 19 | bucket_args["encryption_key"] = bucket_key 20 | bucket_args["encryption"] = BucketEncryption.KMS 21 | 22 | # bucket 23 | bucket_args["block_public_access"] = BlockPublicAccess.BLOCK_ALL 24 | bucket_args["versioned"] = True 25 | 26 | bucket = Bucket(self, f"{id}Bucket", **bucket_args) 27 | 28 | # bucket policy 29 | bucket.add_to_resource_policy( 30 | PolicyStatement(sid='AllowSSLRequestsOnly', 31 | actions=['s3:*'], 32 | effect=Effect.DENY, 33 | resources=[ 34 | bucket.bucket_arn, 35 | f"{bucket.bucket_arn}/*" 36 | ], 37 | conditions={ 38 | "Bool": { 39 | "aws:SecureTransport": "false" 40 | } 41 | }, 42 | principals=[AnyPrincipal()]) 43 | ) 44 | 45 | self.bucket = bucket 46 | -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/cicd/iam_stack.py: -------------------------------------------------------------------------------- 1 | from aws_cdk.aws_iam import Role, PolicyStatement, ManagedPolicy, ServicePrincipal 2 | from constructs import Construct 3 | 4 | 5 | class IAMPipelineStack(Construct): 6 | def __init__(self, 7 | scope: Construct, 8 | construct_id: str, 9 | account: str, 10 | region: str, 11 | repo_name: str, 12 | artifact_bucket_arn: str, 13 | codebuild_prefix: str, 14 | **kwargs) -> None: 15 | super().__init__(scope, construct_id, **kwargs) 16 | 17 | # IAM Role for the AWS Lambda function which creates the branch resources 18 | create_branch_role = Role( 19 | self, 20 | 'LambdaCreateBranchRole', 21 | assumed_by=ServicePrincipal('lambda.amazonaws.com')) 22 | create_branch_role.add_managed_policy( 23 | ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaBasicExecutionRole")) 24 | create_branch_role.add_to_policy(PolicyStatement( 25 | actions=[ 26 | 'codebuild:CreateProject', 27 | 'codebuild:StartBuild' 28 | ], 29 | resources=[f'arn:aws:codebuild:{region}:{account}:project/{codebuild_prefix}*'] 30 | )) 31 | 32 | # IAM Role for the AWS Lambda function which deletes the branch resources 33 | delete_branch_role = Role( 34 | self, 35 | 'LambdaDeleteBranchRole', 36 | assumed_by=ServicePrincipal('lambda.amazonaws.com')) 37 | delete_branch_role.add_managed_policy( 38 | ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaBasicExecutionRole")) 39 | delete_branch_role.add_to_policy(PolicyStatement( 40 | actions=[ 41 | 'codebuild:StartBuild', 42 | 'codebuild:DeleteProject', 43 | 'codebuild:CreateProject' 44 | ], 45 | resources=[f'arn:aws:codebuild:{region}:{account}:project/{codebuild_prefix}*'] 46 | )) 47 | 48 | # IAM Role for the feature branch AWS CodeBuild project. 49 | code_build_role = Role( 50 | self, 51 | 'CodeBuildExecutionRole', 52 | assumed_by=ServicePrincipal('codebuild.amazonaws.com')) 53 | code_build_role.add_to_policy(PolicyStatement( 54 | actions=['cloudformation:DescribeStacks', 'cloudformation:DeleteStack'], 55 | resources=[f'arn:aws:cloudformation:{region}:{account}:stack/*/*'] 56 | )) 57 | code_build_role.add_to_policy(PolicyStatement( 58 | actions=['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], 59 | resources=[ 60 | f'arn:aws:logs:{region}:{account}:log-group:/aws/codebuild/{codebuild_prefix}-*', 61 | f'arn:aws:logs:{region}:{account}:log-group:/aws/codebuild/{codebuild_prefix}-*:*'] 62 | )) 63 | code_build_role.add_to_policy(PolicyStatement( 64 | actions=['codecommit:Get*', 'codecommit:List*', 'codecommit:GitPull'], 65 | resources=[f'arn:aws:codecommit:{region}:{account}:{repo_name}'] 66 | )) 67 | code_build_role.add_to_policy(PolicyStatement( 68 | actions=['s3:DeleteObject', 's3:PutObject', 's3:GetObject', 's3:ListBucket'], 69 | resources=[f'{artifact_bucket_arn}/*', f'{artifact_bucket_arn}'] 70 | )) 71 | code_build_role.add_to_policy(PolicyStatement( 72 | actions=['sts:AssumeRole'], 73 | resources=[f'arn:*:iam::{account}:role/*'], 74 | conditions={ 75 | "ForAnyValue:StringEquals": { 76 | "iam:ResourceTag/aws-cdk:bootstrap-role": [ 77 | "image-publishing", 78 | "file-publishing", 79 | "deploy" 80 | ] 81 | } 82 | } 83 | )) 84 | code_build_role.grant_pass_role(create_branch_role) 85 | code_build_role.grant_pass_role(delete_branch_role) 86 | 87 | self.create_branch_role = create_branch_role 88 | self.delete_branch_role = delete_branch_role 89 | self.code_build_role = code_build_role 90 | -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-branch-cdk-pipelines/dac457d22679e28435b2224b328ac858ce0fb0ea/cdk_pipelines_multi_branch/src/__init__.py -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/src/application_stage.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import Stage, Stack 2 | from constructs import Construct 3 | 4 | from .s3trigger.s3trigger_stack import S3TriggerConstruct 5 | 6 | 7 | class InfraStack(Stack): 8 | def __init__(self, scope: Construct, construct_id: str, branch: str, **kwargs) -> None: 9 | super().__init__(scope, construct_id, **kwargs) 10 | 11 | # - combines single constructs in src/ to one stack 12 | S3TriggerConstruct(self, f'S3Trigger-${branch}') 13 | 14 | 15 | class MainStage(Stage): 16 | 17 | def __init__(self, scope: Construct, construct_id: str, branch: str, **kwargs) -> None: 18 | super().__init__(scope, construct_id, **kwargs) 19 | 20 | main_stack_name = 'InfraStack' 21 | InfraStack(self, f'{main_stack_name}-{branch}', branch) 22 | 23 | self.main_stack_name = main_stack_name 24 | -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/src/lambda/lambda-handler.py: -------------------------------------------------------------------------------- 1 | def main(event, context): 2 | # save event to logs 3 | print(event) 4 | 5 | return { 6 | 'statusCode': 200, 7 | 'body': event 8 | } -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/src/s3trigger/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-branch-cdk-pipelines/dac457d22679e28435b2224b328ac858ce0fb0ea/cdk_pipelines_multi_branch/src/s3trigger/__init__.py -------------------------------------------------------------------------------- /cdk_pipelines_multi_branch/src/s3trigger/s3trigger_stack.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import ( 2 | aws_lambda as _lambda, 3 | aws_s3 as _s3, 4 | aws_s3_notifications 5 | ) 6 | from constructs import Construct 7 | 8 | class S3TriggerConstruct(Construct): 9 | 10 | def __init__(self, scope: Construct, id: str, **kwargs) -> None: 11 | super().__init__(scope, id, **kwargs) 12 | 13 | # create lambda function 14 | function = _lambda.Function(self, "lambda_function", 15 | runtime=_lambda.Runtime.PYTHON_3_9, 16 | handler="lambda-handler.main", 17 | code=_lambda.Code.from_asset("./cdk_pipelines_multi_branch/src/lambda")) 18 | # create s3 bucket 19 | s3 = _s3.Bucket(self, "s3bucket") 20 | 21 | # create s3 notification for lambda function 22 | notification = aws_s3_notifications.LambdaDestination(function) 23 | 24 | # assign notification for the s3 event type (ex: OBJECT_CREATED) 25 | s3.add_event_notification(_s3.EventType.OBJECT_CREATED, notification) -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [general] 2 | repository_name=cdk-pipelines-multi-branch 3 | codebuild_project_name_prefix=CodeBuild 4 | region=us-east-1 5 | -------------------------------------------------------------------------------- /diagrams/architecture.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /diagrams/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-branch-cdk-pipelines/dac457d22679e28435b2224b328ac858ce0fb0ea/diagrams/architecture.png -------------------------------------------------------------------------------- /initial-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #Including .ini file 4 | . ./config.ini 5 | echo ${region} 6 | echo ${repository_name} 7 | 8 | while [ $# -gt 0 ]; do 9 | if [[ $1 == *"--"* ]]; then 10 | param="${1/--/}" 11 | declare $param="$2" 12 | fi 13 | shift 14 | done 15 | 16 | if [[ -z "$dev_account_id" || -z "$dev_profile_name" || -z "$prod_account_id" || -z "$prod_profile_name" ]]; then 17 | echo "The following parameters are required: --dev_account_id, --dev_profile_name, --prod_account_id, --prod_profile_name" 18 | exit 19 | fi 20 | 21 | echo "Dev account id: $dev_account_id" 22 | echo "Dev profile name: $dev_profile_name" 23 | echo "Prod account id: $prod_account_id" 24 | echo "Prod profile name: $prod_profile_name" 25 | echo "Region: $region" 26 | echo "Repository name: $repository_name" 27 | 28 | export DEV_ACCOUNT_ID=$dev_account_id 29 | export PROD_ACCOUNT_ID=$prod_account_id 30 | 31 | # retrieve default branch 32 | export BRANCH=$(aws codecommit get-repository --repository-name ${repository_name} --region ${region} --output json | jq -r '.repositoryMetadata.defaultBranch') 33 | 34 | # bootstrap Development AWS Account 35 | npx cdk bootstrap --profile $dev_profile_name --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess aws://$dev_account_id/${region} 36 | 37 | # bootstrap Production AWS Account and add trust to development account where pipeline resides 38 | npx cdk bootstrap --profile $prod_profile_name --trust $dev_account_id --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess aws://$prod_account_id/${region} 39 | 40 | # deploy pipeline 41 | cdk deploy cdk-pipelines-multi-branch-$BRANCH 42 | 43 | exit $? 44 | 45 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest==6.2.5 2 | flake8==4.0.1 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aws-cdk-lib==2.23.0 2 | boto3==1.18.53 3 | cdk-nag==2.12.46 4 | constructs>=10.0.0,<11.0.0 5 | -------------------------------------------------------------------------------- /source.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem The sole purpose of this script is to make the command 4 | rem 5 | rem source .venv/bin/activate 6 | rem 7 | rem (which activates a Python virtualenv on Linux or Mac OS X) work on Windows. 8 | rem On Windows, this command just runs this batch file (the argument is ignored). 9 | rem 10 | rem Now we don't need to document a Windows command for activating a virtualenv. 11 | 12 | echo Executing .venv\Scripts\activate.bat for you 13 | .venv\Scripts\activate.bat 14 | --------------------------------------------------------------------------------