Module: RspecPuppetFacts

Defined in:
lib/rspec-puppet-facts.rb,
lib/rspec-puppet-facts/version.rb

Overview

The purpose of this module is to simplify the Puppet module’s RSpec tests by looping through all supported OS’es and their facts data which is received from the FacterDB.

Defined Under Namespace

Modules: Version

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.common_factsHash <Symbol => String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

These facts are common for all OS’es and will be added to the facts retrieved from the FacterDB

Returns:

  • (Hash <Symbol => String>)


204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/rspec-puppet-facts.rb', line 204

def self.common_facts
  return @common_facts if @common_facts
  @common_facts = {
      :mco_version   => MCollective::VERSION,
      :puppetversion => Puppet.version,
      :rubysitedir   => RbConfig::CONFIG['sitelibdir'],
      :rubyversion   => RUBY_VERSION,
  }
  if Puppet.features.augeas?
    @common_facts[:augeasversion] = Augeas.open(nil, nil, Augeas::NO_MODL_AUTOLOAD).get('/augeas/version')
  end
  @common_facts
end

.down_facter_version(version, minor_subtractor = 1) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Subtracts from the minor version passed and returns a string representing the next minor version down

Parameters:

  • version (String)

    the Facter version

  • minor_subtractor (int) (defaults to: 1)

    the value which to subtract by

Returns:

  • (String)

    next version below



283
284
285
286
287
# File 'lib/rspec-puppet-facts.rb', line 283

def self.down_facter_version(version, minor_subtractor = 1)
    major, minor, z = version.split('.')
    minor = (minor.to_i - minor_subtractor).to_s
    "#{major}.#{minor}.#{z}"
end

.facter_version_to_filter(version) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Generates a JGrep statement expression for a specific facter version

Parameters:

  • version (String)

    the Facter version

Returns:

  • (String)

    JGrep statement expression



273
274
275
276
# File 'lib/rspec-puppet-facts.rb', line 273

def self.facter_version_to_filter(version)
  major, minor = version.split('.')
  "/\\A#{major}\\.#{minor}\\./"
end

.meta_supported_osArray<Hash>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Get the “operatingsystem_support” structure from the parsed metadata.json file in the metadata

Returns:

  • (Array<Hash>)

Raises:

  • (StandardError)

    if there is no “operatingsystem_support”



224
225
226
227
228
229
# File 'lib/rspec-puppet-facts.rb', line 224

def self.meta_supported_os
  unless ['operatingsystem_support'].is_a? Array
    fail StandardError, 'Unknown operatingsystem support in the metadata file!'
  end
  ['operatingsystem_support']
end

.metadataHash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Read the metadata file and parse its JSON content.

Returns:

  • (Hash)

Raises:

  • (StandardError)

    if the metadata file is missing



236
237
238
239
240
241
242
243
# File 'lib/rspec-puppet-facts.rb', line 236

def self.
  return @metadata if @metadata
  unless File.file? 
    fail StandardError, "Can't find metadata.json... dunno why"
  end
  content = File.read 
  @metadata = JSON.parse content
end

.metadata_fileString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This file contains the Puppet module’s metadata

Returns:

  • (String)


248
249
250
# File 'lib/rspec-puppet-facts.rb', line 248

def self.
  'metadata.json'
end

.register_custom_fact(name, value, options) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Adds a custom fact to the @custom_facts variable.

Parameters:

  • name (String)

    Fact name

  • value (String, Proc)

    Fact value. If proc, takes 2 params: os and facts hash

  • opts (Hash)


159
160
161
162
# File 'lib/rspec-puppet-facts.rb', line 159

def self.register_custom_fact(name, value, options)
  @custom_facts ||= {}
  @custom_facts[name.to_s] = {:options => options, :value => value}
end

.resetObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Reset the memoization to make the saved structures be generated again



263
264
265
266
267
# File 'lib/rspec-puppet-facts.rb', line 263

def self.reset
  @custom_facts = nil
  @common_facts = nil
  @metadata = nil
end

.spec_facts_os_filternil, String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

If provided this filter can be used to limit the set of retrieved facts only to the matched OS names. The value is being taken from the SPEC_FACTS_OS environment variable and

Returns:

  • (nil, String)


189
190
191
# File 'lib/rspec-puppet-facts.rb', line 189

def self.spec_facts_os_filter
  ENV['SPEC_FACTS_OS']
end

.spec_facts_strict?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

If SPEC_FACTS_STRICT is set to ‘yes`, RspecPuppetFacts will error on missing FacterDB entries, instead of warning & skipping the tests, or using an older facter version.

Returns:

  • (Boolean)


196
197
198
# File 'lib/rspec-puppet-facts.rb', line 196

def self.spec_facts_strict?
  ENV['SPEC_FACTS_STRICT'] == 'yes'
end

.warning(message) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Print a warning message to the console

Parameters:

  • message (String)


255
256
257
# File 'lib/rspec-puppet-facts.rb', line 255

def self.warning(message)
  STDERR.puts message
end

.with_custom_facts(os, facts) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Adds any custom facts according to the rules defined for the operating system with the given facts.

Parameters:

  • os (String)

    Name of the operating system

  • facts (Hash)

    Facts hash

Returns:

  • (Hash)

    facts Facts hash with custom facts added



170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/rspec-puppet-facts.rb', line 170

def self.with_custom_facts(os, facts)
  return facts unless @custom_facts

  @custom_facts.each do |name, fact|
    next if fact[:options][:confine] && !fact[:options][:confine].include?(os)
    next if fact[:options][:exclude] && fact[:options][:exclude].include?(os)

    facts[name] = fact[:value].respond_to?(:call) ? fact[:value].call(os, facts) : fact[:value]
  end

  facts
end

Instance Method Details

#add_custom_fact(name, value, options = {}) ⇒ Object

Register a custom fact that will be included in the facts hash. If it should be limited to a particular OS, pass a :confine option that contains the operating system(s) to confine to. If it should be excluded on a particular OS, use :exclude.

Parameters:

  • name (String)

    Fact name

  • value (String, Proc)

    Fact value. If proc, takes 2 params: os and facts hash

  • opts (Hash)


144
145
146
147
148
149
# File 'lib/rspec-puppet-facts.rb', line 144

def add_custom_fact(name, value, options = {})
  options[:confine] = [options[:confine]] if options[:confine].is_a?(String)
  options[:exclude] = [options[:exclude]] if options[:exclude].is_a?(String)

  RspecPuppetFacts.register_custom_fact(name, value, options)
end

#on_supported_os(opts = {}) ⇒ Hash <String => Hash>

Use the provided options or the data from the metadata.json file to find a set of matching facts in the FacterDB. OS names and facts can be used in the Puppet RSpec tests to run the examples against all supported facts combinations.

The list of received OS facts can also be filtered by the SPEC_FACTS_OS environment variable. For example, if the variable is set to “debian” only the OS names which start with “debian” will be returned. It allows a user to quickly run the tests only on a single facts set without any file modifications.

select facts from, e.g.: ‘3.6’ will be used instead of the “operatingsystem_support” section if the metadata file even if the file is missing.

Parameters:

  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :hardwaremodels (String, Array<String>)

    The OS architecture names, i.e. x86_64

  • :supported_os (Array<Hash>)

    If this options is provided the data

  • :facterversion (String)

    the facter version of which to

Returns:

  • (Hash <String => Hash>)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/rspec-puppet-facts.rb', line 30

def on_supported_os(opts = {})
  opts[:hardwaremodels] ||= ['x86_64']
  opts[:hardwaremodels] = [opts[:hardwaremodels]] unless opts[:hardwaremodels].is_a? Array
  opts[:supported_os] ||= RspecPuppetFacts.meta_supported_os
  opts[:facterversion] ||= Facter.version

  unless (facterversion = opts[:facterversion]) =~ /\A\d+\.\d+(?:\.\d+)*\z/
    raise ArgumentError, ":facterversion must be in the format 'n.n' or " \
      "'n.n.n' (n is numeric), not '#{facterversion}'"
  end

  facter_version_filter = RspecPuppetFacts.facter_version_to_filter(facterversion)
  db = FacterDB.get_facts({ :facterversion =>  facter_version_filter })

  version = facterversion
  while db.empty? && version !~ /\d+\.0\.\d+/
    version = RspecPuppetFacts.down_facter_version(version)
    facter_version_filter = RspecPuppetFacts.facter_version_to_filter(version)
    db = FacterDB.get_facts({ :facterversion =>  facter_version_filter})
  end


  filter = []
  opts[:supported_os].map do |os_sup|
    if os_sup['operatingsystemrelease']
      os_sup['operatingsystemrelease'].map do |operatingsystemmajrelease|
        opts[:hardwaremodels].each do |hardwaremodel|

          os_release_filter = "/^#{operatingsystemmajrelease.split(' ')[0]}/"
          if os_sup['operatingsystem'] =~ /BSD/i
            hardwaremodel = 'amd64'
          elsif os_sup['operatingsystem'] =~ /Solaris/i
            hardwaremodel = 'i86pc'
          elsif os_sup['operatingsystem'] =~ /Windows/i
            hardwaremodel = version =~ /^[12]\./ ? 'x64' : 'x86_64'
            os_sup['operatingsystem'] = os_sup['operatingsystem'].downcase
            operatingsystemmajrelease = operatingsystemmajrelease[/\A(?:Server )?(.+)/i, 1]

            # force quoting because windows releases can contain spaces
            os_release_filter = "\"#{operatingsystemmajrelease}\""

            if operatingsystemmajrelease == '2016' && Puppet::Util::Package.versioncmp(version, '3.4') < 0
              os_release_filter = '/^10\\.0\\./'
            end
          end

          filter << {
              :facterversion          => facter_version_filter,
              :operatingsystem        => os_sup['operatingsystem'],
              :operatingsystemrelease => os_release_filter,
              :hardwaremodel          => hardwaremodel,
          }
        end
      end
    else
      opts[:hardwaremodels].each do |hardwaremodel|
        filter << {
            :facterversion   => facter_version_filter,
            :operatingsystem => os_sup['operatingsystem'],
            :hardwaremodel   => hardwaremodel,
        }
      end
    end
  end

  received_facts = FacterDB::get_facts(filter)
  unless received_facts.any?
    RspecPuppetFacts.warning "No facts were found in the FacterDB for: #{filter.inspect}"
    return {}
  end

  unless version == facterversion
    if RspecPuppetFacts.spec_facts_strict?
      raise ArgumentError, "No facts were found in the FacterDB for Facter v#{facterversion}, aborting"
    else
      RspecPuppetFacts.warning "No facts were found in the FacterDB for Facter v#{facterversion}, using v#{version} instead"
    end
  end

  os_facts_hash = {}
  received_facts.map do |facts|
    # Fix facter bug
    if facts[:operatingsystem] == 'Ubuntu'
      operatingsystemmajrelease = facts[:operatingsystemrelease].split('.')[0..1].join('.')
    elsif facts[:operatingsystem] == 'OpenBSD'
      operatingsystemmajrelease = facts[:operatingsystemrelease]
    elsif facts[:operatingsystem] == 'windows' && facts[:operatingsystemrelease].start_with?('10.0.')
      operatingsystemmajrelease = '2016'
    else
      if facts[:operatingsystemmajrelease].nil?
        operatingsystemmajrelease = facts[:operatingsystemrelease].split('.')[0]
      else
        operatingsystemmajrelease = facts[:operatingsystemmajrelease]
      end
    end
    os = "#{facts[:operatingsystem].downcase}-#{operatingsystemmajrelease}-#{facts[:hardwaremodel]}"
    next unless os.start_with? RspecPuppetFacts.spec_facts_os_filter if RspecPuppetFacts.spec_facts_os_filter
    facts.merge! RspecPuppetFacts.common_facts
    os_facts_hash[os] = RspecPuppetFacts.with_custom_facts(os, facts)
  end
  os_facts_hash
end