Skip to content

Commit

Permalink
feat(aws-codebuild): Introduce a CodePipeline test Action. (#873)
Browse files Browse the repository at this point in the history
  • Loading branch information
skinny85 authored Oct 20, 2018
1 parent bf73b09 commit 770f9aa
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ class StageDouble implements cpapi.IStage, cpapi.IInternalStage {
this.pipelineRole = pipelineRole;
}

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

public grantPipelineBucketReadWrite() {
throw new Error('Unsupported');
}
Expand Down
25 changes: 24 additions & 1 deletion packages/@aws-cdk/aws-codebuild/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,30 @@ You can also add the Project to the Pipeline directly:
```ts
// equivalent to the code above:
project.addBuildToPipeline(buildStage, 'CodeBuild');
const buildAction = project.addBuildToPipeline(buildStage, 'CodeBuild');
```
In addition to the build Action,
there is also a test Action.
It works very similarly to the build Action,
the only difference is that the test Action does not always produce an output artifact.
Examples:
```ts
new codebuild.PipelineTestAction(this, 'IntegrationTest', {
stage: buildStage,
project,
// outputArtifactName is optional - if you don't specify it,
// the Action will have an undefined `outputArtifact` property
outputArtifactName: 'IntegrationTestOutput',
});

// equivalent to the code above:
project.addTestToPipeline(buildStage, 'IntegrationTest', {
// of course, this property is optional here as well
outputArtifactName: 'IntegrationTestOutput',
});
```
### Using Project as an event target
Expand Down
89 changes: 70 additions & 19 deletions packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,33 +44,84 @@ export class PipelineBuildAction extends codepipeline.BuildAction {
// https://qiita.com/ikeisuke/items/2fbc0b80b9bbd981b41f

super(parent, name, {
stage: props.stage,
runOrder: props.runOrder,
provider: 'CodeBuild',
inputArtifact: props.inputArtifact,
outputArtifactName: props.outputArtifactName,
configuration: {
ProjectName: props.project.projectName
}
ProjectName: props.project.projectName,
},
...props,
});

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 extends codepipeline.CommonActionProps {
/**
* The source to use as input for this test.
*
* @default CodePipeline will use the output of the last Action from a previous Stage as input
*/
inputArtifact?: codepipeline.Artifact;

/**
* The optional name of the output artifact.
* If you provide a value here,
* then the `outputArtifact` property of your Action will be non-null.
* If you don't, `outputArtifact` will be `null`.
*
* @default the Action will not have an output artifact
*/
outputArtifactName?: string;
}

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

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

// the Action needs write permissions only if it's producing an output artifact
setCodeBuildNeededPermissions(props.stage, props.project, !!props.outputArtifactName);
}
}

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, 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
25 changes: 6 additions & 19 deletions packages/@aws-cdk/aws-codepipeline-api/lib/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ export interface IStage {
*/
readonly _internal: IInternalStage;

/* 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 @@ -238,25 +244,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';
74 changes: 74 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,74 @@
import cdk = require("@aws-cdk/cdk");
import { Action, ActionCategory, CommonActionConstructProps, CommonActionProps } from "./action";
import { Artifact } from "./artifact";

/**
* Construction properties of the low-level {@link TestAction test Action}.
*/
export interface TestActionProps extends CommonActionProps, CommonActionConstructProps {
/**
* The source to use as input for this test.
*
* @default CodePipeline will use the output of the last Action from a previous Stage as input
*/
inputArtifact?: Artifact;

/**
* The optional name of the output artifact.
* If you provide a value here,
* then the `outputArtifact` property of your Action will be non-null.
* If you don't, `outputArtifact` will be `null`.
*
* @default the Action will not have an output artifact
*/
outputArtifactName?: string;

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

/**
* The source action owner (could be 'AWS', 'ThirdParty' or 'Custom').
*
* @default 'AWS'
*/
owner?: 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;
}

/**
* The low-level test Action.
*
* Test Actions are very similar to build Actions -
* the difference is that test Actions don't have to have an output artifact.
*
* You should never need to use this class directly,
* instead preferring the concrete implementations,
* like {@link codebuild.PipelineTestAction}.
*/
export abstract class TestAction extends Action {
public readonly outputArtifact?: Artifact;

constructor(parent: cdk.Construct, name: string, props: TestActionProps) {
super(parent, name, {
category: ActionCategory.Test,
artifactBounds: { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 },
...props,
});

this.addInputArtifact(props.inputArtifact);
if (props.outputArtifactName) {
this.outputArtifact = this.addOutputArtifact(props.outputArtifactName);
}
}
}
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 @@ -110,6 +110,10 @@ export class Stage extends cdk.Construct implements actions.IStage, actions.IInt
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
Loading

0 comments on commit 770f9aa

Please sign in to comment.