module RSpec::Puppet::Support

Attributes

adapter[RW]

@!attribute [r] adapter

@api private
@return [Class < RSpec::Puppet::Adapters::Base]

Public Instance Methods

build_catalog(*args) click to toggle source
# File lib/rspec-puppet/support.rb, line 485
def build_catalog(*args)
  @@cache.get(*args) do |*args|
    if args.length == 1 && args.first.is_a?(Hash)
      build_catalog_without_cache_v2(**args.first)
    else
      build_catalog_without_cache(*args)
    end
  end
end
build_catalog_without_cache(nodename, facts_val, trusted_facts_val, hiera_config_val, code, exported, node_params, *_) click to toggle source
# File lib/rspec-puppet/support.rb, line 416
def build_catalog_without_cache(nodename, facts_val, trusted_facts_val, hiera_config_val, code, exported, node_params, *_)
  build_catalog_without_cache_v2({
    nodename: nodename,
    facts_val: facts_val,
    trusted_facts_val: trusted_facts_val,
    hiera_config_val: hiera_config_val,
    code: code,
    exported: exported,
    node_params: node_params,
    trusted_external: {},
  })
end
build_catalog_without_cache_v2( nodename: nil, facts_val: nil, trusted_facts_val: nil, hiera_config_val: nil, code: nil, exported: nil, node_params: nil, trusted_external_data: nil, ignored_cache_params: {} ) click to toggle source
# File lib/rspec-puppet/support.rb, line 429
def build_catalog_without_cache_v2(
  nodename: nil,
  facts_val: nil,
  trusted_facts_val: nil,
  hiera_config_val: nil,
  code: nil,
  exported: nil,
  node_params: nil,
  trusted_external_data: nil,
  ignored_cache_params: {}
)

  # If we're going to rebuild the catalog, we should clear the cached instance
  # of Hiera that Puppet is using.  This opens the possibility of the catalog
  # now being rebuilt against a differently configured Hiera (i.e. :hiera_config
  # set differently in one example group vs. another).
  # It would be nice if Puppet offered a public API for invalidating their
  # cached instance of Hiera, but que sera sera.  We will go directly against
  # the implementation out of absolute necessity.
  HieraPuppet.instance_variable_set('@hiera', nil) if defined? HieraPuppet

  Puppet[:code] = code

  stub_facts! facts_val

  Puppet::Type.eachtype { |type| type.defaultprovider = nil }

  node_facts = Puppet::Node::Facts.new(nodename, facts_val.dup)
  node_params = facts_val.merge(node_params)

  node_obj = Puppet::Node.new(nodename, { :parameters => node_params, :facts => node_facts })

  trusted_info = ['remote', nodename, trusted_facts_val]
  if Puppet::Util::Package.versioncmp(Puppet.version, '6.14.0') >= 0
    trusted_info.push(trusted_external_data)
  end
  if Puppet::Util::Package.versioncmp(Puppet.version, '4.3.0') >= 0
    Puppet.push_context(
      {
        :trusted_information => Puppet::Context::TrustedInformation.new(*trusted_info)
      },
      "Context for spec trusted hash"
    )

    node_obj.add_server_facts(server_facts_hash) if RSpec.configuration.trusted_server_facts
  end

  adapter.catalog(node_obj, exported)
end
build_code(type, manifest_opts) click to toggle source
# File lib/rspec-puppet/support.rb, line 21
def build_code(type, manifest_opts)
  if Puppet.version.to_f >= 4.0 or Puppet[:parser] == 'future'
    [site_pp_str, pre_cond, test_manifest(type, manifest_opts), post_cond].compact.join("\n")
  else
    [import_str, pre_cond, test_manifest(type, manifest_opts), post_cond].compact.join("\n")
  end
end
class_name() click to toggle source
# File lib/rspec-puppet/support.rb, line 215
def class_name
  self.class.top_level_description.downcase
end
environment() click to toggle source
# File lib/rspec-puppet/support.rb, line 17
def environment
  'rp_env'
end
escape_special_chars(string) click to toggle source
# File lib/rspec-puppet/support.rb, line 507
def escape_special_chars(string)
  string.gsub(/\$/, "\\$")
end
facts_hash(node) click to toggle source
# File lib/rspec-puppet/support.rb, line 243
def facts_hash(node)
  base_facts = {
    'clientversion' => Puppet::PUPPETVERSION,
    'environment'   => environment.to_s,
  }

  node_facts = {
    'hostname'   => node.split('.').first,
    'fqdn'       => node,
    'domain'     => node.split('.', 2).last,
    'clientcert' => node,
    'ipaddress6' => 'FE80:0000:0000:0000:AAAA:AAAA:AAAA',
  }

  networking_facts = {
    'hostname' => node_facts['hostname'],
    'fqdn'     => node_facts['fqdn'],
    'domain'   => node_facts['domain'],
  }

  result_facts = if RSpec.configuration.default_facts.any?
                   munge_facts(RSpec.configuration.default_facts)
                 else
                   {}
                 end

  # Merge in node facts so they always exist by default, but only if they
  # haven't been defined in `RSpec.configuration.default_facts`
  result_facts.merge!(munge_facts(node_facts)) { |_key, old_val, new_val| old_val.nil? ? new_val : old_val }
  (result_facts['networking'] ||= {}).merge!(networking_facts) { |_key, old_val, new_val| old_val.nil? ? new_val : old_val }

  # Merge in `let(:facts)` facts
  result_facts.merge!(munge_facts(base_facts))
  result_facts.merge!(munge_facts(facts)) if self.respond_to?(:facts)

  # Merge node facts again on top of `let(:facts)` facts, but only if
  # a node name is given with `let(:node)`
  if respond_to?(:node) && RSpec.configuration.derive_node_facts_from_nodename
    result_facts.merge!(munge_facts(node_facts))
    (result_facts['networking'] ||= {}).merge!(networking_facts)
  end

  # Facter currently supports lower case facts.  Bug FACT-777 has been submitted to support case sensitive
  # facts.
  downcase_facts = Hash[result_facts.map { |k, v| [k.downcase, v] }]
  downcase_facts
end
find_pretend_platform(test_facts) click to toggle source
# File lib/rspec-puppet/support.rb, line 65
def find_pretend_platform(test_facts)
  from_value = lambda { |value|
    value.to_s.downcase == 'windows' ? :windows : :posix
  }

  ['operatingsystem', 'osfamily'].each do |os_fact|
    return from_value.call(test_facts[os_fact]) if test_facts.key?(os_fact)
  end

  if test_facts.key?('os') && test_facts['os'].is_a?(Hash)
    ['name', 'family'].each do |os_hash_key|
      return from_value.call(test_facts['os'][os_hash_key]) if test_facts['os'].key?(os_hash_key)
    end
  end

  nil
end
fixture_spec_hiera_conf(mod) click to toggle source
# File lib/rspec-puppet/support.rb, line 519
def fixture_spec_hiera_conf(mod)
  return @@fixture_hiera_configs[mod.name] if @@fixture_hiera_configs.key?(mod.name)

  path = Pathname.new(mod.path)
  if path.join('spec').exist?
    path.join('spec').find do |file|
      Find.prune if %w[modules work-dir].any? do |dir|
        file.relative_path_from(path).to_s.start_with?("spec/fixtures/#{dir}")
      end
      if file.basename.to_s.eql?(Puppet::Pops::Lookup::HieraConfig::CONFIG_FILE_NAME)
        return @@fixture_hiera_configs[mod.name] = file.to_s
      end
    end
  end
  @@fixture_hiera_configs[mod.name]
end
guess_type_from_path(path) click to toggle source
# File lib/rspec-puppet/support.rb, line 29
def guess_type_from_path(path)
  case path
  when /spec\/defines/
    :define
  when /spec\/classes/
    :class
  when /spec\/functions/
    :function
  when /spec\/hosts/
    :host
  when /spec\/types/
    :type
  when /spec\/type_aliases/
    :type_alias
  when /spec\/provider/
    :provider
  when /spec\/applications/
    :application
  else
    :unknown
  end
end
import_str() click to toggle source
# File lib/rspec-puppet/support.rb, line 130
def import_str
  import_str = ""
  adapter.modulepath.each { |d|
    if File.exists?(File.join(d, 'manifests', 'init.pp'))
      path_to_manifest = File.join([
        d,
        'manifests',
        class_name.split('::')[1..-1]
      ].flatten)
      import_str = [
        "import '#{d}/manifests/init.pp'",
        "import '#{path_to_manifest}.pp'",
        '',
      ].join("\n")
      break
    elsif File.exists?(d)
      import_str = "import '#{adapter.manifest}'\n"
      break
    end
  }

  import_str
end
load_catalogue(type, exported = false, manifest_opts = {}) click to toggle source
# File lib/rspec-puppet/support.rb, line 84
def load_catalogue(type, exported = false, manifest_opts = {})
  with_vardir do
    node_name = nodename(type)

    hiera_config_value = self.respond_to?(:hiera_config) ? hiera_config : nil
    hiera_data_value = self.respond_to?(:hiera_data) ? hiera_data : nil

    rspec_config_values = [
        :trusted_server_facts,
        :disable_module_hiera,
        :use_fixture_spec_hiera,
        :fixture_hiera_configs,
        :fallback_to_default_hiera,
    ].map { |setting| RSpec.configuration.send(setting) }

    build_facts = facts_hash(node_name)
    catalogue = build_catalog(
      nodename: node_name,
      facts_val: build_facts,
      trusted_facts_val: trusted_facts_hash(node_name),
      hiera_config_val: hiera_config_value,
      code: build_code(type, manifest_opts),
      exported: exported,
      node_params: node_params_hash,
      trusted_external_data: trusted_external_data_hash,
      ignored_cache_params: {
        hiera_data_value: hiera_data_value,
        rspec_config_values: rspec_config_values,
      }
    )

    test_module = type == :host ? nil : class_name.split('::').first
    if type == :define
      RSpec::Puppet::Coverage.add_filter(class_name, title)
    else
      RSpec::Puppet::Coverage.add_filter(type.to_s, class_name)
    end
    RSpec::Puppet::Coverage.add_from_catalog(catalogue, test_module)

    pretend_platform = find_pretend_platform(build_facts)
    Puppet::Util::Platform.pretend_to_be(pretend_platform) unless pretend_platform.nil?

    catalogue
  end
end
munge_facts(facts) click to toggle source
# File lib/rspec-puppet/support.rb, line 495
def munge_facts(facts)
  return facts.reduce({}) do | memo, (k, v)|
    memo.tap { |m| m[k.to_s] = munge_facts(v) }
  end if facts.is_a? Hash

  return facts.reduce([]) do |memo, v|
    memo << munge_facts(v); memo
  end if facts.is_a? Array

  facts
end
node_params_hash() click to toggle source
# File lib/rspec-puppet/support.rb, line 291
def node_params_hash
  params = RSpec.configuration.default_node_params
  if respond_to?(:node_params)
    params.merge(node_params)
  else
    params.dup
  end
end
nodename(type) click to toggle source
# File lib/rspec-puppet/support.rb, line 206
def nodename(type)
  return node if self.respond_to?(:node)
  if [:class, :define, :function, :application].include? type
    Puppet[:certname]
  else
    class_name
  end
end
param_str(params) click to toggle source
# File lib/rspec-puppet/support.rb, line 300
def param_str(params)
  param_str_from_hash(params)
end
param_str_from_hash(params_hash) click to toggle source
# File lib/rspec-puppet/support.rb, line 381
def param_str_from_hash(params_hash)
  # the param_str has special quoting rules, because the top-level keys are the Puppet
  # params, which may not be quoted
  params_hash.collect do |k,v|
    "#{k.to_s} => #{str_from_value(v)}"
  end.join(', ')
end
post_cond() click to toggle source
# File lib/rspec-puppet/support.rb, line 231
def post_cond
  if self.respond_to?(:post_condition) && !post_condition.nil?
    if post_condition.is_a? Array
      post_condition.compact.join("\n")
    else
      post_condition
    end
  else
    nil
  end
end
pre_cond() click to toggle source
# File lib/rspec-puppet/support.rb, line 219
def pre_cond
  if self.respond_to?(:pre_condition) && !pre_condition.nil?
    if pre_condition.is_a? Array
      pre_condition.compact.join("\n")
    else
      pre_condition
    end
  else
    nil
  end
end
ref(type, title) click to toggle source

Helper to return a resource/node reference, so it gets translated in params to a raw string without quotes.

@param [String] type reference type @param [String] title reference title @return [RSpec::Puppet::RawString] return a new RawString with the type/title populated correctly

# File lib/rspec-puppet/support.rb, line 542
def ref(type, title)
  return RSpec::Puppet::RawString.new("#{type}['#{title}']")
end
rspec_compatibility() click to toggle source
# File lib/rspec-puppet/support.rb, line 511
def rspec_compatibility
  if RSpec::Version::STRING < '3'
    # RSpec 2 compatibility:
    alias_method :failure_message_for_should, :failure_message
    alias_method :failure_message_for_should_not, :failure_message_when_negated
  end
end
sanitise_resource_title(title) click to toggle source
# File lib/rspec-puppet/support.rb, line 202
def sanitise_resource_title(title)
  title.include?("'") ? title.inspect : "'#{title}'"
end
sensitive(value) click to toggle source

Helper to return value wrapped in Sensitive type.

@param [Object] value to wrap @return [RSpec::Puppet::Sensitive] a new Sensitive wrapper with the new value

# File lib/rspec-puppet/support.rb, line 550
def sensitive(value)
  return RSpec::Puppet::Sensitive.new(value)
end
server_facts_hash() click to toggle source
# File lib/rspec-puppet/support.rb, line 330
def server_facts_hash
  server_facts = {}

  # Add our server version to the fact list
  server_facts["serverversion"] = Puppet.version.to_s

  # And then add the server name and IP
  {"servername" => "fqdn",
    "serverip" => "ipaddress"
  }.each do |var, fact|
    if value = Facter.value(fact)
      server_facts[var] = value
    else
      warn "Could not retrieve fact #{fact}"
    end
  end

  if server_facts["servername"].nil?
    host = Facter.value(:hostname)
    if domain = Facter.value(:domain)
      server_facts["servername"] = [host, domain].join(".")
    else
      server_facts["servername"] = host
    end
  end
  server_facts
end
setup_puppet() click to toggle source
# File lib/rspec-puppet/support.rb, line 389
def setup_puppet
  vardir = Dir.mktmpdir
  Puppet[:vardir] = vardir

  # Enable app_management by default for Puppet versions that support it
  if Puppet::Util::Package.versioncmp(Puppet.version, '4.3.0') >= 0 && Puppet.version.to_i < 5
    Puppet[:app_management] = ENV.include?('PUPPET_NOAPP_MANAGMENT') ? false : true
  end

  adapter.modulepath.map do |d|
    Dir["#{d}/*/lib"].entries
  end.flatten.each do |lib|
    $LOAD_PATH << lib
  end

  vardir
end
site_pp_str() click to toggle source
# File lib/rspec-puppet/support.rb, line 154
def site_pp_str
  return '' unless (path = adapter.manifest)

  if File.file?(path)
    File.read(path)
  elsif File.directory?(path)
    # Read and concatenate all .pp files.
    Dir[File.join(path, '*.pp')].sort.map do |f|
      File.read(f)
    end.join("\n")
  else
    ''
  end
end
str_from_value(value) click to toggle source
# File lib/rspec-puppet/support.rb, line 358
def str_from_value(value)
  case value
  when Hash
    kvs = value.collect do |k,v|
      "#{str_from_value(k)} => #{str_from_value(v)}"
    end.join(", ")
    "{ #{kvs} }"
  when Array
    vals = value.map do |v|
      str_from_value(v)
    end.join(", ")
    "[ #{vals} ]"
  when :default
    'default'  # verbatim default keyword
  when :undef
    'undef'  # verbatim undef keyword
  when Symbol
    str_from_value(value.to_s)
  else
    escape_special_chars(value.inspect)
  end
end
stub_facts!(facts) click to toggle source
# File lib/rspec-puppet/support.rb, line 479
def stub_facts!(facts)
  Puppet.settings[:autosign] = false if Puppet.settings.include? :autosign
  Facter.clear
  facts.each { |k, v| Facter.add(k, :weight => 999) { setcode { v } } }
end
stub_file_consts(example) click to toggle source
# File lib/rspec-puppet/support.rb, line 52
def stub_file_consts(example)
  if example.respond_to?(:metadata)
    type = example.metadata[:type]
  else
    type = guess_type_from_path(example.example.metadata[:file_path])
  end

  munged_facts = facts_hash(nodename(type))

  pretend_platform = find_pretend_platform(munged_facts)
  RSpec::Puppet::Consts.stub_consts_for(pretend_platform) unless pretend_platform.nil?
end
subject() click to toggle source
# File lib/rspec-puppet/support.rb, line 13
def subject
  lambda { catalogue }
end
test_manifest(type, opts = {}) click to toggle source
# File lib/rspec-puppet/support.rb, line 169
def test_manifest(type, opts = {})
  opts[:params] = params if self.respond_to?(:params)

  if type == :class
    if opts[:params].nil? || opts[:params] == {}
      "include #{class_name}"
    else
      "class { '#{class_name}': #{param_str(opts[:params])} }"
    end
  elsif type == :application
    if opts.has_key?(:params)
      "site { #{class_name} { #{sanitise_resource_title(title)}: #{param_str(opts[:params])} } }"
    else
      raise ArgumentError, "You need to provide params for an application"
    end
  elsif type == :define
    title_str = if title.is_a?(Array)
                  '[' + title.map { |r| sanitise_resource_title(r) }.join(', ') + ']'
                else
                  sanitise_resource_title(title)
                end
    if opts.has_key?(:params)
      "#{class_name} { #{title_str}: #{param_str(opts[:params])} }"
    else
      "#{class_name} { #{title_str}: }"
    end
  elsif type == :host
    nil
  elsif type == :type_alias
    "$test = #{str_from_value(opts[:test_value])}\nassert_type(#{self.class.top_level_description}, $test)"
  end
end
trusted_external_data_hash() click to toggle source
# File lib/rspec-puppet/support.rb, line 317
def trusted_external_data_hash
  return {} unless Puppet::Util::Package.versioncmp(Puppet.version, '6.14.0') >= 0

  external_data = {}

  if RSpec.configuration.default_trusted_external_data.any?
    external_data.merge!(munge_facts(RSpec.configuration.default_trusted_external_data))
  end

  external_data.merge!(munge_facts(trusted_external_data)) if self.respond_to?(:trusted_external_data)
  external_data
end
trusted_facts_hash(node_name) click to toggle source
# File lib/rspec-puppet/support.rb, line 304
def trusted_facts_hash(node_name)
  return {} unless Puppet::Util::Package.versioncmp(Puppet.version, '4.3.0') >= 0

  extensions = {}

  if RSpec.configuration.default_trusted_facts.any?
    extensions.merge!(munge_facts(RSpec.configuration.default_trusted_facts))
  end

  extensions.merge!(munge_facts(trusted_facts)) if self.respond_to?(:trusted_facts)
  extensions
end
with_vardir() { |vardir| ... } click to toggle source
# File lib/rspec-puppet/support.rb, line 407
def with_vardir
  begin
    vardir = setup_puppet
    return yield(vardir) if block_given?
  ensure
    FileUtils.rm_rf(vardir) if vardir && File.directory?(vardir)
  end
end