class Chef::PolicyBuilder::ExpandNodeObject

ExpandNodeObject is the “classic” policy builder implementation. It expands the run_list on a node object and then queries the chef-server to find the correct set of cookbooks, given version constraints of the node’s environment.

Note that this class should only be used via PolicyBuilder::Dynamic and not instantiated directly.

Attributes

events[R]
json_attribs[R]
node[R]
node_name[R]
ohai_data[R]
override_runlist[R]
run_context[R]
run_list_expansion[R]

Public Class Methods

new(node_name, ohai_data, json_attribs, override_runlist, events) click to toggle source
# File lib/chef/policy_builder/expand_node_object.rb, line 50
def initialize(node_name, ohai_data, json_attribs, override_runlist, events)
  @node_name = node_name
  @ohai_data = ohai_data
  @json_attribs = json_attribs
  @override_runlist = override_runlist
  @events = events

  @node = nil
  @run_list_expansion = nil
end

Public Instance Methods

api_service() click to toggle source
# File lib/chef/policy_builder/expand_node_object.rb, line 250
def api_service
  @api_service ||= Chef::ServerAPI.new(config[:chef_server_url], version_class: Chef::CookbookManifestVersions )
end
build_node() click to toggle source

Applies environment, external JSON attributes, and override run list to the node, Then expands the run_list.

Returns

node<Chef::Node>

The modified node object. node is modified in place.

# File lib/chef/policy_builder/expand_node_object.rb, line 124
def build_node
  # Allow user to override the environment of a node by specifying
  # a config parameter.
  if Chef::Config[:environment] && !Chef::Config[:environment].chomp.empty?
    node.chef_environment(Chef::Config[:environment])
  end

  # consume_external_attrs may add items to the run_list. Save the
  # expanded run_list, which we will pass to the server later to
  # determine which versions of cookbooks to use.
  node.reset_defaults_and_overrides
  node.consume_external_attrs(ohai_data, @json_attribs)

  setup_run_list_override

  expand_run_list

  Chef::Log.info("Run List is [#{node.run_list}]")
  Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(", ")}]")

  events.node_load_completed(node, @expanded_run_list_with_versions, Chef::Config)
  events.run_list_expanded(@run_list_expansion)

  node
end
config() click to toggle source
# File lib/chef/policy_builder/expand_node_object.rb, line 254
def config
  Chef::Config
end
expand_run_list() click to toggle source

Expands the node’s run list. Stores the run_list_expansion object for later use.

# File lib/chef/policy_builder/expand_node_object.rb, line 151
def expand_run_list
  @run_list_expansion = if Chef::Config[:solo_legacy_mode]
                          node.expand!("disk")
                        else
                          node.expand!("server")
                        end

  # @run_list_expansion is a RunListExpansion.
  #
  # Convert @expanded_run_list, which is an
  # Array of Hashes of the form
  #   {:name => NAME, :version_constraint => Chef::VersionConstraint },
  # into @expanded_run_list_with_versions, an
  # Array of Strings of the form
  #   "#{NAME}@#{VERSION}"
  @expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings
  @run_list_expansion
rescue Exception => e
  # TODO: wrap/munge exception with useful error output.
  events.run_list_expand_failed(node, e)
  raise
end
finish_load_node(node) click to toggle source
# File lib/chef/policy_builder/expand_node_object.rb, line 115
def finish_load_node(node)
  @node = node
end
runlist_override_sanity_check!() click to toggle source

Ensures runlist override contains RunListItem instances

# File lib/chef/policy_builder/expand_node_object.rb, line 235
def runlist_override_sanity_check!
  # Convert to array and remove whitespace
  if override_runlist.is_a?(String)
    @override_runlist = override_runlist.split(",").map(&:strip)
  end
  @override_runlist = [override_runlist].flatten.compact
  override_runlist.map! do |item|
    if item.is_a?(Chef::RunList::RunListItem)
      item
    else
      Chef::RunList::RunListItem.new(item)
    end
  end
end
setup_chef_class(run_context) click to toggle source

This method injects the run_context and into the Chef class.

NOTE: This is duplicated with the Policyfile implementation. If it gets any more complicated, it needs to be moved elsewhere.

@param run_context [Chef::RunContext] the run_context to inject

# File lib/chef/policy_builder/expand_node_object.rb, line 67
def setup_chef_class(run_context)
  Chef.set_run_context(run_context)
end
setup_run_context(specific_recipes = nil, run_context = nil) click to toggle source

This not only creates the run_context but this is where we kick off compiling the entire expanded run_list, loading all the libraries, resources, attribute files and recipes, and constructing the entire resource collection. (FIXME: break up creating the run_context and compiling the cookbooks)

# File lib/chef/policy_builder/expand_node_object.rb, line 76
def setup_run_context(specific_recipes = nil, run_context = nil)
  run_context ||= Chef::RunContext.new
  run_context.events = events
  run_context.node = node

  cookbook_collection =
    if Chef::Config[:solo_legacy_mode]
      Chef::Cookbook::FileVendor.fetch_from_disk(Chef::Config[:cookbook_path])
      cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path])
      cl.load_cookbooks
      Chef::CookbookCollection.new(cl)
    else
      Chef::Cookbook::FileVendor.fetch_from_remote(api_service)
      cookbook_hash = sync_cookbooks
      Chef::CookbookCollection.new(cookbook_hash)
    end

  cookbook_collection.validate!
  cookbook_collection.install_gems(events)

  run_context.cookbook_collection = cookbook_collection

  # TODO: move this into the cookbook_compilation_start hook
  setup_chef_class(run_context)

  events.cookbook_compilation_start(run_context)

  run_context.load(@run_list_expansion)
  if specific_recipes
    specific_recipes.each do |recipe_file|
      run_context.load_recipe_file(recipe_file)
    end
  end

  events.cookbook_compilation_complete(run_context)

  run_context
end
setup_run_list_override() click to toggle source

Internal public API

# File lib/chef/policy_builder/expand_node_object.rb, line 224
def setup_run_list_override
  unless override_runlist.nil?
    runlist_override_sanity_check!
    node.override_runlist = override_runlist
    Chef::Log.warn "Run List override has been provided."
    Chef::Log.warn "Original Run List: [#{node.primary_runlist}]"
    Chef::Log.warn "Overridden Run List: [#{node.run_list}]"
  end
end
sync_cookbooks() click to toggle source

Sync_cookbooks eagerly loads all files except files and templates. It returns the cookbook_hash – the return result from /environments/#{node.chef_environment}/cookbook_versions, which we will use for our run_context.

Returns

Hash

The hash of cookbooks with download URLs as given by the server

# File lib/chef/policy_builder/expand_node_object.rb, line 181
def sync_cookbooks
  Chef::Log.trace("Synchronizing cookbooks")

  begin
    events.cookbook_resolution_start(@expanded_run_list_with_versions)
    cookbook_hash = api_service.post("environments/#{node.chef_environment}/cookbook_versions",
      { run_list: @expanded_run_list_with_versions })

    cookbook_hash = cookbook_hash.inject({}) do |memo, (key, value)|
      memo[key] = Chef::CookbookVersion.from_hash(value)
      memo
    end
  rescue Exception => e
    # TODO: wrap/munge exception to provide helpful error output
    events.cookbook_resolution_failed(@expanded_run_list_with_versions, e)
    raise
  else
    events.cookbook_resolution_complete(cookbook_hash)
  end

  synchronizer = Chef::CookbookSynchronizer.new(cookbook_hash, events)
  if temporary_policy?
    synchronizer.remove_obsoleted_files = false
  end
  synchronizer.sync_cookbooks

  # register the file cache path in the cookbook path so that CookbookLoader actually picks up the synced cookbooks
  Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks")

  cookbook_hash
end
temporary_policy?() click to toggle source

Indicates whether the policy is temporary, which means an override_runlist was provided. Chef::Client uses this to decide whether to do the final node save at the end of the run or not.

# File lib/chef/policy_builder/expand_node_object.rb, line 216
def temporary_policy?
  node.override_runlist_set?
end