Class: CFnDK::Stack

Inherits:
Object
  • Object
show all
Defined in:
lib/cfndk/stack.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, data, option, global_config, credentials) ⇒ Stack

Returns a new instance of Stack.



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/cfndk/stack.rb', line 4

def initialize(name, data, option, global_config, credentials)
  @global_config = global_config
  @name = name
  @template_file = data['template_file'] || ''
  @parameter_input = data['parameter_input'] || ''
  @capabilities = data['capabilities'] || []
  @depends = data['depends'] || []
  @region = data['region'] || @global_config.region
  @role_arn = @global_config.role_arn
  @package = data['package'] || @global_config.package
  @pre_command = data['pre_command'] || nil
  @post_command = data['post_command'] || nil
  @enabled = true
  @enabled = false if data['enabled'] === false 
  @timeout_in_minutes = data['timeout_in_minutes'] || @global_config.timeout_in_minutes
  @override_parameters = data['parameters'] || {}
  @option = option
  @client = Aws::CloudFormation::Client.new(credentials: credentials, region: @region)
  @s3_client = Aws::S3::Client.new(credentials: credentials, region: @region)
  @sts_client = Aws::STS::Client.new(credentials: credentials, region: @region)
  @tp = CFnDK::TemplatePackager.new(@template_file, @region, @package, @global_config, @s3_client, @sts_client)
end

Instance Attribute Details

#capabilitiesObject (readonly)

Returns the value of attribute capabilities.



3
4
5
# File 'lib/cfndk/stack.rb', line 3

def capabilities
  @capabilities
end

#dependsObject (readonly)

Returns the value of attribute depends.



3
4
5
# File 'lib/cfndk/stack.rb', line 3

def depends
  @depends
end

#enabledObject (readonly)

Returns the value of attribute enabled.



3
4
5
# File 'lib/cfndk/stack.rb', line 3

def enabled
  @enabled
end

#packageObject (readonly)

Returns the value of attribute package.



3
4
5
# File 'lib/cfndk/stack.rb', line 3

def package
  @package
end

#parameter_inputObject (readonly)

Returns the value of attribute parameter_input.



3
4
5
# File 'lib/cfndk/stack.rb', line 3

def parameter_input
  @parameter_input
end

#post_commandObject (readonly)

Returns the value of attribute post_command.



3
4
5
# File 'lib/cfndk/stack.rb', line 3

def post_command
  @post_command
end

#pre_commandObject (readonly)

Returns the value of attribute pre_command.



3
4
5
# File 'lib/cfndk/stack.rb', line 3

def pre_command
  @pre_command
end

#regionObject (readonly)

Returns the value of attribute region.



3
4
5
# File 'lib/cfndk/stack.rb', line 3

def region
  @region
end

#role_arnObject (readonly)

Returns the value of attribute role_arn.



3
4
5
# File 'lib/cfndk/stack.rb', line 3

def role_arn
  @role_arn
end

#template_fileObject (readonly)

Returns the value of attribute template_file.



3
4
5
# File 'lib/cfndk/stack.rb', line 3

def template_file
  @template_file
end

#timeout_in_minutesObject (readonly)

Returns the value of attribute timeout_in_minutes.



3
4
5
# File 'lib/cfndk/stack.rb', line 3

def timeout_in_minutes
  @timeout_in_minutes
end

Instance Method Details

#available_change_set?Boolean

Returns:

  • (Boolean)


382
383
384
385
386
387
388
389
390
391
# File 'lib/cfndk/stack.rb', line 382

def available_change_set?
  resp = @client.describe_change_set(
    change_set_name: change_set_name,
    stack_name: name
  )
  return true if resp.execution_status == 'AVAILABLE'
  false
rescue Aws::CloudFormation::Errors::ChangeSetNotFound
  false
end

#change_set_nameObject



503
504
505
# File 'lib/cfndk/stack.rb', line 503

def change_set_name
  [@name, @option[:change_set_uuid]].compact.join('-')
end

#createObject



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/cfndk/stack.rb', line 27

def create
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  CFnDK.logger.info(('creating stack: ' + name).color(:green))
  CFnDK.logger.debug('Name        :' + name)
  CFnDK.logger.debug('Parametres  :' + parameters.inspect)
  CFnDK.logger.debug('Capabilities:' + capabilities.inspect)
  CFnDK.logger.debug('Timeout     :' + timeout_in_minutes.to_s)
  CFnDK.logger.debug('Region      :' + region)
  tags = [
    {
      key: 'origina_name',
      value: @name,
    },
  ]
  tags.push(
    key: 'UUID',
    value: @option[:uuid]
  ) if @option[:uuid]
  hash = {
    stack_name: name,
    parameters: parameters,
    capabilities: capabilities,
    timeout_in_minutes: timeout_in_minutes,
    tags: tags,
  }
  hash[:role_arn] = @role_arn if @role_arn

  if @tp.large_template?
    hash[:template_url] = @tp.upload_template_file()
  else
    hash[:template_body] = @tp.template_body()
  end
  @client.create_stack(
    hash
  )
end

#create_change_setObject



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/cfndk/stack.rb', line 170

def create_change_set
  return nil if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  CFnDK.logger.info(('creating change set: ' + change_set_name).color(:green))
  CFnDK.logger.debug('Parametres  :' + parameters.inspect)
  CFnDK.logger.debug('Capabilities:' + capabilities.inspect)
  CFnDK.logger.debug('Region      :' + region)
  tags = [
    {
      key: 'origina_name',
      value: @name,
    },
  ]
  tags.push(
    key: 'UUID',
    value: @option[:uuid]
  ) if @option[:uuid]
  tags.push(
    key: 'CHANGE_SET_UUID',
    value: @option[:change_set_uuid]
  ) if @option[:change_set_uuid]
  hash = {
    stack_name: name,
    parameters: parameters,
    capabilities: capabilities,
    change_set_name: change_set_name,
    change_set_type: exits? ? 'UPDATE' : 'CREATE',
    tags: tags,
  }
  hash[:role_arn] = @role_arn if @role_arn
  if @tp.large_template?
    hash[:template_url] = @tp.upload_template_file()
  else
    hash[:template_body] = @tp.template_body()
  end
  @client.create_change_set(
    hash
  )
  @name
rescue Aws::CloudFormation::Errors::ValidationError => ex
  if review_in_progress?
    CFnDK.logger.warn("failed create change set because the stack on REVIEW_IN_PROGRESS already exist : #{change_set_name}".color(:orange))
    nil
  else
    CFnDK.logger.error("failed create change set: #{change_set_name}".color(:red))
    raise ex
  end
end

#created?Boolean

Returns:

  • (Boolean)


362
363
364
365
366
367
368
369
370
# File 'lib/cfndk/stack.rb', line 362

def created?
  resp = @client.describe_stacks(
    stack_name: name
  )
  return false if resp.stacks[0].stack_status == 'REVIEW_IN_PROGRESS'
  true
rescue Aws::CloudFormation::Errors::ValidationError
  false
end

#delete_change_setObject



262
263
264
265
266
267
268
269
270
271
# File 'lib/cfndk/stack.rb', line 262

def delete_change_set
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  CFnDK.logger.info(('deleting change set: ' + change_set_name).color(:green))
  @client.delete_change_set(
    stack_name: name,
    change_set_name: change_set_name
  )
  CFnDK.logger.info(('deleted change set: ' + change_set_name).color(:green))
end

#destroyObject



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/cfndk/stack.rb', line 136

def destroy
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  if exits?
    CFnDK.logger.info(('deleting stack: ' + name).color(:green))
    CFnDK.logger.debug('Name        :' + name)
    CFnDK.logger.debug('Region      :' + region)
    hash = {
      stack_name: name,
    }
    hash[:role_arn] = @role_arn if @role_arn
    @client.delete_stack(
      hash
    )
  else
    CFnDK.logger.info(('do not delete stack: ' + name).color(:red))
  end
end

#execute_change_setObject



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/cfndk/stack.rb', line 245

def execute_change_set
  return nil if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  if available_change_set?
    CFnDK.logger.info(('executing change set: ' + change_set_name).color(:green))
    @client.execute_change_set(
      stack_name: name,
      change_set_name: change_set_name
    )
    CFnDK.logger.info(('execute change set: ' + change_set_name).color(:green))
    @name
  else
    CFnDK.logger.warn("failed execute change set because this change set is not AVAILABLE: #{change_set_name}".color(:orange))
    nil
  end
end

#exits?Boolean

Returns:

  • (Boolean)


353
354
355
356
357
358
359
360
# File 'lib/cfndk/stack.rb', line 353

def exits?
  @client.describe_stacks(
    stack_name: name
  )
  true
rescue Aws::CloudFormation::Errors::ValidationError
  false
end

#nameObject



499
500
501
# File 'lib/cfndk/stack.rb', line 499

def name
  [@name, @option[:uuid]].compact.join('-')
end

#parametersObject



507
508
509
510
511
512
513
514
515
516
# File 'lib/cfndk/stack.rb', line 507

def parameters
  json = JSON.load(open(@parameter_input).read)
  json['Parameters'].map do |item|
    next if item.empty?
    {
      parameter_key: item['ParameterKey'],
      parameter_value: eval_override_parameter(item['ParameterKey'], item['ParameterValue']),
    }
  end.compact
end

#post_command_executeObject



532
533
534
535
536
537
538
539
540
541
542
543
544
# File 'lib/cfndk/stack.rb', line 532

def post_command_execute
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  if @post_command
    CFnDK.logger.info(('execute post command: ' + @post_command).color(:green))
    IO.popen(@post_command, :err => [:child, :out]) do |io|
      io.each_line do |line|
        CFnDK.logger.info((line).color(:green))
      end
    end
    raise 'post command is error. status: ' + $?.exitstatus.to_s + ' command: ' + @post_command if $?.exitstatus != 0
  end
end

#pre_command_executeObject



518
519
520
521
522
523
524
525
526
527
528
529
530
# File 'lib/cfndk/stack.rb', line 518

def pre_command_execute
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  if @pre_command
    CFnDK.logger.info(('execute pre command: ' + @pre_command).color(:green))
    IO.popen(@pre_command, :err => [:child, :out]) do |io|
      io.each_line do |line|
        CFnDK.logger.info((line).color(:green))
      end
    end
    raise 'pre command is error. status: ' + $?.exitstatus.to_s + ' command: ' + @pre_command if $?.exitstatus != 0
  end
end

#reportObject



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/cfndk/stack.rb', line 393

def report
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  CFnDK.logger.info('*****************************************************'.color(:green))
  CFnDK.logger.info(('stack: ' + name).color(:green))
  CFnDK.logger.info('*****************************************************'.color(:green))
  CFnDK.logger.info('')
  begin
    resp = @client.describe_stacks(
      stack_name: name
    ).stacks[0]
    CFnDK.logger.info('Status: '.color(:green) + colored_status(resp.stack_status))
    CFnDK.logger.info('Reason: '.color(:green) + resp.stack_status_reason) if resp.stack_status_reason
    if @option[:types].instance_of?(Array) && @option[:types].include?('tag')
      CFnDK.logger.info('Tags:'.color(:green))
      tags_rows = resp.tags.map do |item|
        [
          item.key,
          item.value,
        ]
      end
      unless tags_rows.empty?
        table = Terminal::Table.new headings: %w(Key Value), rows: tags_rows
        CFnDK.logger.info table
      end
    end
    if @option[:types].instance_of?(Array) && @option[:types].include?('parameter')
      CFnDK.logger.info('Parameters:'.color(:green))
      parameter_rows = resp.parameters.map do |item|
        [
          item.parameter_key,
          item.parameter_value,
          item.use_previous_value,
          item.resolved_value,
        ]
      end
      unless parameter_rows.empty?
        table = Terminal::Table.new headings: ['Key', 'Value', 'Use Previous Value', 'Resolved Value'], rows: parameter_rows
        CFnDK.logger.info table
      end
    end
    if @option[:types].instance_of?(Array) && @option[:types].include?('output')
      CFnDK.logger.info('Outputs:'.color(:green))
      output_rows = resp.outputs.map do |item|
        [
          item.output_key,
          item.output_value,
          item.export_name,
          item.description,
        ]
      end
      unless output_rows.empty?
        table = Terminal::Table.new headings: ['Key', 'Value', 'Export Name', 'Description'], rows: output_rows
        CFnDK.logger.info table
      end
    end
  rescue Aws::CloudFormation::Errors::ValidationError => ex
    CFnDK.logger.warn "#{ex.class}: #{ex.message}".color(:red)
  end
  if @option[:types].instance_of?(Array) && @option[:types].include?('resource')
    begin
      CFnDK.logger.info('Resources:'.color(:green))
      rows = @client.describe_stack_resources(
        stack_name: name
      ).stack_resources.map do |item|
        [
          item.logical_resource_id,
          item.physical_resource_id,
          item.resource_type,
          item.timestamp,
          colored_status(item.resource_status),
          item.resource_status_reason,
          item.description,
        ]
      end
      unless rows.empty?
        table = Terminal::Table.new headings: %w(Logical Physical Type Timestamp Status Reason Desc), rows: rows
        CFnDK.logger.info table
      end
    rescue Aws::CloudFormation::Errors::ValidationError => ex
      CFnDK.logger.warn "#{ex.class}: #{ex.message}".color(:red)
    end
  end
  if @option[:types].instance_of?(Array) && @option[:types].include?('event')
    CFnDK.logger.info('Events:'.color(:green))
    begin
      rows = @client.describe_stack_events(
        stack_name: name
      ).stack_events.map do |item|
        [
          item.resource_type,
          item.timestamp,
          colored_status(item.resource_status),
          item.resource_status_reason,
        ]
      end
      unless rows.empty?
        table = Terminal::Table.new headings: %w(Type Time Status Reason), rows: rows
        CFnDK.logger.info table
      end
    rescue Aws::CloudFormation::Errors::ValidationError => ex
      CFnDK.logger.warn "#{ex.class}: #{ex.message}".color(:red)
    end
  end
end

#report_change_setObject



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/cfndk/stack.rb', line 273

def report_change_set
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  CFnDK.logger.info('*****************************************************'.color(:green))
  CFnDK.logger.info(('change set: ' + change_set_name).color(:green))
  CFnDK.logger.info('*****************************************************'.color(:green))
  CFnDK.logger.info('')
  resp = @client.describe_change_set(
    change_set_name: change_set_name,
    stack_name: name
  )
  CFnDK.logger.info('Execution Status: '.color(:green) + colored_status(resp.execution_status))
  CFnDK.logger.info('Status:           '.color(:green) + colored_status(resp.status))
  CFnDK.logger.info('Reason:           '.color(:green) + resp.status_reason) if resp.status_reason
  if @option[:types].instance_of?(Array) && @option[:types].include?('tag')
    CFnDK.logger.info('Tags:'.color(:green))
    tags_rows = resp.tags.map do |item|
      [
        item.key,
        item.value,
      ]
    end
    unless tags_rows.empty?
      table = Terminal::Table.new headings: %w(Key Value), rows: tags_rows
      CFnDK.logger.info table
    end
  end
  if @option[:types].instance_of?(Array) && @option[:types].include?('parameter')
    CFnDK.logger.info('Parameters:'.color(:green))
    parameter_rows = resp.parameters.map do |item|
      [
        item.parameter_key,
        item.parameter_value,
        item.use_previous_value,
        item.resolved_value,
      ]
    end
    unless parameter_rows.empty?
      table = Terminal::Table.new headings: ['Key', 'Value', 'Use Previous Value', 'Resolved Value'], rows: parameter_rows
      CFnDK.logger.info table
    end
  end
  if @option[:types].instance_of?(Array) && @option[:types].include?('changes')
    CFnDK.logger.info('Changes:'.color(:green))
    changes_rows = resp.changes.map do |item|
      [
        item.resource_change.action,
        item.resource_change.logical_resource_id,
        item.resource_change.physical_resource_id,
        item.resource_change.resource_type,
        item.resource_change.replacement,
      ]
    end
    unless changes_rows.empty?
      table = Terminal::Table.new headings: %w(Action Logical Physical Type Replacement), rows: changes_rows
      CFnDK.logger.info table
    end
  end
rescue Aws::CloudFormation::Errors::ValidationError => ex
  CFnDK.logger.warn "#{ex.class}: #{ex.message}".color(:red)
rescue Aws::CloudFormation::Errors::ChangeSetNotFound => ex
  CFnDK.logger.warn "#{ex.class}: #{ex.message}".color(:red)
end

#review_in_progress?Boolean

Returns:

  • (Boolean)


372
373
374
375
376
377
378
379
380
# File 'lib/cfndk/stack.rb', line 372

def review_in_progress?
  resp = @client.describe_stacks(
    stack_name: name
  )
  return true if resp.stacks[0].stack_status == 'REVIEW_IN_PROGRESS'
  false
rescue Aws::CloudFormation::Errors::ValidationError
  false
end

#updateObject



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/cfndk/stack.rb', line 86

def update
  return false if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  CFnDK.logger.info(('updating stack: ' + name).color(:green))
  CFnDK.logger.debug('Name        :' + name)
  CFnDK.logger.debug('Parametres  :' + parameters.inspect)
  CFnDK.logger.debug('Capabilities:' + capabilities.inspect)
  CFnDK.logger.debug('Timeout     :' + timeout_in_minutes.to_s)
  CFnDK.logger.debug('Region      :' + region)
  begin
    hash = {
      stack_name: name,
      parameters: parameters,
      capabilities: capabilities,
    }
    hash[:role_arn] = @role_arn if @role_arn
    if @tp.large_template?
      hash[:template_url] = @tp.upload_template_file()
    else
      hash[:template_body] = @tp.template_body()
    end
    @client.update_stack(
      hash
    )
    true
  rescue Aws::CloudFormation::Errors::ValidationError => ex
    case ex.message
    when 'No updates are to be performed.'
      CFnDK.logger.warn "#{ex.message}: #{name}".color(:red)
      false
    else
      raise ex
    end
  end
end

#validateObject



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/cfndk/stack.rb', line 337

def validate
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  CFnDK.logger.info(('validate stack: ' + name).color(:green))
  CFnDK.logger.debug('Name        :' + @name)
  hash = {}
  if @tp.large_template?
    hash[:template_url] = @tp.upload_template_file()
  else
    hash[:template_body] = @tp.template_body()
  end
  @client.validate_template(
    hash
  )
end

#wait_until_createObject



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/cfndk/stack.rb', line 65

def wait_until_create
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  CFnDK.logger.info(('waiting create stack: ' + name).color(:green))
  begin
    @client.wait_until(
      :stack_create_complete,
      stack_name: name
    ) do |w|
      w.max_attempts = 360
      w.delay = 10
    end
    CFnDK.logger.info(('created stack: ' + name).color(:green))
  rescue Aws::Waiters::Errors::FailureStateError => ex
    CFnDK.logger.error "#{ex.class}: #{ex.message}".color(:red)
    @option[:type] = %w(tag output parameter resource event)
    report
    raise ex
  end
end

#wait_until_create_change_setObject



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/cfndk/stack.rb', line 219

def wait_until_create_change_set
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  return unless exits?
  CFnDK.logger.info(('waiting create change set: ' + change_set_name).color(:green))
  @client.wait_until(
    :change_set_create_complete,
    stack_name: name,
    change_set_name: change_set_name
  ) do |w|
    w.max_attempts = 360
    w.delay = 10
  end
  CFnDK.logger.info("created change set: #{change_set_name}".color(:green))
rescue Aws::Waiters::Errors::FailureStateError => ex
  case ex.message
  when 'stopped waiting, encountered a failure state'
    unless available_change_set?
      delete_change_set
      CFnDK.logger.warn("failed create change set because this change set is UNAVAILABLE: #{change_set_name}".color(:orange))
      return
    end
  end
  raise ex
end

#wait_until_destroyObject



155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/cfndk/stack.rb', line 155

def wait_until_destroy
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  return unless exits?
  CFnDK.logger.info(('waiting delete stack: ' + name).color(:green))
  @client.wait_until(
    :stack_delete_complete,
    stack_name: name
  ) do |w|
    w.max_attempts = 360
    w.delay = 10
  end
  CFnDK.logger.info(('deleted stack: ' + name).color(:green))
end

#wait_until_updateObject



122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/cfndk/stack.rb', line 122

def wait_until_update
  return if @option[:stack_names].instance_of?(Array) && !@option[:stack_names].include?(@name)
  return unless @enabled
  CFnDK.logger.info(('waiting update stack: ' + name).color(:green))
  @client.wait_until(
    :stack_update_complete,
    stack_name: name
  ) do |w|
    w.max_attempts = 360
    w.delay = 10
  end
  CFnDK.logger.info(('updated stack: ' + name).color(:green))
end