module PuppetLitmus::RakeHelper

helper methods for the litmus rake tasks

Public Instance Methods

build_module(module_dir = nil, target_dir = nil) click to toggle source

Build the module in `module_dir` and put the resulting compressed tarball into `target_dir`.

@param opts Hash of options to build the module @param module_dir [String] The path of the module to build. If missing defaults to Dir.pwd @param target_dir [String] The path the module will be built into. The default is <module_dir>/pkg @return [String] The path to the built module

# File lib/puppet_litmus/rake_helper.rb, line 251
def build_module(module_dir = nil, target_dir = nil)
  require 'puppet/modulebuilder'

  module_dir ||= Dir.pwd
  target_dir ||= File.join(source_dir, 'pkg')

  puts "Building '#{module_dir}' into '#{target_dir}'"
  builder = Puppet::Modulebuilder::Builder.new(module_dir, target_dir, nil)

  # Force the metadata to be read. Raises if metadata could not be found
  _metadata = builder.metadata

  builder.build
end
build_modules_in_dir(source_dir, target_dir = nil) click to toggle source

Builds all the modules in a specified directory

@param source_dir [String] the directory to get the modules from @param target_dir [String] temporary location to store tarballs before uploading. This directory will be cleaned before use. The default is <source_dir>/pkg @return [Array] an array of module tars' filenames

# File lib/puppet_litmus/rake_helper.rb, line 271
def build_modules_in_dir(source_dir, target_dir = nil)
  target_dir ||= File.join(Dir.pwd, 'pkg')
  # remove old build dir if exists, before we build afresh
  FileUtils.rm_rf(target_dir) if File.directory?(target_dir)

  module_tars = Dir.entries(source_dir).map do |entry|
    next if ['.', '..'].include? entry

    module_dir = File.join(source_dir, entry)
    next unless File.directory? module_dir

    build_module(module_dir, target_dir)
  end
  module_tars.compact
end
build_modules_in_folder(source_folder) click to toggle source

@deprecated Use `build_modules_in_dir` instead

# File lib/puppet_litmus/rake_helper.rb, line 288
def build_modules_in_folder(source_folder)
  build_modules_in_dir(source_folder)
end
check_bolt_errors(result_set) click to toggle source

Parse out errors messages in result set returned by Bolt command.

@param result_set [Array] result set returned by Bolt command. @return [Hash] Errors grouped by target.

# File lib/puppet_litmus/rake_helper.rb, line 400
def check_bolt_errors(result_set)
  errors = {}
  # iterate through each error
  result_set.each do |target_result|
    status = target_result['status']
    # jump to the next one when there is not fail
    next if status != 'failure'

    target = target_result['target']
    # get some info from error
    errors[target] = target_result['value']
  end
  errors
end
check_connectivity?(inventory_hash, target_node_name) click to toggle source
# File lib/puppet_litmus/rake_helper.rb, line 357
def check_connectivity?(inventory_hash, target_node_name)
  Honeycomb.start_span(name: 'litmus.check_connectivity') do |span|
    ENV['HONEYCOMB_TRACE'] = span.to_trace_header
    # if we're only checking connectivity for a single node
    if target_node_name
      span.add_field('litmus.target_node_name', target_node_name)
      add_platform_field(inventory_hash, target_node_name)
    end

    include ::BoltSpec::Run
    target_nodes = find_targets(inventory_hash, target_node_name)
    puts "Checking connectivity for #{target_nodes.inspect}"
    span.add_field('litmus.target_nodes', target_nodes)

    results = run_command('cd .', target_nodes, config: nil, inventory: inventory_hash)
    span.add_field('litmus.bolt_result', results)
    failed = []
    results.reject { |r| r['status'] == 'success' }.each do |result|
      puts "Failure connecting to #{result['target']}:\n#{result.inspect}"
      failed.push(result['target'])
    end
    span.add_field('litmus.connectivity_success', results.select { |r| r['status'] == 'success' })
    span.add_field('litmus.connectivity_failure', results.reject { |r| r['status'] == 'success' })
    raise "Connectivity has failed on: #{failed}" unless failed.length.zero?

    puts 'Connectivity check PASSED.'
    true
  end
end
configure_path(inventory_hash) click to toggle source
# File lib/puppet_litmus/rake_helper.rb, line 231
def configure_path(inventory_hash)
  results = []
  # fix the path on ssh_nodes
  unless inventory_hash['groups'].select { |group| group['name'] == 'ssh_nodes' && !group['targets'].empty? }.size.zero?
    results << run_command('echo PATH="$PATH:/opt/puppetlabs/puppet/bin" > /etc/environment',
                           'ssh_nodes', config: nil, inventory: inventory_hash)
  end
  unless inventory_hash['groups'].select { |group| group['name'] == 'winrm_nodes' && !group['targets'].empty? }.size.zero?
    results << run_command('[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\Program Files\Puppet Labs\Puppet\bin;C:\Program Files (x86)\Puppet Labs\Puppet\bin", "Machine")',
                           'winrm_nodes', config: nil, inventory: inventory_hash)
  end
  results
end
get_metadata_operating_systems(metadata) { |"#{os_name}-#{downcase}-x86_64".delete(' ')| ... } click to toggle source

Gets a string representing the operating system and version.

@param metadata [Hash] metadata to parse for operating system info @return [String] the operating system string with version info for use in provisioning.

# File lib/puppet_litmus/rake_helper.rb, line 64
def get_metadata_operating_systems(metadata)
  return unless metadata.is_a?(Hash)
  return unless metadata['operatingsystem_support'].is_a?(Array)

  metadata['operatingsystem_support'].each do |os_info|
    next unless os_info['operatingsystem'] && os_info['operatingsystemrelease']

    os_name = case os_info['operatingsystem']
              when 'Amazon', 'Archlinux', 'AIX', 'OSX'
                next
              when 'OracleLinux'
                'oracle'
              when 'Windows'
                'win'
              else
                os_info['operatingsystem'].downcase
              end

    os_info['operatingsystemrelease'].each do |release|
      version = case os_name
                when 'ubuntu', 'osx'
                  release.sub('.', '')
                when 'sles'
                  release.gsub(%r{ SP[14]}, '')
                when 'win'
                  release = release.delete('.') if release.include? '8.1'
                  release.sub('Server', '').sub('10', '10-pro')
                else
                  release
                end

      yield "#{os_name}-#{version.downcase}-x86_64".delete(' ')
    end
  end
end
install_agent(collection, targets, inventory_hash) click to toggle source
# File lib/puppet_litmus/rake_helper.rb, line 209
def install_agent(collection, targets, inventory_hash)
  Honeycomb.start_span(name: 'litmus.install_agent') do |span|
    ENV['HONEYCOMB_TRACE'] = span.to_trace_header
    span.add_field('litmus.collection', collection)
    span.add_field('litmus.targets', targets)

    include ::BoltSpec::Run
    params = if collection.nil?
               {}
             else
               { 'collection' => collection }
             end
    raise "puppet_agent was not found in #{DEFAULT_CONFIG_DATA['modulepath']}, please amend the .fixtures.yml file" \
     unless File.directory?(File.join(DEFAULT_CONFIG_DATA['modulepath'], 'puppet_agent'))

    # using boltspec, when the runner is called it changes the inventory_hash dropping the version field. The clone works around this
    bolt_result = run_task('puppet_agent::install', targets, params, config: DEFAULT_CONFIG_DATA, inventory: inventory_hash.clone)
    raise_bolt_errors(bolt_result, 'Installation of agent failed.')
    bolt_result
  end
end
install_module(inventory_hash, target_node_name, module_tar, module_repository = nil, ignore_dependencies = false) click to toggle source

Install a specific module tarball to the specified target. This method installs dependencies using a forge repository.

@param inventory_hash [Hash] the pre-loaded inventory @param target_node_name [String] the name of the target where the module should be installed @param module_tar [String] the filename of the module tarball to upload @param module_repository [String] the URL for the forge to use for downloading modules. Defaults to the public Forge API. @param ignore_dependencies [Boolean] flag used to ignore module dependencies defaults to false. @return a bolt result

# File lib/puppet_litmus/rake_helper.rb, line 301
def install_module(inventory_hash, target_node_name, module_tar, module_repository = nil, ignore_dependencies = false) # rubocop:disable Style/OptionalBooleanParameter
  Honeycomb.start_span(name: 'install_module') do |span|
    ENV['HONEYCOMB_TRACE'] = span.to_trace_header
    span.add_field('litmus.target_node_name', target_node_name)
    span.add_field('litmus.module_tar', module_tar)

    # make sure the module to install is not installed
    # otherwise `puppet module install` might silently skip it
    module_name = File.basename(module_tar, '.tar.gz').split('-', 3)[0..1].join('-')
    uninstall_module(inventory_hash.clone, target_node_name, module_name, force: true)

    include ::BoltSpec::Run

    target_nodes = find_targets(inventory_hash, target_node_name)
    span.add_field('litmus.target_nodes', target_nodes)
    bolt_result = upload_file(module_tar, File.basename(module_tar), target_nodes, options: {}, config: nil, inventory: inventory_hash.clone)
    raise_bolt_errors(bolt_result, 'Failed to upload module.')

    module_repository_opts = "--module_repository '#{module_repository}'" unless module_repository.nil?
    install_module_command = "puppet module install #{module_repository_opts} #{File.basename(module_tar)}"
    install_module_command += ' --ignore-dependencies --force' if ignore_dependencies.to_s.downcase == 'true'
    span.add_field('litmus.install_module_command', install_module_command)

    bolt_result = run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash.clone)
    raise_bolt_errors(bolt_result, "Installation of package #{File.basename(module_tar)} failed.")
    bolt_result
  end
end
metadata_module_name() click to toggle source
# File lib/puppet_litmus/rake_helper.rb, line 330
def metadata_module_name
  require 'json'
  raise 'Could not find metadata.json' unless File.exist?(File.join(Dir.pwd, 'metadata.json'))

  metadata = JSON.parse(File.read(File.join(Dir.pwd, 'metadata.json')))
  raise 'Could not read module name from metadata.json' if metadata['name'].nil?

  metadata['name']
end
provision(provisioner, platform, inventory_vars) click to toggle source
# File lib/puppet_litmus/rake_helper.rb, line 119
def provision(provisioner, platform, inventory_vars)
  include ::BoltSpec::Run
  raise "the provision module was not found in #{DEFAULT_CONFIG_DATA['modulepath']}, please amend the .fixtures.yml file" unless
    File.directory?(File.join(DEFAULT_CONFIG_DATA['modulepath'], 'provision'))

  params = { 'action' => 'provision', 'platform' => platform, 'inventory' => Dir.pwd }
  params['vars'] = inventory_vars unless inventory_vars.nil?

  Honeycomb.add_field_to_trace('litmus.provisioner', provisioner)
  Honeycomb.start_span(name: 'litmus.provision') do |span|
    ENV['HONEYCOMB_TRACE'] = span.to_trace_header
    span.add_field('litmus.platform', platform)

    task_name = provisioner_task(provisioner)
    span.add_field('litmus.task_name', task_name)
    span.add_field('litmus.params', params)
    span.add_field('litmus.config', DEFAULT_CONFIG_DATA)

    bolt_result = run_task(task_name, 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
    span.add_field('litmus.result', bolt_result)
    span.add_field('litmus.node_name', bolt_result&.first&.dig('value', 'node_name'))

    raise_bolt_errors(bolt_result, "provisioning of #{platform} failed.")

    bolt_result
  end
end
provision_list(provision_hash, key) click to toggle source
# File lib/puppet_litmus/rake_helper.rb, line 147
def provision_list(provision_hash, key)
  provisioner = provision_hash[key]['provisioner']
  inventory_vars = provision_hash[key]['vars']
  # Splat the params into environment variables to pass to the provision task but only in this runspace
  provision_hash[key]['params']&.each { |k, value| ENV[k.upcase] = value.to_s }
  results = []

  Honeycomb.current_span.add_field('litmus.images', provision_hash[key]['images'])
  provision_hash[key]['images'].each do |image|
    results << provision(provisioner, image, inventory_vars)
  end
  results
end
provisioner_task(provisioner) click to toggle source
# File lib/puppet_litmus/rake_helper.rb, line 387
def provisioner_task(provisioner)
  if SUPPORTED_PROVISIONERS.include?(provisioner)
    "provision::#{provisioner}"
  else
    warn "WARNING: Unsuported provisioner '#{provisioner}', try #{SUPPORTED_PROVISIONERS.join('/')}"
    provisioner.to_s
  end
end
raise_bolt_errors(result_set, error_msg) click to toggle source

Parse out errors messages in result set returned by Bolt command. If there are errors, raise them.

@param result_set [Array] result set returned by Bolt command. @param error_msg [String] error message to raise when errors are detected. The actual errors will be appended.

# File lib/puppet_litmus/rake_helper.rb, line 419
def raise_bolt_errors(result_set, error_msg)
  errors = check_bolt_errors(result_set)

  unless errors.empty?
    formatted_results = errors.map { |k, v| "  #{k}: #{v.inspect}" }.join("\n")
    raise "#{error_msg}\nResults:\n#{formatted_results}}"
  end

  nil
end
run_local_command(command) click to toggle source

Executes a command on the test runner.

@param command [String] command to execute. @return [Object] the standard out stream.

# File lib/puppet_litmus/rake_helper.rb, line 104
def run_local_command(command)
  Honeycomb.start_span(name: 'litmus.run_local_command') do |span|
    ENV['HONEYCOMB_TRACE'] = span.to_trace_header
    span.add_field('litmus.command', command)

    require 'open3'
    stdout, stderr, status = Open3.capture3(command)
    error_message = "Attempted to run\ncommand:'#{command}'\nstdout:#{stdout}\nstderr:#{stderr}"

    raise error_message unless status.to_i.zero?

    stdout
  end
end
start_spinner(message) click to toggle source
# File lib/puppet_litmus/rake_helper.rb, line 430
def start_spinner(message)
  if (ENV['CI'] || '').downcase == 'true'
    puts message
    spinner = Thread.new do
      # CI systems are strange beasts, we only output a '.' every wee while to keep the terminal alive.
      loop do
        printf '.'
        sleep(10)
      end
    end
  else
    require 'tty-spinner'
    spinner = TTY::Spinner.new("[:spinner] #{message}")
    spinner.auto_spin
  end
  spinner
end
stop_spinner(spinner) click to toggle source
# File lib/puppet_litmus/rake_helper.rb, line 448
def stop_spinner(spinner)
  if (ENV['CI'] || '').downcase == 'true'
    Thread.kill(spinner)
  else
    spinner.success
  end
end
tear_down(node_name, inventory_hash) click to toggle source
# File lib/puppet_litmus/rake_helper.rb, line 193
def tear_down(node_name, inventory_hash)
  Honeycomb.start_span(name: 'litmus.tear_down') do |span|
    ENV['HONEYCOMB_TRACE'] = span.to_trace_header
    # how do we know what provisioner to use

    span.add_field('litmus.node_name', node_name)
    add_platform_field(inventory_hash, node_name)

    params = { 'action' => 'tear_down', 'node_name' => node_name, 'inventory' => Dir.pwd }
    node_facts = facts_from_node(inventory_hash, node_name)
    bolt_result = run_task(provisioner_task(node_facts['provisioner']), 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
    raise_bolt_errors(bolt_result, "tear_down of #{node_name} failed.")
    bolt_result
  end
end
tear_down_nodes(targets, inventory_hash) click to toggle source
# File lib/puppet_litmus/rake_helper.rb, line 161
def tear_down_nodes(targets, inventory_hash)
  Honeycomb.start_span(name: 'litmus.tear_down_nodes') do |span|
    ENV['HONEYCOMB_TRACE'] = span.to_trace_header
    span.add_field('litmus.targets', targets)

    include ::BoltSpec::Run
    config_data = { 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') }
    raise "the provision module was not found in #{config_data['modulepath']}, please amend the .fixtures.yml file" unless File.directory?(File.join(config_data['modulepath'], 'provision'))

    results = {}
    targets.each do |node_name|
      next if node_name == 'litmus_localhost'

      result = tear_down(node_name, inventory_hash)
      # Some provisioners tear_down targets that were created as a batch job.
      # These provisioners should return the list of additional targets
      # removed so that we do not attempt to process them.
      if result != [] && result[0]['value'].key?('removed')
        removed_targets = result[0]['value']['removed']
        result[0]['value'].delete('removed')
        removed_targets.each do |removed_target|
          targets.delete(removed_target)
          results[removed_target] = result
        end
      end

      results[node_name] = result unless result == []
    end
    results
  end
end
uninstall_module(inventory_hash, target_node_name, module_to_remove = nil, **opts) click to toggle source

Uninstall a module from a specified target @param inventory_hash [Hash] the pre-loaded inventory @param target_node_name [String] the name of the target where the module should be uninstalled @param module_to_remove [String] the name of the module to remove. Defaults to the module under test. @param opts [Hash] additional options to pass on to `puppet module uninstall`

# File lib/puppet_litmus/rake_helper.rb, line 345
def uninstall_module(inventory_hash, target_node_name, module_to_remove = nil, **opts)
  include ::BoltSpec::Run
  module_name = module_to_remove || metadata_module_name
  target_nodes = find_targets(inventory_hash, target_node_name)
  install_module_command = "puppet module uninstall #{module_name}"
  install_module_command += ' --force' if opts[:force]
  bolt_result = run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash)
  # `puppet module uninstall --force` fails if the module is not installed. Ignore errors when force is set
  raise_bolt_errors(bolt_result, "uninstalling #{module_name} failed.") unless opts[:force]
  bolt_result
end
with_retries(options: { tries: Float::INFINITY }, max_wait_minutes: 15) { || ... } click to toggle source
# File lib/puppet_litmus/rake_helper.rb, line 467
def with_retries(options: { tries: Float::INFINITY }, max_wait_minutes: 15)
  stop = Time.now + (max_wait_minutes * 60)
  Retryable.retryable(options.merge(not: [LitmusTimeoutError])) do
    raise LitmusTimeoutError if Time.now > stop

    yield
  end
end