stackup 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES.md +4 -0
- data/README.md +11 -3
- data/bin/stackup +2 -545
- data/lib/stackup/main_command.rb +557 -0
- data/lib/stackup/version.rb +1 -1
- data/spec/stackup/main_command_spec.rb +23 -0
- data/spec/stackup/stack_spec.rb +17 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 281a4cc419891f70b5a8251d52c080a678e21d2a76a57a0e715b478987c5d967
|
4
|
+
data.tar.gz: e1848f7cccad92a7a6883f3947daa68e70d1b72ad00bfa72727f0def2590ee89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59cd63007ce56b284e7176c82f354174bb78dec2fba84443e2b1a28465df5ed68cd0bc1ccd2474dab7572c61ebb85d9de328e12216395d0e68c8101863b0110a
|
7
|
+
data.tar.gz: 3acf8d1d83307ef2851001558c7880c4d43692f740254531369713e4cb7d38f3595398195cfd8f67460b10c1bd94cd7dca08a833fdf51371ef0a1c914addcffc
|
data/CHANGES.md
CHANGED
data/README.md
CHANGED
@@ -216,6 +216,8 @@ You can also create, list, inspect, apply and delete [change sets](http://docs.a
|
|
216
216
|
|
217
217
|
The change-set name defaults to "pending", but can be overridden using `--name`.
|
218
218
|
|
219
|
+
The `change-set create` subcommand, like the `up` command, supports `--service-role-arn` to specify a service role.
|
220
|
+
|
219
221
|
## Programmatic usage
|
220
222
|
|
221
223
|
Get a handle to a `Stack` object as follows:
|
@@ -300,13 +302,19 @@ This policy grants the principal all actions required by `stackup up` for any cl
|
|
300
302
|
}
|
301
303
|
```
|
302
304
|
|
303
|
-
##
|
305
|
+
## Development
|
306
|
+
|
307
|
+
### Running tests
|
308
|
+
|
309
|
+
`auto/test` will run the tests in a Docker container.
|
310
|
+
|
311
|
+
### Releasing
|
304
312
|
|
305
|
-
The release process will push tags to GitHub, push the gem to rubygems and push the docker image to DockerHub.
|
313
|
+
Releasing is done manually, not by CI. The release process will push tags to GitHub, push the gem to rubygems and push the docker image to DockerHub.
|
306
314
|
|
307
315
|
Prerequisites:
|
308
316
|
|
309
|
-
* logged into
|
317
|
+
* You must be logged into docker hub via `docker login`. Your user must have permission to push to `realestate/stackup`
|
310
318
|
* You must have a rubygems account with permission to push to the `stackup` gem. (`auto/release` will ask for your username and password)
|
311
319
|
* You must have cloned this repo via HTTPS and have a github account with permission to push. (`auto/release` will ask for your username and a GitHub personal access token)
|
312
320
|
|
data/bin/stackup
CHANGED
@@ -2,552 +2,9 @@
|
|
2
2
|
|
3
3
|
$LOAD_PATH << File.expand_path("../lib", __dir__)
|
4
4
|
|
5
|
-
require "
|
6
|
-
require "console_logger"
|
7
|
-
require "multi_json"
|
8
|
-
require "securerandom"
|
9
|
-
require "stackup"
|
10
|
-
require "stackup/differ"
|
11
|
-
require "stackup/source"
|
12
|
-
require "stackup/version"
|
13
|
-
require "stackup/yaml"
|
5
|
+
require "stackup/main_command"
|
14
6
|
|
15
7
|
$stdout.sync = true
|
16
8
|
$stderr.sync = true
|
17
9
|
|
18
|
-
|
19
|
-
|
20
|
-
option ["-L", "--list"], :flag, "list stacks" do
|
21
|
-
list_stacks
|
22
|
-
exit 0
|
23
|
-
end
|
24
|
-
|
25
|
-
option ["-Y", "--yaml"], :flag, "output data in YAML format"
|
26
|
-
|
27
|
-
option ["--region"], "REGION", "set region" do |arg|
|
28
|
-
raise ArgumentError, "#{arg.inspect} doesn't look like a region" unless arg =~ /^[a-z]{2}-[a-z]+-\d$/
|
29
|
-
|
30
|
-
arg
|
31
|
-
end
|
32
|
-
|
33
|
-
option ["--with-role"], "ROLE_ARN", "assume this role",
|
34
|
-
:attribute_name => :role_arn
|
35
|
-
|
36
|
-
option ["--retry-limit"], "N", "maximum number of retries for API calls",
|
37
|
-
:environment_variable => "AWS_API_RETRY_LIMIT" do |arg|
|
38
|
-
Integer(arg)
|
39
|
-
end
|
40
|
-
|
41
|
-
option ["--[no-]wait"], :flag, "wait for stack updates to complete",
|
42
|
-
:default => true
|
43
|
-
|
44
|
-
option ["--wait-poll-interval"], "N", "polling interval (in seconds) while waiting for updates",
|
45
|
-
:default => 5, &method(:Integer)
|
46
|
-
|
47
|
-
option "--debug", :flag, "enable debugging"
|
48
|
-
|
49
|
-
option ["--version"], :flag, "display version" do
|
50
|
-
puts "stackup v#{Stackup::VERSION}"
|
51
|
-
exit 0
|
52
|
-
end
|
53
|
-
|
54
|
-
parameter "NAME", "Name of stack", :attribute_name => :stack_name
|
55
|
-
|
56
|
-
def run(arguments)
|
57
|
-
super(arguments)
|
58
|
-
rescue Stackup::Source::ReadError => e
|
59
|
-
signal_error e.message
|
60
|
-
rescue Stackup::ServiceError => e
|
61
|
-
signal_error e.message
|
62
|
-
rescue Aws::Errors::MissingCredentialsError
|
63
|
-
signal_error "no credentials provided"
|
64
|
-
rescue Aws::Errors::ServiceError => e
|
65
|
-
signal_error e.message
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
def logger
|
71
|
-
@logger ||= ConsoleLogger.new($stdout, debug?)
|
72
|
-
end
|
73
|
-
|
74
|
-
def format_data(data)
|
75
|
-
if yaml?
|
76
|
-
YAML.dump(data)
|
77
|
-
else
|
78
|
-
MultiJson.dump(data, :pretty => true)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def display_data(data)
|
83
|
-
puts format_data(data)
|
84
|
-
end
|
85
|
-
|
86
|
-
def role_arn=(arg)
|
87
|
-
raise ArgumentError, "#{arg.inspect} doesn't look like a role ARN" unless arg =~ %r{^arn:aws:iam::\d+:role/}
|
88
|
-
|
89
|
-
@role_arn = arg
|
90
|
-
end
|
91
|
-
|
92
|
-
def stackup
|
93
|
-
Stackup(aws_config)
|
94
|
-
end
|
95
|
-
|
96
|
-
def base_aws_config
|
97
|
-
{
|
98
|
-
:log_level => :debug,
|
99
|
-
:logger => logger,
|
100
|
-
:region => region,
|
101
|
-
:retry_limit => retry_limit
|
102
|
-
}.reject { |_k, v| v.nil? }
|
103
|
-
end
|
104
|
-
|
105
|
-
def aws_config
|
106
|
-
return base_aws_config unless role_arn
|
107
|
-
|
108
|
-
assumed_credentials = Aws::AssumeRoleCredentials.new(
|
109
|
-
:client => Aws::STS::Client.new(base_aws_config),
|
110
|
-
:role_arn => role_arn,
|
111
|
-
:role_session_name => "stackup-#{SecureRandom.hex(8)}"
|
112
|
-
)
|
113
|
-
base_aws_config.merge(:credentials => assumed_credentials)
|
114
|
-
end
|
115
|
-
|
116
|
-
def stack
|
117
|
-
stackup.stack(stack_name, :wait => wait?, :wait_poll_interval => wait_poll_interval)
|
118
|
-
end
|
119
|
-
|
120
|
-
def list_stacks
|
121
|
-
stackup.stack_names.each do |name|
|
122
|
-
puts name
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def report_change
|
127
|
-
final_status = yield
|
128
|
-
puts final_status unless final_status.nil?
|
129
|
-
end
|
130
|
-
|
131
|
-
subcommand "status", "Print stack status." do
|
132
|
-
|
133
|
-
def execute
|
134
|
-
puts stack.status
|
135
|
-
end
|
136
|
-
|
137
|
-
end
|
138
|
-
|
139
|
-
module HasParameters
|
140
|
-
|
141
|
-
extend Clamp::Option::Declaration
|
142
|
-
|
143
|
-
option ["-p", "--parameters"], "FILE", "parameters file (last wins)",
|
144
|
-
:multivalued => true,
|
145
|
-
:attribute_name => :parameter_sources,
|
146
|
-
&Stackup::Source.method(:new)
|
147
|
-
|
148
|
-
option ["-o", "--override"], "PARAM=VALUE", "parameter overrides",
|
149
|
-
:multivalued => true,
|
150
|
-
:attribute_name => :override_list
|
151
|
-
|
152
|
-
private
|
153
|
-
|
154
|
-
def parameters
|
155
|
-
parameters_from_files.merge(parameter_overrides)
|
156
|
-
end
|
157
|
-
|
158
|
-
def parameters_from_files
|
159
|
-
parameter_sources.map do |src|
|
160
|
-
Stackup::Parameters.new(src.data).to_hash
|
161
|
-
end.inject({}, :merge)
|
162
|
-
end
|
163
|
-
|
164
|
-
def parameter_overrides
|
165
|
-
{}.tap do |result|
|
166
|
-
override_list.each do |override|
|
167
|
-
key, value = override.split("=", 2)
|
168
|
-
result[key] = value
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
end
|
174
|
-
|
175
|
-
subcommand "up", "Create/update the stack." do
|
176
|
-
|
177
|
-
option ["-t", "--template"], "FILE", "template source",
|
178
|
-
:attribute_name => :template_source,
|
179
|
-
&Stackup::Source.method(:new)
|
180
|
-
|
181
|
-
option ["-T", "--use-previous-template"], :flag,
|
182
|
-
"reuse the existing template"
|
183
|
-
|
184
|
-
option ["-P", "--preserve-template-formatting"], :flag,
|
185
|
-
"do not normalise the template when calling the Cloudformation APIs; useful for preserving YAML and comments"
|
186
|
-
|
187
|
-
include HasParameters
|
188
|
-
|
189
|
-
option "--tags", "FILE", "stack tags file",
|
190
|
-
:attribute_name => :tag_source,
|
191
|
-
&Stackup::Source.method(:new)
|
192
|
-
|
193
|
-
option "--policy", "FILE", "stack policy file",
|
194
|
-
:attribute_name => :policy_source,
|
195
|
-
&Stackup::Source.method(:new)
|
196
|
-
|
197
|
-
option "--service-role-arn", "SERVICE_ROLE_ARN", "cloudformation service role ARN" do |arg|
|
198
|
-
raise ArgumentError, "#{arg.inspect} doesn't look like a role ARN" unless arg =~ %r{^arn:aws:iam::\d+:role/}
|
199
|
-
|
200
|
-
arg
|
201
|
-
end
|
202
|
-
|
203
|
-
option "--on-failure", "ACTION",
|
204
|
-
"when stack creation fails: DO_NOTHING, ROLLBACK, or DELETE",
|
205
|
-
:default => "ROLLBACK"
|
206
|
-
|
207
|
-
option "--capability", "CAPABILITY", "cloudformation capability",
|
208
|
-
:multivalued => true, :default => ["CAPABILITY_NAMED_IAM"]
|
209
|
-
|
210
|
-
def execute
|
211
|
-
unless template_source || use_previous_template?
|
212
|
-
signal_usage_error "Specify either --template or --use-previous-template"
|
213
|
-
end
|
214
|
-
options = {}
|
215
|
-
if template_source
|
216
|
-
if template_source.s3?
|
217
|
-
options[:template_url] = template_source.location
|
218
|
-
else
|
219
|
-
options[:template] = template_source.data
|
220
|
-
options[:template_orig] = template_source.body
|
221
|
-
end
|
222
|
-
end
|
223
|
-
options[:on_failure] = on_failure
|
224
|
-
options[:parameters] = parameters
|
225
|
-
options[:tags] = tag_source.data if tag_source
|
226
|
-
if policy_source
|
227
|
-
if policy_source.s3?
|
228
|
-
options[:stack_policy_url] = policy_source.location
|
229
|
-
else
|
230
|
-
options[:stack_policy] = policy_source.data
|
231
|
-
end
|
232
|
-
end
|
233
|
-
options[:role_arn] = service_role_arn if service_role_arn
|
234
|
-
options[:use_previous_template] = use_previous_template?
|
235
|
-
options[:capabilities] = capability_list
|
236
|
-
options[:preserve] = preserve_template_formatting?
|
237
|
-
report_change do
|
238
|
-
stack.create_or_update(options)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
end
|
243
|
-
|
244
|
-
subcommand ["change-sets"], "List change-sets." do
|
245
|
-
|
246
|
-
def execute
|
247
|
-
stack.change_set_summaries.each do |change_set|
|
248
|
-
puts [
|
249
|
-
pad(change_set.change_set_name, 36),
|
250
|
-
pad(change_set.status, 20),
|
251
|
-
pad(change_set.execution_status, 24)
|
252
|
-
].join(" ")
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
private
|
257
|
-
|
258
|
-
def pad(s, width)
|
259
|
-
(s || "").ljust(width)
|
260
|
-
end
|
261
|
-
|
262
|
-
end
|
263
|
-
|
264
|
-
subcommand ["change-set"], "Change-set operations." do
|
265
|
-
|
266
|
-
option "--name", "NAME", "Name of change-set",
|
267
|
-
:attribute_name => :change_set_name,
|
268
|
-
:default => "pending"
|
269
|
-
|
270
|
-
subcommand "create", "Create a change-set." do
|
271
|
-
|
272
|
-
option ["-d", "--description"], "DESC",
|
273
|
-
"Change-set description"
|
274
|
-
|
275
|
-
option ["-t", "--template"], "FILE", "template source",
|
276
|
-
:attribute_name => :template_source,
|
277
|
-
&Stackup::Source.method(:new)
|
278
|
-
|
279
|
-
option ["-T", "--use-previous-template"], :flag,
|
280
|
-
"reuse the existing template"
|
281
|
-
|
282
|
-
option ["-P", "--preserve-template-formatting"], :flag,
|
283
|
-
"do not normalise the template when calling the Cloudformation APIs; useful for preserving YAML and comments"
|
284
|
-
|
285
|
-
option ["--force"], :flag,
|
286
|
-
"replace existing change-set of the same name"
|
287
|
-
|
288
|
-
include HasParameters
|
289
|
-
|
290
|
-
option "--tags", "FILE", "stack tags file",
|
291
|
-
:attribute_name => :tag_source,
|
292
|
-
&Stackup::Source.method(:new)
|
293
|
-
|
294
|
-
option "--capability", "CAPABILITY", "cloudformation capability",
|
295
|
-
:multivalued => true, :default => ["CAPABILITY_NAMED_IAM"]
|
296
|
-
|
297
|
-
def execute
|
298
|
-
unless template_source || use_previous_template?
|
299
|
-
signal_usage_error "Specify either --template or --use-previous-template"
|
300
|
-
end
|
301
|
-
options = {}
|
302
|
-
if template_source
|
303
|
-
if template_source.s3?
|
304
|
-
options[:template_url] = template_source.location
|
305
|
-
else
|
306
|
-
options[:template] = template_source.data
|
307
|
-
options[:template_orig] = template_source.body
|
308
|
-
end
|
309
|
-
end
|
310
|
-
options[:parameters] = parameters
|
311
|
-
options[:description] = description if description
|
312
|
-
options[:tags] = tag_source.data if tag_source
|
313
|
-
options[:use_previous_template] = use_previous_template?
|
314
|
-
options[:force] = force?
|
315
|
-
options[:capabilities] = capability_list
|
316
|
-
options[:preserve] = preserve_template_formatting?
|
317
|
-
report_change do
|
318
|
-
change_set.create(options)
|
319
|
-
end
|
320
|
-
end
|
321
|
-
|
322
|
-
end
|
323
|
-
|
324
|
-
subcommand "changes", "Describe the change-set." do
|
325
|
-
|
326
|
-
def execute
|
327
|
-
display_data(change_set.describe.changes.map(&:to_h))
|
328
|
-
end
|
329
|
-
|
330
|
-
end
|
331
|
-
|
332
|
-
subcommand "inspect", "Show full change-set details." do
|
333
|
-
|
334
|
-
def execute
|
335
|
-
display_data(change_set.describe.to_h)
|
336
|
-
end
|
337
|
-
|
338
|
-
end
|
339
|
-
|
340
|
-
subcommand ["apply", "execute"], "Apply the change-set." do
|
341
|
-
|
342
|
-
def execute
|
343
|
-
report_change do
|
344
|
-
change_set.execute
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
end
|
349
|
-
|
350
|
-
subcommand "delete", "Delete the change-set." do
|
351
|
-
|
352
|
-
def execute
|
353
|
-
report_change do
|
354
|
-
change_set.delete
|
355
|
-
end
|
356
|
-
end
|
357
|
-
|
358
|
-
end
|
359
|
-
|
360
|
-
private
|
361
|
-
|
362
|
-
def change_set
|
363
|
-
stack.change_set(change_set_name)
|
364
|
-
end
|
365
|
-
|
366
|
-
end
|
367
|
-
|
368
|
-
subcommand "diff", "Compare template/params to current stack." do
|
369
|
-
|
370
|
-
option "--diff-format", "FORMAT", "'text', 'color', or 'html'", :default => "color"
|
371
|
-
|
372
|
-
option ["-C", "--context-lines"], "LINES", "number of lines of context to show", :default => 10_000
|
373
|
-
|
374
|
-
option ["-t", "--template"], "FILE", "template source",
|
375
|
-
:attribute_name => :template_source,
|
376
|
-
&Stackup::Source.method(:new)
|
377
|
-
|
378
|
-
include HasParameters
|
379
|
-
|
380
|
-
option "--tags", "FILE", "stack tags file",
|
381
|
-
:attribute_name => :tag_source,
|
382
|
-
&Stackup::Source.method(:new)
|
383
|
-
|
384
|
-
def execute
|
385
|
-
current = {}
|
386
|
-
planned = {}
|
387
|
-
if template_source
|
388
|
-
current["Template"] = stack.template
|
389
|
-
planned["Template"] = template_source.data
|
390
|
-
end
|
391
|
-
unless parameter_sources.empty?
|
392
|
-
current["Parameters"] = existing_parameters.sort.to_h
|
393
|
-
planned["Parameters"] = new_parameters.sort.to_h
|
394
|
-
end
|
395
|
-
if tag_source
|
396
|
-
current["Tags"] = stack.tags.sort.to_h
|
397
|
-
planned["Tags"] = tag_source.data.sort.to_h
|
398
|
-
end
|
399
|
-
signal_usage_error "specify '--template' or '--parameters'" if planned.empty?
|
400
|
-
puts differ.diff(current, planned, context_lines)
|
401
|
-
end
|
402
|
-
|
403
|
-
private
|
404
|
-
|
405
|
-
def differ
|
406
|
-
Stackup::Differ.new(diff_format, &method(:format_data))
|
407
|
-
end
|
408
|
-
|
409
|
-
def existing_parameters
|
410
|
-
@existing_parameters ||= stack.parameters
|
411
|
-
end
|
412
|
-
|
413
|
-
def new_parameters
|
414
|
-
existing_parameters.merge(parameters)
|
415
|
-
end
|
416
|
-
|
417
|
-
end
|
418
|
-
|
419
|
-
subcommand ["down", "delete"], "Remove the stack." do
|
420
|
-
|
421
|
-
def execute
|
422
|
-
report_change do
|
423
|
-
stack.delete
|
424
|
-
end
|
425
|
-
end
|
426
|
-
|
427
|
-
end
|
428
|
-
|
429
|
-
subcommand "cancel-update", "Cancel the update in-progress." do
|
430
|
-
|
431
|
-
def execute
|
432
|
-
report_change do
|
433
|
-
stack.cancel_update
|
434
|
-
end
|
435
|
-
end
|
436
|
-
|
437
|
-
end
|
438
|
-
|
439
|
-
subcommand "wait", "Wait until stack is stable." do
|
440
|
-
|
441
|
-
def execute
|
442
|
-
puts stack.wait
|
443
|
-
end
|
444
|
-
|
445
|
-
end
|
446
|
-
|
447
|
-
subcommand "events", "List stack events." do
|
448
|
-
|
449
|
-
option ["-f", "--follow"], :flag, "follow new events"
|
450
|
-
option ["--data"], :flag, "display events as data"
|
451
|
-
|
452
|
-
def execute
|
453
|
-
stack.watch(false) do |watcher|
|
454
|
-
loop do
|
455
|
-
watcher.each_new_event do |event|
|
456
|
-
display_event(event)
|
457
|
-
end
|
458
|
-
break unless follow?
|
459
|
-
|
460
|
-
sleep 5
|
461
|
-
end
|
462
|
-
end
|
463
|
-
end
|
464
|
-
|
465
|
-
private
|
466
|
-
|
467
|
-
def display_event(e)
|
468
|
-
if data?
|
469
|
-
display_data(event_data(e))
|
470
|
-
else
|
471
|
-
puts event_summary(e)
|
472
|
-
end
|
473
|
-
end
|
474
|
-
|
475
|
-
def event_data(e)
|
476
|
-
{
|
477
|
-
"timestamp" => e.timestamp.localtime,
|
478
|
-
"logical_resource_id" => e.logical_resource_id,
|
479
|
-
"physical_resource_id" => e.physical_resource_id,
|
480
|
-
"resource_status" => e.resource_status,
|
481
|
-
"resource_status_reason" => e.resource_status_reason
|
482
|
-
}.reject { |_k, v| blank?(v) }
|
483
|
-
end
|
484
|
-
|
485
|
-
def blank?(v)
|
486
|
-
v.nil? || v.respond_to?(:empty?) && v.empty?
|
487
|
-
end
|
488
|
-
|
489
|
-
def event_summary(e)
|
490
|
-
summary = "[#{e.timestamp.localtime.iso8601}] #{e.logical_resource_id}"
|
491
|
-
summary += " - #{e.resource_status}"
|
492
|
-
summary += " - #{e.resource_status_reason}" if e.resource_status_reason
|
493
|
-
summary
|
494
|
-
end
|
495
|
-
|
496
|
-
end
|
497
|
-
|
498
|
-
subcommand "template", "Display stack template." do
|
499
|
-
|
500
|
-
def execute
|
501
|
-
display_data(stack.template)
|
502
|
-
end
|
503
|
-
|
504
|
-
end
|
505
|
-
|
506
|
-
subcommand ["parameters", "params"], "Display stack parameters." do
|
507
|
-
|
508
|
-
def execute
|
509
|
-
display_data(stack.parameters)
|
510
|
-
end
|
511
|
-
|
512
|
-
end
|
513
|
-
|
514
|
-
subcommand "tags", "Display stack tags." do
|
515
|
-
|
516
|
-
def execute
|
517
|
-
display_data(stack.tags)
|
518
|
-
end
|
519
|
-
|
520
|
-
end
|
521
|
-
|
522
|
-
subcommand "resources", "Display stack resources." do
|
523
|
-
|
524
|
-
def execute
|
525
|
-
display_data(stack.resources)
|
526
|
-
end
|
527
|
-
|
528
|
-
end
|
529
|
-
|
530
|
-
subcommand "outputs", "Display stack outputs." do
|
531
|
-
|
532
|
-
def execute
|
533
|
-
display_data(stack.outputs)
|
534
|
-
end
|
535
|
-
|
536
|
-
end
|
537
|
-
|
538
|
-
subcommand "inspect", "Display stack particulars." do
|
539
|
-
|
540
|
-
def execute
|
541
|
-
data = {
|
542
|
-
"Status" => stack.status,
|
543
|
-
"Parameters" => stack.parameters,
|
544
|
-
"Tags" => stack.tags,
|
545
|
-
"Resources" => stack.resources,
|
546
|
-
"Outputs" => stack.outputs
|
547
|
-
}
|
548
|
-
display_data(data)
|
549
|
-
end
|
550
|
-
|
551
|
-
end
|
552
|
-
|
553
|
-
end
|
10
|
+
Stackup::MainCommand.run
|
@@ -0,0 +1,557 @@
|
|
1
|
+
require "clamp"
|
2
|
+
require "console_logger"
|
3
|
+
require "multi_json"
|
4
|
+
require "securerandom"
|
5
|
+
require "stackup"
|
6
|
+
require "stackup/differ"
|
7
|
+
require "stackup/source"
|
8
|
+
require "stackup/version"
|
9
|
+
require "stackup/yaml"
|
10
|
+
|
11
|
+
module Stackup
|
12
|
+
|
13
|
+
class MainCommand < Clamp::Command
|
14
|
+
|
15
|
+
option ["-L", "--list"], :flag, "list stacks" do
|
16
|
+
list_stacks
|
17
|
+
exit 0
|
18
|
+
end
|
19
|
+
|
20
|
+
option ["-Y", "--yaml"], :flag, "output data in YAML format"
|
21
|
+
|
22
|
+
option ["--region"], "REGION", "set region" do |arg|
|
23
|
+
raise ArgumentError, "#{arg.inspect} doesn't look like a region" unless arg =~ /^[a-z]{2}-[a-z]+-\d$/
|
24
|
+
|
25
|
+
arg
|
26
|
+
end
|
27
|
+
|
28
|
+
option ["--with-role"], "ROLE_ARN", "assume this role",
|
29
|
+
:attribute_name => :role_arn
|
30
|
+
|
31
|
+
option ["--retry-limit"], "N", "maximum number of retries for API calls",
|
32
|
+
:environment_variable => "AWS_API_RETRY_LIMIT" do |arg|
|
33
|
+
Integer(arg)
|
34
|
+
end
|
35
|
+
|
36
|
+
option ["--[no-]wait"], :flag, "wait for stack updates to complete",
|
37
|
+
:default => true
|
38
|
+
|
39
|
+
option ["--wait-poll-interval"], "N", "polling interval (in seconds) while waiting for updates",
|
40
|
+
:default => 5, &method(:Integer)
|
41
|
+
|
42
|
+
option "--debug", :flag, "enable debugging"
|
43
|
+
|
44
|
+
option ["--version"], :flag, "display version" do
|
45
|
+
puts "stackup v#{Stackup::VERSION}"
|
46
|
+
exit 0
|
47
|
+
end
|
48
|
+
|
49
|
+
parameter "NAME", "Name of stack", :attribute_name => :stack_name
|
50
|
+
|
51
|
+
def run(arguments)
|
52
|
+
super(arguments)
|
53
|
+
rescue Stackup::Source::ReadError => e
|
54
|
+
signal_error e.message
|
55
|
+
rescue Stackup::ServiceError => e
|
56
|
+
signal_error e.message
|
57
|
+
rescue Aws::Errors::MissingCredentialsError
|
58
|
+
signal_error "no credentials provided"
|
59
|
+
rescue Aws::Errors::ServiceError => e
|
60
|
+
signal_error e.message
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def logger
|
66
|
+
@logger ||= ConsoleLogger.new($stdout, debug?)
|
67
|
+
end
|
68
|
+
|
69
|
+
def format_data(data)
|
70
|
+
if yaml?
|
71
|
+
YAML.dump(data)
|
72
|
+
else
|
73
|
+
MultiJson.dump(data, :pretty => true)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def display_data(data)
|
78
|
+
puts format_data(data)
|
79
|
+
end
|
80
|
+
|
81
|
+
def role_arn=(arg)
|
82
|
+
raise ArgumentError, "#{arg.inspect} doesn't look like a role ARN" unless arg =~ %r{^arn:aws:iam::\d+:role/}
|
83
|
+
|
84
|
+
@role_arn = arg
|
85
|
+
end
|
86
|
+
|
87
|
+
def stackup
|
88
|
+
Stackup(aws_config)
|
89
|
+
end
|
90
|
+
|
91
|
+
def base_aws_config
|
92
|
+
{
|
93
|
+
:log_level => :debug,
|
94
|
+
:logger => logger,
|
95
|
+
:region => region,
|
96
|
+
:retry_limit => retry_limit
|
97
|
+
}.reject { |_k, v| v.nil? }
|
98
|
+
end
|
99
|
+
|
100
|
+
def aws_config
|
101
|
+
return base_aws_config unless role_arn
|
102
|
+
|
103
|
+
assumed_credentials = Aws::AssumeRoleCredentials.new(
|
104
|
+
:client => Aws::STS::Client.new(base_aws_config),
|
105
|
+
:role_arn => role_arn,
|
106
|
+
:role_session_name => "stackup-#{SecureRandom.hex(8)}"
|
107
|
+
)
|
108
|
+
base_aws_config.merge(:credentials => assumed_credentials)
|
109
|
+
end
|
110
|
+
|
111
|
+
def stack
|
112
|
+
stackup.stack(stack_name, :wait => wait?, :wait_poll_interval => wait_poll_interval)
|
113
|
+
end
|
114
|
+
|
115
|
+
def list_stacks
|
116
|
+
stackup.stack_names.each do |name|
|
117
|
+
puts name
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def report_change
|
122
|
+
final_status = yield
|
123
|
+
puts final_status unless final_status.nil?
|
124
|
+
end
|
125
|
+
|
126
|
+
subcommand "status", "Print stack status." do
|
127
|
+
|
128
|
+
def execute
|
129
|
+
puts stack.status
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
module HasParameters
|
135
|
+
|
136
|
+
extend Clamp::Option::Declaration
|
137
|
+
|
138
|
+
option ["-p", "--parameters"], "FILE", "parameters file (last wins)",
|
139
|
+
:multivalued => true,
|
140
|
+
:attribute_name => :parameter_sources,
|
141
|
+
&Stackup::Source.method(:new)
|
142
|
+
|
143
|
+
option ["-o", "--override"], "PARAM=VALUE", "parameter overrides",
|
144
|
+
:multivalued => true,
|
145
|
+
:attribute_name => :override_list
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def parameters
|
150
|
+
parameters_from_files.merge(parameter_overrides)
|
151
|
+
end
|
152
|
+
|
153
|
+
def parameters_from_files
|
154
|
+
parameter_sources.map do |src|
|
155
|
+
Stackup::Parameters.new(src.data).to_hash
|
156
|
+
end.inject({}, :merge)
|
157
|
+
end
|
158
|
+
|
159
|
+
def parameter_overrides
|
160
|
+
{}.tap do |result|
|
161
|
+
override_list.each do |override|
|
162
|
+
key, value = override.split("=", 2)
|
163
|
+
result[key] = value
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
subcommand "up", "Create/update the stack." do
|
171
|
+
|
172
|
+
option ["-t", "--template"], "FILE", "template source",
|
173
|
+
:attribute_name => :template_source,
|
174
|
+
&Stackup::Source.method(:new)
|
175
|
+
|
176
|
+
option ["-T", "--use-previous-template"], :flag,
|
177
|
+
"reuse the existing template"
|
178
|
+
|
179
|
+
option ["-P", "--preserve-template-formatting"], :flag,
|
180
|
+
"do not normalise the template when calling the Cloudformation APIs; useful for preserving YAML and comments"
|
181
|
+
|
182
|
+
include HasParameters
|
183
|
+
|
184
|
+
option "--tags", "FILE", "stack tags file",
|
185
|
+
:attribute_name => :tag_source,
|
186
|
+
&Stackup::Source.method(:new)
|
187
|
+
|
188
|
+
option "--policy", "FILE", "stack policy file",
|
189
|
+
:attribute_name => :policy_source,
|
190
|
+
&Stackup::Source.method(:new)
|
191
|
+
|
192
|
+
option "--service-role-arn", "SERVICE_ROLE_ARN", "cloudformation service role ARN" do |arg|
|
193
|
+
raise ArgumentError, "#{arg.inspect} doesn't look like a role ARN" unless arg =~ %r{^arn:aws:iam::\d+:role/}
|
194
|
+
|
195
|
+
arg
|
196
|
+
end
|
197
|
+
|
198
|
+
option "--on-failure", "ACTION",
|
199
|
+
"when stack creation fails: DO_NOTHING, ROLLBACK, or DELETE",
|
200
|
+
:default => "ROLLBACK"
|
201
|
+
|
202
|
+
option "--capability", "CAPABILITY", "cloudformation capability",
|
203
|
+
:multivalued => true, :default => ["CAPABILITY_NAMED_IAM"]
|
204
|
+
|
205
|
+
def execute
|
206
|
+
unless template_source || use_previous_template?
|
207
|
+
signal_usage_error "Specify either --template or --use-previous-template"
|
208
|
+
end
|
209
|
+
options = {}
|
210
|
+
if template_source
|
211
|
+
if template_source.s3?
|
212
|
+
options[:template_url] = template_source.location
|
213
|
+
else
|
214
|
+
options[:template] = template_source.data
|
215
|
+
options[:template_orig] = template_source.body
|
216
|
+
end
|
217
|
+
end
|
218
|
+
options[:on_failure] = on_failure
|
219
|
+
options[:parameters] = parameters
|
220
|
+
options[:tags] = tag_source.data if tag_source
|
221
|
+
if policy_source
|
222
|
+
if policy_source.s3?
|
223
|
+
options[:stack_policy_url] = policy_source.location
|
224
|
+
else
|
225
|
+
options[:stack_policy] = policy_source.data
|
226
|
+
end
|
227
|
+
end
|
228
|
+
options[:role_arn] = service_role_arn if service_role_arn
|
229
|
+
options[:use_previous_template] = use_previous_template?
|
230
|
+
options[:capabilities] = capability_list
|
231
|
+
options[:preserve] = preserve_template_formatting?
|
232
|
+
report_change do
|
233
|
+
stack.create_or_update(options)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
subcommand ["change-sets"], "List change-sets." do
|
240
|
+
|
241
|
+
def execute
|
242
|
+
stack.change_set_summaries.each do |change_set|
|
243
|
+
puts [
|
244
|
+
pad(change_set.change_set_name, 36),
|
245
|
+
pad(change_set.status, 20),
|
246
|
+
pad(change_set.execution_status, 24)
|
247
|
+
].join(" ")
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
def pad(s, width)
|
254
|
+
(s || "").ljust(width)
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
258
|
+
|
259
|
+
subcommand ["change-set"], "Change-set operations." do
|
260
|
+
|
261
|
+
option "--name", "NAME", "Name of change-set",
|
262
|
+
:attribute_name => :change_set_name,
|
263
|
+
:default => "pending"
|
264
|
+
|
265
|
+
subcommand "create", "Create a change-set." do
|
266
|
+
|
267
|
+
option ["-d", "--description"], "DESC",
|
268
|
+
"Change-set description"
|
269
|
+
|
270
|
+
option ["-t", "--template"], "FILE", "template source",
|
271
|
+
:attribute_name => :template_source,
|
272
|
+
&Stackup::Source.method(:new)
|
273
|
+
|
274
|
+
option ["-T", "--use-previous-template"], :flag,
|
275
|
+
"reuse the existing template"
|
276
|
+
|
277
|
+
option ["-P", "--preserve-template-formatting"], :flag,
|
278
|
+
"do not normalise the template when calling the Cloudformation APIs; useful for preserving YAML and comments"
|
279
|
+
|
280
|
+
option ["--force"], :flag,
|
281
|
+
"replace existing change-set of the same name"
|
282
|
+
|
283
|
+
include HasParameters
|
284
|
+
|
285
|
+
option "--tags", "FILE", "stack tags file",
|
286
|
+
:attribute_name => :tag_source,
|
287
|
+
&Stackup::Source.method(:new)
|
288
|
+
|
289
|
+
option "--service-role-arn", "SERVICE_ROLE_ARN", "cloudformation service role ARN" do |arg|
|
290
|
+
raise ArgumentError, "#{arg.inspect} doesn't look like a role ARN" unless arg =~ %r{^arn:aws:iam::\d+:role/}
|
291
|
+
|
292
|
+
arg
|
293
|
+
end
|
294
|
+
|
295
|
+
option "--capability", "CAPABILITY", "cloudformation capability",
|
296
|
+
:multivalued => true, :default => ["CAPABILITY_NAMED_IAM"]
|
297
|
+
|
298
|
+
def execute
|
299
|
+
unless template_source || use_previous_template?
|
300
|
+
signal_usage_error "Specify either --template or --use-previous-template"
|
301
|
+
end
|
302
|
+
options = {}
|
303
|
+
if template_source
|
304
|
+
if template_source.s3?
|
305
|
+
options[:template_url] = template_source.location
|
306
|
+
else
|
307
|
+
options[:template] = template_source.data
|
308
|
+
options[:template_orig] = template_source.body
|
309
|
+
end
|
310
|
+
end
|
311
|
+
options[:parameters] = parameters
|
312
|
+
options[:description] = description if description
|
313
|
+
options[:tags] = tag_source.data if tag_source
|
314
|
+
options[:role_arn] = service_role_arn if service_role_arn
|
315
|
+
options[:use_previous_template] = use_previous_template?
|
316
|
+
options[:force] = force?
|
317
|
+
options[:capabilities] = capability_list
|
318
|
+
options[:preserve] = preserve_template_formatting?
|
319
|
+
report_change do
|
320
|
+
change_set.create(options)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
325
|
+
|
326
|
+
subcommand "changes", "Describe the change-set." do
|
327
|
+
|
328
|
+
def execute
|
329
|
+
display_data(change_set.describe.changes.map(&:to_h))
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
|
334
|
+
subcommand "inspect", "Show full change-set details." do
|
335
|
+
|
336
|
+
def execute
|
337
|
+
display_data(change_set.describe.to_h)
|
338
|
+
end
|
339
|
+
|
340
|
+
end
|
341
|
+
|
342
|
+
subcommand ["apply", "execute"], "Apply the change-set." do
|
343
|
+
|
344
|
+
def execute
|
345
|
+
report_change do
|
346
|
+
change_set.execute
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
351
|
+
|
352
|
+
subcommand "delete", "Delete the change-set." do
|
353
|
+
|
354
|
+
def execute
|
355
|
+
report_change do
|
356
|
+
change_set.delete
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
end
|
361
|
+
|
362
|
+
private
|
363
|
+
|
364
|
+
def change_set
|
365
|
+
stack.change_set(change_set_name)
|
366
|
+
end
|
367
|
+
|
368
|
+
end
|
369
|
+
|
370
|
+
subcommand "diff", "Compare template/params to current stack." do
|
371
|
+
|
372
|
+
option "--diff-format", "FORMAT", "'text', 'color', or 'html'", :default => "color"
|
373
|
+
|
374
|
+
option ["-C", "--context-lines"], "LINES", "number of lines of context to show", :default => 10_000
|
375
|
+
|
376
|
+
option ["-t", "--template"], "FILE", "template source",
|
377
|
+
:attribute_name => :template_source,
|
378
|
+
&Stackup::Source.method(:new)
|
379
|
+
|
380
|
+
include HasParameters
|
381
|
+
|
382
|
+
option "--tags", "FILE", "stack tags file",
|
383
|
+
:attribute_name => :tag_source,
|
384
|
+
&Stackup::Source.method(:new)
|
385
|
+
|
386
|
+
def execute
|
387
|
+
current = {}
|
388
|
+
planned = {}
|
389
|
+
if template_source
|
390
|
+
current["Template"] = stack.template
|
391
|
+
planned["Template"] = template_source.data
|
392
|
+
end
|
393
|
+
unless parameter_sources.empty?
|
394
|
+
current["Parameters"] = existing_parameters.sort.to_h
|
395
|
+
planned["Parameters"] = new_parameters.sort.to_h
|
396
|
+
end
|
397
|
+
if tag_source
|
398
|
+
current["Tags"] = stack.tags.sort.to_h
|
399
|
+
planned["Tags"] = tag_source.data.sort.to_h
|
400
|
+
end
|
401
|
+
signal_usage_error "specify '--template' or '--parameters'" if planned.empty?
|
402
|
+
puts differ.diff(current, planned, context_lines)
|
403
|
+
end
|
404
|
+
|
405
|
+
private
|
406
|
+
|
407
|
+
def differ
|
408
|
+
Stackup::Differ.new(diff_format, &method(:format_data))
|
409
|
+
end
|
410
|
+
|
411
|
+
def existing_parameters
|
412
|
+
@existing_parameters ||= stack.parameters
|
413
|
+
end
|
414
|
+
|
415
|
+
def new_parameters
|
416
|
+
existing_parameters.merge(parameters)
|
417
|
+
end
|
418
|
+
|
419
|
+
end
|
420
|
+
|
421
|
+
subcommand ["down", "delete"], "Remove the stack." do
|
422
|
+
|
423
|
+
def execute
|
424
|
+
report_change do
|
425
|
+
stack.delete
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
end
|
430
|
+
|
431
|
+
subcommand "cancel-update", "Cancel the update in-progress." do
|
432
|
+
|
433
|
+
def execute
|
434
|
+
report_change do
|
435
|
+
stack.cancel_update
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
end
|
440
|
+
|
441
|
+
subcommand "wait", "Wait until stack is stable." do
|
442
|
+
|
443
|
+
def execute
|
444
|
+
puts stack.wait
|
445
|
+
end
|
446
|
+
|
447
|
+
end
|
448
|
+
|
449
|
+
subcommand "events", "List stack events." do
|
450
|
+
|
451
|
+
option ["-f", "--follow"], :flag, "follow new events"
|
452
|
+
option ["--data"], :flag, "display events as data"
|
453
|
+
|
454
|
+
def execute
|
455
|
+
stack.watch(false) do |watcher|
|
456
|
+
loop do
|
457
|
+
watcher.each_new_event do |event|
|
458
|
+
display_event(event)
|
459
|
+
end
|
460
|
+
break unless follow?
|
461
|
+
|
462
|
+
sleep 5
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
private
|
468
|
+
|
469
|
+
def display_event(e)
|
470
|
+
if data?
|
471
|
+
display_data(event_data(e))
|
472
|
+
else
|
473
|
+
puts event_summary(e)
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def event_data(e)
|
478
|
+
{
|
479
|
+
"timestamp" => e.timestamp.localtime,
|
480
|
+
"logical_resource_id" => e.logical_resource_id,
|
481
|
+
"physical_resource_id" => e.physical_resource_id,
|
482
|
+
"resource_status" => e.resource_status,
|
483
|
+
"resource_status_reason" => e.resource_status_reason
|
484
|
+
}.reject { |_k, v| blank?(v) }
|
485
|
+
end
|
486
|
+
|
487
|
+
def blank?(v)
|
488
|
+
v.nil? || v.respond_to?(:empty?) && v.empty?
|
489
|
+
end
|
490
|
+
|
491
|
+
def event_summary(e)
|
492
|
+
summary = "[#{e.timestamp.localtime.iso8601}] #{e.logical_resource_id}"
|
493
|
+
summary += " - #{e.resource_status}"
|
494
|
+
summary += " - #{e.resource_status_reason}" if e.resource_status_reason
|
495
|
+
summary
|
496
|
+
end
|
497
|
+
|
498
|
+
end
|
499
|
+
|
500
|
+
subcommand "template", "Display stack template." do
|
501
|
+
|
502
|
+
def execute
|
503
|
+
display_data(stack.template)
|
504
|
+
end
|
505
|
+
|
506
|
+
end
|
507
|
+
|
508
|
+
subcommand ["parameters", "params"], "Display stack parameters." do
|
509
|
+
|
510
|
+
def execute
|
511
|
+
display_data(stack.parameters)
|
512
|
+
end
|
513
|
+
|
514
|
+
end
|
515
|
+
|
516
|
+
subcommand "tags", "Display stack tags." do
|
517
|
+
|
518
|
+
def execute
|
519
|
+
display_data(stack.tags)
|
520
|
+
end
|
521
|
+
|
522
|
+
end
|
523
|
+
|
524
|
+
subcommand "resources", "Display stack resources." do
|
525
|
+
|
526
|
+
def execute
|
527
|
+
display_data(stack.resources)
|
528
|
+
end
|
529
|
+
|
530
|
+
end
|
531
|
+
|
532
|
+
subcommand "outputs", "Display stack outputs." do
|
533
|
+
|
534
|
+
def execute
|
535
|
+
display_data(stack.outputs)
|
536
|
+
end
|
537
|
+
|
538
|
+
end
|
539
|
+
|
540
|
+
subcommand "inspect", "Display stack particulars." do
|
541
|
+
|
542
|
+
def execute
|
543
|
+
data = {
|
544
|
+
"Status" => stack.status,
|
545
|
+
"Parameters" => stack.parameters,
|
546
|
+
"Tags" => stack.tags,
|
547
|
+
"Resources" => stack.resources,
|
548
|
+
"Outputs" => stack.outputs
|
549
|
+
}
|
550
|
+
display_data(data)
|
551
|
+
end
|
552
|
+
|
553
|
+
end
|
554
|
+
|
555
|
+
end
|
556
|
+
|
557
|
+
end
|
data/lib/stackup/version.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "stackup/main_command"
|
2
|
+
|
3
|
+
describe Stackup::MainCommand do
|
4
|
+
context "change-set create --service-role-arn"
|
5
|
+
it "invokes stack.change_set.create with role arn passed through" do
|
6
|
+
mock_stackup = double()
|
7
|
+
mock_stack = double()
|
8
|
+
mock_change_set = double()
|
9
|
+
allow_any_instance_of(Stackup::MainCommand).to receive(:Stackup).and_return(mock_stackup)
|
10
|
+
allow(mock_stackup).to receive(:stack).and_return(mock_stack)
|
11
|
+
allow(mock_stack).to receive(:change_set).and_return(mock_change_set)
|
12
|
+
|
13
|
+
expected_args = {
|
14
|
+
role_arn: "arn:aws:iam::000000000000:role/example"
|
15
|
+
}
|
16
|
+
expect(mock_change_set).to receive(:create).with(hash_including(expected_args))
|
17
|
+
|
18
|
+
Stackup::MainCommand.run("stackup", [
|
19
|
+
"STACK-NAME", "change-set", "create",
|
20
|
+
"--template", "examples/template.yml",
|
21
|
+
"--service-role-arn", "arn:aws:iam::000000000000:role/example"])
|
22
|
+
end
|
23
|
+
end
|
data/spec/stackup/stack_spec.rb
CHANGED
@@ -342,6 +342,23 @@ describe Stackup::Stack do
|
|
342
342
|
.with(hash_including(expected_args))
|
343
343
|
end
|
344
344
|
|
345
|
+
it "passes options through to :create_change_set" do
|
346
|
+
options = {
|
347
|
+
:template_body => template,
|
348
|
+
:role_arn => "service role arn"
|
349
|
+
}
|
350
|
+
expected_args = {
|
351
|
+
:stack_name => stack_name,
|
352
|
+
:change_set_name => change_set_name,
|
353
|
+
:change_set_type => "CREATE",
|
354
|
+
:template_body => template,
|
355
|
+
:role_arn => "service role arn"
|
356
|
+
}
|
357
|
+
stack.change_set(change_set_name).create(options)
|
358
|
+
expect(cf_client).to have_received(:create_change_set)
|
359
|
+
.with(hash_including(expected_args))
|
360
|
+
end
|
361
|
+
|
345
362
|
context "with :template as data" do
|
346
363
|
|
347
364
|
let(:options) do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stackup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Williams
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-11-
|
12
|
+
date: 2020-11-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk-cloudformation
|
@@ -99,6 +99,7 @@ files:
|
|
99
99
|
- lib/stackup/differ.rb
|
100
100
|
- lib/stackup/error_handling.rb
|
101
101
|
- lib/stackup/errors.rb
|
102
|
+
- lib/stackup/main_command.rb
|
102
103
|
- lib/stackup/parameters.rb
|
103
104
|
- lib/stackup/rake_tasks.rb
|
104
105
|
- lib/stackup/service.rb
|
@@ -109,6 +110,7 @@ files:
|
|
109
110
|
- lib/stackup/version.rb
|
110
111
|
- lib/stackup/yaml.rb
|
111
112
|
- spec/spec_helper.rb
|
113
|
+
- spec/stackup/main_command_spec.rb
|
112
114
|
- spec/stackup/parameters_spec.rb
|
113
115
|
- spec/stackup/rake_tasks_spec.rb
|
114
116
|
- spec/stackup/source_spec.rb
|
@@ -141,6 +143,7 @@ specification_version: 4
|
|
141
143
|
summary: Manage CloudFormation stacks
|
142
144
|
test_files:
|
143
145
|
- spec/spec_helper.rb
|
146
|
+
- spec/stackup/main_command_spec.rb
|
144
147
|
- spec/stackup/parameters_spec.rb
|
145
148
|
- spec/stackup/rake_tasks_spec.rb
|
146
149
|
- spec/stackup/source_spec.rb
|