@@ -33,7 +33,23 @@ function randomTargetId(stateMachineName, status) {
33
33
return `${ stateMachineName } -${ status } -${ suffix } ` ;
34
34
}
35
35
36
- function compileTarget ( stateMachineName , status , targetObj ) {
36
+ function randomLogicalId ( prefix ) {
37
+ const suffix = chance . string ( {
38
+ length : 5 ,
39
+ pool : 'ABCDEFGHIJKLMNOPQRSTUFWXYZ' ,
40
+ } ) ;
41
+ return `${ prefix } ${ suffix } ` ;
42
+ }
43
+
44
+ function randomPolicyName ( status , targetType ) {
45
+ const suffix = chance . string ( {
46
+ length : 5 ,
47
+ pool : 'abcdefghijklmnopqrstufwxyzABCDEFGHIJKLMNOPQRSTUFWXYZ' ,
48
+ } ) ;
49
+ return `${ status } -${ targetType } -${ suffix } ` ;
50
+ }
51
+
52
+ function compileTarget ( stateMachineName , status , targetObj , iamRoleLogicalId ) {
37
53
// SQS and Kinesis are special cases as they can have additional props
38
54
if ( _ . has ( targetObj , 'sqs.arn' ) ) {
39
55
return {
@@ -51,7 +67,16 @@ function compileTarget(stateMachineName, status, targetObj) {
51
67
PartitionKeyPath : targetObj . kinesis . partitionKeyPath ,
52
68
} ,
53
69
} ;
70
+ } else if ( _ . has ( targetObj , 'stepFunctions' ) ) {
71
+ return {
72
+ Arn : targetObj . stepFunctions ,
73
+ Id : randomTargetId ( stateMachineName , status ) ,
74
+ RoleArn : {
75
+ 'Fn::GetAtt' : [ iamRoleLogicalId , 'Arn' ] ,
76
+ } ,
77
+ } ;
54
78
}
79
+
55
80
const targetType = supportedTargets . find ( t => _ . has ( targetObj , t ) ) ;
56
81
const arn = _ . get ( targetObj , targetType ) ;
57
82
return {
@@ -60,24 +85,110 @@ function compileTarget(stateMachineName, status, targetObj) {
60
85
} ;
61
86
}
62
87
63
- function compileIamPermission ( targetObj ) {
64
- const targetType = supportedTargets . find ( t => _ . has ( targetObj , t ) ) ;
65
- const action = targetPermissions [ targetType ] ;
88
+ function compileSnsPolicy ( status , snsTarget ) {
89
+ return {
90
+ Type : 'AWS::SNS::TopicPolicy' ,
91
+ Properties : {
92
+ PolicyDocument : {
93
+ Version : '2012-10-17' ,
94
+ Statement : {
95
+ Sid : randomPolicyName ( status , 'sns' ) ,
96
+ Principal : {
97
+ Service : 'events.amazonaws.com' ,
98
+ } ,
99
+ Effect : 'Allow' ,
100
+ Action : 'sns:Publish' ,
101
+ Resource : snsTarget ,
102
+ } ,
103
+ } ,
104
+ Topics : [ snsTarget ] ,
105
+ } ,
106
+ } ;
107
+ }
66
108
67
- // SQS and Kinesis are special cases as they can have additional props
68
- if ( _ . has ( targetObj , 'sqs.arn' ) ) {
109
+ function convertToQueueUrl ( sqsArn ) {
110
+ if ( _ . isString ( sqsArn ) ) {
111
+ const segments = sqsArn . split ( ':' ) ;
112
+ const queueName = _ . last ( segments ) ;
69
113
return {
70
- action,
71
- resource : _ . get ( targetObj , 'sqs.arn' ) ,
114
+ 'Fn::Sub' : [
115
+ 'https://sqs.${AWS::Region}.amazonaws.com/${AWS::AccountId}/${QueueName}' ,
116
+ { QueueName : queueName } ,
117
+ ] ,
72
118
} ;
73
- } else if ( _ . has ( targetObj , 'kinesis.arn' ) ) {
119
+ } else if ( sqsArn [ 'Fn::GetAtt' ] ) {
120
+ const logicalId = sqsArn [ 'Fn::GetAtt' ] [ 0 ] ;
121
+ return { Ref : logicalId } ;
122
+ }
123
+ throw new Error (
124
+ `Unable to convert SQS ARN [${ sqsArn } ] to SQS Url. ` +
125
+ 'This is required for setting up Step Functions notifications to SQS. ' +
126
+ 'Try using Fn::GetAtt when setting the SQS arn.' ) ;
127
+ }
128
+
129
+ function compileSqsPolicy ( status , sqsTarget ) {
130
+ return {
131
+ Type : 'AWS::SQS::QueuePolicy' ,
132
+ Properties : {
133
+ PolicyDocument : {
134
+ Version : '2012-10-17' ,
135
+ Statement : {
136
+ Sid : randomPolicyName ( status , 'sqs' ) ,
137
+ Principal : {
138
+ Service : 'events.amazonaws.com' ,
139
+ } ,
140
+ Effect : 'Allow' ,
141
+ Action : 'sqs:SendMessage' ,
142
+ Resource : sqsTarget ,
143
+ } ,
144
+ } ,
145
+ Queues : [ convertToQueueUrl ( sqsTarget ) ] ,
146
+ } ,
147
+ } ;
148
+ }
149
+
150
+ function compileLambdaPermission ( lambdaTarget ) {
151
+ return {
152
+ Type : 'AWS::Lambda::Permission' ,
153
+ Properties : {
154
+ Action : 'lambda:InvokeFunction' ,
155
+ FunctionName : lambdaTarget ,
156
+ Principal : 'events.amazonaws.com' ,
157
+ } ,
158
+ } ;
159
+ }
160
+
161
+ function compilePermissionForTarget ( status , targetObj ) {
162
+ if ( targetObj . sns ) {
163
+ return {
164
+ type : 'policy' ,
165
+ resource : compileSnsPolicy ( status , targetObj . sns ) ,
166
+ } ;
167
+ } else if ( targetObj . sqs ) {
168
+ const arn = _ . get ( targetObj , 'sqs.arn' , targetObj . sqs ) ;
169
+ return {
170
+ type : 'policy' ,
171
+ resource : compileSqsPolicy ( status , arn ) ,
172
+ } ;
173
+ } else if ( targetObj . kinesis ) {
174
+ const arn = _ . get ( targetObj , 'kinesis.arn' , targetObj . kinesis ) ;
175
+ return {
176
+ type : 'iam' ,
177
+ action : 'kinesis:PutRecord' ,
178
+ resource : arn ,
179
+ } ;
180
+ } else if ( targetObj . lambda ) {
74
181
return {
75
- action ,
76
- resource : _ . get ( targetObj , 'kinesis.arn' ) ,
182
+ type : 'policy' ,
183
+ resource : compileLambdaPermission ( targetObj . lambda ) ,
77
184
} ;
78
185
}
79
186
187
+ const targetType = supportedTargets . find ( t => _ . has ( targetObj , t ) ) ;
188
+ const action = targetPermissions [ targetType ] ;
189
+
80
190
return {
191
+ type : 'iam' ,
81
192
action,
82
193
resource : targetObj [ targetType ] ,
83
194
} ;
@@ -96,42 +207,74 @@ function bootstrapIamRole() {
96
207
} ,
97
208
} ,
98
209
} ,
99
- Policies : [
100
- {
101
- PolicyName : 'root' ,
102
- PolicyDocument : {
103
- Version : '2012-10-17' ,
104
- Statement : [ ] ,
105
- } ,
106
- } ,
107
- ] ,
210
+ Policies : [ ] ,
108
211
} ,
109
212
} ;
110
- const addPermission = ( action , resource ) => {
111
- iamRole . Properties . Policies [ 0 ] . PolicyDocument . Statement . push ( {
112
- Effect : 'Allow' ,
113
- Action : action ,
114
- Resource : resource ,
213
+ const addPolicy = ( name , action , resource ) => {
214
+ iamRole . Properties . Policies . push ( {
215
+ PolicyName : name ,
216
+ PolicyDocument : {
217
+ Version : '2012-10-17' ,
218
+ Statement : [ {
219
+ Effect : 'Allow' ,
220
+ Action : action ,
221
+ Resource : resource ,
222
+ } ] ,
223
+ } ,
115
224
} ) ;
116
225
} ;
117
226
118
- return { iamRole, addPermission } ;
227
+ return { iamRole, addPolicy } ;
228
+ }
229
+
230
+ function * compilePermissionResources ( stateMachineLogicalId , iamRoleLogicalId , targets ) {
231
+ const { iamRole, addPolicy } = bootstrapIamRole ( ) ;
232
+
233
+ for ( const { status, target } of targets ) {
234
+ const perm = compilePermissionForTarget ( status , target ) ;
235
+ if ( perm . type === 'iam' ) {
236
+ const targetType = _ . keys ( target ) [ 0 ] ;
237
+ addPolicy (
238
+ randomPolicyName ( status , targetType ) ,
239
+ perm . action ,
240
+ perm . resource ) ;
241
+ } else if ( perm . type === 'policy' ) {
242
+ yield {
243
+ logicalId : randomLogicalId ( `${ stateMachineLogicalId } ResourcePolicy` ) ,
244
+ resource : perm . resource ,
245
+ } ;
246
+ }
247
+ }
248
+
249
+ if ( ! _ . isEmpty ( iamRole . Properties . Policies ) ) {
250
+ yield {
251
+ logicalId : iamRoleLogicalId ,
252
+ resource : iamRole ,
253
+ } ;
254
+ }
119
255
}
120
256
121
257
function * compileResources ( stateMachineLogicalId , stateMachineName , notificationsObj ) {
122
258
const iamRoleLogicalId = `${ stateMachineLogicalId } NotificationsIamRole` ;
123
- const { iamRole, addPermission } = bootstrapIamRole ( ) ;
259
+ const allTargets = _ . flatMap ( executionStatuses , status =>
260
+ _ . get ( notificationsObj , status , [ ] ) . map ( target => ( { status, target } ) ) ) ;
261
+ const permissions = compilePermissionResources (
262
+ stateMachineLogicalId , iamRoleLogicalId , allTargets ) ;
263
+ const permissionResources = Array . from ( permissions ) ;
264
+ for ( const { logicalId, resource } of permissionResources ) {
265
+ yield [ logicalId , resource ] ;
266
+ }
267
+
268
+ const needRoleArn = permissionResources . some ( ( { logicalId } ) => logicalId === iamRoleLogicalId ) ;
269
+ const roleArn = needRoleArn
270
+ ? { 'Fn::GetAtt' : [ iamRoleLogicalId , 'Arn' ] }
271
+ : undefined ;
124
272
125
273
for ( const status of executionStatuses ) {
126
274
const targets = notificationsObj [ status ] ;
127
275
if ( ! _ . isEmpty ( targets ) ) {
128
- const cfnTargets = targets
129
- . map ( t => compileTarget ( stateMachineName , status , t ) )
130
- . filter ( _ . isObjectLike ) ;
131
- targets
132
- . map ( compileIamPermission )
133
- . filter ( _ . isObjectLike )
134
- . forEach ( ( { action, resource } ) => addPermission ( action , resource ) ) ;
276
+ const cfnTargets = targets . map ( t =>
277
+ compileTarget ( stateMachineName , status , t , iamRoleLogicalId ) ) ;
135
278
136
279
const eventRuleLogicalId =
137
280
`${ stateMachineLogicalId } Notifications${ status . replace ( '_' , '' ) } EventRule` ;
@@ -144,22 +287,19 @@ function* compileResources(stateMachineLogicalId, stateMachineName, notification
144
287
'detail-type' : [ 'Step Functions Execution Status Change' ] ,
145
288
detail : {
146
289
status : [ status ] ,
290
+ stateMachineArn : [ {
291
+ Ref : stateMachineLogicalId ,
292
+ } ] ,
147
293
} ,
148
294
} ,
149
295
Name : `${ stateMachineName } -${ status } -notification` ,
150
- RoleArn : {
151
- 'Fn::GetAtt' : [ iamRoleLogicalId , 'Arn' ] ,
152
- } ,
296
+ RoleArn : roleArn ,
153
297
Targets : cfnTargets ,
154
298
} ,
155
299
} ;
156
300
yield [ eventRuleLogicalId , eventRule ] ;
157
301
}
158
302
}
159
-
160
- if ( ! _ . isEmpty ( iamRole . Properties . Policies [ 0 ] . PolicyDocument . Statement ) ) {
161
- yield [ iamRoleLogicalId , iamRole ] ;
162
- }
163
303
}
164
304
165
305
function validateConfig ( serverless , stateMachineName , notificationsObj ) {
0 commit comments