Skip to content

Commit 770f9aa

Browse files
authored
feat(aws-codebuild): Introduce a CodePipeline test Action. (#873)
1 parent bf73b09 commit 770f9aa

File tree

10 files changed

+271
-40
lines changed

10 files changed

+271
-40
lines changed

packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ class StageDouble implements cpapi.IStage, cpapi.IInternalStage {
196196
this.pipelineRole = pipelineRole;
197197
}
198198

199+
public grantPipelineBucketRead() {
200+
throw new Error('Unsupported');
201+
}
202+
199203
public grantPipelineBucketReadWrite() {
200204
throw new Error('Unsupported');
201205
}

packages/@aws-cdk/aws-codebuild/README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,30 @@ You can also add the Project to the Pipeline directly:
7575
7676
```ts
7777
// equivalent to the code above:
78-
project.addBuildToPipeline(buildStage, 'CodeBuild');
78+
const buildAction = project.addBuildToPipeline(buildStage, 'CodeBuild');
79+
```
80+
81+
In addition to the build Action,
82+
there is also a test Action.
83+
It works very similarly to the build Action,
84+
the only difference is that the test Action does not always produce an output artifact.
85+
86+
Examples:
87+
88+
```ts
89+
new codebuild.PipelineTestAction(this, 'IntegrationTest', {
90+
stage: buildStage,
91+
project,
92+
// outputArtifactName is optional - if you don't specify it,
93+
// the Action will have an undefined `outputArtifact` property
94+
outputArtifactName: 'IntegrationTestOutput',
95+
});
96+
97+
// equivalent to the code above:
98+
project.addTestToPipeline(buildStage, 'IntegrationTest', {
99+
// of course, this property is optional here as well
100+
outputArtifactName: 'IntegrationTestOutput',
101+
});
79102
```
80103
81104
### Using Project as an event target

packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,33 +44,84 @@ export class PipelineBuildAction extends codepipeline.BuildAction {
4444
// https://qiita.com/ikeisuke/items/2fbc0b80b9bbd981b41f
4545

4646
super(parent, name, {
47-
stage: props.stage,
48-
runOrder: props.runOrder,
4947
provider: 'CodeBuild',
50-
inputArtifact: props.inputArtifact,
51-
outputArtifactName: props.outputArtifactName,
5248
configuration: {
53-
ProjectName: props.project.projectName
54-
}
49+
ProjectName: props.project.projectName,
50+
},
51+
...props,
5552
});
5653

57-
const actions = [
54+
setCodeBuildNeededPermissions(props.stage, props.project, true);
55+
}
56+
}
57+
58+
/**
59+
* Common properties for creating {@link PipelineTestAction} -
60+
* either directly, through its constructor,
61+
* or through {@link ProjectRef#addTestToPipeline}.
62+
*/
63+
export interface CommonPipelineTestActionProps extends codepipeline.CommonActionProps {
64+
/**
65+
* The source to use as input for this test.
66+
*
67+
* @default CodePipeline will use the output of the last Action from a previous Stage as input
68+
*/
69+
inputArtifact?: codepipeline.Artifact;
70+
71+
/**
72+
* The optional name of the output artifact.
73+
* If you provide a value here,
74+
* then the `outputArtifact` property of your Action will be non-null.
75+
* If you don't, `outputArtifact` will be `null`.
76+
*
77+
* @default the Action will not have an output artifact
78+
*/
79+
outputArtifactName?: string;
80+
}
81+
82+
/**
83+
* Construction properties of the {@link PipelineTestAction CodeBuild test CodePipeline Action}.
84+
*/
85+
export interface PipelineTestActionProps extends CommonPipelineTestActionProps,
86+
codepipeline.CommonActionConstructProps {
87+
/**
88+
* The build Project.
89+
*/
90+
project: ProjectRef;
91+
}
92+
93+
export class PipelineTestAction extends codepipeline.TestAction {
94+
constructor(parent: cdk.Construct, name: string, props: PipelineTestActionProps) {
95+
super(parent, name, {
96+
provider: 'CodeBuild',
97+
configuration: {
98+
ProjectName: props.project.projectName,
99+
},
100+
...props,
101+
});
102+
103+
// the Action needs write permissions only if it's producing an output artifact
104+
setCodeBuildNeededPermissions(props.stage, props.project, !!props.outputArtifactName);
105+
}
106+
}
107+
108+
function setCodeBuildNeededPermissions(stage: codepipeline.IStage, project: ProjectRef,
109+
needsPipelineBucketWrite: boolean) {
110+
// grant the Pipeline role the required permissions to this Project
111+
stage.pipelineRole.addToPolicy(new iam.PolicyStatement()
112+
.addResource(project.projectArn)
113+
.addActions(
58114
'codebuild:BatchGetBuilds',
59115
'codebuild:StartBuild',
60116
'codebuild:StopBuild',
61-
];
117+
));
62118

63-
props.stage.pipelineRole.addToPolicy(new iam.PolicyStatement()
64-
.addResource(props.project.projectArn)
65-
.addActions(...actions));
66-
67-
// allow codebuild to read and write artifacts to the pipline's artifact bucket.
68-
if (props.project.role) {
69-
props.stage.grantPipelineBucketReadWrite(props.project.role);
119+
// allow the Project access to the Pipline's artifact Bucket
120+
if (project.role) {
121+
if (needsPipelineBucketWrite) {
122+
stage.grantPipelineBucketReadWrite(project.role);
123+
} else {
124+
stage.grantPipelineBucketRead(project.role);
70125
}
71-
72-
// policy must be added as a dependency to the pipeline!!
73-
// TODO: grants - build.addResourcePermission() and also make sure permission
74-
// includes the pipeline role AWS principal.
75126
}
76127
}

packages/@aws-cdk/aws-codebuild/lib/project.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import s3 = require('@aws-cdk/aws-s3');
88
import cdk = require('@aws-cdk/cdk');
99
import { BuildArtifacts, CodePipelineBuildArtifacts, NoBuildArtifacts } from './artifacts';
1010
import { cloudformation } from './codebuild.generated';
11-
import { CommonPipelineBuildActionProps, PipelineBuildAction } from './pipeline-actions';
11+
import {
12+
CommonPipelineBuildActionProps, CommonPipelineTestActionProps,
13+
PipelineBuildAction, PipelineTestAction
14+
} from './pipeline-actions';
1215
import { BuildSource, NoSource } from './source';
1316

1417
const CODEPIPELINE_TYPE = 'CODEPIPELINE';
@@ -97,6 +100,23 @@ export abstract class ProjectRef extends cdk.Construct implements events.IEventR
97100
});
98101
}
99102

103+
/**
104+
* Convenience method for creating a new {@link PipelineTestAction} test Action,
105+
* and adding it to the given Stage.
106+
*
107+
* @param stage the Pipeline Stage to add the new Action to
108+
* @param name the name of the newly created Action
109+
* @param props the properties of the new Action
110+
* @returns the newly created {@link PipelineBuildAction} test Action
111+
*/
112+
public addTestToPipeline(stage: codepipeline.IStage, name: string, props: CommonPipelineTestActionProps = {}): PipelineTestAction {
113+
return new PipelineTestAction(this, name, {
114+
stage,
115+
project: this,
116+
...props,
117+
});
118+
}
119+
100120
/**
101121
* Defines a CloudWatch event rule triggered when the build project state
102122
* changes. You can filter specific build status events using an event

packages/@aws-cdk/aws-codepipeline-api/lib/action.ts

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ export interface IStage {
9292
*/
9393
readonly _internal: IInternalStage;
9494

95+
/* Grants read permissions to the Pipeline's S3 Bucket to the given Identity.
96+
*
97+
* @param identity the IAM Identity to grant the permissions to
98+
*/
99+
grantPipelineBucketRead(identity: iam.IPrincipal): void;
100+
95101
/**
96102
* Grants read & write permissions to the Pipeline's S3 Bucket to the given Identity.
97103
*
@@ -238,25 +244,6 @@ export abstract class Action extends cdk.Construct {
238244
}
239245
}
240246

241-
// export class TestAction extends Action {
242-
// constructor(parent: Stage, name: string, provider: string, artifactBounds: ActionArtifactBounds, configuration?: any) {
243-
// super(parent, name, {
244-
// category: ActionCategory.Test,
245-
// provider,
246-
// artifactBounds,
247-
// configuration
248-
// });
249-
// }
250-
// }
251-
252-
// export class CodeBuildTest extends TestAction {
253-
// constructor(parent: Stage, name: string, project: codebuild.ProjectArnAttribute) {
254-
// super(parent, name, 'CodeBuild', { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 1 }, {
255-
// ProjectName: project
256-
// });
257-
// }
258-
// }
259-
260247
// export class ElasticBeanstalkDeploy extends DeployAction {
261248
// constructor(parent: Stage, name: string, applicationName: string, environmentName: string) {
262249
// super(parent, name, 'ElasticBeanstalk', { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 }, {

packages/@aws-cdk/aws-codepipeline-api/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export * from './action';
33
export * from './build-action';
44
export * from './deploy-action';
55
export * from './source-action';
6+
export * from './test-action';
67
export * from './validation';
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import cdk = require("@aws-cdk/cdk");
2+
import { Action, ActionCategory, CommonActionConstructProps, CommonActionProps } from "./action";
3+
import { Artifact } from "./artifact";
4+
5+
/**
6+
* Construction properties of the low-level {@link TestAction test Action}.
7+
*/
8+
export interface TestActionProps extends CommonActionProps, CommonActionConstructProps {
9+
/**
10+
* The source to use as input for this test.
11+
*
12+
* @default CodePipeline will use the output of the last Action from a previous Stage as input
13+
*/
14+
inputArtifact?: Artifact;
15+
16+
/**
17+
* The optional name of the output artifact.
18+
* If you provide a value here,
19+
* then the `outputArtifact` property of your Action will be non-null.
20+
* If you don't, `outputArtifact` will be `null`.
21+
*
22+
* @default the Action will not have an output artifact
23+
*/
24+
outputArtifactName?: string;
25+
26+
/**
27+
* The service provider that the action calls.
28+
*
29+
* @example 'CodeBuild'
30+
*/
31+
provider: string;
32+
33+
/**
34+
* The source action owner (could be 'AWS', 'ThirdParty' or 'Custom').
35+
*
36+
* @default 'AWS'
37+
*/
38+
owner?: string;
39+
40+
/**
41+
* The action's configuration. These are key-value pairs that specify input values for an action.
42+
* For more information, see the AWS CodePipeline User Guide.
43+
*
44+
* http://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html#action-requirements
45+
*/
46+
configuration?: any;
47+
}
48+
49+
/**
50+
* The low-level test Action.
51+
*
52+
* Test Actions are very similar to build Actions -
53+
* the difference is that test Actions don't have to have an output artifact.
54+
*
55+
* You should never need to use this class directly,
56+
* instead preferring the concrete implementations,
57+
* like {@link codebuild.PipelineTestAction}.
58+
*/
59+
export abstract class TestAction extends Action {
60+
public readonly outputArtifact?: Artifact;
61+
62+
constructor(parent: cdk.Construct, name: string, props: TestActionProps) {
63+
super(parent, name, {
64+
category: ActionCategory.Test,
65+
artifactBounds: { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 },
66+
...props,
67+
});
68+
69+
this.addInputArtifact(props.inputArtifact);
70+
if (props.outputArtifactName) {
71+
this.outputArtifact = this.addOutputArtifact(props.outputArtifactName);
72+
}
73+
}
74+
}

packages/@aws-cdk/aws-codepipeline/lib/stage.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ export class Stage extends cdk.Construct implements actions.IStage, actions.IInt
110110
return this.validateHasActions();
111111
}
112112

113+
public grantPipelineBucketRead(identity: iam.IPrincipal): void {
114+
this.pipeline.artifactBucket.grantRead(identity);
115+
}
116+
113117
public grantPipelineBucketReadWrite(identity: iam.IPrincipal): void {
114118
this.pipeline.artifactBucket.grantReadWrite(identity);
115119
}

0 commit comments

Comments
 (0)