@@ -11,17 +11,12 @@ class ServerlessPlugin {
11
11
this . commands = {
12
12
deploy : {
13
13
commands : {
14
- additionalstack : {
14
+ additionalstacks : {
15
15
usage : 'Deploy additional stacks' ,
16
16
lifecycleEvents : [
17
17
'deploy' ,
18
18
] ,
19
19
options : {
20
- all : {
21
- usage : 'Deploy all additional stacks' ,
22
- shortcut : 'a' ,
23
- required : false ,
24
- } ,
25
20
stack : {
26
21
usage : 'Additional stack name to deploy' ,
27
22
shortcut : 'k' ,
@@ -36,7 +31,7 @@ class ServerlessPlugin {
36
31
this . hooks = {
37
32
'before:deploy:deploy' : this . beforeDeployGlobal . bind ( this ) ,
38
33
'after:deploy:deploy' : this . afterDeployGlobal . bind ( this ) ,
39
- 'deploy:additionalstack :deploy' : this . deployAdditionalStackDeploy . bind ( this ) ,
34
+ 'deploy:additionalstacks :deploy' : this . deployAdditionalStackDeploy . bind ( this ) ,
40
35
}
41
36
}
42
37
@@ -90,16 +85,7 @@ class ServerlessPlugin {
90
85
deployAdditionalStackDeploy ( ) {
91
86
const stacks = this . getAdditionalStacks ( )
92
87
93
- if ( this . options . all ) {
94
- // Deploy all additional stacks
95
- if ( Object . keys ( stacks ) . length > 0 ) {
96
- this . serverless . cli . log ( 'Deploying all additional stacks...' )
97
- return this . deployStacks ( stacks )
98
- } else {
99
- this . serverless . cli . log ( 'No additional stacks defined. Add a custom.additionalStacks section to serverless.yml.' )
100
- return Promise . resolve ( )
101
- }
102
- } else if ( this . options . stack ) {
88
+ if ( this . options . stack ) {
103
89
const stack = stacks [ this . options . stack ]
104
90
if ( stack ) {
105
91
this . serverless . cli . log ( 'Deploying additional stack ' + this . options . stack + '...' )
@@ -108,10 +94,22 @@ class ServerlessPlugin {
108
94
return Promise . reject ( new Error ( 'Additional stack not found: ' + this . options . stack ) )
109
95
}
110
96
} else {
111
- return Promise . reject ( new Error ( 'Please specify either sls deploy additionalstack -a to deploy all additional stacks or -k [stackName] to deploy a single stack' ) )
97
+ // Deploy all additional stacks
98
+ if ( Object . keys ( stacks ) . length > 0 ) {
99
+ this . serverless . cli . log ( 'Deploying all additional stacks...' )
100
+ return this . deployStacks ( stacks )
101
+ } else {
102
+ this . serverless . cli . log ( 'No additional stacks defined. Add a custom.additionalStacks section to serverless.yml.' )
103
+ return Promise . resolve ( )
104
+ }
112
105
}
113
106
}
114
107
108
+ // Generate a full name for an additional stack (used in AWS)
109
+ getFullStackName ( stackName ) {
110
+ return this . provider . naming . getStackName ( ) + '-' + stackName
111
+ }
112
+
115
113
// This deploys all the specified stacks
116
114
deployStacks ( stacks ) {
117
115
let promise = Promise . resolve ( )
@@ -126,7 +124,132 @@ class ServerlessPlugin {
126
124
127
125
// This is where we actually handle the deployment to AWS
128
126
deployStack ( stackName , stack ) {
129
- this . serverless . cli . log ( 'DEPLOYING NOW ' + stackName + ' ' + JSON . stringify ( stack ) )
127
+ // Generate the CloudFormation template
128
+ const compiledCloudFormationTemplate = {
129
+ "AWSTemplateFormatVersion" : "2010-09-09" ,
130
+ "Description" : stack . Description || "Additional AWS CloudFormation template for this Serverless application" ,
131
+ "Metadata" : stack . Metadata || undefined ,
132
+ "Parameters" : stack . Parameters || undefined ,
133
+ "Mappings" : stack . Mappings || undefined ,
134
+ "Conditions" : stack . Conditions || undefined ,
135
+ "Transform" : stack . Transform || undefined ,
136
+ "Resources" : stack . Resources || undefined ,
137
+ "Outputs" : stack . Outputs || undefined ,
138
+ }
139
+
140
+ // Generate tags
141
+ const stackTags = {
142
+ STAGE : this . options . stage || this . serverless . service . provider . stage
143
+ }
144
+ if ( typeof stack . Tags === 'object' ) {
145
+ // Add custom tags
146
+ Object . assign ( stackTags , stack . Tags )
147
+ }
148
+
149
+ // Generate full stack name
150
+ const fullStackName = this . getFullStackName ( stackName )
151
+
152
+ return this . describeStack ( fullStackName )
153
+ . then ( stackStatus => {
154
+ if ( ! stackStatus ) {
155
+ // Create stack
156
+ this . serverless . cli . log ( 'Creating CloudFormation stack ' + fullStackName + '...' )
157
+ console . log ( 'TEMPL' , compiledCloudFormationTemplate )
158
+ console . log ( 'TAGS' , stackTags )
159
+ return this . createStack ( fullStackName , compiledCloudFormationTemplate , stackTags )
160
+ } else {
161
+ // Update stack
162
+ this . serverless . cli . log ( 'Updating CloudFormation stack ' + fullStackName + '...' )
163
+ return this . updateStack ( fullStackName , compiledCloudFormationTemplate , stackTags )
164
+ }
165
+ } )
166
+ }
167
+
168
+ describeStack ( fullStackName ) {
169
+ return this . provider . request (
170
+ 'CloudFormation' ,
171
+ 'describeStacks' , {
172
+ StackName : fullStackName ,
173
+ } ,
174
+ this . options . stage ,
175
+ this . options . region
176
+ )
177
+ . then ( response => {
178
+ return response . Stacks && response . Stacks [ 0 ]
179
+ } )
180
+ . then ( null , err => {
181
+ if ( err . message && err . message . match ( / d o e s n o t e x i s t $ / ) ) {
182
+ // Stack doesn't exist yet
183
+ return null
184
+ } else {
185
+ // Some other error, let it throw
186
+ return Promise . reject ( err )
187
+ }
188
+ } )
189
+ }
190
+
191
+ createStack ( fullStackName , compiledCloudFormationTemplate , stackTags ) {
192
+ // These are the same parameters that Serverless uses in https://github.com/serverless/serverless/blob/master/lib/plugins/aws/deploy/lib/createStack.js
193
+ const params = {
194
+ StackName : fullStackName ,
195
+ OnFailure : 'ROLLBACK' ,
196
+ Capabilities : [
197
+ 'CAPABILITY_IAM' ,
198
+ 'CAPABILITY_NAMED_IAM' ,
199
+ ] ,
200
+ Parameters : [ ] ,
201
+ TemplateBody : JSON . stringify ( compiledCloudFormationTemplate ) ,
202
+ Tags : Object . keys ( stackTags ) . map ( ( key ) => ( { Key : key , Value : stackTags [ key ] } ) ) ,
203
+ }
204
+
205
+ return this . provider . request (
206
+ 'CloudFormation' ,
207
+ 'createStack' ,
208
+ params ,
209
+ this . options . stage ,
210
+ this . options . region
211
+ )
212
+ }
213
+
214
+ updateStack ( fullStackName , compiledCloudFormationTemplate , stackTags ) {
215
+ // These are the same parameters that Serverless uses in https://github.com/serverless/serverless/blob/master/lib/plugins/aws/lib/updateStack.js
216
+ const params = {
217
+ StackName : fullStackName ,
218
+ Capabilities : [
219
+ 'CAPABILITY_IAM' ,
220
+ 'CAPABILITY_NAMED_IAM' ,
221
+ ] ,
222
+ Parameters : [ ] ,
223
+ TemplateBody : JSON . stringify ( compiledCloudFormationTemplate ) ,
224
+ Tags : Object . keys ( stackTags ) . map ( ( key ) => ( { Key : key , Value : stackTags [ key ] } ) ) ,
225
+ }
226
+
227
+ return this . provider . request (
228
+ 'CloudFormation' ,
229
+ 'updateStack' ,
230
+ params ,
231
+ this . options . stage ,
232
+ this . options . region
233
+ )
234
+ . then ( null , err => {
235
+ if ( err . message && err . message . match ( / ^ N o u p d a t e s / ) ) {
236
+ // Stack is unchanged, ignore error
237
+ return Promise . resolve ( )
238
+ } else {
239
+ return Promise . reject ( err )
240
+ }
241
+ } )
242
+ }
243
+
244
+ waitForStack ( fullStackName ) {
245
+ const readMore = ( ) => {
246
+ return describeStack ( fullStackName )
247
+ . then ( response => {
248
+ console . log ( 'STATUS' , response )
249
+ setTimeout ( readMore , 5000 )
250
+ } )
251
+ }
252
+ return readMore ( )
130
253
}
131
254
}
132
255
0 commit comments