(@aws-cdk/pipelines) 应用程序源代码的构建阶段
(@aws-cdk/pipelines) Build stage for application source code
我完成了 CDK Pipelines: Continuous delivery for AWS CDK applications tutorial, which gave an overview of creating a self-mutating CDK pipeline with the new CodePipeline API。
本教程创建了一个 CodePipeline,每次将更改推送到 master 分支时,CDK 源代码都会自动从 GitHub 存储库中检索。 CDK 代码定义了一个 lambda,其中包含与 CDK 一起定义的打字稿处理程序。
对于我的用例,我想定义一个自变异 CodePipeline,每当我推送到包含我的应用程序源代码的第二个存储库时,它也会被触发。第二个存储库还将包含一个构建规范,该构建规范使用我的应用程序生成一个 Docker 图像并将该图像上传到 ECR。然后,新映像将在我的管道的应用程序阶段部署到 Fargate 集群。
我在 PublishAssets 阶段之后创建了一个 ApplicationBuild
阶段,其中包括一个 CodeBuild 项目。 CodeBuild 项目从我的存储库中读取并构建/上传图像到 ECR;但是,我需要一种方法将此 CodeBuild link 部署到管道中。我不清楚如何使用新的 cdk CodePipeline API.
如果有人遇到同样的问题,我可以使用我在问题中提到的教程的 legacy CdkPipeline API following the archived version 来破解一个解决方案。
这是一个最小的可行管道堆栈,其中包括...
- CDK 流水线源操作(在“源”阶段)
- 应用程序源操作(在“源”阶段)
- 一个 CDK 构建操作(在“Build”阶段)+ 自变异管道(“UpdatePipeline”阶段)
- 应用程序构建操作(在“构建”阶段)
lib/cdkpipelines-demo-pipeline-stack.ts
import * as codepipeline from '@aws-cdk/aws-codepipeline';
import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions';
import * as core from '@aws-cdk/core';
import {Construct, SecretValue, Stack, StackProps} from '@aws-cdk/core';
import {CdkPipeline, SimpleSynthAction} from "@aws-cdk/pipelines";
import * as iam from "@aws-cdk/aws-iam";
import * as ecr from "@aws-cdk/aws-ecr";
import * as codebuild from "@aws-cdk/aws-codebuild";
/**
* The stack that defines the application pipeline
*/
export class CdkpipelinesDemoPipelineStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const sourceArtifact = new codepipeline.Artifact();
const cloudAssemblyArtifact = new codepipeline.Artifact();
const pipeline = new CdkPipeline(this, 'Pipeline', {
// The pipeline name
pipelineName: 'MyServicePipeline',
cloudAssemblyArtifact,
// Where the source can be found
sourceAction: new codepipeline_actions.GitHubSourceAction({
actionName: 'GitHub',
output: sourceArtifact,
oauthToken: SecretValue.secretsManager('github-token'),
owner: 'OWNER',
repo: 'REPO',
}),
// How it will be built and synthesized
synthAction: SimpleSynthAction.standardNpmSynth({
sourceArtifact,
cloudAssemblyArtifact,
// We need a build step to compile the TypeScript Lambda
buildCommand: 'npm run build'
}),
});
const pipelineRole = pipeline.codePipeline.role;
// Add application source action
const appSourceArtifact = new codepipeline.Artifact();
const appSourceAction = this.createAppSourceAction(appSourceArtifact);
const sourceStage = pipeline.stage("Source");
sourceStage.addAction(appSourceAction);
// Add application build action
const codeBuildServiceRole = this.createCodeBuildServiceRole(this, pipelineRole);
const repository = this.createApplicationRepository(this, codeBuildServiceRole);
const pipelineProject = this.createCodeBuildPipelineProject(
this, codeBuildServiceRole, repository, 'REGION', 'ACCOUNT_ID');
const appBuildOutput = new codepipeline.Artifact();
const appBuildAction = this.createAppCodeBuildAction(
this, appSourceArtifact, appBuildOutput, pipelineProject, codeBuildServiceRole);
const buildStage = pipeline.stage("Build");
buildStage.addAction(appBuildAction);
// This is where we add the application stages...
}
createAppSourceAction(appSourceArtifact: codepipeline.Artifact): codepipeline_actions.GitHubSourceAction {
return new codepipeline_actions.GitHubSourceAction({
actionName: 'GitHub-App-Source',
output: appSourceArtifact,
oauthToken: SecretValue.secretsManager('github-token'),
owner: 'SOURCE-OWNER',
repo: 'SOURCE-REPO',
});
}
createCodeBuildServiceRole(scope: core.Construct, pipelineRole: iam.IRole): iam.Role {
const role = new iam.Role(scope, 'CodeBuildServiceRole', {
assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
});
role.assumeRolePolicy?.addStatements(new iam.PolicyStatement({
sid: "PipelineAssumeCodeBuildServiceRole",
effect: iam.Effect.ALLOW,
actions: ["sts:AssumeRole"],
principals: [pipelineRole]
}));
// Required policies to create an AWS CodeBuild service role
role.addToPolicy(new iam.PolicyStatement({
sid: "CloudWatchLogsPolicy",
effect: iam.Effect.ALLOW,
actions: [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
resources: ["*"]
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "CodeCommitPolicy",
effect: iam.Effect.ALLOW,
actions: ["codecommit:GitPull"],
resources: ["*"]
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "S3GetObjectPolicy",
effect: iam.Effect.ALLOW,
actions: [
"s3:GetObject",
"s3:GetObjectVersion"
],
resources: ["*"]
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "S3PutObjectPolicy",
effect: iam.Effect.ALLOW,
actions: [
"s3:PutObject"
],
resources: ["*"]
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "S3BucketIdentity",
effect: iam.Effect.ALLOW,
actions: [
"s3:GetBucketAcl",
"s3:GetBucketLocation"
],
resources: ["*"]
}));
// This statement allows CodeBuild to upload Docker images to Amazon ECR repositories.
// source: https://docs.aws.amazon.com/codebuild/latest/userguide/sample-docker.html#sample-docker-running
role.addToPolicy(new iam.PolicyStatement({
sid: "ECRUploadPolicy",
effect: iam.Effect.ALLOW,
actions: [
"ecr:BatchCheckLayerAvailability",
"ecr:CompleteLayerUpload",
"ecr:GetAuthorizationToken",
"ecr:InitiateLayerUpload",
"ecr:PutImage",
"ecr:UploadLayerPart"
],
resources: ["*"]
}));
return role;
}
createApplicationRepository(scope: core.Construct, codeBuildServiceRole: iam.Role): ecr.Repository {
const repository = new ecr.Repository(scope, 'Repository', {
repositoryName: 'cdkpipelines-demo-image-repository'
});
repository.grantPullPush(codeBuildServiceRole);
return repository;
}
createCodeBuildPipelineProject(scope: core.Construct,
codeBuildServiceRole: iam.Role,
repository: ecr.Repository,
region: string,
accountId: string): codebuild.PipelineProject {
return new codebuild.PipelineProject(scope, 'BuildProject', {
buildSpec: codebuild.BuildSpec.fromSourceFilename("buildspec.yml"),
environment: {
buildImage: codebuild.LinuxBuildImage.fromCodeBuildImageId("aws/codebuild/standard:4.0"),
privileged: true,
computeType: codebuild.ComputeType.SMALL,
environmentVariables: {
AWS_DEFAULT_REGION: {value: region},
AWS_ACCOUNT_ID: {value: accountId},
IMAGE_REPO_NAME: {value: repository.repositoryName},
IMAGE_TAG: {value: "latest"},
}
},
role: codeBuildServiceRole
});
}
createAppCodeBuildAction(scope: core.Construct,
input: codepipeline.Artifact,
output: codepipeline.Artifact,
pipelineProject: codebuild.PipelineProject,
serviceRole: iam.Role) {
return new codepipeline_actions.CodeBuildAction({
actionName: "App-Build",
checkSecretsInPlainTextEnvVariables: false,
input: input,
outputs: [output],
project: pipelineProject,
role: serviceRole,
type: codepipeline_actions.CodeBuildActionType.BUILD,
})
}
}
我完成了 CDK Pipelines: Continuous delivery for AWS CDK applications tutorial, which gave an overview of creating a self-mutating CDK pipeline with the new CodePipeline API。
本教程创建了一个 CodePipeline,每次将更改推送到 master 分支时,CDK 源代码都会自动从 GitHub 存储库中检索。 CDK 代码定义了一个 lambda,其中包含与 CDK 一起定义的打字稿处理程序。
对于我的用例,我想定义一个自变异 CodePipeline,每当我推送到包含我的应用程序源代码的第二个存储库时,它也会被触发。第二个存储库还将包含一个构建规范,该构建规范使用我的应用程序生成一个 Docker 图像并将该图像上传到 ECR。然后,新映像将在我的管道的应用程序阶段部署到 Fargate 集群。
我在 PublishAssets 阶段之后创建了一个 ApplicationBuild
阶段,其中包括一个 CodeBuild 项目。 CodeBuild 项目从我的存储库中读取并构建/上传图像到 ECR;但是,我需要一种方法将此 CodeBuild link 部署到管道中。我不清楚如何使用新的 cdk CodePipeline API.
如果有人遇到同样的问题,我可以使用我在问题中提到的教程的 legacy CdkPipeline API following the archived version 来破解一个解决方案。
这是一个最小的可行管道堆栈,其中包括...
- CDK 流水线源操作(在“源”阶段)
- 应用程序源操作(在“源”阶段)
- 一个 CDK 构建操作(在“Build”阶段)+ 自变异管道(“UpdatePipeline”阶段)
- 应用程序构建操作(在“构建”阶段)
lib/cdkpipelines-demo-pipeline-stack.ts
import * as codepipeline from '@aws-cdk/aws-codepipeline';
import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions';
import * as core from '@aws-cdk/core';
import {Construct, SecretValue, Stack, StackProps} from '@aws-cdk/core';
import {CdkPipeline, SimpleSynthAction} from "@aws-cdk/pipelines";
import * as iam from "@aws-cdk/aws-iam";
import * as ecr from "@aws-cdk/aws-ecr";
import * as codebuild from "@aws-cdk/aws-codebuild";
/**
* The stack that defines the application pipeline
*/
export class CdkpipelinesDemoPipelineStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const sourceArtifact = new codepipeline.Artifact();
const cloudAssemblyArtifact = new codepipeline.Artifact();
const pipeline = new CdkPipeline(this, 'Pipeline', {
// The pipeline name
pipelineName: 'MyServicePipeline',
cloudAssemblyArtifact,
// Where the source can be found
sourceAction: new codepipeline_actions.GitHubSourceAction({
actionName: 'GitHub',
output: sourceArtifact,
oauthToken: SecretValue.secretsManager('github-token'),
owner: 'OWNER',
repo: 'REPO',
}),
// How it will be built and synthesized
synthAction: SimpleSynthAction.standardNpmSynth({
sourceArtifact,
cloudAssemblyArtifact,
// We need a build step to compile the TypeScript Lambda
buildCommand: 'npm run build'
}),
});
const pipelineRole = pipeline.codePipeline.role;
// Add application source action
const appSourceArtifact = new codepipeline.Artifact();
const appSourceAction = this.createAppSourceAction(appSourceArtifact);
const sourceStage = pipeline.stage("Source");
sourceStage.addAction(appSourceAction);
// Add application build action
const codeBuildServiceRole = this.createCodeBuildServiceRole(this, pipelineRole);
const repository = this.createApplicationRepository(this, codeBuildServiceRole);
const pipelineProject = this.createCodeBuildPipelineProject(
this, codeBuildServiceRole, repository, 'REGION', 'ACCOUNT_ID');
const appBuildOutput = new codepipeline.Artifact();
const appBuildAction = this.createAppCodeBuildAction(
this, appSourceArtifact, appBuildOutput, pipelineProject, codeBuildServiceRole);
const buildStage = pipeline.stage("Build");
buildStage.addAction(appBuildAction);
// This is where we add the application stages...
}
createAppSourceAction(appSourceArtifact: codepipeline.Artifact): codepipeline_actions.GitHubSourceAction {
return new codepipeline_actions.GitHubSourceAction({
actionName: 'GitHub-App-Source',
output: appSourceArtifact,
oauthToken: SecretValue.secretsManager('github-token'),
owner: 'SOURCE-OWNER',
repo: 'SOURCE-REPO',
});
}
createCodeBuildServiceRole(scope: core.Construct, pipelineRole: iam.IRole): iam.Role {
const role = new iam.Role(scope, 'CodeBuildServiceRole', {
assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
});
role.assumeRolePolicy?.addStatements(new iam.PolicyStatement({
sid: "PipelineAssumeCodeBuildServiceRole",
effect: iam.Effect.ALLOW,
actions: ["sts:AssumeRole"],
principals: [pipelineRole]
}));
// Required policies to create an AWS CodeBuild service role
role.addToPolicy(new iam.PolicyStatement({
sid: "CloudWatchLogsPolicy",
effect: iam.Effect.ALLOW,
actions: [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
resources: ["*"]
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "CodeCommitPolicy",
effect: iam.Effect.ALLOW,
actions: ["codecommit:GitPull"],
resources: ["*"]
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "S3GetObjectPolicy",
effect: iam.Effect.ALLOW,
actions: [
"s3:GetObject",
"s3:GetObjectVersion"
],
resources: ["*"]
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "S3PutObjectPolicy",
effect: iam.Effect.ALLOW,
actions: [
"s3:PutObject"
],
resources: ["*"]
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "S3BucketIdentity",
effect: iam.Effect.ALLOW,
actions: [
"s3:GetBucketAcl",
"s3:GetBucketLocation"
],
resources: ["*"]
}));
// This statement allows CodeBuild to upload Docker images to Amazon ECR repositories.
// source: https://docs.aws.amazon.com/codebuild/latest/userguide/sample-docker.html#sample-docker-running
role.addToPolicy(new iam.PolicyStatement({
sid: "ECRUploadPolicy",
effect: iam.Effect.ALLOW,
actions: [
"ecr:BatchCheckLayerAvailability",
"ecr:CompleteLayerUpload",
"ecr:GetAuthorizationToken",
"ecr:InitiateLayerUpload",
"ecr:PutImage",
"ecr:UploadLayerPart"
],
resources: ["*"]
}));
return role;
}
createApplicationRepository(scope: core.Construct, codeBuildServiceRole: iam.Role): ecr.Repository {
const repository = new ecr.Repository(scope, 'Repository', {
repositoryName: 'cdkpipelines-demo-image-repository'
});
repository.grantPullPush(codeBuildServiceRole);
return repository;
}
createCodeBuildPipelineProject(scope: core.Construct,
codeBuildServiceRole: iam.Role,
repository: ecr.Repository,
region: string,
accountId: string): codebuild.PipelineProject {
return new codebuild.PipelineProject(scope, 'BuildProject', {
buildSpec: codebuild.BuildSpec.fromSourceFilename("buildspec.yml"),
environment: {
buildImage: codebuild.LinuxBuildImage.fromCodeBuildImageId("aws/codebuild/standard:4.0"),
privileged: true,
computeType: codebuild.ComputeType.SMALL,
environmentVariables: {
AWS_DEFAULT_REGION: {value: region},
AWS_ACCOUNT_ID: {value: accountId},
IMAGE_REPO_NAME: {value: repository.repositoryName},
IMAGE_TAG: {value: "latest"},
}
},
role: codeBuildServiceRole
});
}
createAppCodeBuildAction(scope: core.Construct,
input: codepipeline.Artifact,
output: codepipeline.Artifact,
pipelineProject: codebuild.PipelineProject,
serviceRole: iam.Role) {
return new codepipeline_actions.CodeBuildAction({
actionName: "App-Build",
checkSecretsInPlainTextEnvVariables: false,
input: input,
outputs: [output],
project: pipelineProject,
role: serviceRole,
type: codepipeline_actions.CodeBuildActionType.BUILD,
})
}
}