@@ -375,10 +375,7 @@ export class Pipeline extends PipelineBase {
375
375
throw new Error ( "You need to specify an explicit account when using CodePipeline's cross-region support" ) ;
376
376
}
377
377
378
- const app = this . node . root ;
379
- if ( ! app || ! App . isApp ( app ) ) {
380
- throw new Error ( `Pipeline stack which uses cross region actions must be part of a CDK app` ) ;
381
- }
378
+ const app = this . requireApp ( ) ;
382
379
const crossRegionScaffoldStack = new CrossRegionSupportStack ( app , `cross-region-stack-${ pipelineAccount } :${ region } ` , {
383
380
pipelineStackName : pipelineStack . stackName ,
384
381
region,
@@ -404,44 +401,16 @@ export class Pipeline extends PipelineBase {
404
401
405
402
/**
406
403
* Gets the role used for this action,
407
- * including handling the case when the action is supposed to be cross-region .
404
+ * including handling the case when the action is supposed to be cross-account .
408
405
*
409
406
* @param stage the stage the action belongs to
410
407
* @param action the action to return/create a role for
408
+ * @param actionScope the scope, unique to the action, to create new resources in
411
409
*/
412
410
private getRoleForAction ( stage : Stage , action : IAction , actionScope : Construct ) : iam . IRole | undefined {
413
411
const pipelineStack = Stack . of ( this ) ;
414
412
415
- let actionRole : iam . IRole | undefined ;
416
- if ( action . actionProperties . role ) {
417
- if ( ! this . isAwsOwned ( action ) ) {
418
- throw new Error ( "Specifying a Role is not supported for actions with an owner different than 'AWS' - " +
419
- `got '${ action . actionProperties . owner } ' (Action: '${ action . actionProperties . actionName } ' in Stage: '${ stage . stageName } ')` ) ;
420
- }
421
- actionRole = action . actionProperties . role ;
422
- } else if ( action . actionProperties . resource ) {
423
- const resourceStack = Stack . of ( action . actionProperties . resource ) ;
424
- // check if resource is from a different account
425
- if ( pipelineStack . environment !== resourceStack . environment ) {
426
- // if it is, the pipeline's bucket must have a KMS key
427
- if ( ! this . artifactBucket . encryptionKey ) {
428
- throw new Error ( 'The Pipeline is being used in a cross-account manner, ' +
429
- 'but its artifact bucket does not have a KMS key defined. ' +
430
- 'A KMS key is required for a cross-account Pipeline. ' +
431
- 'Make sure to pass a Bucket with a Key when creating the Pipeline' ) ;
432
- }
433
-
434
- // generate a role in the other stack, that the Pipeline will assume for executing this action
435
- actionRole = new iam . Role ( resourceStack ,
436
- `${ this . node . uniqueId } -${ stage . stageName } -${ action . actionProperties . actionName } -ActionRole` , {
437
- assumedBy : new iam . AccountPrincipal ( pipelineStack . account ) ,
438
- roleName : PhysicalName . GENERATE_IF_NEEDED ,
439
- } ) ;
440
-
441
- // the other stack has to be deployed before the pipeline stack
442
- pipelineStack . addDependency ( resourceStack ) ;
443
- }
444
- }
413
+ let actionRole = this . getRoleFromActionPropsOrGenerateIfCrossAccount ( stage , action ) ;
445
414
446
415
if ( ! actionRole && this . isAwsOwned ( action ) ) {
447
416
// generate a Role for this specific Action
@@ -461,6 +430,107 @@ export class Pipeline extends PipelineBase {
461
430
return actionRole ;
462
431
}
463
432
433
+ private getRoleFromActionPropsOrGenerateIfCrossAccount ( stage : Stage , action : IAction ) : iam . IRole | undefined {
434
+ const pipelineStack = Stack . of ( this ) ;
435
+
436
+ // if a Role has been passed explicitly, always use it
437
+ // (even if the backing resource is from a different account -
438
+ // this is how the user can override our default support logic)
439
+ if ( action . actionProperties . role ) {
440
+ if ( this . isAwsOwned ( action ) ) {
441
+ // the role has to be deployed before the pipeline
442
+ const roleStack = Stack . of ( action . actionProperties . role ) ;
443
+ pipelineStack . addDependency ( roleStack ) ;
444
+
445
+ return action . actionProperties . role ;
446
+ } else {
447
+ // ...except if the Action is not owned by 'AWS',
448
+ // as that would be rejected by CodePipeline at deploy time
449
+ throw new Error ( "Specifying a Role is not supported for actions with an owner different than 'AWS' - " +
450
+ `got '${ action . actionProperties . owner } ' (Action: '${ action . actionProperties . actionName } ' in Stage: '${ stage . stageName } ')` ) ;
451
+ }
452
+ }
453
+
454
+ // if we don't have a Role passed,
455
+ // and the action is cross-account,
456
+ // generate a Role in that other account stack
457
+ const otherAccountStack = this . getOtherStackIfActionIsCrossAccount ( action ) ;
458
+ if ( ! otherAccountStack ) {
459
+ return undefined ;
460
+ }
461
+
462
+ // if we have a cross-account action, the pipeline's bucket must have a KMS key
463
+ if ( ! this . artifactBucket . encryptionKey ) {
464
+ throw new Error ( 'The Pipeline is being used in a cross-account manner, ' +
465
+ 'but its artifact bucket does not have a KMS key defined. ' +
466
+ 'A KMS key is required for a cross-account Pipeline. ' +
467
+ 'Make sure to pass a Bucket with a Key when creating the Pipeline' ) ;
468
+ }
469
+
470
+ // generate a role in the other stack, that the Pipeline will assume for executing this action
471
+ const ret = new iam . Role ( otherAccountStack ,
472
+ `${ this . node . uniqueId } -${ stage . stageName } -${ action . actionProperties . actionName } -ActionRole` , {
473
+ assumedBy : new iam . AccountPrincipal ( pipelineStack . account ) ,
474
+ roleName : PhysicalName . GENERATE_IF_NEEDED ,
475
+ } ) ;
476
+ // the other stack with the role has to be deployed before the pipeline stack
477
+ // (CodePipeline verifies you can assume the action Role on creation)
478
+ pipelineStack . addDependency ( otherAccountStack ) ;
479
+
480
+ return ret ;
481
+ }
482
+
483
+ /**
484
+ * Returns the Stack this Action belongs to if this is a cross-account Action.
485
+ * If this Action is not cross-account (i.e., it lives in the same account as the Pipeline),
486
+ * it returns undefined.
487
+ *
488
+ * @param action the Action to return the Stack for
489
+ */
490
+ private getOtherStackIfActionIsCrossAccount ( action : IAction ) : Stack | undefined {
491
+ const pipelineStack = Stack . of ( this ) ;
492
+
493
+ if ( action . actionProperties . resource ) {
494
+ const resourceStack = Stack . of ( action . actionProperties . resource ) ;
495
+ // check if resource is from a different account
496
+ return pipelineStack . account === resourceStack . account
497
+ ? undefined
498
+ : resourceStack ;
499
+ }
500
+
501
+ if ( ! action . actionProperties . account ) {
502
+ return undefined ;
503
+ }
504
+
505
+ const targetAccount = action . actionProperties . account ;
506
+ // check whether the account is a static string
507
+ if ( Token . isUnresolved ( targetAccount ) ) {
508
+ throw new Error ( `The 'account' property must be a concrete value (action: '${ action . actionProperties . actionName } ')` ) ;
509
+ }
510
+ // check whether the pipeline account is a static string
511
+ if ( Token . isUnresolved ( pipelineStack . account ) ) {
512
+ throw new Error ( "Pipeline stack which uses cross-environment actions must have an explicitly set account" ) ;
513
+ }
514
+
515
+ if ( pipelineStack . account === targetAccount ) {
516
+ return undefined ;
517
+ }
518
+
519
+ const stackId = `cross-account-support-stack-${ targetAccount } ` ;
520
+ const app = this . requireApp ( ) ;
521
+ let targetAccountStack = app . node . tryFindChild ( stackId ) as Stack ;
522
+ if ( ! targetAccountStack ) {
523
+ targetAccountStack = new Stack ( app , stackId , {
524
+ stackName : `${ pipelineStack . stackName } -support-${ targetAccount } ` ,
525
+ env : {
526
+ account : targetAccount ,
527
+ region : action . actionProperties . region ? action . actionProperties . region : pipelineStack . region ,
528
+ } ,
529
+ } ) ;
530
+ }
531
+ return targetAccountStack ;
532
+ }
533
+
464
534
private isAwsOwned ( action : IAction ) {
465
535
const owner = action . actionProperties . owner ;
466
536
return ! owner || owner === 'AWS' ;
@@ -626,10 +696,18 @@ export class Pipeline extends PipelineBase {
626
696
private requireRegion ( ) : string {
627
697
const region = Stack . of ( this ) . region ;
628
698
if ( Token . isUnresolved ( region ) ) {
629
- throw new Error ( `You need to specify an explicit region when using CodePipeline's cross-region support ` ) ;
699
+ throw new Error ( `Pipeline stack which uses cross-environment actions must have an explicitly set region ` ) ;
630
700
}
631
701
return region ;
632
702
}
703
+
704
+ private requireApp ( ) : App {
705
+ const app = this . node . root ;
706
+ if ( ! app || ! App . isApp ( app ) ) {
707
+ throw new Error ( `Pipeline stack which uses cross-environment actions must be part of a CDK app` ) ;
708
+ }
709
+ return app ;
710
+ }
633
711
}
634
712
635
713
/**
0 commit comments