Skip to content

Commit

Permalink
feat(aws-codebuild): Introduce a CodePipeline test Action.
Browse files Browse the repository at this point in the history
  • Loading branch information
skinny85 committed Oct 8, 2018
1 parent 1561a4d commit 22b0557
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ class StageDouble implements cpapi.IStage {
this.pipelineRole = pipelineRole;
}

public grantPipelineBucketRead() {
throw new Error('Unsupported');
}

public grantPipelineBucketReadWrite() {
throw new Error('Unsupported');
}
Expand Down
69 changes: 56 additions & 13 deletions packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,23 +48,66 @@ export class PipelineBuildAction extends codepipeline.BuildAction {
}
});

const actions = [
setCodeBuildNeededPermissions(props.stage, props.project, true);
}
}

/**
* Common properties for creating {@link PipelineTestAction} -
* either directly, through its constructor,
* or through {@link ProjectRef#addTestToPipeline}.
*/
export interface CommonPipelineTestActionProps {
/**
* The source to use as input for this test.
*/
inputArtifact: codepipeline.Artifact;
}

/**
* Construction properties of the {@link PipelineTestAction CodeBuild test CodePipeline Action}.
*/
export interface PipelineTestActionProps extends CommonPipelineTestActionProps, codepipeline.CommonActionProps {
/**
* The build Project.
*/
project: ProjectRef;
}

export class PipelineTestAction extends codepipeline.TestAction {
constructor(parent: cdk.Construct, name: string, props: PipelineTestActionProps) {
super(parent, name, {
stage: props.stage,
provider: 'CodeBuild',
inputArtifact: props.inputArtifact,
configuration: {
ProjectName: props.project.projectName
},
});

// since test Actions never produce any output,
// we only need read access to the Pipeline's Bucket
setCodeBuildNeededPermissions(props.stage, props.project, false);
}
}

function setCodeBuildNeededPermissions(stage: codepipeline.IStage, project: ProjectRef,
needsPipelineBucketWrite: boolean) {
// grant the Pipeline role the required permissions to this Project
stage.pipelineRole.addToPolicy(new iam.PolicyStatement()
.addResource(project.projectArn)
.addActions(
'codebuild:BatchGetBuilds',
'codebuild:StartBuild',
'codebuild:StopBuild',
];

props.stage.pipelineRole.addToPolicy(new iam.PolicyStatement()
.addResource(props.project.projectArn)
.addActions(...actions));
));

// allow codebuild to read and write artifacts to the pipline's artifact bucket.
if (props.project.role) {
props.stage.grantPipelineBucketReadWrite(props.project.role);
// allow the Project access to the Pipline's artifact Bucket
if (project.role) {
if (needsPipelineBucketWrite) {
stage.grantPipelineBucketReadWrite(project.role);
} else {
stage.grantPipelineBucketRead(project.role);
}

// policy must be added as a dependency to the pipeline!!
// TODO: grants - build.addResourcePermission() and also make sure permission
// includes the pipeline role AWS principal.
}
}
22 changes: 21 additions & 1 deletion packages/@aws-cdk/aws-codebuild/lib/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import s3 = require('@aws-cdk/aws-s3');
import cdk = require('@aws-cdk/cdk');
import { BuildArtifacts, CodePipelineBuildArtifacts, NoBuildArtifacts } from './artifacts';
import { cloudformation } from './codebuild.generated';
import { CommonPipelineBuildActionProps, PipelineBuildAction } from './pipeline-actions';
import {
CommonPipelineBuildActionProps, CommonPipelineTestActionProps,
PipelineBuildAction, PipelineTestAction
} from './pipeline-actions';
import { BuildSource, NoSource } from './source';

const CODEPIPELINE_TYPE = 'CODEPIPELINE';
Expand Down Expand Up @@ -97,6 +100,23 @@ export abstract class ProjectRef extends cdk.Construct implements events.IEventR
});
}

/**
* Convenience method for creating a new {@link PipelineTestAction} test Action,
* and adding it to the given Stage.
*
* @param stage the Pipeline Stage to add the new Action to
* @param name the name of the newly created Action
* @param props the properties of the new Action
* @returns the newly created {@link PipelineBuildAction} test Action
*/
public addTestToPipeline(stage: codepipeline.IStage, name: string, props: CommonPipelineTestActionProps): PipelineTestAction {
return new PipelineTestAction(this.parent!, name, {
stage,
project: this,
...props,
});
}

/**
* Defines a CloudWatch event rule triggered when the build project state
* changes. You can filter specific build status events using an event
Expand Down
26 changes: 7 additions & 19 deletions packages/@aws-cdk/aws-codepipeline-api/lib/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ export interface IStage {
*/
readonly pipelineRole: iam.Role;

/**
* Grants read permissions to the Pipeline's S3 Bucket to the given Identity.
*
* @param identity the IAM Identity to grant the permissions to
*/
grantPipelineBucketRead(identity: iam.IPrincipal): void;

/**
* Grants read & write permissions to the Pipeline's S3 Bucket to the given Identity.
*
Expand Down Expand Up @@ -203,25 +210,6 @@ export abstract class Action extends cdk.Construct {
}
}

// export class TestAction extends Action {
// constructor(parent: Stage, name: string, provider: string, artifactBounds: ActionArtifactBounds, configuration?: any) {
// super(parent, name, {
// category: ActionCategory.Test,
// provider,
// artifactBounds,
// configuration
// });
// }
// }

// export class CodeBuildTest extends TestAction {
// constructor(parent: Stage, name: string, project: codebuild.ProjectArnAttribute) {
// super(parent, name, 'CodeBuild', { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 1 }, {
// ProjectName: project
// });
// }
// }

// export class ElasticBeanstalkDeploy extends DeployAction {
// constructor(parent: Stage, name: string, applicationName: string, environmentName: string) {
// super(parent, name, 'ElasticBeanstalk', { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 }, {
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-codepipeline-api/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './action';
export * from './build-action';
export * from './deploy-action';
export * from './source-action';
export * from './test-action';
export * from './validation';
42 changes: 42 additions & 0 deletions packages/@aws-cdk/aws-codepipeline-api/lib/test-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import cdk = require("@aws-cdk/cdk");
import { Action, ActionCategory, CommonActionProps } from "./action";
import { Artifact } from "./artifact";

/**
* Construction properties of the low-level {@link TestAction test Action}.
*/
export interface TestActionProps extends CommonActionProps {
/**
* The source to use as input for this test.
*/
inputArtifact: Artifact;

/**
* The service provider that the action calls.
*
* @example 'CodeBuild'
*/
provider: string;

/**
* The action's configuration. These are key-value pairs that specify input values for an action.
* For more information, see the AWS CodePipeline User Guide.
*
* http://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html#action-requirements
*/
configuration?: any;
}

export abstract class TestAction extends Action {
constructor(parent: cdk.Construct, name: string, props: TestActionProps) {
super(parent, name, {
stage: props.stage,
category: ActionCategory.Test,
artifactBounds: { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 },
provider: props.provider,
configuration: props.configuration,
});

this.addInputArtifact(props.inputArtifact);
}
}
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-codepipeline/lib/stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ export class Stage extends cdk.Construct implements actions.IStage {
return this.validateHasActions();
}

public grantPipelineBucketRead(identity: iam.IPrincipal): void {
this.pipeline.artifactBucket.grantRead(identity);
}

public grantPipelineBucketReadWrite(identity: iam.IPrincipal): void {
this.pipeline.artifactBucket.grantReadWrite(identity);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@
]
}
},
{
"Action": [
"codebuild:BatchGetBuilds",
"codebuild:StartBuild",
"codebuild:StopBuild"
],
"Effect": "Allow",
"Resource": {
"Fn::GetAtt": [
"MyBuildProject30DB9D6E",
"Arn"
]
}
},
{
"Action": [
"codebuild:BatchGetBuilds",
Expand Down Expand Up @@ -177,6 +191,27 @@
"Name": "build",
"OutputArtifacts": [],
"RunOrder": 1
},
{
"ActionTypeId": {
"Category": "Test",
"Owner": "AWS",
"Provider": "CodeBuild",
"Version": "1"
},
"Configuration": {
"ProjectName": {
"Ref": "MyBuildProject30DB9D6E"
}
},
"InputArtifacts": [
{
"Name": "SourceArtifact"
}
],
"Name": "test",
"OutputArtifacts": [],
"RunOrder": 2
}
],
"Name": "build"
Expand Down Expand Up @@ -326,6 +361,37 @@
]
}
]
},
{
"Action": [
"s3:GetObject*",
"s3:GetBucket*",
"s3:List*"
],
"Effect": "Allow",
"Resource": [
{
"Fn::GetAtt": [
"PipelineArtifactsBucket22248F97",
"Arn"
]
},
{
"Fn::Join": [
"",
[
{
"Fn::GetAtt": [
"PipelineArtifactsBucket22248F97",
"Arn"
]
},
"/",
"*"
]
]
}
]
}
],
"Version": "2012-10-17"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,9 @@ const buildStage = new codepipeline.Stage(pipeline, 'build', { pipeline });
project.addBuildToPipeline(buildStage, 'build', {
inputArtifact: source.artifact,
});
const testAction = project.addTestToPipeline(buildStage, 'test', {
inputArtifact: source.artifact,
});
testAction.runOrder = 2;

app.run();

0 comments on commit 22b0557

Please sign in to comment.