class ChefSpec::SoloRunner

def self.converge(*recipe_names)


ChefSpec::SoloRunner.converge('cookbook::recipe')
@example

initializing the runner with custom options.
Handy class method for just converging a runner if you do not care about
def self.converge(*recipe_names)
  new.tap do |instance|
    instance.converge(*recipe_names)
  end
end

def self.define_runner_method(resource_name)

Deprecated:
  • {ChefSpec::Runner.define_runner_method} is deprecated. Please
def self.define_runner_method(resource_name)
  deprecated "`ChefSpec::Runner.define_runner_method' is deprecated." \
    " It is being used in the #{resource_name} resource matcher." \
    " Please use `ChefSpec.define_matcher' instead."
  ChefSpec.define_matcher(resource_name)
end

def apply_chef_config!

Returns:
  • (void) -

Other tags:
    Api: - private
def apply_chef_config!
  Chef::Log.level = @options[:log_level]
  Chef::Config.reset!
  Chef::Config.formatters.clear
  Chef::Config.add_formatter('chefspec')
  Chef::Config[:cache_type]      = 'Memory'
  Chef::Config[:client_key]      = nil
  Chef::Config[:client_name]     = nil
  Chef::Config[:node_name]       = nil
  Chef::Config[:file_cache_path] = @options[:file_cache_path] || file_cache_path
  Chef::Config[:cookbook_path]   = Array(@options[:cookbook_path])
  # If the word cookbook is in the folder name, treat it as the path. Otherwise
  # it's probably not a cookbook path and so we activate the gross hack mode.
  if Chef::Config[:cookbook_path].size == 1 && Chef::Config[:cookbook_path].first !~ /cookbook/
    Chef::Config[:chefspec_cookbook_root] = @options[:cookbook_root]
  end
  Chef::Config[:no_lazy_load]    = true
  Chef::Config[:role_path]       = Array(@options[:role_path])
  Chef::Config[:force_logger]    = true
  Chef::Config[:solo]            = true
  Chef::Config[:solo_legacy_mode] = true
  Chef::Config[:use_policyfile]  = false
  Chef::Config[:environment_path] = @options[:environment_path]
end

def calling_cookbook_path(options, kaller)

Returns:
  • (String) -

Parameters:
  • kaller (Array) --
  • options (Hash) --
def calling_cookbook_path(options, kaller)
  File.expand_path(File.join(calling_cookbook_root(options, kaller), '..'))
end

def calling_cookbook_root(options, kaller)

Returns:
  • (String) -

Parameters:
  • kaller (Array) --
  • options (Hash) --
def calling_cookbook_root(options, kaller)
  calling_spec = options[:spec_declaration_locations] || kaller.find { |line| line =~ /\/spec/ }
  raise Error::CookbookPathNotFound if calling_spec.nil?
  bits = calling_spec.split(/:[0-9]/, 2).first.split(File::SEPARATOR)
  spec_dir = bits.index('spec') || 0
  File.join(bits.slice(0, spec_dir))
end

def client

Returns:
  • (Chef::Runner) -
def client
  return @client if @client
  @client = Chef::Client.new
  @client.ohai.data = Mash.from_hash(Fauxhai.mock(options).data)
  @client.load_node
  @client.build_node
  @client.save_updated_node
  @client
end

def compiling?

Returns:
  • (true, false) -
def compiling?
  !@converging
end

def converge(*recipe_names)

Returns:
  • (ChefSpec::SoloRunner) -

Parameters:
  • recipe_names (Array) --

Other tags:
    Example: Converging multiple recipes -
    Example: Converging a single recipe -
def converge(*recipe_names)
  # Re-apply the Chef config before converging in case something else
  # called Config.reset too.
  apply_chef_config!
  @converging = false
  node.run_list.reset!
  recipe_names.each { |recipe_name| node.run_list.add(recipe_name) }
  return self if dry_run?
  # Expand the run_list
  expand_run_list!
  # Merge in provided node attributes. Default and override use the role_
  # levels so they win over the relevant bits from cookbooks since otherwise
  # they would not and that would be confusing.
  node.attributes.role_default = Chef::Mixin::DeepMerge.merge(node.attributes.role_default, options[:default_attributes]) if options[:default_attributes]
  node.attributes.normal = Chef::Mixin::DeepMerge.merge(node.attributes.normal, options[:normal_attributes]) if options[:normal_attributes]
  node.attributes.role_override = Chef::Mixin::DeepMerge.merge(node.attributes.role_override, options[:override_attributes]) if options[:override_attributes]
  node.attributes.automatic = Chef::Mixin::DeepMerge.merge(node.attributes.automatic, options[:automatic_attributes]) if options[:automatic_attributes]
  # Setup the run_context, rescuing the exception that happens when a
  # resource is not defined on a particular platform
  begin
    @run_context = client.setup_run_context
  rescue Chef::Exceptions::NoSuchResourceType => e
    raise Error::MayNeedToSpecifyPlatform.new(original_error: e.message)
  end
  # Allow stubbing/mocking after the cookbook has been compiled but before the converge
  yield node if block_given?
  @converging = true
  converge_val = @client.converge(@run_context)
  if converge_val.is_a?(Exception)
    raise converge_val
  end
  self
end

def converge_block(&block)

Returns:
  • (ChefSpec::SoloRunner) -

Parameters:
  • block (Proc) --
def converge_block(&block)
  converge do
    recipe = Chef::Recipe.new(cookbook_name, '_test', run_context)
    recipe.instance_exec(&block)
  end
end

def cookbook

Returns:
  • (Chef::Cookbook::Metadata) -
def cookbook
  @cookbook ||= Chef::Cookbook::Metadata.new.tap {|m| m.from_file("#{options[:cookbook_root]}/metadata.rb") }
end

def cookbook_name

Returns:
  • (String) -
def cookbook_name
  # Try to figure out the name of this cookbook, pretending this block
  # is in the name context as the cookbook under test.
  begin
    cookbook.name
  rescue IOError
    # Old cookbook, has no metadata, use the folder name I guess.
    File.basename(options[:cookbook_root])
  end
end

def default_environment_path

Returns:
  • (String, nil) -
def default_environment_path
  Pathname.new(Dir.pwd).ascend do |path|
    possible = File.join(path, 'environments')
    return possible if File.exist?(possible)
  end
  nil
end

def default_role_path

Returns:
  • (String, nil) -
def default_role_path
  Pathname.new(Dir.pwd).ascend do |path|
    possible = File.join(path, 'roles')
    return possible if File.exist?(possible)
  end
  nil
end

def dry_run?

Returns:
  • (true, false) -
def dry_run?
  !!options[:dry_run]
end

def expand_run_list!

Other tags:
    Todo: - Remove in Chef 13
def expand_run_list!
  # Recent versions of Chef include a method to expand the +run_list+,
  # setting the correct instance variables on the policy builder. We use
  # that, unless the user is running an older version of Chef which
  # doesn't include this method.
  if client.respond_to?(:expanded_run_list)
    client.expanded_run_list
  else
    # Sadly, if we got this far, it means that the current Chef version
    # does not include the +expanded_run_list+ method, so we need to
    # manually expand the +run_list+. The following code has been known
    # to make kittens cry, so please read with extreme caution.
    client.instance_eval do
      @run_list_expansion = expand_run_list
      @expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings
    end
  end
end

def file_cache_path


you should cache the result to a variable inside you system.
**This method creates a new temporary directory on each call!** As such,

temporary directory is deleted when the system exits.
{Dir.mktmpdir}. The method adds a {Kernel.at_exit} handler to ensure the
The path to cache files on disk. This value is created using
def file_cache_path
  path = Dir.mktmpdir
  at_exit { FileUtils.rm_rf(path) }
  path
end

def find_resource(type, name, action = nil)

Returns:
  • (Chef::Resource, nil) -

Parameters:
  • action (Symbol) --
  • name (String, Regexp) --
  • type (Symbol) --

Other tags:
    Example: Find a template at `/etc/foo` -
def find_resource(type, name, action = nil)
  resource_collection.all_resources.reverse_each.find do |resource|
    resource.declared_type == type.to_sym && (name === resource.identity || name === resource.name) && (action.nil? || resource.performed_action?(action))
  end
end

def find_resources(type)

Returns:
  • (Array) -

Parameters:
  • type (Symbol) --

Other tags:
    Example: Find all template resources -
def find_resources(type)
  resource_collection.all_resources.select do |resource|
    resource_name(resource) == type.to_sym
  end
end

def initialize(options = {})

Other tags:
    Yield: - Configuration block for Chef::Node

Options Hash: (**options)
  • String (]) -- tring] :file_cache_path
  • :step_into (Array) --
  • :path (String) --
  • :version (String) --
  • :platform (String) --
  • :log_level (Symbol) --

Parameters:
  • options (Hash) --

Other tags:
    Example: Specifying the log level -
    Example: Specifying the cookbook path -
    Example: Specifying the platform and version -
    Example: Instantiate a new Runner -
def initialize(options = {})
  @options = with_default_options(options)
  apply_chef_config!
  yield node if block_given?
end

def inspect

Returns:
  • (String) -
def inspect
  "#<#{self.class.name}" \
  " options: #{options.inspect}," \
  " run_list: [#{node.run_list}]>"
end

def method_missing(m, *args, &block)


Respond to custom matchers defined by the user.
def method_missing(m, *args, &block)
  if block = ChefSpec.matchers[resource_name(m.to_sym)]
    instance_exec(args.first, &block)
  else
    super
  end
end

def node

Returns:
  • (Chef::Node) -
def node
  runner = self
  @node ||= begin
    apply_chef_config!
    client.build_node.tap do |node|
      node.define_singleton_method(:runner) { runner }
    end
  end
end

def preload!

Returns:
  • (void) -
def preload!
  # Flag to disable preloading for situations where it doesn't make sense.
  return if ENV['CHEFSPEC_NO_PRELOAD']
  begin
    old_preload = $CHEFSPEC_PRELOAD
    $CHEFSPEC_PRELOAD = true
    converge("recipe[#{cookbook_name}]")
    node.run_list.reset!
  ensure
    $CHEFSPEC_PRELOAD = old_preload
  end
end

def resource_collection

Returns:
  • (Hash) -
def resource_collection
  @resource_collection ||= @run_context.resource_collection
end

def respond_to_missing?(m, include_private = false)


matchers.
Inform Ruby that we respond to methods that are defined as custom
def respond_to_missing?(m, include_private = false)
  ChefSpec.matchers.key?(m.to_sym) || super
end

def step_into?(resource)

Returns:
  • (true, false) -

Parameters:
  • resource (Chef::Resource) --
def step_into?(resource)
  key = resource_name(resource)
  Array(options[:step_into]).map(&method(:resource_name)).include?(key)
end

def to_s

Returns:
  • (String) - Currently includes the run_list. Format of the string
def to_s
  "#<#{self.class.name} run_list: [#{node.run_list}]>"
end

def with_default_options(options)

Returns:
  • (Hash) - options

Parameters:
  • options (Hash) --
def with_default_options(options)
  config = RSpec.configuration
  {
    cookbook_root: config.cookbook_root || calling_cookbook_root(options, caller),
    cookbook_path: config.cookbook_path || calling_cookbook_path(options, caller),
    role_path:     config.role_path || default_role_path,
    environment_path: config.environment_path || default_environment_path,
    file_cache_path: config.file_cache_path,
    log_level:     config.log_level,
    path:          config.path,
    platform:      config.platform,
    version:       config.version,
  }.merge(options)
end