module Stacker
Attributes
credentials[R]
region[R]
Public Instance Methods
all_stack_names()
click to toggle source
# File lib/autostacker24/stacker.rb, line 159 def all_stack_names all_stacks.map{|s| s.stack_name} end
all_stacks()
click to toggle source
# File lib/autostacker24/stacker.rb, line 163 def all_stacks cloud_formation.describe_stacks.stacks end
cloud_formation()
click to toggle source
# File lib/autostacker24/stacker.rb, line 205 def cloud_formation # lazy CloudFormation client unless @lazy_cloud_formation params = @cloud_formation_params || {} params[:credentials] = @credentials if @credentials params[:region] = @region if @region params[:retry_limit] = 10 @lazy_cloud_formation = Aws::CloudFormation::Client.new(params) end @lazy_cloud_formation end
cloud_formation_params=(params)
click to toggle source
set custom cloudformation parameters
# File lib/autostacker24/stacker.rb, line 32 def cloud_formation_params=(params) unless params == @cloud_formation_params @lazy_cloud_formation = nil @cloud_formation_params = params end end
create_or_update_stack(stack_name, template, parameters, parent_stack_name = nil, tags = nil, timeout_in_minutes = DEFAULT_TIMEOUT, role_arn: nil)
click to toggle source
# File lib/autostacker24/stacker.rb, line 39 def create_or_update_stack(stack_name, template, parameters, parent_stack_name = nil, tags = nil, timeout_in_minutes = DEFAULT_TIMEOUT, role_arn: nil) if find_stack(stack_name).nil? create_stack(stack_name, template, parameters, parent_stack_name, tags, timeout_in_minutes, role_arn: role_arn) else update_stack(stack_name, template, parameters, parent_stack_name, tags, timeout_in_minutes, role_arn: role_arn) end end
create_stack(stack_name, template, parameters, parent_stack_name = nil, tags = nil, timeout_in_minutes = DEFAULT_TIMEOUT, role_arn: nil)
click to toggle source
# File lib/autostacker24/stacker.rb, line 47 def create_stack(stack_name, template, parameters, parent_stack_name = nil, tags = nil, timeout_in_minutes = DEFAULT_TIMEOUT, role_arn: nil) merge_and_validate(template, parameters, parent_stack_name) cloud_formation.create_stack(stack_name: stack_name, template_body: template_body(template), on_failure: 'DELETE', parameters: transform_params(parameters), capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], role_arn: role_arn, tags: transform_tags(tags)) wait_for_stack(stack_name, :create, Set.new, timeout_in_minutes) end
credentials=(credentials)
click to toggle source
delete_stack(stack_name, timeout_in_minutes = 60, role_arn = nil)
click to toggle source
# File lib/autostacker24/stacker.rb, line 116 def delete_stack(stack_name, timeout_in_minutes = 60, role_arn = nil) seen_events = get_stack_events(stack_name).map {|e| e[:event_id]} cloud_formation.delete_stack(stack_name: stack_name, role_arn: role_arn) wait_for_stack(stack_name, :delete, seen_events, timeout_in_minutes) end
estimate_template_cost(template, parameters, parent_stack_name = nil)
click to toggle source
# File lib/autostacker24/stacker.rb, line 167 def estimate_template_cost(template, parameters, parent_stack_name = nil) merge_and_validate(template, parameters, parent_stack_name) cloud_formation.estimate_template_cost(:template_body => template_body(template), :parameters => transform_params(parameters)) end
find_stack(stack_name)
click to toggle source
# File lib/autostacker24/stacker.rb, line 152 def 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_events(stack_name_or_id)
click to toggle source
# File lib/autostacker24/stacker.rb, line 201 def get_stack_events(stack_name_or_id) cloud_formation.describe_stack_events(stack_name: stack_name_or_id).data.stack_events end
get_stack_output(stack_name)
click to toggle source
# File lib/autostacker24/stacker.rb, line 176 def get_stack_output(stack_name) stack = find_stack(stack_name) raise "stack #{stack_name} not found" unless stack transform_output(stack.outputs).freeze end
get_stack_outputs(stack_name)
click to toggle source
# File lib/autostacker24/stacker.rb, line 172 def get_stack_outputs(stack_name) get_stack_output(stack_name) end
get_stack_resources(stack_name)
click to toggle source
# File lib/autostacker24/stacker.rb, line 196 def 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
get_template(stack_name)
click to toggle source
# File lib/autostacker24/stacker.rb, line 148 def get_template(stack_name) cloud_formation.get_template(stack_name: stack_name).template_body end
list_stacks()
click to toggle source
# File lib/autostacker24/stacker.rb, line 78 def list_stacks() next_token = nil loop do res = cloud_formation.list_stacks(next_token: next_token) res.stack_summaries.each { |summary| print_stack_summary(summary) } next_token = res.next_token break if next_token.nil? end end
print_stack_summary(summary)
click to toggle source
# File lib/autostacker24/stacker.rb, line 89 def print_stack_summary(summary) change_date = summary.last_updated_time ? summary.last_updated_time : summary.creation_time puts "#{summary.stack_name}\t#{summary.stack_status}\t#{change_date}" unless summary.stack_status == 'DELETE_COMPLETE' end
region=(region)
click to toggle source
# File lib/autostacker24/stacker.rb, line 24 def region=(region) # use ENV['AWS_REGION'] or ENV['AWS_DEFAULT_REGION'] unless region == @region @lazy_cloud_formation = nil @region = region end end
template_body(template)
click to toggle source
# File lib/autostacker24/stacker.rb, line 216 def template_body(template) wd = Dir.getwd() if File.exists?(template) # template is a valid filename td = File.dirname(template) template = File.read(template) Dir.chdir(td) end AutoStacker24::Preprocessor.preprocess(template) ensure Dir.chdir(wd) end
transform_output(output)
click to toggle source
# File lib/autostacker24/stacker.rb, line 182 def transform_output(output) output.inject({}) { |m, o| m.merge(o.output_key.to_sym => o.output_value) } end
transform_params(input)
click to toggle source
# File lib/autostacker24/stacker.rb, line 186 def transform_params(input) input.each{|k,v| raise "#{k} must not be nil" if v.nil? } input.inject([]) { |m, kv| m << {parameter_key: kv[0].to_s, parameter_value: kv[1].to_s} } end
update_stack(stack_name, template, parameters, parent_stack_name = nil, tags = nil, timeout_in_minutes = DEFAULT_TIMEOUT, role_arn: nil)
click to toggle source
# File lib/autostacker24/stacker.rb, line 59 def update_stack(stack_name, template, parameters, parent_stack_name = nil, tags = nil, timeout_in_minutes = DEFAULT_TIMEOUT, role_arn: nil) seen_events = get_stack_events(stack_name).map {|e| e[:event_id]} begin merge_and_validate(template, parameters, parent_stack_name) cloud_formation.update_stack(stack_name: stack_name, template_body: template_body(template), parameters: transform_params(parameters), capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], role_arn: role_arn, tags: transform_tags(tags)) 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 puts "stack #{stack_name} is already up to date" find_stack(stack_name) else wait_for_stack(stack_name, :update, seen_events, timeout_in_minutes) end end
validate_template(template)
click to toggle source
# File lib/autostacker24/stacker.rb, line 112 def validate_template(template) cloud_formation.validate_template(template_body: template_body(template)) end
wait_for_stack(stack_name, operation, seen_events = Set.new, timeout_in_minutes = DEFAULT_TIMEOUT)
click to toggle source
# File lib/autostacker24/stacker.rb, line 122 def wait_for_stack(stack_name, operation, seen_events = Set.new, timeout_in_minutes = DEFAULT_TIMEOUT) stop_time = Time.now + timeout_in_minutes * 60 finished = /(CREATE_COMPLETE|UPDATE_COMPLETE|DELETE_COMPLETE|ROLLBACK_COMPLETE|ROLLBACK_FAILED|CREATE_FAILED|DELETE_FAILED)$/ puts "waiting for #{operation} stack #{stack_name}" stack_id = find_stack(stack_name)[:stack_id] while Time.now < stop_time sleep(10) 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 new_events = get_stack_events(stack_id).select{|e| !seen_events.include?(e[:event_id])}.sort_by{|e| e[:timestamp]} new_events.each do |e| seen_events << e[:event_id] puts "#{e[:timestamp]}\t#{e[:resource_status].ljust(20)}\t#{e[:resource_type].ljust(40)}\t#{e[:logical_resource_id].ljust(30)}\t#{e[:resource_status_reason]}" end return true if status =~ expected_status raise "#{operation} #{stack_name} failed, current status #{status}" if status =~ finished end raise "waiting for #{operation} stack #{stack_name} timed out after #{timeout_in_minutes} minutes" end
Private Instance Methods
merge_and_validate(template, parameters, stack_name)
click to toggle source
if stack_name is given assign read the output parameters and copy them to the given template parameters if a aparameter is already defined, it will not be overwritten finally, if mandatory parameters are missing, an error will be raised
# File lib/autostacker24/stacker.rb, line 97 def merge_and_validate(template, parameters, stack_name) valid = validate_template(template).parameters if stack_name present = valid.map{|p| p.parameter_key.to_sym} get_stack_output(stack_name).each do |key, value| parameters[key] ||= value if present.include?(key) end end mandatory = valid.select{|p| p.default_value.nil?}.map{|p| p.parameter_key.to_sym} diff = mandatory.to_set - parameters.keys.to_set raise "Missing one ore more mandatory parameters: #{diff.to_a.join(', ')}" if diff.length > 0 parameters end