class Sfn::Utils::StackExporter

Stack serialization helper

Constants

DEFAULT_CHEF_ENVIRONMENT

default chef environment name

DEFAULT_EXPORT_STRUCTURE

default structure of export payload

DEFAULT_OPTIONS

default instance options

Attributes

options[R]

@return [Hash]

stack[R]

@return [Miasma::Models::Orchestration::Stack]

stack_export[R]

@return [Hash]

Public Class Methods

new(stack, options = {}) click to toggle source

Create new instance

@param stack [Miasma::Models::Orchestration::Stack] @param options [Hash] @option options [KnifeCloudformation::Provider] :provider @option options [TrueClass, FalseClass] :chef_popsicle freeze run list @option options [Array<String>] :ignored_parameters @option options [String] :chef_environment_parameter

# File lib/sfn/utils/stack_exporter.rb, line 57
def initialize(stack, options = {})
  @stack = stack
  @options = DEFAULT_OPTIONS.merge(options)
  @stack_export = Smash.new
end

Public Instance Methods

export() click to toggle source

Export stack

@return [Hash] exported stack

# File lib/sfn/utils/stack_exporter.rb, line 66
def export
  @stack_export = Smash.new(DEFAULT_EXPORT_STRUCTURE).tap do |stack_export|
    [:parameters, :capabilities, :notification_topics].each do |key|
      if val = stack.send(key)
        stack_export[:stack][key] = val
      end
    end
    stack_export[:stack][:template] = stack.template
    stack_export[:generator][:timestamp] = Time.now.to_i
    stack_export[:generator][:provider] = stack.provider.connection.provider
    if chef_popsicle? && defined?(Chef)
      freeze_runlists(stack_export)
    end
    remove_ignored_parameters(stack_export)
    stack_export[:stack][:template] = _to_json(
      stack_export[:stack][:template]
    )
  end
end
method_missing(*args) click to toggle source

Provide query methods on options hash

@param args [Object] argument list @return [Object]

Calls superclass method
# File lib/sfn/utils/stack_exporter.rb, line 90
def method_missing(*args)
  m = args.first.to_s
  if m.end_with?("?") && options.has_key?(k = m.sub("?", "").to_sym)
    !!options[k]
  else
    super
  end
end

Protected Instance Methods

allowed_cookbook_version(cookbook) click to toggle source

Find latest available cookbook version within the configured environment

@param cookbook [String] name of cookbook @return [Chef::Version]

# File lib/sfn/utils/stack_exporter.rb, line 139
def allowed_cookbook_version(cookbook)
  restriction = environment.cookbook_versions[cookbook]
  requirement = Gem::Requirement.new(restriction)
  Chef::CookbookVersion.available_versions(cookbook).detect do |v|
    requirement.satisfied_by?(Gem::Version.new(v))
  end
end
cf_join(delim, args) click to toggle source

Apply Join function

@param delim [String] join delimiter @param args [String, Hash] items to join @return [String]

# File lib/sfn/utils/stack_exporter.rb, line 263
def cf_join(delim, args)
  args.map do |arg|
    if arg.is_a?(Hash)
      cf_replace(arg)
    else
      arg.to_s
    end
  end.join(delim)
end
cf_ref(ref_name) click to toggle source

Apply Ref function

@param ref_name [Hash] @return [Object] value in parameters

# File lib/sfn/utils/stack_exporter.rb, line 249
def cf_ref(ref_name)
  if stack.parameters.has_key?(ref_name)
    stack.parameters[ref_name]
  else
    raise KeyError.new("No parameter found with given reference name (#{ref_name}). " <<
                       "Only parameter based references supported!")
  end
end
cf_replace(hsh) click to toggle source

Apply cloudformation function to data

@param hsh [Object] stack template item @return [Object]

# File lib/sfn/utils/stack_exporter.rb, line 230
def cf_replace(hsh)
  if hsh.is_a?(Hash)
    case hsh.keys.first
    when "Fn::Join"
      cf_join(*hsh.values.first)
    when "Ref"
      cf_ref(hsh.values.first)
    else
      hsh
    end
  else
    hsh
  end
end
chef_environment_name(export) click to toggle source

Environment name to use when interacting with Chef

@param export [Hash] current export state @return [String] environment name

# File lib/sfn/utils/stack_exporter.rb, line 119
def chef_environment_name(export)
  if chef_environment_parameter?
    name = export[:stack][:options][:parameters][options[:chef_environment_parameter]]
  end
  name || DEFAULT_CHEF_ENVIRONMENT
end
environment() click to toggle source

@return [Chef::Environment]

# File lib/sfn/utils/stack_exporter.rb, line 127
def environment
  unless @env
    @env = Chef::Environment.load("_default")
  end
  @env
end
extract_runlist_item(item) click to toggle source

Extract the runlist item. Fully expands roles and provides version pegged runlist.

@param item [Chef::RunList::RunListItem, Array<String>] @return [Hash] new chef configuration hash @note this will expand all roles

# File lib/sfn/utils/stack_exporter.rb, line 153
def extract_runlist_item(item)
  rl_item = item.is_a?(Chef::RunList::RunListItem) ? item : Chef::RunList::RunListItem.new(item)
  static_content = Mash.new(:run_list => [])
  if rl_item.recipe?
    cookbook, recipe = rl_item.name.split("::")
    peg_version = allowed_cookbook_version(cookbook)
    static_content[:run_list] << "recipe[#{[cookbook, recipe || "default"].join("::")}@#{peg_version}]"
  elsif rl_item.role?
    role = Chef::Role.load(rl_item.name)
    role.run_list.each do |item|
      static_content = Chef::Mixin::DeepMerge.merge(static_content, extract_runlist_item(item))
    end
    static_content = Chef::Mixin::DeepMerge.merge(
      static_content, Chef::Mixin::DeepMerge.merge(role.default_attributes, role.override_attributes)
    )
  else
    raise TypeError.new("Unknown chef run list item encountered: #{rl_item.inspect}")
  end
  static_content
end
freeze_runlists(exported) click to toggle source

Freeze chef run lists

@param exported [Hash] stack export @return [Hash]

# File lib/sfn/utils/stack_exporter.rb, line 195
def freeze_runlists(exported)
  first_runs = locate_runlists(exported)
  first_runs.each do |first_run|
    unpack_and_freeze_runlist(first_run)
  end
  exported
end
locate_runlists(thing) click to toggle source

Locate chef run lists within data collection

@param thing [Enumerable] collection from export @return [Enumerable] updated collection from export

# File lib/sfn/utils/stack_exporter.rb, line 207
def locate_runlists(thing)
  result = []
  case thing
  when Hash
    if thing["content"] && thing["content"]["run_list"]
      result << thing["content"]
    else
      thing.each do |k, v|
        result += locate_runlists(v)
      end
    end
  when Array
    thing.each do |v|
      result += locate_runlists(v)
    end
  end
  result
end
remove_ignored_parameters(export) click to toggle source

Remove parameter values from export that are configured to be ignored

@param export [Hash] stack export @return [Hash]

# File lib/sfn/utils/stack_exporter.rb, line 106
def remove_ignored_parameters(export)
  options[:ignored_parameters].each do |param|
    if export[:stack][:options][:parameters]
      export[:stack][:options][:parameters].delete(param)
    end
  end
  export
end
unpack_and_freeze_runlist(first_run) click to toggle source

Expand any detected chef run lists and freeze them within the stack template

@param first_run [Hash] chef first run hash @return [Hash]

# File lib/sfn/utils/stack_exporter.rb, line 179
def unpack_and_freeze_runlist(first_run)
  extracted_runlists = first_run["run_list"].map do |item|
    extract_runlist_item(cf_replace(item))
  end
  first_run.delete("run_list")
  first_run.replace(
    extracted_runlists.inject(first_run) do |memo, first_run_item|
      Chef::Mixin::DeepMerge.merge(memo, first_run_item)
    end
  )
end