cfndk 0.1.0 → 0.1.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +23 -14
  3. data/.gitignore +0 -1
  4. data/.rspec_parallel +6 -0
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +811 -0
  7. data/README.md +122 -10
  8. data/cfndk.gemspec +1 -0
  9. data/lib/cfndk/change_set_command.rb +97 -0
  10. data/lib/cfndk/command.rb +15 -181
  11. data/lib/cfndk/config_file_loadable.rb +13 -0
  12. data/lib/cfndk/global_config.rb +15 -0
  13. data/lib/cfndk/key_pair.rb +7 -4
  14. data/lib/cfndk/key_pair_command.rb +53 -0
  15. data/lib/cfndk/key_pairs.rb +2 -1
  16. data/lib/cfndk/logger.rb +1 -1
  17. data/lib/cfndk/stack.rb +382 -103
  18. data/lib/cfndk/stack_command.rb +110 -0
  19. data/lib/cfndk/stacks.rb +40 -14
  20. data/lib/cfndk/subcommand_help_returnable.rb +16 -0
  21. data/lib/cfndk/version.rb +1 -1
  22. data/lib/cfndk.rb +6 -0
  23. data/skel/cfndk.yml +4 -0
  24. data/spec/cfndk_change_set_create_spec.rb +436 -0
  25. data/spec/cfndk_change_set_destroy_spec.rb +160 -0
  26. data/spec/cfndk_change_set_execute_spec.rb +179 -0
  27. data/spec/cfndk_change_set_report_spec.rb +107 -0
  28. data/spec/cfndk_change_set_spec.rb +37 -0
  29. data/spec/cfndk_create_spec.rb +56 -141
  30. data/spec/cfndk_destroy_spec.rb +4 -2
  31. data/spec/cfndk_keypiar_spec.rb +11 -9
  32. data/spec/cfndk_report_spec.rb +3 -1
  33. data/spec/cfndk_spec.rb +5 -3
  34. data/spec/cfndk_stack_create_spec.rb +454 -0
  35. data/spec/cfndk_stack_destroy_spec.rb +161 -0
  36. data/spec/cfndk_stack_report_spec.rb +181 -0
  37. data/spec/cfndk_stack_spec.rb +6 -1146
  38. data/spec/cfndk_stack_update_spec.rb +467 -0
  39. data/spec/spec_helper.rb +4 -1
  40. data/spec/support/aruba.rb +1 -0
  41. metadata +42 -2
data/lib/cfndk/stack.rb CHANGED
@@ -1,16 +1,20 @@
1
1
  module CFnDK
2
2
  class Stack
3
- attr_reader :template_file, :parameter_input, :capabilities, :depends, :timeout_in_minutes
4
- def initialize(name, data, option, credentials)
3
+ attr_reader :template_file, :parameter_input, :capabilities, :depends, :timeout_in_minutes, :region
4
+ def initialize(name, data, option, global_config, credentials)
5
+ @global_config = global_config
5
6
  @name = name
6
7
  @template_file = data['template_file'] || ''
7
8
  @parameter_input = data['parameter_input'] || ''
8
9
  @capabilities = data['capabilities'] || []
9
10
  @depends = data['depends'] || []
10
- @timeout_in_minutes = data['timeout_in_minutes'] || 1
11
+ @region = data['region'] || @global_config.region
12
+ @timeout_in_minutes = data['timeout_in_minutes'] || @global_config.timeout_in_minutes
11
13
  @override_parameters = data['parameters'] || {}
12
14
  @option = option
13
- @client = Aws::CloudFormation::Client.new(credentials: credentials)
15
+ @client = Aws::CloudFormation::Client.new(credentials: credentials, region: @region)
16
+ @s3_client = Aws::S3::Client.new(credentials: credentials, region: @region)
17
+ @sts_client = Aws::STS::Client.new(credentials: credentials, region: @region)
14
18
  end
15
19
 
16
20
  def create
@@ -20,26 +24,50 @@ module CFnDK
20
24
  CFnDK.logger.debug('Parametres :' + parameters.inspect)
21
25
  CFnDK.logger.debug('Capabilities:' + capabilities.inspect)
22
26
  CFnDK.logger.debug('Timeout :' + timeout_in_minutes.to_s)
23
- @client.create_stack(
27
+ CFnDK.logger.debug('Region :' + region)
28
+ tags = [
29
+ {
30
+ key: 'origina_name',
31
+ value: @name,
32
+ },
33
+ ]
34
+ tags.push(
35
+ key: 'UUID',
36
+ value: @option[:uuid]
37
+ ) if @option[:uuid]
38
+ hash = {
24
39
  stack_name: name,
25
- template_body: template_body,
26
40
  parameters: parameters,
27
41
  capabilities: capabilities,
28
- timeout_in_minutes: timeout_in_minutes
42
+ timeout_in_minutes: timeout_in_minutes,
43
+ tags: tags,
44
+ }
45
+ if large_template?
46
+ hash[:template_url] = upload_template_file()
47
+ else
48
+ hash[:template_body] = template_body()
49
+ end
50
+ @client.create_stack(
51
+ hash
29
52
  )
30
53
  end
31
54
 
32
55
  def wait_until_create
33
56
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
57
+ CFnDK.logger.info(('waiting create stack: ' + name).color(:green))
34
58
  begin
35
59
  @client.wait_until(
36
60
  :stack_create_complete,
37
61
  stack_name: name
38
- )
62
+ ) do |w|
63
+ w.max_attempts = 360
64
+ w.delay = 10
65
+ end
39
66
  CFnDK.logger.info(('created stack: ' + name).color(:green))
40
67
  rescue Aws::Waiters::Errors::FailureStateError => ex
41
- CFnDK.logger.error ex.message
42
- report_event
68
+ CFnDK.logger.error "#{ex.class}: #{ex.message}".color(:red)
69
+ @option[:type] = %w(tag output parameter resource event)
70
+ report
43
71
  raise ex
44
72
  end
45
73
  end
@@ -51,12 +79,20 @@ module CFnDK
51
79
  CFnDK.logger.debug('Parametres :' + parameters.inspect)
52
80
  CFnDK.logger.debug('Capabilities:' + capabilities.inspect)
53
81
  CFnDK.logger.debug('Timeout :' + timeout_in_minutes.to_s)
82
+ CFnDK.logger.debug('Region :' + region)
54
83
  begin
55
- @client.update_stack(
84
+ hash = {
56
85
  stack_name: name,
57
- template_body: template_body,
58
86
  parameters: parameters,
59
- capabilities: capabilities
87
+ capabilities: capabilities,
88
+ }
89
+ if large_template?
90
+ hash[:template_url] = upload_template_file()
91
+ else
92
+ hash[:template_body] = template_body()
93
+ end
94
+ @client.update_stack(
95
+ hash
60
96
  )
61
97
  true
62
98
  rescue Aws::CloudFormation::Errors::ValidationError => ex
@@ -65,17 +101,21 @@ module CFnDK
65
101
  CFnDK.logger.warn "#{ex.message}: #{name}".color(:red)
66
102
  false
67
103
  else
68
- raise ex
104
+ raise ex
69
105
  end
70
106
  end
71
107
  end
72
108
 
73
109
  def wait_until_update
74
110
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
111
+ CFnDK.logger.info(('waiting update stack: ' + name).color(:green))
75
112
  @client.wait_until(
76
113
  :stack_update_complete,
77
114
  stack_name: name
78
- )
115
+ ) do |w|
116
+ w.max_attempts = 360
117
+ w.delay = 10
118
+ end
79
119
  CFnDK.logger.info(('updated stack: ' + name).color(:green))
80
120
  end
81
121
 
@@ -84,6 +124,7 @@ module CFnDK
84
124
  if exits?
85
125
  CFnDK.logger.info(('deleting stack: ' + name).color(:green))
86
126
  CFnDK.logger.debug('Name :' + name)
127
+ CFnDK.logger.debug('Region :' + region)
87
128
  @client.delete_stack(
88
129
  stack_name: name
89
130
  )
@@ -95,63 +136,190 @@ module CFnDK
95
136
  def wait_until_destroy
96
137
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
97
138
  return unless exits?
139
+ CFnDK.logger.info(('waiting delete stack: ' + name).color(:green))
98
140
  @client.wait_until(
99
141
  :stack_delete_complete,
100
142
  stack_name: name
101
- )
143
+ ) do |w|
144
+ w.max_attempts = 360
145
+ w.delay = 10
146
+ end
102
147
  CFnDK.logger.info(('deleted stack: ' + name).color(:green))
103
148
  end
104
149
 
105
150
  def create_change_set
106
- return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
107
- CFnDK.logger.info(('creating change set: ' + name).color(:green))
108
- CFnDK.logger.debug('Name :' + name)
151
+ return nil if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
152
+ CFnDK.logger.info(('creating change set: ' + change_set_name).color(:green))
109
153
  CFnDK.logger.debug('Parametres :' + parameters.inspect)
110
154
  CFnDK.logger.debug('Capabilities:' + capabilities.inspect)
111
- @client.create_change_set(
155
+ CFnDK.logger.debug('Region :' + region)
156
+ tags = [
157
+ {
158
+ key: 'origina_name',
159
+ value: @name,
160
+ },
161
+ ]
162
+ tags.push(
163
+ key: 'UUID',
164
+ value: @option[:uuid]
165
+ ) if @option[:uuid]
166
+ tags.push(
167
+ key: 'CHANGE_SET_UUID',
168
+ value: @option[:change_set_uuid]
169
+ ) if @option[:change_set_uuid]
170
+ hash = {
112
171
  stack_name: name,
113
- template_body: template_body,
114
172
  parameters: parameters,
115
173
  capabilities: capabilities,
116
- change_set_name: name
174
+ change_set_name: change_set_name,
175
+ change_set_type: exits? ? 'UPDATE' : 'CREATE',
176
+ tags: tags,
177
+ }
178
+ if large_template?
179
+ hash[:template_url] = upload_template_file()
180
+ else
181
+ hash[:template_body] = template_body()
182
+ end
183
+ @client.create_change_set(
184
+ hash
117
185
  )
186
+ @name
187
+ rescue Aws::CloudFormation::Errors::ValidationError => ex
188
+ if review_in_progress?
189
+ CFnDK.logger.warn("failed create change set because the stack on REVIEW_IN_PROGRESS already exist : #{change_set_name}".color(:orange))
190
+ nil
191
+ else
192
+ CFnDK.logger.error("failed create change set: #{change_set_name}".color(:red))
193
+ raise ex
194
+ end
118
195
  end
119
196
 
120
197
  def wait_until_create_change_set
121
198
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
122
- begin
123
- @client.wait_until(
124
- :change_set_create_complete,
199
+ return unless exits?
200
+ CFnDK.logger.info(('waiting create change set: ' + change_set_name).color(:green))
201
+ @client.wait_until(
202
+ :change_set_create_complete,
203
+ stack_name: name,
204
+ change_set_name: change_set_name
205
+ ) do |w|
206
+ w.max_attempts = 360
207
+ w.delay = 10
208
+ end
209
+ CFnDK.logger.info("created change set: #{change_set_name}".color(:green))
210
+ rescue Aws::Waiters::Errors::FailureStateError => ex
211
+ case ex.message
212
+ when 'stopped waiting, encountered a failure state'
213
+ unless available_change_set?
214
+ delete_change_set
215
+ CFnDK.logger.warn("failed create change set because this change set is UNAVAILABLE: #{change_set_name}".color(:orange))
216
+ return
217
+ end
218
+ end
219
+ raise ex
220
+ end
221
+
222
+ def execute_change_set
223
+ return nil if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
224
+ if available_change_set?
225
+ CFnDK.logger.info(('executing change set: ' + change_set_name).color(:green))
226
+ @client.execute_change_set(
125
227
  stack_name: name,
126
- change_set_name: name
127
- )
128
- CFnDK.logger.info(('created chnage set: ' + name).color(:green))
129
- rescue Aws::Waiters::Errors::FailureStateError => ex
130
- resp = @client.describe_change_set(
131
- change_set_name: name,
132
- stack_name: name
228
+ change_set_name: change_set_name
133
229
  )
134
- if resp.status_reason != "The submitted information didn't contain changes. Submit different information to create a change set."
135
- CFnDK.logger.error ex.message.color(:red)
136
- raise ex
137
- else
138
- CFnDK.logger.error(('failed create change set: ' + name).color(:red))
139
- CFnDK.logger.error resp.status_reason
140
- @client.delete_change_set(
141
- change_set_name: name,
142
- stack_name: name
143
- )
144
- CFnDK.logger.info(('deleted change set: ' + name).color(:red))
230
+ CFnDK.logger.info(('execute change set: ' + change_set_name).color(:green))
231
+ @name
232
+ else
233
+ CFnDK.logger.warn("failed execute change set because this change set is not AVAILABLE: #{change_set_name}".color(:orange))
234
+ nil
235
+ end
236
+ end
237
+
238
+ def delete_change_set
239
+ return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
240
+ CFnDK.logger.info(('deleting change set: ' + change_set_name).color(:green))
241
+ @client.delete_change_set(
242
+ stack_name: name,
243
+ change_set_name: change_set_name
244
+ )
245
+ CFnDK.logger.info(('deleted change set: ' + change_set_name).color(:green))
246
+ end
247
+
248
+ def report_change_set
249
+ return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
250
+ CFnDK.logger.info('*****************************************************'.color(:green))
251
+ CFnDK.logger.info(('change set: ' + change_set_name).color(:green))
252
+ CFnDK.logger.info('*****************************************************'.color(:green))
253
+ CFnDK.logger.info('')
254
+ resp = @client.describe_change_set(
255
+ change_set_name: change_set_name,
256
+ stack_name: name
257
+ )
258
+ CFnDK.logger.info('Execution Status: '.color(:green) + colored_status(resp.execution_status))
259
+ CFnDK.logger.info('Status: '.color(:green) + colored_status(resp.status))
260
+ CFnDK.logger.info('Reason: '.color(:green) + resp.status_reason) if resp.status_reason
261
+ if @option[:types].instance_of?(Array) && @option[:types].include?('tag')
262
+ CFnDK.logger.info('Tags:'.color(:green))
263
+ tags_rows = resp.tags.map do |item|
264
+ [
265
+ item.key,
266
+ item.value,
267
+ ]
268
+ end
269
+ unless tags_rows.empty?
270
+ table = Terminal::Table.new headings: %w(Key Value), rows: tags_rows
271
+ CFnDK.logger.info table
272
+ end
273
+ end
274
+ if @option[:types].instance_of?(Array) && @option[:types].include?('parameter')
275
+ CFnDK.logger.info('Parameters:'.color(:green))
276
+ parameter_rows = resp.parameters.map do |item|
277
+ [
278
+ item.parameter_key,
279
+ item.parameter_value,
280
+ item.use_previous_value,
281
+ item.resolved_value,
282
+ ]
283
+ end
284
+ unless parameter_rows.empty?
285
+ table = Terminal::Table.new headings: ['Key', 'Value', 'Use Previous Value', 'Resolved Value'], rows: parameter_rows
286
+ CFnDK.logger.info table
145
287
  end
146
288
  end
289
+ if @option[:types].instance_of?(Array) && @option[:types].include?('changes')
290
+ CFnDK.logger.info('Changes:'.color(:green))
291
+ changes_rows = resp.changes.map do |item|
292
+ [
293
+ item.resource_change.action,
294
+ item.resource_change.logical_resource_id,
295
+ item.resource_change.physical_resource_id,
296
+ item.resource_change.resource_type,
297
+ item.resource_change.replacement,
298
+ ]
299
+ end
300
+ unless changes_rows.empty?
301
+ table = Terminal::Table.new headings: %w(Action Logical Physical Type Replacement), rows: changes_rows
302
+ CFnDK.logger.info table
303
+ end
304
+ end
305
+ rescue Aws::CloudFormation::Errors::ValidationError => ex
306
+ CFnDK.logger.warn "#{ex.class}: #{ex.message}".color(:red)
307
+ rescue Aws::CloudFormation::Errors::ChangeSetNotFound => ex
308
+ CFnDK.logger.warn "#{ex.class}: #{ex.message}".color(:red)
147
309
  end
148
310
 
149
311
  def validate
150
312
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
151
313
  CFnDK.logger.info(('validate stack: ' + name).color(:green))
152
314
  CFnDK.logger.debug('Name :' + @name)
315
+ hash = {}
316
+ if large_template?
317
+ hash[:template_url] = upload_template_file()
318
+ else
319
+ hash[:template_body] = template_body()
320
+ end
153
321
  @client.validate_template(
154
- template_body: template_body
322
+ hash
155
323
  )
156
324
  end
157
325
 
@@ -164,77 +332,139 @@ module CFnDK
164
332
  false
165
333
  end
166
334
 
167
- def report
168
- report_stack
169
- report_stack_resource
170
- report_event
335
+ def created?
336
+ resp = @client.describe_stacks(
337
+ stack_name: name
338
+ )
339
+ return false if resp.stacks[0].stack_status == 'REVIEW_IN_PROGRESS'
340
+ true
341
+ rescue Aws::CloudFormation::Errors::ValidationError
342
+ false
171
343
  end
172
344
 
173
- def report_stack
174
- return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
175
- CFnDK.logger.info(('stack: ' + name).color(:green))
176
- CFnDK.logger.debug('Name :' + name)
177
- begin
178
- rows = @client.describe_stacks(
179
- stack_name: name
180
- ).stacks.map do |item|
181
- [
182
- item.stack_name,
183
- item.creation_time,
184
- item.deletion_time,
185
- colored_status(item.stack_status),
186
- item.stack_status_reason]
187
- end
188
- table = Terminal::Table.new headings: %w(Name Creation Deletion Status Reason), rows: rows
189
- CFnDK.logger.info table
190
- rescue Aws::CloudFormation::Errors::ValidationError => ex
191
- CFnDK.logger.warn ex.message
192
- end
345
+ def review_in_progress?
346
+ resp = @client.describe_stacks(
347
+ stack_name: name
348
+ )
349
+ return true if resp.stacks[0].stack_status == 'REVIEW_IN_PROGRESS'
350
+ false
351
+ rescue Aws::CloudFormation::Errors::ValidationError
352
+ false
193
353
  end
194
354
 
195
- def report_event
196
- return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
197
- CFnDK.logger.info(('stack: ' + name).color(:green))
198
- CFnDK.logger.debug('Name :' + name)
199
- begin
200
- rows = @client.describe_stack_events(
201
- stack_name: name
202
- ).stack_events.map do |item|
203
- [
204
- item.resource_type,
205
- item.timestamp,
206
- colored_status(item.resource_status),
207
- item.resource_status_reason]
208
- end
209
- table = Terminal::Table.new headings: %w(Type Time Status Reason), rows: rows
210
- CFnDK.logger.info table
211
- rescue Aws::CloudFormation::Errors::ValidationError => ex
212
- CFnDK.logger.warn ex.message
213
- end
355
+ def available_change_set?
356
+ resp = @client.describe_change_set(
357
+ change_set_name: change_set_name,
358
+ stack_name: name
359
+ )
360
+ return true if resp.execution_status == 'AVAILABLE'
361
+ false
362
+ rescue Aws::CloudFormation::Errors::ChangeSetNotFound
363
+ false
214
364
  end
215
365
 
216
- def report_stack_resource
366
+ def report
217
367
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
368
+ CFnDK.logger.info('*****************************************************'.color(:green))
218
369
  CFnDK.logger.info(('stack: ' + name).color(:green))
219
- CFnDK.logger.debug('Name :' + name)
370
+ CFnDK.logger.info('*****************************************************'.color(:green))
371
+ CFnDK.logger.info('')
220
372
  begin
221
- rows = @client.describe_stack_resources(
373
+ resp = @client.describe_stacks(
222
374
  stack_name: name
223
- ).stack_resources.map do |item|
224
- [
225
- item.logical_resource_id,
226
- item.physical_resource_id,
227
- item.resource_type,
228
- item.timestamp,
229
- colored_status(item.resource_status),
230
- item.resource_status_reason,
231
- item.description,
232
- ]
375
+ ).stacks[0]
376
+ CFnDK.logger.info('Status: '.color(:green) + colored_status(resp.stack_status))
377
+ CFnDK.logger.info('Reason: '.color(:green) + resp.stack_status_reason) if resp.stack_status_reason
378
+ if @option[:types].instance_of?(Array) && @option[:types].include?('tag')
379
+ CFnDK.logger.info('Tags:'.color(:green))
380
+ tags_rows = resp.tags.map do |item|
381
+ [
382
+ item.key,
383
+ item.value,
384
+ ]
385
+ end
386
+ unless tags_rows.empty?
387
+ table = Terminal::Table.new headings: %w(Key Value), rows: tags_rows
388
+ CFnDK.logger.info table
389
+ end
390
+ end
391
+ if @option[:types].instance_of?(Array) && @option[:types].include?('parameter')
392
+ CFnDK.logger.info('Parameters:'.color(:green))
393
+ parameter_rows = resp.parameters.map do |item|
394
+ [
395
+ item.parameter_key,
396
+ item.parameter_value,
397
+ item.use_previous_value,
398
+ item.resolved_value,
399
+ ]
400
+ end
401
+ unless parameter_rows.empty?
402
+ table = Terminal::Table.new headings: ['Key', 'Value', 'Use Previous Value', 'Resolved Value'], rows: parameter_rows
403
+ CFnDK.logger.info table
404
+ end
405
+ end
406
+ if @option[:types].instance_of?(Array) && @option[:types].include?('output')
407
+ CFnDK.logger.info('Outputs:'.color(:green))
408
+ output_rows = resp.outputs.map do |item|
409
+ [
410
+ item.output_key,
411
+ item.output_value,
412
+ item.export_name,
413
+ item.description,
414
+ ]
415
+ end
416
+ unless output_rows.empty?
417
+ table = Terminal::Table.new headings: ['Key', 'Value', 'Export Name', 'Description'], rows: output_rows
418
+ CFnDK.logger.info table
419
+ end
233
420
  end
234
- table = Terminal::Table.new headings: %w(L-name P-name Type Timestamp Status Reason Desc), rows: rows
235
- CFnDK.logger.info table
236
421
  rescue Aws::CloudFormation::Errors::ValidationError => ex
237
- CFnDK.logger.warn ex.message
422
+ CFnDK.logger.warn "#{ex.class}: #{ex.message}".color(:red)
423
+ end
424
+ if @option[:types].instance_of?(Array) && @option[:types].include?('resource')
425
+ begin
426
+ CFnDK.logger.info('Resources:'.color(:green))
427
+ rows = @client.describe_stack_resources(
428
+ stack_name: name
429
+ ).stack_resources.map do |item|
430
+ [
431
+ item.logical_resource_id,
432
+ item.physical_resource_id,
433
+ item.resource_type,
434
+ item.timestamp,
435
+ colored_status(item.resource_status),
436
+ item.resource_status_reason,
437
+ item.description,
438
+ ]
439
+ end
440
+ unless rows.empty?
441
+ table = Terminal::Table.new headings: %w(Logical Physical Type Timestamp Status Reason Desc), rows: rows
442
+ CFnDK.logger.info table
443
+ end
444
+ rescue Aws::CloudFormation::Errors::ValidationError => ex
445
+ CFnDK.logger.warn "#{ex.class}: #{ex.message}".color(:red)
446
+ end
447
+ end
448
+ if @option[:types].instance_of?(Array) && @option[:types].include?('event')
449
+ CFnDK.logger.info('Events:'.color(:green))
450
+ begin
451
+ rows = @client.describe_stack_events(
452
+ stack_name: name
453
+ ).stack_events.map do |item|
454
+ [
455
+ item.resource_type,
456
+ item.timestamp,
457
+ colored_status(item.resource_status),
458
+ item.resource_status_reason,
459
+ ]
460
+ end
461
+ unless rows.empty?
462
+ table = Terminal::Table.new headings: %w(Type Time Status Reason), rows: rows
463
+ CFnDK.logger.info table
464
+ end
465
+ rescue Aws::CloudFormation::Errors::ValidationError => ex
466
+ CFnDK.logger.warn "#{ex.class}: #{ex.message}".color(:red)
467
+ end
238
468
  end
239
469
  end
240
470
 
@@ -242,10 +472,18 @@ module CFnDK
242
472
  [@name, @option[:uuid]].compact.join('-')
243
473
  end
244
474
 
475
+ def change_set_name
476
+ [@name, @option[:change_set_uuid]].compact.join('-')
477
+ end
478
+
245
479
  def template_body
246
480
  File.open(@template_file, 'r').read
247
481
  end
248
482
 
483
+ def large_template?
484
+ File.size(@template_file) > 51200
485
+ end
486
+
249
487
  def parameters
250
488
  json = JSON.load(open(@parameter_input).read)
251
489
  json['Parameters'].map do |item|
@@ -259,6 +497,47 @@ module CFnDK
259
497
 
260
498
  private
261
499
 
500
+ def upload_template_file
501
+ begin
502
+ @s3_client.head_bucket(bucket: bucket_name)
503
+ rescue Aws::S3::Errors::NotFound, Aws::S3::Errors::Forbidden
504
+ @s3_client.create_bucket(bucket: bucket_name)
505
+ CFnDK.logger.info('Creatt S3 bucket: ' + bucket_name)
506
+ @s3_client.put_bucket_lifecycle_configuration(
507
+ bucket: bucket_name,
508
+ lifecycle_configuration: {
509
+ rules: [
510
+ {
511
+ expiration: {
512
+ days: 1,
513
+ },
514
+ status: 'Enabled',
515
+ id: 'Delete Old Files',
516
+ prefix: '',
517
+ abort_incomplete_multipart_upload: {
518
+ days_after_initiation: 1,
519
+ },
520
+ },
521
+ ],
522
+ }
523
+ )
524
+ end
525
+ key = [@global_config.s3_template_hash, @template_file].compact.join('/')
526
+ @s3_client.put_object(
527
+ body: template_body,
528
+ bucket: bucket_name,
529
+ key: key
530
+ )
531
+ url = "https://s3.amazonaws.com/#{bucket_name}/#{key}"
532
+ CFnDK.logger.info('Put S3 object: ' + url)
533
+ url
534
+ end
535
+
536
+ def bucket_name
537
+ resp = @sts_client.get_caller_identity({})
538
+ resp.account + '-' + @region + '-' + @global_config.s3_template_bucket
539
+ end
540
+
262
541
  def colored_status(str)
263
542
  case str
264
543
  when 'CREATE_FAILED' then