@@ -7,7 +7,7 @@ import cloudformation = require('../lib');
77
88export = nodeunit . testCase ( {
99 'CreateReplaceChangeSet' : {
10- works ( test : nodeunit . Test ) {
10+ ' works' ( test : nodeunit . Test ) {
1111 const stack = new cdk . Stack ( ) ;
1212 const pipelineRole = new RoleDouble ( stack , 'PipelineRole' ) ;
1313 const stage = new StageDouble ( { pipelineRole } ) ;
@@ -22,8 +22,8 @@ export = nodeunit.testCase({
2222 _assertPermissionGranted ( test , pipelineRole . statements , 'iam:PassRole' , action . role . roleArn ) ;
2323
2424 const stackArn = _stackArn ( 'MyStack' ) ;
25- const changeSetCondition = { StringEquals : { 'cloudformation:ChangeSetName' : 'MyChangeSet' } } ;
26- _assertPermissionGranted ( test , pipelineRole . statements , 'cloudformation:DescribeStacks' , stackArn ) ;
25+ const changeSetCondition = { StringEqualsIfExists : { 'cloudformation:ChangeSetName' : 'MyChangeSet' } } ;
26+ _assertPermissionGranted ( test , pipelineRole . statements , 'cloudformation:DescribeStacks' , stackArn , changeSetCondition ) ;
2727 _assertPermissionGranted ( test , pipelineRole . statements , 'cloudformation:DescribeChangeSet' , stackArn , changeSetCondition ) ;
2828 _assertPermissionGranted ( test , pipelineRole . statements , 'cloudformation:CreateChangeSet' , stackArn , changeSetCondition ) ;
2929 _assertPermissionGranted ( test , pipelineRole . statements , 'cloudformation:DeleteChangeSet' , stackArn , changeSetCondition ) ;
@@ -37,11 +37,64 @@ export = nodeunit.testCase({
3737 ChangeSetName : 'MyChangeSet'
3838 } ) ;
3939
40+ test . done ( ) ;
41+ } ,
42+
43+ 'uses a single permission statement if the same ChangeSet name is used' ( test : nodeunit . Test ) {
44+ const stack = new cdk . Stack ( ) ;
45+ const pipelineRole = new RoleDouble ( stack , 'PipelineRole' ) ;
46+ const stage = new StageDouble ( { pipelineRole } ) ;
47+ const artifact = new cpapi . Artifact ( stack as any , 'TestArtifact' ) ;
48+ new cloudformation . PipelineCreateReplaceChangeSetAction ( stack , 'ActionA' , {
49+ stage,
50+ changeSetName : 'MyChangeSet' ,
51+ stackName : 'StackA' ,
52+ templatePath : artifact . atPath ( 'path/to/file' )
53+ } ) ;
54+
55+ new cloudformation . PipelineCreateReplaceChangeSetAction ( stack , 'ActionB' , {
56+ stage,
57+ changeSetName : 'MyChangeSet' ,
58+ stackName : 'StackB' ,
59+ templatePath : artifact . atPath ( 'path/to/other/file' )
60+ } ) ;
61+
62+ test . deepEqual (
63+ cdk . resolve ( pipelineRole . statements ) ,
64+ [
65+ {
66+ Action : 'iam:PassRole' ,
67+ Effect : 'Allow' ,
68+ Resource : [
69+ { 'Fn::GetAtt' : [ 'ActionARole72759154' , 'Arn' ] } ,
70+ { 'Fn::GetAtt' : [ 'ActionBRole6A2F6804' , 'Arn' ] }
71+ ] ,
72+ } ,
73+ {
74+ Action : [
75+ 'cloudformation:CreateChangeSet' ,
76+ 'cloudformation:DeleteChangeSet' ,
77+ 'cloudformation:DescribeChangeSet' ,
78+ 'cloudformation:DescribeStacks'
79+ ] ,
80+ Condition : { StringEqualsIfExists : { 'cloudformation:ChangeSetName' : 'MyChangeSet' } } ,
81+ Effect : 'Allow' ,
82+ Resource : [
83+ // tslint:disable-next-line:max-line-length
84+ { 'Fn::Join' : [ '' , [ 'arn:' , { Ref : 'AWS::Partition' } , ':cloudformation:' , { Ref : 'AWS::Region' } , ':' , { Ref : 'AWS::AccountId' } , ':stack/StackA/*' ] ] } ,
85+ // tslint:disable-next-line:max-line-length
86+ { 'Fn::Join' : [ '' , [ 'arn:' , { Ref : 'AWS::Partition' } , ':cloudformation:' , { Ref : 'AWS::Region' } , ':' , { Ref : 'AWS::AccountId' } , ':stack/StackB/*' ] ] }
87+ ] ,
88+ }
89+ ]
90+ ) ;
91+
4092 test . done ( ) ;
4193 }
4294 } ,
95+
4396 'ExecuteChangeSet' : {
44- works ( test : nodeunit . Test ) {
97+ ' works' ( test : nodeunit . Test ) {
4598 const stack = new cdk . Stack ( ) ;
4699 const pipelineRole = new RoleDouble ( stack , 'PipelineRole' ) ;
47100 const stage = new StageDouble ( { pipelineRole } ) ;
@@ -61,6 +114,42 @@ export = nodeunit.testCase({
61114 ChangeSetName : 'MyChangeSet'
62115 } ) ;
63116
117+ test . done ( ) ;
118+ } ,
119+
120+ 'uses a single permission statement if the same ChangeSet name is used' ( test : nodeunit . Test ) {
121+ const stack = new cdk . Stack ( ) ;
122+ const pipelineRole = new RoleDouble ( stack , 'PipelineRole' ) ;
123+ const stage = new StageDouble ( { pipelineRole } ) ;
124+ new cloudformation . PipelineExecuteChangeSetAction ( stack , 'ActionA' , {
125+ stage,
126+ changeSetName : 'MyChangeSet' ,
127+ stackName : 'StackA' ,
128+ } ) ;
129+
130+ new cloudformation . PipelineExecuteChangeSetAction ( stack , 'ActionB' , {
131+ stage,
132+ changeSetName : 'MyChangeSet' ,
133+ stackName : 'StackB' ,
134+ } ) ;
135+
136+ test . deepEqual (
137+ cdk . resolve ( pipelineRole . statements ) ,
138+ [
139+ {
140+ Action : 'cloudformation:ExecuteChangeSet' ,
141+ Condition : { StringEquals : { 'cloudformation:ChangeSetName' : 'MyChangeSet' } } ,
142+ Effect : 'Allow' ,
143+ Resource : [
144+ // tslint:disable-next-line:max-line-length
145+ { 'Fn::Join' : [ '' , [ 'arn:' , { Ref : 'AWS::Partition' } , ':cloudformation:' , { Ref : 'AWS::Region' } , ':' , { Ref : 'AWS::AccountId' } , ':stack/StackA/*' ] ] } ,
146+ // tslint:disable-next-line:max-line-length
147+ { 'Fn::Join' : [ '' , [ 'arn:' , { Ref : 'AWS::Partition' } , ':cloudformation:' , { Ref : 'AWS::Region' } , ':' , { Ref : 'AWS::AccountId' } , ':stack/StackB/*' ] ] }
148+ ] ,
149+ }
150+ ]
151+ ) ;
152+
64153 test . done ( ) ;
65154 }
66155 } ,
@@ -72,6 +161,7 @@ export = nodeunit.testCase({
72161 stage : new StageDouble ( { pipelineRole } ) ,
73162 templatePath : new cpapi . Artifact ( stack as any , 'TestArtifact' ) . atPath ( 'some/file' ) ,
74163 stackName : 'MyStack' ,
164+ replaceOnFailure : true ,
75165 } ) ;
76166 const stackArn = _stackArn ( 'MyStack' ) ;
77167
@@ -144,12 +234,13 @@ function _hasAction(actions: cpapi.Action[], owner: string, provider: string, ca
144234 return false ;
145235}
146236
147- function _assertPermissionGranted ( test : nodeunit . Test , statements : PolicyStatementJson [ ] , action : string , resource : string , conditions ?: any ) {
237+ function _assertPermissionGranted ( test : nodeunit . Test , statements : iam . PolicyStatement [ ] , action : string , resource : string , conditions ?: any ) {
148238 const conditionStr = conditions
149239 ? ` with condition(s) ${ JSON . stringify ( cdk . resolve ( conditions ) ) } `
150240 : '' ;
151- const statementsStr = JSON . stringify ( cdk . resolve ( statements ) , null , 2 ) ;
152- test . ok ( _grantsPermission ( statements , action , resource , conditions ) ,
241+ const resolvedStatements = cdk . resolve ( statements ) ;
242+ const statementsStr = JSON . stringify ( resolvedStatements , null , 2 ) ;
243+ test . ok ( _grantsPermission ( resolvedStatements , action , resource , conditions ) ,
153244 `Expected to find a statement granting ${ action } on ${ JSON . stringify ( cdk . resolve ( resource ) ) } ${ conditionStr } , found:\n${ statementsStr } ` ) ;
154245}
155246
@@ -218,14 +309,14 @@ class StageDouble implements cpapi.IStage, cpapi.IInternalStage {
218309}
219310
220311class RoleDouble extends iam . Role {
221- public readonly statements = new Array < PolicyStatementJson > ( ) ;
312+ public readonly statements = new Array < iam . PolicyStatement > ( ) ;
222313
223314 constructor ( parent : cdk . Construct , id : string , props : iam . RoleProps = { assumedBy : new iam . ServicePrincipal ( 'test' ) } ) {
224315 super ( parent , id , props ) ;
225316 }
226317
227318 public addToPolicy ( statement : iam . PolicyStatement ) {
228319 super . addToPolicy ( statement ) ;
229- this . statements . push ( statement . toJson ( ) ) ;
320+ this . statements . push ( statement ) ;
230321 }
231322}
0 commit comments