in lib/codecommit-devops-model-stack.ts [16:460]
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// The code that defines your stack goes here
const stack = cdk.Stack.of(this);
const bizTags = [
{
name: 'app',
value: 'my-app-1',
},
{
name: 'cost-center',
value: '12345',
},
{
name: 'team',
value: 'abc',
}
];
const repo1 = new codecommit.Repository(this, 'Repository1', {
repositoryName: `${stack.stackName}-MyApp1`,
description: 'Repo for App1.', // optional property
});
bizTags.forEach(tag => { cdk.Tag.add(repo1, tag.name, tag.value )});
const repo2 = new codecommit.Repository(this, 'Repository2', {
repositoryName: `${stack.stackName}-MyApp2`,
description: 'Repo for App2.', // optional property
});
cdk.Tag.add(repo2, 'app', 'my-app-2');
cdk.Tag.add(repo2, 'team', 'abc');
const codeCollaboratorModel = new CodecommitCollaborationModel(this, `CodecommitCollaborationModel`, {
name: 'MyApp1',
tags: {
'app': 'my-app-1',
'team': 'abc',
}
});
const repo1AdminRole = new iam.Role(this, 'Repo1AdminRole', {
assumedBy: new iam.AccountPrincipal(this.account),
managedPolicies: [codeCollaboratorModel.codeCommitAdminPolicy],
path: '/codecommitmodel/',
});
const repo1CollaboratorRole = new iam.Role(this, 'Repo1CollaboratorRole', {
assumedBy: new iam.AccountPrincipal(stack.account),
managedPolicies: [codeCollaboratorModel.codeCommitCollaboratorPolicy],
path: '/codecommitmodel/',
});
// create a repo without tags either 'app' or 'team'
const repo3 = new codecommit.Repository(this, 'Repository3', {
repositoryName: `${stack.stackName}-MyApp3`,
description: 'Repo for App3.',
});
// Add PR build and trigger on PR created/updated
const codeBuildPolicy = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"codebuild:BatchGetReports",
"codebuild:DescribeTestCases",
"codebuild:ListReportGroups",
"codebuild:CreateReportGroup",
"codebuild:CreateReport",
"codebuild:BatchPutTestCases",
"codebuild:UpdateReport",
],
resources: [ '*' ]
});
const prBuildRole = new iam.Role(this, `CodeCommit-Repo1-PR-Build-Role`, {
assumedBy: new iam.ServicePrincipal("codebuild.amazonaws.com"),
inlinePolicies: {
codebuild: new iam.PolicyDocument({
statements: [codeBuildPolicy]
}),
}
});
const prBuild = new codebuild.Project(this, `Repo1-PRBuild`, {
role: prBuildRole,
buildSpec: codebuild.BuildSpec.fromObject({
version: '0.2',
env: {
variables: {
},
},
phases: {
install: {
'runtime-versions': {
nodejs: '12',
},
},
pre_build: {
commands: [
`echo Build PR $pullRequestId of repo $repositoryName...`,
]
},
build: {
commands: [
'echo Source repo $CODEBUILD_SOURCE_REPO_URL with version $CODEBUILD_SOURCE_VERSION',
'echo here is building task',
],
},
},
}),
environment: {
buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_3,
computeType: codebuild.ComputeType.SMALL,
},
source: codebuild.Source.codeCommit({ repository: repo1 }),
cache: codebuild.Cache.local(
codebuild.LocalCacheMode.SOURCE,
codebuild.LocalCacheMode.DOCKER_LAYER,
codebuild.LocalCacheMode.CUSTOM
),
timeout: cdk.Duration.minutes(30),
});
bizTags.forEach(tag => { cdk.Tag.add(prBuild, tag.name, tag.value )});
// create lambda to listen on the state changed of PR Build
const codecommitPolicy = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"codecommit:PostCommentForPullRequest",
"codecommit:UpdatePullRequestApprovalState",
],
resources: [
repo1.repositoryArn
],
});
const prBuildEventLambaRole = new iam.Role(this, `CodeCommitRepo1PRBuildEventLambdaRole`, {
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
inlinePolicies: {
codecommit: new iam.PolicyDocument({
statements: [codecommitPolicy]
}),
},
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
]
});
const prBuildEventHandler = new lambdaNodejs.NodejsFunction(this, `CodeCommitRepo1PRBuildEventHandler`, {
role: prBuildEventLambaRole,
runtime: lambda.Runtime.NODEJS_14_X,
entry: path.join(__dirname, '../assets/pr-build-events/handler.ts'),
handler: 'prBuildStateChanged',
timeout: cdk.Duration.minutes(1),
bundling: {
sourceMap: true,
minify: false,
},
});
prBuild.onBuildStarted(`Repo1PRBuildStarted`, {
target: new targets.LambdaFunction(prBuildEventHandler),
});
prBuild.onBuildSucceeded(`Repo1PRBuildSuccessed`, {
target: new targets.LambdaFunction(prBuildEventHandler),
});
prBuild.onBuildFailed(`Repo1PRBuildFailed`, {
target: new targets.LambdaFunction(prBuildEventHandler),
});
// create cloudwatch event to trigger pr build when pr is create or the source branch is updated
const prRule = repo1.onPullRequestStateChange('PRBuild', {
target: new targets.CodeBuildProject(prBuild, {
event: events.RuleTargetInput.fromObject({
sourceVersion: events.EventField.fromPath('$.detail.sourceCommit'),
environmentVariablesOverride: [
{
"name": "pullRequestId",
"value": events.EventField.fromPath('$.detail.pullRequestId'),
"type": "PLAINTEXT"
},
{
"name": "repositoryName",
"value": events.EventField.fromPath('$.detail.repositoryNames[0]'),
"type": "PLAINTEXT"
},
{
"name": "sourceCommit",
"value": events.EventField.fromPath('$.detail.sourceCommit'),
"type": "PLAINTEXT"
},
{
"name": "destinationCommit",
"value": events.EventField.fromPath('$.detail.destinationCommit'),
"type": "PLAINTEXT"
},
{
"name": "title",
"value": events.EventField.fromPath('$.detail.title'),
"type": "PLAINTEXT"
},
{
"name": "description",
"value": events.EventField.fromPath('$.detail.notificationBody'),
"type": "PLAINTEXT"
},
{
"name": "revisionId",
"value": events.EventField.fromPath('$.detail.revisionId'),
"type": "PLAINTEXT"
}
]
}),
}),
});
prRule.addEventPattern({
detail: {
event: [
'pullRequestSourceBranchUpdated',
'pullRequestCreated'
]
}
});
// Add deployment build and trigger on master branch changes with least privileges to run `cdk deploy`
const stackName = 'TheStackNameDefinedInCDK'; // the stack name in your cdk project
const cloudformationPolicyCreation = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"cloudformation:CreateStack",
],
resources: ["*"],
});
const cloudformationPolicyCDKStack = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"cloudformation:DescribeStacks",
],
resources: [
cdk.Arn.format({
service: 'cloudformation',
resource: 'stack',
region: '*',
resourceName: `CDKToolkit/*`,
sep: '/',
}, stack),
],
});
const cloudformationPolicyUpdation = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"cloudformation:GetTemplate",
"cloudformation:DescribeStacks",
"cloudformation:UpdateStack",
"cloudformation:CreateChangeSet",
"cloudformation:DescribeChangeSet",
"cloudformation:ExecuteChangeSet",
"cloudformation:DescribeStackEvents",
],
resources: [
cdk.Arn.format({
service: 'cloudformation',
resource: 'stack',
resourceName: `${stackName}/*`,
sep: '/',
}, stack),
],
});
const cdkS3Policy = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"s3:*Object",
"s3:ListBucket",
],
resources: [
cdk.Arn.format({
service: 's3',
region: '',
account: '',
resource: `cdktoolkit-stagingbucket-*`,
}, stack),
],
});
const buildDeploymentRole = new iam.Role(this, `CodeCommit-Repo1-Deployment-Build-Role`, {
assumedBy: new iam.ServicePrincipal("codebuild.amazonaws.com"),
inlinePolicies: {
cloudformation: new iam.PolicyDocument({
statements: [
cloudformationPolicyCreation,
cloudformationPolicyUpdation,
cloudformationPolicyCDKStack,
]
}),
s3: new iam.PolicyDocument({
statements: [cdkS3Policy],
}),
}
});
const deploymentBuild = new codebuild.Project(this, `Repo1-DeploymentBuild`, {
role: buildDeploymentRole,
buildSpec: codebuild.BuildSpec.fromObject({
version: '0.2',
env: {
variables: {
},
},
phases: {
install: {
'runtime-versions': {
nodejs: '12',
},
},
pre_build: {
commands: [
`echo deplyment build of repo ${repo1.repositoryName}...`,
]
},
build: {
commands: [
`echo Here running 'cdk deploy --require-approval never'`,
],
},
},
}),
environment: {
buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_3,
computeType: codebuild.ComputeType.SMALL,
},
source: codebuild.Source.codeCommit({ repository: repo1 }),
cache: codebuild.Cache.local(
codebuild.LocalCacheMode.SOURCE,
codebuild.LocalCacheMode.DOCKER_LAYER,
codebuild.LocalCacheMode.CUSTOM
),
timeout: cdk.Duration.minutes(30),
});
repo1.onCommit('CommitOnMaster', {
branches: ['master'],
target: new targets.CodeBuildProject(deploymentBuild),
});
bizTags.forEach(tag => { cdk.Tag.add(deploymentBuild, tag.name, tag.value )});
// create lambda based custom resource to create approval rule template
const codecommitApprovalRulePolicy = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"codecommit:CreateApprovalRuleTemplate",
"codecommit:DeleteApprovalRuleTemplate",
"codecommit:GetApprovalRuleTemplate",
"codecommit:UpdateApprovalRuleTemplateContent",
"codecommit:UpdateApprovalRuleTemplateDescription",
"codecommit:UpdateApprovalRuleTemplateName",
"codecommit:AssociateApprovalRuleTemplateWithRepository",
"codecommit:BatchAssociateApprovalRuleTemplateWithRepositories",
"codecommit:BatchDisassociateApprovalRuleTemplateFromRepositories",
"codecommit:DisassociateApprovalRuleTemplateFromRepository",
"codecommit:ListAssociatedApprovalRuleTemplatesForRepository",
],
resources: [ '*' ],
});
const codeCommitApprovalRuleTemplateRole = new iam.Role(this, `CustomResource-CodeCommit-Role`, {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
inlinePolicies: {
codecommit: new iam.PolicyDocument({
statements: [ codecommitApprovalRulePolicy ]
})
},
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
]
});
const approvalRuleTemplateProvider = new lambdaNodejs.NodejsFunction(this, `CodeCommitApprovalRuleTemplate`, {
role: codeCommitApprovalRuleTemplateRole,
runtime: lambda.Runtime.NODEJS_14_X,
entry: path.join(__dirname, '../assets/approval-rule-template/codecommit.ts'),
handler: 'approvalRuleTemplate',
timeout: cdk.Duration.minutes(5),
bundling: {
sourceMap: true,
minify: false,
},
});
const approvalRuleTemplaate = new cloudformation.CustomResource(this, 'CustomResource-CodeCommit-ApprovalRuleTemplate', {
provider: cloudformation.CustomResourceProvider.lambda(approvalRuleTemplateProvider),
resourceType: 'Custom::CodeCommitApprovalRuleTemplate',
properties: {
ApprovalRuleTemplateName: `approval-rule-template-${repo1.repositoryName}`,
ApprovalRuleTemplateDescription: `Approval rule template for repo ${repo1.repositoryName}`, // optional
Template: {
destinationReferences: [ 'refs/heads/master' ], // optional or non empty valid git references list
approvers: {
numberOfApprovalsNeeded: 2,
approvalPoolMembers: [ // optional
cdk.Arn.format({
service: 'sts',
region: '',
resource: 'assumed-role',
resourceName: `${repo1AdminRole.roleName}/*`,
sep: '/'
}, stack),
cdk.Arn.format({
service: 'sts',
region: '',
resource: 'assumed-role',
resourceName: `${prBuildEventLambaRole.roleName}/*`,
sep: '/'
}, stack)
]
}
}
}
});
// create lambda based custom resource to associate/disassociate approval rule with repos
const approvalRuleTemplateRepoAssociationProvider = new lambdaNodejs.NodejsFunction(this, `CodeCommitApprovalRuleTemplateRepoAssociation`, {
role: codeCommitApprovalRuleTemplateRole,
runtime: lambda.Runtime.NODEJS_12_X,
entry: path.join(__dirname, '../assets/approval-rule-template/codecommit.ts'),
handler: 'approvalRuleRepoAssociation',
timeout: cdk.Duration.minutes(5),
bundling: {
sourceMap: true,
minify: false,
},
});
new cloudformation.CustomResource(this, 'CustomResource-CodeCommit-ApprovalRuleTemplate-Repos-Association', {
provider: cloudformation.CustomResourceProvider.lambda(approvalRuleTemplateRepoAssociationProvider),
resourceType: 'Custom::CodeCommitApprovalRuleTemplateReposAssociation',
properties: {
ApprovalRuleTemplateName: approvalRuleTemplaate.getAttString('approvalRuleTemplateName'),
RepositoryNames: [
repo1.repositoryName,
repo2.repositoryName,
],
}
});
new cdk.CfnOutput(this, 'Repo1AdminRoleOutput', {
value: `${repo1AdminRole.roleArn}`,
exportName: `${stack.stackName}-Repo1AdminRole`,
description: 'admin role of repo1'
});
new cdk.CfnOutput(this, 'Repo1CollaboratorRoleOutput', {
value: `${repo1CollaboratorRole.roleArn}`,
exportName: `${stack.stackName}-Repo1CollaboratorRole`,
description: 'collaborator role of repo1'
});
}