diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts index dc40545e04e49..33a53ac34239f 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts @@ -60,6 +60,21 @@ export interface CodeBuildActionProps extends codepipeline.CommonAwsActionProps * @default CodeBuildActionType.BUILD */ readonly type?: CodeBuildActionType; + + /** + * Whether to validate if the action, + * if it references a project in a different account, + * does not have any outputs. + * This is a known CodeBuild limitation. + * If the action is cross-account, and has outputs, + * and this property is true (which is the default), + * an exception will be thrown. + * You can set this to false to skip this validation. + * + * @default true + * @see https://github.com/aws/aws-cdk/issues/4169 + */ + readonly validateCrossAccountOutputs?: boolean; } /** @@ -83,8 +98,21 @@ export class CodeBuildAction extends Action { this.props = props; } - protected bound(_scope: cdk.Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): + protected bound(scope: cdk.Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): codepipeline.ActionConfig { + // check for a cross-account action if there are any outputs + if (this.props.validateCrossAccountOutputs !== false && + (this.actionProperties.outputs || []).length > 0) { + const pipelineStack = cdk.Stack.of(scope); + const projectStack = cdk.Stack.of(this.props.project); + if (pipelineStack.account !== projectStack.account) { + throw new Error('A cross-account CodeBuild action cannot have outputs. ' + + 'This is a know CodeBuild limitation. ' + + 'See https://github.com/aws/aws-cdk/issues/4169 for details. ' + + 'You can pass the validateCrossAccountOutputs property as false to skip this validation'); + } + } + // grant the Pipeline role the required permissions to this Project options.role.addToPolicy(new iam.PolicyStatement({ resources: [this.props.project.projectArn], diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/codebuild/test.codebuild-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/codebuild/test.codebuild-action.ts new file mode 100644 index 0000000000000..335e9d0e8cd03 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/codebuild/test.codebuild-action.ts @@ -0,0 +1,112 @@ +import codebuild = require('@aws-cdk/aws-codebuild'); +import codecommit = require('@aws-cdk/aws-codecommit'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import { App, Stack } from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import cpactions = require('../../lib'); + +// tslint:disable:object-literal-key-quotes + +export = { + 'a cross-account CodeBuild action with outputs': { + 'causes an error by default'(test: Test) { + const app = new App(); + + const projectStack = new Stack(app, 'ProjectStack', { + env: { + region: 'us-west-2', + account: '012345678901', + }, + }); + const project = new codebuild.PipelineProject(projectStack, 'Project'); + + const pipelineStack = new Stack(app, 'PipelineStack', { + env: { + region: 'us-west-2', + account: '123456789012', + }, + }); + const sourceOutput = new codepipeline.Artifact(); + const pipeline = new codepipeline.Pipeline(pipelineStack, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [new cpactions.CodeCommitSourceAction({ + actionName: 'CodeCommit', + repository: codecommit.Repository.fromRepositoryName(pipelineStack, 'Repo', 'repo-name'), + output: sourceOutput, + })], + }, + ], + }); + const buildStage = pipeline.addStage({ + stageName: 'Build', + }); + + // this works fine - no outputs! + buildStage.addAction(new cpactions.CodeBuildAction({ + actionName: 'Build1', + input: sourceOutput, + project, + })); + + const buildAction2 = new cpactions.CodeBuildAction({ + actionName: 'Build2', + input: sourceOutput, + project, + outputs: [new codepipeline.Artifact()], + }); + + test.throws(() => { + buildStage.addAction(buildAction2); + }, /https:\/\/github\.com\/aws\/aws-cdk\/issues\/4169/); + + test.done(); + }, + + 'works if validateCrossAccountOutputs is passed as false'(test: Test) { + const app = new App(); + + const projectStack = new Stack(app, 'ProjectStack', { + env: { + region: 'us-west-2', + account: '012345678901', + }, + }); + const project = new codebuild.PipelineProject(projectStack, 'Project'); + + const pipelineStack = new Stack(app, 'PipelineStack', { + env: { + region: 'us-west-2', + account: '123456789012', + }, + }); + const sourceOutput = new codepipeline.Artifact(); + const pipeline = new codepipeline.Pipeline(pipelineStack, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [new cpactions.CodeCommitSourceAction({ + actionName: 'CodeCommit', + repository: codecommit.Repository.fromRepositoryName(pipelineStack, 'Repo', 'repo-name'), + output: sourceOutput, + })], + }, + ], + }); + const buildStage = pipeline.addStage({ + stageName: 'Build', + }); + + buildStage.addAction(new cpactions.CodeBuildAction({ + actionName: 'Build2', + input: sourceOutput, + project, + outputs: [new codepipeline.Artifact()], + validateCrossAccountOutputs: false, + })); + + test.done(); + }, + }, +};