stackup 1.5.0 → 1.7.1
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 +17 -0
- data/README.md +21 -12
- data/bin/stackup +3 -545
- data/lib/stackup/change_set.rb +24 -13
- data/lib/stackup/differ.rb +2 -0
- data/lib/stackup/error_handling.rb +2 -0
- data/lib/stackup/errors.rb +2 -0
- data/lib/stackup/main_command.rb +551 -0
- data/lib/stackup/parameters.rb +8 -6
- data/lib/stackup/rake_tasks.rb +3 -2
- data/lib/stackup/service.rb +2 -0
- data/lib/stackup/source.rb +3 -1
- data/lib/stackup/stack_watcher.rb +4 -4
- data/lib/stackup/utils.rb +2 -0
- data/lib/stackup/version.rb +3 -1
- data/lib/stackup/yaml.rb +2 -0
- data/lib/stackup.rb +2 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/stackup/main_command_spec.rb +61 -0
- data/spec/stackup/parameters_spec.rb +2 -0
- data/spec/stackup/rake_tasks_spec.rb +2 -0
- data/spec/stackup/source_spec.rb +12 -0
- data/spec/stackup/stack_spec.rb +51 -0
- data/spec/stackup/stack_watcher_spec.rb +2 -0
- data/spec/stackup/utils_spec.rb +2 -0
- data/spec/stackup/yaml_spec.rb +2 -0
- metadata +12 -9
data/lib/stackup/change_set.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "stackup/error_handling"
|
2
4
|
|
3
5
|
module Stackup
|
@@ -51,19 +53,9 @@ module Stackup
|
|
51
53
|
# @raise [Stackup::NoSuchStack] if the stack doesn't exist
|
52
54
|
#
|
53
55
|
def create(options = {})
|
54
|
-
options = options
|
55
|
-
|
56
|
-
|
57
|
-
options[:change_set_type] = stack.exists? ? "UPDATE" : "CREATE"
|
58
|
-
force = options.delete(:force)
|
59
|
-
options[:template_body] = MultiJson.dump(options.delete(:template)) if options[:template]
|
60
|
-
# optionally override template_body with the original template to preserve formatting (& comments in YAML)
|
61
|
-
template_orig = options.delete(:template_orig)
|
62
|
-
options[:template_body] = template_orig if options.delete(:preserve)
|
63
|
-
options[:parameters] = Parameters.new(options[:parameters]).to_a if options[:parameters]
|
64
|
-
options[:tags] = normalize_tags(options[:tags]) if options[:tags]
|
65
|
-
options[:capabilities] ||= ["CAPABILITY_NAMED_IAM"]
|
66
|
-
delete if force
|
56
|
+
options = validate_options(options)
|
57
|
+
delete if options.delete(:force)
|
58
|
+
allow_empty_change_set = options.delete(:allow_empty_change_set)
|
67
59
|
handling_cf_errors do
|
68
60
|
cf_client.create_change_set(options)
|
69
61
|
loop do
|
@@ -73,6 +65,10 @@ module Stackup
|
|
73
65
|
when /COMPLETE/
|
74
66
|
return current.status
|
75
67
|
when "FAILED"
|
68
|
+
if allow_empty_change_set && (current.status_reason == "The submitted information didn't contain changes. Submit different information to create a change set.")
|
69
|
+
return current.status_reason
|
70
|
+
end
|
71
|
+
|
76
72
|
logger.error(current.status_reason)
|
77
73
|
raise StackUpdateError, "change-set creation failed" if status == "FAILED"
|
78
74
|
end
|
@@ -145,6 +141,21 @@ module Stackup
|
|
145
141
|
stack.send(:wait_poll_interval)
|
146
142
|
end
|
147
143
|
|
144
|
+
def validate_options(original)
|
145
|
+
options = original.dup
|
146
|
+
options[:stack_name] = stack.name
|
147
|
+
options[:change_set_name] = name
|
148
|
+
options[:change_set_type] = stack.exists? ? "UPDATE" : "CREATE"
|
149
|
+
options[:template_body] = MultiJson.dump(options.delete(:template)) if options[:template]
|
150
|
+
# optionally override template_body with the original template to preserve formatting (& comments in YAML)
|
151
|
+
template_orig = options.delete(:template_orig)
|
152
|
+
options[:template_body] = template_orig if options.delete(:preserve)
|
153
|
+
options[:parameters] = Parameters.new(options[:parameters]).to_a if options[:parameters]
|
154
|
+
options[:tags] = normalize_tags(options[:tags]) if options[:tags]
|
155
|
+
options[:capabilities] ||= ["CAPABILITY_NAMED_IAM"]
|
156
|
+
options
|
157
|
+
end
|
158
|
+
|
148
159
|
end
|
149
160
|
|
150
161
|
end
|
data/lib/stackup/differ.rb
CHANGED
data/lib/stackup/errors.rb
CHANGED
@@ -0,0 +1,551 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "clamp"
|
4
|
+
require "console_logger"
|
5
|
+
require "multi_json"
|
6
|
+
require "securerandom"
|
7
|
+
require "stackup"
|
8
|
+
require "stackup/differ"
|
9
|
+
require "stackup/source"
|
10
|
+
require "stackup/version"
|
11
|
+
require "stackup/yaml"
|
12
|
+
|
13
|
+
module Stackup
|
14
|
+
|
15
|
+
class MainCommand < Clamp::Command
|
16
|
+
|
17
|
+
option ["-L", "--list"], :flag, "list stacks" do
|
18
|
+
list_stacks
|
19
|
+
exit 0
|
20
|
+
end
|
21
|
+
|
22
|
+
option ["-Y", "--yaml"], :flag, "output data in YAML format"
|
23
|
+
|
24
|
+
option ["--region"], "REGION", "set region" do |arg|
|
25
|
+
raise ArgumentError, "#{arg.inspect} doesn't look like a region" unless arg =~ /^[a-z]{2}-[a-z]+-\d$/
|
26
|
+
|
27
|
+
arg
|
28
|
+
end
|
29
|
+
|
30
|
+
option ["--with-role"], "ROLE_ARN", "assume this role",
|
31
|
+
:attribute_name => :role_arn
|
32
|
+
|
33
|
+
option ["--retry-limit"], "N", "maximum number of retries for API calls",
|
34
|
+
:environment_variable => "AWS_API_RETRY_LIMIT" do |arg|
|
35
|
+
Integer(arg)
|
36
|
+
end
|
37
|
+
|
38
|
+
option ["--[no-]wait"], :flag, "wait for stack updates to complete",
|
39
|
+
:default => true
|
40
|
+
|
41
|
+
option ["--wait-poll-interval"], "N", "polling interval (in seconds) while waiting for updates",
|
42
|
+
:default => 5, &method(:Integer)
|
43
|
+
|
44
|
+
option "--debug", :flag, "enable debugging"
|
45
|
+
|
46
|
+
option ["--version"], :flag, "display version" do
|
47
|
+
puts "stackup v#{Stackup::VERSION}"
|
48
|
+
exit 0
|
49
|
+
end
|
50
|
+
|
51
|
+
parameter "NAME", "Name of stack", :attribute_name => :stack_name
|
52
|
+
|
53
|
+
def run(arguments)
|
54
|
+
super(arguments)
|
55
|
+
rescue Stackup::Source::ReadError => e
|
56
|
+
signal_error e.message
|
57
|
+
rescue Stackup::ServiceError => e
|
58
|
+
signal_error e.message
|
59
|
+
rescue Aws::Errors::MissingCredentialsError
|
60
|
+
signal_error "no credentials provided"
|
61
|
+
rescue Aws::Errors::ServiceError => e
|
62
|
+
signal_error e.message
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def logger
|
68
|
+
@logger ||= ConsoleLogger.new($stdout, debug?)
|
69
|
+
end
|
70
|
+
|
71
|
+
def format_data(data)
|
72
|
+
if yaml?
|
73
|
+
YAML.dump(data)
|
74
|
+
else
|
75
|
+
MultiJson.dump(data, :pretty => true)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def display_data(data)
|
80
|
+
puts format_data(data)
|
81
|
+
end
|
82
|
+
|
83
|
+
def role_arn=(arg)
|
84
|
+
raise ArgumentError, "#{arg.inspect} doesn't look like a role ARN" unless arg =~ %r{^arn:aws:iam::\d+:role/}
|
85
|
+
|
86
|
+
@role_arn = arg
|
87
|
+
end
|
88
|
+
|
89
|
+
def stackup
|
90
|
+
Stackup(aws_config)
|
91
|
+
end
|
92
|
+
|
93
|
+
def base_aws_config
|
94
|
+
{
|
95
|
+
:log_level => :debug,
|
96
|
+
:logger => logger,
|
97
|
+
:region => region,
|
98
|
+
:retry_limit => retry_limit
|
99
|
+
}.reject { |_k, v| v.nil? }
|
100
|
+
end
|
101
|
+
|
102
|
+
def aws_config
|
103
|
+
return base_aws_config unless role_arn
|
104
|
+
|
105
|
+
assumed_credentials = Aws::AssumeRoleCredentials.new(
|
106
|
+
:client => Aws::STS::Client.new(base_aws_config),
|
107
|
+
:role_arn => role_arn,
|
108
|
+
:role_session_name => "stackup-#{SecureRandom.hex(8)}"
|
109
|
+
)
|
110
|
+
base_aws_config.merge(:credentials => assumed_credentials)
|
111
|
+
end
|
112
|
+
|
113
|
+
def stack
|
114
|
+
stackup.stack(stack_name, :wait => wait?, :wait_poll_interval => wait_poll_interval)
|
115
|
+
end
|
116
|
+
|
117
|
+
def list_stacks
|
118
|
+
stackup.stack_names.each do |name|
|
119
|
+
puts name
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def report_change
|
124
|
+
final_status = yield
|
125
|
+
puts final_status unless final_status.nil?
|
126
|
+
end
|
127
|
+
|
128
|
+
subcommand "status", "Print stack status." do
|
129
|
+
|
130
|
+
def execute
|
131
|
+
puts stack.status
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
module HasParameters
|
137
|
+
|
138
|
+
extend Clamp::Option::Declaration
|
139
|
+
|
140
|
+
option ["-p", "--parameters"], "FILE", "parameters file (last wins)",
|
141
|
+
:multivalued => true,
|
142
|
+
:attribute_name => :parameter_sources,
|
143
|
+
&Stackup::Source.method(:new)
|
144
|
+
|
145
|
+
option ["-o", "--override"], "PARAM=VALUE", "parameter overrides",
|
146
|
+
:multivalued => true,
|
147
|
+
:attribute_name => :override_list
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def parameters
|
152
|
+
parameters_from_files.merge(parameter_overrides)
|
153
|
+
end
|
154
|
+
|
155
|
+
def parameters_from_files
|
156
|
+
parameter_sources.map do |src|
|
157
|
+
Stackup::Parameters.new(src.data).to_hash
|
158
|
+
end.inject({}, :merge)
|
159
|
+
end
|
160
|
+
|
161
|
+
def parameter_overrides
|
162
|
+
{}.tap do |result|
|
163
|
+
override_list.each do |override|
|
164
|
+
key, value = override.split("=", 2)
|
165
|
+
result[key] = value
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
subcommand "up", "Create/update the stack." do
|
173
|
+
|
174
|
+
option ["-t", "--template"], "FILE", "template source",
|
175
|
+
:attribute_name => :template_source,
|
176
|
+
&Stackup::Source.method(:new)
|
177
|
+
|
178
|
+
option ["-T", "--use-previous-template"], :flag,
|
179
|
+
"reuse the existing template"
|
180
|
+
|
181
|
+
option ["-P", "--preserve-template-formatting"], :flag,
|
182
|
+
"do not normalise the template when calling the Cloudformation APIs; useful for preserving YAML and comments"
|
183
|
+
|
184
|
+
include HasParameters
|
185
|
+
|
186
|
+
option "--tags", "FILE", "stack tags file",
|
187
|
+
:attribute_name => :tag_source,
|
188
|
+
&Stackup::Source.method(:new)
|
189
|
+
|
190
|
+
option "--policy", "FILE", "stack policy file",
|
191
|
+
:attribute_name => :policy_source,
|
192
|
+
&Stackup::Source.method(:new)
|
193
|
+
|
194
|
+
option "--service-role-arn", "SERVICE_ROLE_ARN", "cloudformation service role ARN" do |arg|
|
195
|
+
raise ArgumentError, "#{arg.inspect} doesn't look like a role ARN" unless arg =~ %r{^arn:aws:iam::\d+:role/}
|
196
|
+
|
197
|
+
arg
|
198
|
+
end
|
199
|
+
|
200
|
+
option "--on-failure", "ACTION",
|
201
|
+
"when stack creation fails: DO_NOTHING, ROLLBACK, or DELETE",
|
202
|
+
:default => "ROLLBACK"
|
203
|
+
|
204
|
+
option "--capability", "CAPABILITY", "cloudformation capability",
|
205
|
+
:multivalued => true, :default => ["CAPABILITY_NAMED_IAM"]
|
206
|
+
|
207
|
+
def execute
|
208
|
+
signal_usage_error "Specify either --template or --use-previous-template" unless template_source || use_previous_template?
|
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
|
+
def pad(s, width)
|
252
|
+
(s || "").ljust(width)
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
subcommand ["change-set"], "Change-set operations." do
|
258
|
+
|
259
|
+
option "--name", "NAME", "Name of change-set",
|
260
|
+
:attribute_name => :change_set_name,
|
261
|
+
:default => "pending"
|
262
|
+
|
263
|
+
subcommand "create", "Create a change-set." do
|
264
|
+
|
265
|
+
option ["-d", "--description"], "DESC",
|
266
|
+
"Change-set description"
|
267
|
+
|
268
|
+
option ["-t", "--template"], "FILE", "template source",
|
269
|
+
:attribute_name => :template_source,
|
270
|
+
&Stackup::Source.method(:new)
|
271
|
+
|
272
|
+
option ["-T", "--use-previous-template"], :flag,
|
273
|
+
"reuse the existing template"
|
274
|
+
|
275
|
+
option ["-P", "--preserve-template-formatting"], :flag,
|
276
|
+
"do not normalise the template when calling the Cloudformation APIs; useful for preserving YAML and comments"
|
277
|
+
|
278
|
+
option ["--force"], :flag,
|
279
|
+
"replace existing change-set of the same name"
|
280
|
+
|
281
|
+
option ["--no-fail-on-empty-change-set"], :flag, "don't fail on empty change-set",
|
282
|
+
:attribute_name => :allow_empty_change_set
|
283
|
+
|
284
|
+
include HasParameters
|
285
|
+
|
286
|
+
option "--tags", "FILE", "stack tags file",
|
287
|
+
:attribute_name => :tag_source,
|
288
|
+
&Stackup::Source.method(:new)
|
289
|
+
|
290
|
+
option "--service-role-arn", "SERVICE_ROLE_ARN", "cloudformation service role ARN" do |arg|
|
291
|
+
raise ArgumentError, "#{arg.inspect} doesn't look like a role ARN" unless arg =~ %r{^arn:aws:iam::\d+:role/}
|
292
|
+
|
293
|
+
arg
|
294
|
+
end
|
295
|
+
|
296
|
+
option "--capability", "CAPABILITY", "cloudformation capability",
|
297
|
+
:multivalued => true, :default => ["CAPABILITY_NAMED_IAM"]
|
298
|
+
|
299
|
+
def execute
|
300
|
+
signal_usage_error "Specify either --template or --use-previous-template" unless template_source || use_previous_template?
|
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[:role_arn] = service_role_arn if service_role_arn
|
314
|
+
options[:use_previous_template] = use_previous_template?
|
315
|
+
options[:force] = force?
|
316
|
+
options[:allow_empty_change_set] = allow_empty_change_set?
|
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
|
+
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
|
+
def differ
|
404
|
+
Stackup::Differ.new(diff_format, &method(:format_data))
|
405
|
+
end
|
406
|
+
|
407
|
+
def existing_parameters
|
408
|
+
@existing_parameters ||= stack.parameters
|
409
|
+
end
|
410
|
+
|
411
|
+
def new_parameters
|
412
|
+
existing_parameters.merge(parameters)
|
413
|
+
end
|
414
|
+
|
415
|
+
end
|
416
|
+
|
417
|
+
subcommand ["down", "delete"], "Remove the stack." do
|
418
|
+
|
419
|
+
def execute
|
420
|
+
report_change do
|
421
|
+
stack.delete
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
end
|
426
|
+
|
427
|
+
subcommand "cancel-update", "Cancel the update in-progress." do
|
428
|
+
|
429
|
+
def execute
|
430
|
+
report_change do
|
431
|
+
stack.cancel_update
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
end
|
436
|
+
|
437
|
+
subcommand "wait", "Wait until stack is stable." do
|
438
|
+
|
439
|
+
def execute
|
440
|
+
puts stack.wait
|
441
|
+
end
|
442
|
+
|
443
|
+
end
|
444
|
+
|
445
|
+
subcommand "events", "List stack events." do
|
446
|
+
|
447
|
+
option ["-f", "--follow"], :flag, "follow new events"
|
448
|
+
option ["--data"], :flag, "display events as data"
|
449
|
+
|
450
|
+
def execute
|
451
|
+
stack.watch(false) do |watcher|
|
452
|
+
loop do
|
453
|
+
watcher.each_new_event do |event|
|
454
|
+
display_event(event)
|
455
|
+
end
|
456
|
+
break unless follow?
|
457
|
+
|
458
|
+
sleep 5
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
def display_event(e)
|
464
|
+
if data?
|
465
|
+
display_data(event_data(e))
|
466
|
+
else
|
467
|
+
puts event_summary(e)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
def event_data(e)
|
472
|
+
{
|
473
|
+
"timestamp" => e.timestamp.localtime,
|
474
|
+
"logical_resource_id" => e.logical_resource_id,
|
475
|
+
"physical_resource_id" => e.physical_resource_id,
|
476
|
+
"resource_status" => e.resource_status,
|
477
|
+
"resource_status_reason" => e.resource_status_reason
|
478
|
+
}.reject { |_k, v| blank?(v) }
|
479
|
+
end
|
480
|
+
|
481
|
+
def blank?(v)
|
482
|
+
v.nil? || v.respond_to?(:empty?) && v.empty?
|
483
|
+
end
|
484
|
+
|
485
|
+
def event_summary(e)
|
486
|
+
summary = "[#{e.timestamp.localtime.iso8601}] #{e.logical_resource_id}"
|
487
|
+
summary += " - #{e.resource_status}"
|
488
|
+
summary += " - #{e.resource_status_reason}" if e.resource_status_reason
|
489
|
+
summary
|
490
|
+
end
|
491
|
+
|
492
|
+
end
|
493
|
+
|
494
|
+
subcommand "template", "Display stack template." do
|
495
|
+
|
496
|
+
def execute
|
497
|
+
display_data(stack.template)
|
498
|
+
end
|
499
|
+
|
500
|
+
end
|
501
|
+
|
502
|
+
subcommand ["parameters", "params"], "Display stack parameters." do
|
503
|
+
|
504
|
+
def execute
|
505
|
+
display_data(stack.parameters)
|
506
|
+
end
|
507
|
+
|
508
|
+
end
|
509
|
+
|
510
|
+
subcommand "tags", "Display stack tags." do
|
511
|
+
|
512
|
+
def execute
|
513
|
+
display_data(stack.tags)
|
514
|
+
end
|
515
|
+
|
516
|
+
end
|
517
|
+
|
518
|
+
subcommand "resources", "Display stack resources." do
|
519
|
+
|
520
|
+
def execute
|
521
|
+
display_data(stack.resources)
|
522
|
+
end
|
523
|
+
|
524
|
+
end
|
525
|
+
|
526
|
+
subcommand "outputs", "Display stack outputs." do
|
527
|
+
|
528
|
+
def execute
|
529
|
+
display_data(stack.outputs)
|
530
|
+
end
|
531
|
+
|
532
|
+
end
|
533
|
+
|
534
|
+
subcommand "inspect", "Display stack particulars." do
|
535
|
+
|
536
|
+
def execute
|
537
|
+
data = {
|
538
|
+
"Status" => stack.status,
|
539
|
+
"Parameters" => stack.parameters,
|
540
|
+
"Tags" => stack.tags,
|
541
|
+
"Resources" => stack.resources,
|
542
|
+
"Outputs" => stack.outputs
|
543
|
+
}
|
544
|
+
display_data(data)
|
545
|
+
end
|
546
|
+
|
547
|
+
end
|
548
|
+
|
549
|
+
end
|
550
|
+
|
551
|
+
end
|