Module: Stacker

Defined in:
lib/autostacker24/stacker.rb

Class Method Summary collapse

Class Method Details

.cloud_formationObject

lazy CloudFormation client



90
91
92
# File 'lib/autostacker24/stacker.rb', line 90

def self.cloud_formation # lazy CloudFormation client
  @lazy_cloud_formation ||= Aws::CloudFormation::Client.new(region: ENV['AWS_DEFAULT_REGION'] || 'eu-west-1')
end

.create_or_update_stack(stack_name, template_body, parameters) ⇒ Object



5
6
7
8
9
10
11
# File 'lib/autostacker24/stacker.rb', line 5

def self.create_or_update_stack(stack_name, template_body, parameters)
  if find_stack(stack_name).nil?
    create_stack(stack_name, template_body, parameters)
  else
    update_stack(stack_name, template_body, parameters)
  end
end

.create_stack(stack_name, template_body, parameters) ⇒ Object



13
14
15
16
17
18
19
20
# File 'lib/autostacker24/stacker.rb', line 13

def self.create_stack(stack_name, template_body, parameters)
  cloud_formation.create_stack(stack_name:    stack_name,
                               template_body: template_body,
                               on_failure:    'DELETE',
                               parameters:    transform_parameters(parameters),
                               capabilities:  ['CAPABILITY_IAM'])
  wait_for_stack(stack_name, :create)
end

.delete_stack(stack_name) ⇒ Object



36
37
38
39
# File 'lib/autostacker24/stacker.rb', line 36

def self.delete_stack(stack_name)
  cloud_formation.delete_stack(stack_name: stack_name)
  wait_for_stack(stack_name, :delete)
end

.estimate_template_cost(template_body, parameters) ⇒ Object



67
68
69
# File 'lib/autostacker24/stacker.rb', line 67

def self.estimate_template_cost(template_body, parameters)
  cloud_formation.estimate_template_cost(:template_body => template_body, :parameters => transform_parameters(parameters))
end

.find_stack(stack_name) ⇒ Object



60
61
62
63
64
65
# File 'lib/autostacker24/stacker.rb', line 60

def self.find_stack(stack_name)
  cloud_formation.describe_stacks(stack_name: stack_name).stacks.first
rescue Aws::CloudFormation::Errors::ValidationError => error
  raise error unless error.message =~ /does not exist/i # may be flaky, do more research in API
  nil
end

.get_stack_outputs(stack_name, hash = {}) ⇒ Object



71
72
73
74
75
# File 'lib/autostacker24/stacker.rb', line 71

def self.get_stack_outputs(stack_name, hash = {})
  stack = find_stack(stack_name)
  fail "stack #{stack_name} not found" unless stack
  transform_outputs(stack.outputs, hash).freeze
end

.get_stack_resources(stack_name) ⇒ Object



85
86
87
88
# File 'lib/autostacker24/stacker.rb', line 85

def self.get_stack_resources(stack_name)
  resources = cloud_formation.describe_stack_resources(stack_name: stack_name).data.stack_resources
  resources.inject({}){|map, resource| map.merge(resource.logical_resource_id.to_sym => resource)}.freeze
end

.transform_outputs(outputs, existing_outputs = {}) ⇒ Object



77
78
79
# File 'lib/autostacker24/stacker.rb', line 77

def self.transform_outputs(outputs, existing_outputs = {})
  outputs.inject(existing_outputs) { |m, o| m.merge(o.output_key.to_sym => o.output_value) }
end

.transform_parameters(parameters) ⇒ Object



81
82
83
# File 'lib/autostacker24/stacker.rb', line 81

def self.transform_parameters(parameters)
  parameters.inject([]) { |m, kv| m << {parameter_key: kv[0].to_s, parameter_value: kv[1].to_s} }
end

.update_stack(stack_name, template_body, parameters) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/autostacker24/stacker.rb', line 22

def self.update_stack(stack_name, template_body, parameters)
  begin
    cloud_formation.update_stack(stack_name:    stack_name,
                                 template_body: template_body,
                                 parameters:    transform_parameters(parameters),
                                 capabilities:  ['CAPABILITY_IAM'])
  rescue Aws::CloudFormation::Errors::ValidationError => error
    raise error unless error.message =~ /No updates are to be performed/i # may be flaky, do more research in API
    find_stack(stack_name)
  else
    wait_for_stack(stack_name, :update)
  end
end

.wait_for_stack(stack_name, operation, timeout_in_minutes = 15) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/autostacker24/stacker.rb', line 41

def self.wait_for_stack(stack_name, operation, timeout_in_minutes = 15)
  stop_time = Time.now + timeout_in_minutes * 60
  finished = /(CREATE_COMPLETE|UPDATE_COMPLETE|DELETE_COMPLETE|ROLLBACK_COMPLETE|ROLLBACK_FAILED|CREATE_FAILED|DELETE_FAILED)$/
  while Time.now < stop_time
    stack = find_stack(stack_name)
    status = stack ? stack.stack_status : 'DELETE_COMPLETE'
    expected_status = case operation
                        when :create then /CREATE_COMPLETE$/
                        when :update then /UPDATE_COMPLETE$/
                        when :delete then /DELETE_COMPLETE$/
                      end
    return true if status =~ expected_status
    fail "#{stack_name} failed, current status #{status}" if status =~ finished
    puts "waiting for #{stack_name}, current status #{status}"
    sleep(7)
  end
  fail "waiting for stack timeout after #{timeout_in_minutes} minutes"
end