diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts index 5bb4fafc565c6..bc679d1747da0 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts @@ -959,5 +959,168 @@ export = { test.done(); }, + + 'adds a dependency on the Stack containing a new action Role'(test: Test) { + const region = 'us-west-2'; + const pipelineAccount = '123456789012'; + const buildAccount = '901234567890'; + const app = new App(); + + const buildStack = new Stack(app, 'BuildStack', { + env: { account: buildAccount, region }, + }); + const actionRolePhysicalName = 'ProjectRolePhysicalName'; + const actionRoleInOtherAccount = new iam.Role(buildStack, 'ProjectRole', { + assumedBy: new iam.AccountPrincipal(pipelineAccount), + roleName: actionRolePhysicalName, + }); + const projectPhysicalName = 'ProjectPhysicalName'; + const project = codebuild.Project.fromProjectName(buildStack, 'Project', + projectPhysicalName); + + const pipelineStack = new Stack(app, 'PipelineStack', { + env: { account: pipelineAccount, region }, + }); + const bucket = new s3.Bucket(pipelineStack, 'ArtifactBucket', { + bucketName: 'source-bucket', + encryption: s3.BucketEncryption.KMS, + }); + const sourceOutput = new codepipeline.Artifact(); + new codepipeline.Pipeline(pipelineStack, 'Pipeline', { + artifactBucket: bucket, + stages: [ + { + stageName: 'Source', + actions: [ + new cpactions.S3SourceAction({ + actionName: 'S3', + bucket, + bucketKey: 'path/to/file.zip', + output: sourceOutput, + }), + ], + }, + { + stageName: 'Build', + actions: [ + new cpactions.CodeBuildAction({ + actionName: 'CodeBuild', + project, + input: sourceOutput, + role: actionRoleInOtherAccount, + }), + ], + }, + ], + }); + + expect(pipelineStack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + "Stages": [ + { + "Name": "Source", + }, + { + "Name": "Build", + "Actions": [ + { + "Name": "CodeBuild", + "Configuration": { + "ProjectName": projectPhysicalName, + }, + "RoleArn": { + "Fn::Join": ["", [ + "arn:", + { "Ref": "AWS::Partition" }, + `:iam::${buildAccount}:role/${actionRolePhysicalName}`, + ]], + }, + }, + ], + }, + ], + })); + + test.equal(pipelineStack.dependencies.length, 1); + + test.done(); + }, + + 'does not add a dependency on the Stack containing an imported action Role'(test: Test) { + const region = 'us-west-2'; + const pipelineAccount = '123456789012'; + const buildAccount = '901234567890'; + const app = new App(); + + const buildStack = new Stack(app, 'BuildStack', { + env: { account: buildAccount, region }, + }); + const actionRolePhysicalName = 'ProjectRolePhysicalName'; + const actionRoleInOtherAccount = iam.Role.fromRoleArn(buildStack, 'ProjectRole', + `arn:aws:iam::${buildAccount}:role/${actionRolePhysicalName}`); + const projectPhysicalName = 'ProjectPhysicalName'; + const project = new codebuild.PipelineProject(buildStack, 'Project', { + projectName: projectPhysicalName, + }); + + const pipelineStack = new Stack(app, 'PipelineStack', { + env: { account: pipelineAccount, region }, + }); + const bucket = new s3.Bucket(pipelineStack, 'ArtifactBucket', { + bucketName: 'source-bucket', + encryption: s3.BucketEncryption.KMS, + }); + const sourceOutput = new codepipeline.Artifact(); + new codepipeline.Pipeline(pipelineStack, 'Pipeline', { + artifactBucket: bucket, + stages: [ + { + stageName: 'Source', + actions: [ + new cpactions.S3SourceAction({ + actionName: 'S3', + bucket, + bucketKey: 'path/to/file.zip', + output: sourceOutput, + }), + ], + }, + { + stageName: 'Build', + actions: [ + new cpactions.CodeBuildAction({ + actionName: 'CodeBuild', + project, + input: sourceOutput, + role: actionRoleInOtherAccount, + }), + ], + }, + ], + }); + + expect(pipelineStack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + "Stages": [ + { + "Name": "Source", + }, + { + "Name": "Build", + "Actions": [ + { + "Name": "CodeBuild", + "Configuration": { + "ProjectName": projectPhysicalName, + }, + "RoleArn": `arn:aws:iam::${buildAccount}:role/${actionRolePhysicalName}`, + }, + ], + }, + ], + })); + + test.equal(pipelineStack.dependencies.length, 0); + + test.done(); + }, }, }; diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index e8b7831d29a51..9250def339fbb 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -541,8 +541,14 @@ export class Pipeline extends PipelineBase { if (action.actionProperties.role) { if (this.isAwsOwned(action)) { // the role has to be deployed before the pipeline - const roleStack = Stack.of(action.actionProperties.role); - pipelineStack.addDependency(roleStack); + // (our magical cross-stack dependencies will not work, + // because the role might be from a different environment), + // but _only_ if it's a new Role - + // an imported Role should not add the dependency + if (action.actionProperties.role instanceof iam.Role) { + const roleStack = Stack.of(action.actionProperties.role); + pipelineStack.addDependency(roleStack); + } return action.actionProperties.role; } else {