@@ -8,43 +8,9 @@ const NO_UPDATE_MESSAGE = 'No updates are to be performed.';
8
8
9
9
module . exports = {
10
10
11
- aliasGetAliasStackTemplate ( ) {
11
+ aliasCreateStackChanges ( currentTemplate , aliasStackTemplates , currentAliasStackTemplate ) {
12
12
13
- const stackName = `${ this . _provider . naming . getStackName ( ) } -${ this . _alias } ` ;
14
-
15
- // Get current aliasTemplate
16
- const params = {
17
- StackName : stackName ,
18
- TemplateStage : 'Processed'
19
- } ;
20
-
21
- return this . _provider . request ( 'CloudFormation' ,
22
- 'getTemplate' ,
23
- params ,
24
- this . _options . stage ,
25
- this . _options . region )
26
- . then ( cfData => {
27
- try {
28
- return BbPromise . resolve ( JSON . parse ( cfData . TemplateBody ) ) ;
29
- } catch ( e ) {
30
- return BbPromise . reject ( new Error ( 'Received malformed response from CloudFormation' ) ) ;
31
- }
32
- } )
33
- . catch ( err => {
34
- if ( _ . includes ( err . message , 'does not exist' ) ) {
35
- const message = `Alias ${ this . _alias } is not deployed.` ;
36
- throw new this . _serverless . classes . Error ( new Error ( message ) ) ;
37
- }
38
-
39
- throw new this . _serverless . classes . Error ( err ) ;
40
- } ) ;
41
-
42
- } ,
43
-
44
- aliasCreateStackChanges ( currentTemplate , aliasStackTemplates ) {
45
-
46
- return this . aliasGetAliasStackTemplate ( )
47
- . then ( aliasTemplate => {
13
+ return BbPromise . try ( ( ) => {
48
14
49
15
const usedFuncRefs = _ . uniq (
50
16
_ . flatMap ( aliasStackTemplates , template => {
@@ -72,7 +38,7 @@ module.exports = {
72
38
const obsoleteFuncRefs = _ . reject ( _ . map (
73
39
_ . assign ( { } ,
74
40
_ . pickBy (
75
- _ . get ( aliasTemplate , 'Resources' , { } ) ,
41
+ _ . get ( currentAliasStackTemplate , 'Resources' , { } ) ,
76
42
[ 'Type' , 'AWS::Lambda::Alias' ] ) ) ,
77
43
( value , key ) => {
78
44
return _ . replace ( key , / A l i a s $ / , '' ) ;
@@ -85,13 +51,39 @@ module.exports = {
85
51
name => `${ name } LambdaFunctionArn` ) ;
86
52
87
53
const obsoleteResources = _ . reject (
88
- JSON . parse ( _ . get ( aliasTemplate , 'Outputs.AliasResources.Value' , "[]" ) ) ,
54
+ JSON . parse ( _ . get ( currentAliasStackTemplate , 'Outputs.AliasResources.Value' , "[]" ) ) ,
89
55
resource => _ . includes ( usedResources , resource ) ) ;
90
56
91
57
const obsoleteOutputs = _ . reject (
92
- JSON . parse ( _ . get ( aliasTemplate , 'Outputs.AliasOutputs.Value' , "[]" ) ) ,
58
+ JSON . parse ( _ . get ( currentAliasStackTemplate , 'Outputs.AliasOutputs.Value' , "[]" ) ) ,
93
59
output => _ . includes ( usedOutputs , output ) ) ;
94
60
61
+ // Check for aliased authorizers thhat reference a removed function
62
+ _ . forEach ( obsoleteFuncRefs , obsoleteFuncRef => {
63
+ const authorizerName = `${ obsoleteFuncRef } ApiGatewayAuthorizer${ this . _alias } ` ;
64
+ if ( _ . has ( currentTemplate . Resources , authorizerName ) ) {
65
+ // find obsolete references
66
+ const authRefs = utils . findReferences ( currentTemplate . Resources , authorizerName ) ;
67
+ _ . forEach ( authRefs , authRef => {
68
+ if ( _ . endsWith ( authRef , '.AuthorizerId' ) ) {
69
+ const parent = _ . get ( currentTemplate . Resources , _ . replace ( authRef , '.AuthorizerId' , '' ) ) ;
70
+ delete parent . AuthorizerId ;
71
+ parent . AuthorizationType = "NONE" ;
72
+ }
73
+ } ) ;
74
+ // find dependencies
75
+ _ . forOwn ( currentTemplate . Resources , resource => {
76
+ if ( _ . isArray ( resource . DependsOn ) && _ . includes ( resource . DependsOn , authorizerName ) ) {
77
+ resource . DependsOn = _ . without ( resource . DependsOn , authorizerName ) ;
78
+ } else if ( resource . DependsOn === authorizerName ) {
79
+ delete resource . DependsOn ;
80
+ }
81
+ } ) ;
82
+ // Add authorizer to obsolete resources
83
+ obsoleteResources . push ( authorizerName ) ;
84
+ }
85
+ } ) ;
86
+
95
87
// Remove all alias references that are not used in other stacks
96
88
_ . assign ( currentTemplate , {
97
89
Resources : _ . assign ( { } , _ . omit ( currentTemplate . Resources , obsoleteFuncResources , obsoleteResources ) ) ,
@@ -101,32 +93,22 @@ module.exports = {
101
93
if ( this . options . verbose ) {
102
94
this . _serverless . cli . log ( `Remove unused resources:` ) ;
103
95
_ . forEach ( obsoleteResources , resource => this . _serverless . cli . log ( ` * ${ resource } ` ) ) ;
104
- this . options . verbose && this . _serverless . cli . log ( `Adjust IAM policies` ) ;
105
96
}
106
97
107
- // Adjust IAM policies
108
- const currentRolePolicies = _ . get ( currentTemplate , 'Resources.IamRoleLambdaExecution.Properties.Policies' , [ ] ) ;
109
- const currentRolePolicyStatements = _ . get ( currentRolePolicies [ 0 ] , 'PolicyDocument.Statement' , [ ] ) ;
98
+ this . options . verbose && this . _serverless . cli . log ( `Remove alias IAM policy` ) ;
99
+ // Remove the alias IAM policy if it is not referenced in the current stage stack
100
+ // We cannot remove it otherwise, because the $LATEST function versions might still reference it.
101
+ // Then it will be deleted on the next deployment or the stage removal, whatever happend first.
102
+ const aliasPolicyName = `IamRoleLambdaExecution${ this . _alias } ` ;
103
+ if ( _ . isEmpty ( utils . findReferences ( currentTemplate . Resources , aliasPolicyName ) ) ) {
104
+ delete currentTemplate . Resources [ `IamRoleLambdaExecution${ this . _alias } ` ] ;
105
+ } else {
106
+ this . _serverless . cli . log ( `IAM policy removal delayed - will be removed on next deployment` ) ;
107
+ }
110
108
109
+ // Adjust IAM policies
111
110
const obsoleteRefs = _ . concat ( obsoleteFuncResources , obsoleteResources ) ;
112
111
113
- // Remove all obsolete resource references from the IAM policy statements
114
- const emptyStatements = [ ] ;
115
- const statementResources = utils . findReferences ( currentRolePolicyStatements , obsoleteRefs ) ;
116
- _ . forEach ( statementResources , resourcePath => {
117
- const indices = / .* ?\[ ( [ 0 - 9 ] + ) \] .* ?\[ ( [ 0 - 9 ] + ) \] / . exec ( resourcePath ) ;
118
- if ( indices ) {
119
- const statementIndex = indices [ 1 ] ;
120
- const resourceIndex = indices [ 2 ] ;
121
-
122
- _ . pullAt ( currentRolePolicyStatements [ statementIndex ] . Resource , resourceIndex ) ;
123
- if ( _ . isEmpty ( currentRolePolicyStatements [ statementIndex ] . Resource ) ) {
124
- emptyStatements . push ( statementIndex ) ;
125
- }
126
- }
127
- } ) ;
128
- _ . pullAt ( currentRolePolicyStatements , emptyStatements ) ;
129
-
130
112
// Set references to obsoleted resources in fct env to "REMOVED" in case
131
113
// the alias that is removed was the last deployment of the stage.
132
114
// This will change the function definition, but that does not matter
@@ -149,11 +131,11 @@ module.exports = {
149
131
delete currentTemplate . Outputs . ServiceEndpoint ;
150
132
}
151
133
152
- return BbPromise . resolve ( [ currentTemplate , aliasStackTemplates ] ) ;
134
+ return BbPromise . resolve ( [ currentTemplate , aliasStackTemplates , currentAliasStackTemplate ] ) ;
153
135
} ) ;
154
136
} ,
155
137
156
- aliasApplyStackChanges ( currentTemplate , aliasStackTemplates ) {
138
+ aliasApplyStackChanges ( currentTemplate , aliasStackTemplates , currentAliasStackTemplate ) {
157
139
158
140
const stackName = this . _provider . naming . getStackName ( ) ;
159
141
@@ -177,6 +159,8 @@ module.exports = {
177
159
Tags : _ . map ( _ . keys ( stackTags ) , key => ( { Key : key , Value : stackTags [ key ] } ) ) ,
178
160
} ;
179
161
162
+ this . options . verbose && this . _serverless . cli . log ( `Checking stack policy` ) ;
163
+
180
164
// Policy must have at least one statement, otherwise no updates would be possible at all
181
165
if ( this . serverless . service . provider . stackPolicy &&
182
166
this . serverless . service . provider . stackPolicy . length ) {
@@ -185,23 +169,23 @@ module.exports = {
185
169
} ) ;
186
170
}
187
171
188
- return this . provider . request ( 'CloudFormation' ,
172
+ return this . _provider . request ( 'CloudFormation' ,
189
173
'updateStack' ,
190
174
params ,
191
175
this . options . stage ,
192
176
this . options . region )
193
177
. then ( cfData => this . monitorStack ( 'update' , cfData ) )
194
- . then ( ( ) => BbPromise . resolve ( [ currentTemplate , aliasStackTemplates ] ) )
178
+ . then ( ( ) => BbPromise . resolve ( [ currentTemplate , aliasStackTemplates , currentAliasStackTemplate ] ) )
195
179
. catch ( err => {
196
180
if ( err . message === NO_UPDATE_MESSAGE ) {
197
- return BbPromise . resolve ( [ currentTemplate , aliasStackTemplates ] ) ;
181
+ return BbPromise . resolve ( [ currentTemplate , aliasStackTemplates , currentAliasStackTemplate ] ) ;
198
182
}
199
- throw new this . _serverless . classes . Error ( err ) ;
183
+ throw err ;
200
184
} ) ;
201
185
202
186
} ,
203
187
204
- aliasRemoveAliasStack ( currentTemplate , aliasStackTemplates ) {
188
+ aliasRemoveAliasStack ( currentTemplate , aliasStackTemplates , currentAliasStackTemplate ) {
205
189
206
190
const stackName = `${ this . _provider . naming . getStackName ( ) } -${ this . _alias } ` ;
207
191
@@ -218,33 +202,47 @@ module.exports = {
218
202
return this . monitorStack ( 'removal' , cfData ) ;
219
203
} )
220
204
. then ( ( ) => {
221
- return BbPromise . resolve ( [ currentTemplate , aliasStackTemplates ] ) ;
205
+ return BbPromise . resolve ( [ currentTemplate , aliasStackTemplates , currentAliasStackTemplate ] ) ;
222
206
} )
223
207
. catch ( e => {
224
208
if ( _ . includes ( e . message , 'does not exist' ) ) {
225
209
const message = `Alias ${ this . _alias } is not deployed.` ;
226
- throw new this . _serverless . classes . Error ( new Error ( message ) ) ;
210
+ throw new this . _serverless . classes . Error ( message ) ;
227
211
}
228
212
229
- throw new this . _serverless . classes . Error ( e ) ;
213
+ throw e ;
230
214
} ) ;
231
215
232
216
} ,
233
217
234
- removeAlias ( currentTemplate , aliasStackTemplates ) {
235
-
236
- if ( this . _stage && this . _stage === this . _alias ) {
237
- const message = `Cannot delete the stage alias. Did you intend to remove the service instead?` ;
238
- throw new this . _serverless . classes . Error ( new Error ( message ) ) ;
239
- }
218
+ removeAlias ( currentTemplate , aliasStackTemplates , currentAliasStackTemplate ) {
240
219
241
220
if ( this . _options . noDeploy ) {
221
+ this . _serverless . cli . log ( 'noDeploy option active - will do nothing' ) ;
242
222
return BbPromise . resolve ( ) ;
243
223
}
244
224
225
+ if ( this . _stage && this . _stage === this . _alias ) {
226
+ // Removal of the master alias is requested -> check if any other aliases are still deployed.
227
+ const aliases = _ . map ( aliasStackTemplates , aliasTemplate => _ . get ( aliasTemplate , 'Outputs.ServerlessAliasName.Value' ) ) ;
228
+ if ( ! _ . isEmpty ( aliases ) ) {
229
+ throw new this . _serverless . classes . Error ( `Remove the other deployed aliases before removing the service: ${ _ . without ( aliases , this . _alias ) } ` ) ;
230
+ }
231
+ if ( _ . isEmpty ( currentAliasStackTemplate ) ) {
232
+ throw new this . _serverless . classes . Error ( `Internal error: Stack for master alias ${ this . _alias } is not deployed. Try to solve the problem by manual interaction with the AWS console.` ) ;
233
+ }
234
+
235
+ // We're ready for removal
236
+ this . _serverless . cli . log ( `Removing master alias and stage ${ this . _alias } ...` ) ;
237
+
238
+ return BbPromise . resolve ( [ currentTemplate , aliasStackTemplates , currentAliasStackTemplate ] ) . bind ( this )
239
+ . spread ( this . aliasRemoveAliasStack )
240
+ . then ( ( ) => this . _serverless . pluginManager . spawn ( 'remove' ) ) ;
241
+ }
242
+
245
243
this . _serverless . cli . log ( `Removing alias ${ this . _alias } ...` ) ;
246
244
247
- return BbPromise . resolve ( [ currentTemplate , aliasStackTemplates ] ) . bind ( this )
245
+ return BbPromise . resolve ( [ currentTemplate , aliasStackTemplates , currentAliasStackTemplate ] ) . bind ( this )
248
246
. spread ( this . aliasCreateStackChanges )
249
247
. spread ( this . aliasRemoveAliasStack )
250
248
. spread ( this . aliasApplyStackChanges )
0 commit comments