class PDQTest::Puppet
Constants
- CLASS_RE
- CONTAINER_PATHS
platform paths
- DEFAULT_FACTS_C
- EXAMPLES_DIR
- FIXTURES
- HOST_PATHS
path for common things on the host computer running pdqtest (vm, laptop, etc)
- MANIFESTS_DIR
- METADATA
- SETTINGS
- TMP_PUPPETFILE
- XATS_TESTS
statics
Public Class Methods
class2filename(c)
click to toggle source
# File lib/pdqtest/puppet.rb, line 204 def self.class2filename(c) if c == module_name f = "#{MANIFESTS_DIR}#{File::ALT_SEPARATOR||File::SEPARATOR}init.pp" else f = c.gsub(module_name, MANIFESTS_DIR).gsub('::', (File::ALT_SEPARATOR||File::SEPARATOR)) + '.pp' end f end
cp(key)
click to toggle source
# File lib/pdqtest/puppet.rb, line 91 def self.cp(key) CONTAINER_PATHS[Util.host_platform][key] || raise("missing variable CONTAINER_PATHS[#{Util.host_platform}][#{key}]") end
debug_arg()
click to toggle source
# File lib/pdqtest/puppet.rb, line 409 def self.debug_arg @@debug ? "--debug --trace --evaltrace" : "" end
filename2class(f)
click to toggle source
# File lib/pdqtest/puppet.rb, line 214 def self.filename2class(f) # strip any leading `./` f.sub!(/^\.\//,'') if f == "#{MANIFESTS_DIR}#{File::ALT_SEPARATOR||File::SEPARATOR}init.pp" c = module_name else c = f.gsub(MANIFESTS_DIR, "#{module_name}").gsub(File::ALT_SEPARATOR||File::SEPARATOR, '::').gsub('.pp','') end c end
find_classes()
click to toggle source
find the available classes in this module
# File lib/pdqtest/puppet.rb, line 241 def self.find_classes() mod_name = module_name classes = [] if Dir.exists?(MANIFESTS_DIR) Find.find(MANIFESTS_DIR) do |m| if m =~ /\.pp$/ # check the file contains a valid class if ! File.readlines(m).grep(CLASS_RE).empty? # Class detected, work out class name and add to list of found classes classes << filename2class(m) else $logger.info "no puppet class found in #{m}" end end end end classes end
find_examples()
click to toggle source
# File lib/pdqtest/puppet.rb, line 227 def self.find_examples() examples = [] if Dir.exists?(EXAMPLES_DIR) Find.find(EXAMPLES_DIR) do |e| if ! File.directory?(e) && ! File.readlines(e).grep(setting(:magic_marker_re)).empty? examples << e end end end $logger.info "examples to run" + examples.to_s examples end
fixtures_yml()
click to toggle source
Regenerate .fixtures.yml from metadata github.com/puppetlabs/puppetlabs_spec_helper#using-fixtures The format looks like this:
“`
fixtures: forge_modules: stdlib: repo: "puppetlabs/stdlib" ref: "2.6.0"
Note that ref doesn't accept a range like metadata.json does, but then r10k doesn't follow dependencies anyway so may as well just code a static known good working version in it and not worry —> use a known good version for testing, not a range `metadata.json`
# File lib/pdqtest/puppet.rb, line 170 def self.fixtures_yml fixtures = { "fixtures" => { "forge_modules" => {} } } module_metadata["dependencies"].each do |dep| forge_name = dep["name"] puppet_name = dep["name"].split("-")[1] ref = dep["version_requirement"] fixtures["fixtures"]["forge_modules"][puppet_name] = { "repo" => forge_name, "ref" => ref, } end # now we have our list of fixtures from `metadata.json`, merge it with any # exiting .fixtures.yml content to preserve any git fixtures that may have # been added manually. Clobber/update any existing content that is NOT # from `metadata.json` while preserving things that are from git. If we # have ended up declaring different versions of the same module then its # up to user's to resolve this by removing the dependency from either # `metadata.json` or `.fixtures.yml`. You shouldn't be depending on git # resources in `metadata.json` anyway so this shoudln't be an issue if File.exist?(FIXTURES) existing_fixtures = YAML.load_file(FIXTURES) existing_fixtures.deep_merge(fixtures) fixtures = existing_fixtures end File.open(FIXTURES, 'w') { |f| YAML.dump(fixtures, f) } end
get_bats_executed()
click to toggle source
# File lib/pdqtest/puppet.rb, line 118 def self.get_bats_executed @@bats_executed end
get_setup_executed()
click to toggle source
# File lib/pdqtest/puppet.rb, line 122 def self.get_setup_executed @@setup_executed end
hp(key)
click to toggle source
# File lib/pdqtest/puppet.rb, line 96 def self.hp(key) HOST_PATHS[Util.host_platform][key] || raise("missing variable HOST_PATHS[#{Util.host_platform}][#{key}]") end
info()
click to toggle source
# File lib/pdqtest/puppet.rb, line 417 def self.info $logger.info "Parsed module name: #{module_name}" end
install_modules()
click to toggle source
extract a Puppetfile from metadata.json and install modules using r10k
# File lib/pdqtest/puppet.rb, line 422 def self.install_modules() json = JSON.parse(File.read(METADATA)) puppetfile = [] if json.has_key?("dependencies") json["dependencies"].each { |dependency| line = "mod '#{dependency['name']}'" if dependency.has_key?("version_requirement") # R10K supports specifc named version or 'latest', not the rich versions defined in metadata. To make this # work we will drop any version that specifies a range and just install the latest if dependency['version_requirement'].match?(/^\d/) line += ", '#{dependency['version_requirement']}'" end end puppetfile << line } end File.open(TMP_PUPPETFILE, "w") do |f| f.puts(puppetfile) end PDQTest::Emoji.emoji_message(:slow, "I'm downloading The Internet, please hold...") cmd = "bundle exec r10k puppetfile install --verbose --moduledir ./spec/fixtures/modules --puppetfile #{TMP_PUPPETFILE}" status = system(cmd) if ! status $logger.error "Failed to run the R10K command: #{cmd}" end status end
module_metadata()
click to toggle source
# File lib/pdqtest/puppet.rb, line 130 def self.module_metadata if File.exist? METADATA file = File.read(METADATA) JSON.parse(file) else raise("Puppet metadata not found at #{METADATA} - not a valid puppet module") end end
module_name()
click to toggle source
# File lib/pdqtest/puppet.rb, line 145 def self.module_name module_metadata['name'].split(/[\/-]/)[1] end
os_support()
click to toggle source
# File lib/pdqtest/puppet.rb, line 149 def self.os_support module_metadata['operatingsystem_support'] || [] end
puppet_apply(example)
click to toggle source
# File lib/pdqtest/puppet.rb, line 413 def self.puppet_apply(example) "cd #{Docker.test_dir} ; #{setting(:puppet)} apply #{debug_arg} --detailed-exitcodes #{example}" end
puppet_strings()
click to toggle source
# File lib/pdqtest/puppet.rb, line 508 def self.puppet_strings # Runs in the context of _our_ PDQTest bundle so doesn't need clean env system("bundle exec puppet strings generate --format markdown") end
reset_bats_executed()
click to toggle source
# File lib/pdqtest/puppet.rb, line 110 def self.reset_bats_executed @@bats_executed = [] end
reset_setup_executed()
click to toggle source
# File lib/pdqtest/puppet.rb, line 114 def self.reset_setup_executed @@setup_executed = [] end
run(cc, container, example=nil)
click to toggle source
# File lib/pdqtest/puppet.rb, line 353 def self.run(cc, container, example=nil) # we must always have ./spec/fixtures/modules because we need to create a # symlink back to the main module inside here... # (spec/fixtures/modules/foo -> /testcase) if ! Dir.exists?('spec/fixtures/modules') $logger.info "creating empty spec/fixtures/modules, if you module fails to run due " "to missing dependencies run `make` or `pdqtest all` to retrieve them" FileUtils.mkdir_p('spec/fixtures/modules') end status = true $logger.info "...running container setup" setup_start = Time.now res = PDQTest::Execution.exec(cc, container, setup) setup_end = Time.now status &= PDQTest::Execution.exec_status(res) if Util.is_windows # write a script to allow user to update modules $logger.info "wasted #{((setup_end - setup_start))} seconds of your life on windows tax" File.open("refresh.ps1", 'w') do |file| res[:REAL_CMD].each do |c| file.puts("#{c[0]} #{c[1]} \"#{c[2]}\"") end end Emoji.emoji_message( :shame, "run refresh.ps1 to update container after changing files on host!", ::Logger::WARN) end if status $logger.info "...run tests" if example status &= run_example(cc, container, example) if ! status $logger.error "Example #{example} failed!" end else find_examples.each { |e| if status status &= run_example(cc, container, e) if ! status $logger.error "Example #{e} failed! - skipping rest of tests" end end } end else PDQTest::Execution.log_all(res) $logger.error "Error running puppet setup, see previous error, command was: #{res[:REAL_CMD]}" end PDQTest::Emoji.partial_status(status, 'Puppet') status end
run_example(cc, container, example)
click to toggle source
# File lib/pdqtest/puppet.rb, line 309 def self.run_example(cc, container, example) $logger.info "testing #{example}" status = false if setup_test(cc, container, example) # see if we should run a bats test before running puppet if xats_test(cc, container, example, setting(:before_suffix)) # run puppet apply - 1st run res = PDQTest::Execution.exec(cc, container, puppet_apply(example)) PDQTest::Execution.log_out(res) if PDQTest::Execution.exec_status(res, true) # allow 2 as exit status if @@skip_second_run $logger.info "Skipping idempotency check as you requested..." # check the system right now since puppet ran OK once status = xats_test(cc, container, example, setting(:after_suffix)) else # run puppet apply - 2nd run (check for idempotencey/no more changes) res = PDQTest::Execution.exec(cc, container, puppet_apply(example)) PDQTest::Execution.log_out(res) # run the bats test if nothing failed yet if PDQTest::Execution.exec_status(res) # only allow 0 as exit status status = xats_test(cc, container, example, setting(:after_suffix)) else $logger.error "Not idempotent: #{example}" end end else $logger.error "First puppet run of #{example} failed (status: #{res[:STATUS]})" end else $logger.error "#{setting(:name)} tests to run before #{example} failed" end else $logger.error "Setup script for #{example} failed (see previous error)" end status end
save_module_metadata(metadata)
click to toggle source
# File lib/pdqtest/puppet.rb, line 139 def self.save_module_metadata(metadata) File.open(METADATA,"w") do |f| f.write(JSON.pretty_generate(metadata)) end end
set_debug(debug)
click to toggle source
# File lib/pdqtest/puppet.rb, line 126 def self.set_debug(debug) @@debug = debug end
setting(key)
click to toggle source
# File lib/pdqtest/puppet.rb, line 101 def self.setting(key) SETTINGS[Util.host_platform][key] || raise("missing variable SETTINGS[#{Util.host_platform}][#{key}]") end
setup()
click to toggle source
# File lib/pdqtest/puppet.rb, line 455 def self.setup commands = [] # link testcase module commands << Util.mk_link( Util.joinp(cp(:module_dir), module_name), PDQTest::Docker.test_dir ) # link dependency modules sfm = Util.joinp("spec", "fixtures", "modules") if ! File.exist? sfm raise("Modules directory does not exist - please run unit tests first to create it") end Dir.entries(sfm).select { |entry| File.directory?(Util.joinp(sfm, entry)) && !(entry =='.' || entry == '..') }.reject { |entry| # do not copy the symlink of ourself (pdk creates it) entry == module_name }.each { |entry| commands << Util.mk_link( Util.joinp(cp(:module_dir), entry), Util.joinp(PDQTest::Docker.test_dir, sfm, entry) ) } # link hieradata if Dir.exist? hp(:hiera_dir) commands << Util.mk_link( cp(:hiera_dir), Util.joinp(Docker.test_dir, hp(:hiera_dir)) ) end # link hiera.yaml if File.exist? hp(:hiera_yaml) commands << Util.mk_link( cp(:hiera_yaml), Util.joinp(Docker.test_dir, hp(:hiera_yaml)) ) end # link external facts commands << Util.mk_link( Util.joinp(cp(:facts_dir), DEFAULT_FACTS_C), Util.joinp(Docker.test_dir, hp(:default_facts)) ) commands end
setup_test(cc, container, example)
click to toggle source
# File lib/pdqtest/puppet.rb, line 285 def self.setup_test(cc, container, example) setup_script = Util.joinp(XATS_TESTS, test_basename(example)) + setting(:setup_suffix) if File.exists?(setup_script) script = File.read(setup_script) $logger.debug "setup script: \n #{script}" if script.strip.empty? $logger.info "skipping empty setup script at #{setup_script}" status = true else $logger.info "Setting up test for #{example}" res = PDQTest::Execution.exec(cc, container, script) status = PDQTest::Execution.exec_status(res) PDQTest::Execution.log_all(res) end @@setup_executed << setup_script else $logger.info "no setup file for #{example} (should be in #{setup_script})" status = true end status end
skip_second_run(skip_second_run)
click to toggle source
# File lib/pdqtest/puppet.rb, line 106 def self.skip_second_run(skip_second_run) @@skip_second_run = skip_second_run end
test_basename(t)
click to toggle source
# File lib/pdqtest/puppet.rb, line 261 def self.test_basename(t) # remove any leading `./` t.sub!(/^\.\//, '') # remove examples/ and .pp # eg ./examples/apache/mod/mod_php.pp --> apache/mod/mod_php t.gsub(EXAMPLES_DIR + '/','').gsub('.pp','') end
xats_test(cc, container, example, suffix)
click to toggle source
# File lib/pdqtest/puppet.rb, line 269 def self.xats_test(cc, container, example, suffix) testcase = Util.joinp(XATS_TESTS, test_basename(example) + suffix) if File.exists?(testcase) $logger.info "*** #{setting(:name)} test **** #{setting(:test_cmd)} #{testcase}" res = PDQTest::Execution.exec(cc, container, "cd #{Docker.test_dir} ; #{setting(:test_cmd)} #{testcase}") status = PDQTest::Execution.exec_status(res) PDQTest::Execution.log_all(res) @@bats_executed << testcase else $logger.info "no #{suffix} tests for #{example} (should be at #{testcase})" status = true end status end