Module: PuppetLitmus::RakeHelper

Included in:
PuppetLitmus
Defined in:
lib/puppet_litmus/rake_helper.rb

Overview

helper methods for the litmus rake tasks

Defined Under Namespace

Classes: LitmusTimeoutError

Constant Summary collapse

DEFAULT_CONFIG_DATA =

DEFAULT_CONFIG_DATA should be frozen for our safety, but it needs to work around github.com/puppetlabs/bolt/pull/1696

{ 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') }
SUPPORTED_PROVISIONERS =
%w[abs docker docker_exp provision_service vagrant vmpooler].freeze

Instance Method Summary collapse

Instance Method Details

#build_module(module_dir = nil, target_dir = nil) ⇒ String

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

Parameters:

  • opts

    Hash of options to build the module

  • module_dir (String) (defaults to: nil)

    The path of the module to build. If missing defaults to Dir.pwd

  • target_dir (String) (defaults to: nil)

    The path the module will be built into. The default is <module_dir>/pkg

Returns:

  • (String)

    The path to the built module



180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/puppet_litmus/rake_helper.rb', line 180

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
   = builder.

  builder.build
end

#build_modules_in_dir(source_dir, target_dir = nil) ⇒ Array

Builds all the modules in a specified directory

Parameters:

  • source_dir (String)

    the directory to get the modules from

  • target_dir (String) (defaults to: nil)

    temporary location to store tarballs before uploading. This directory will be cleaned before use. The default is <source_dir>/pkg

Returns:

  • (Array)

    an array of module tars’ filenames



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/puppet_litmus/rake_helper.rb', line 200

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) ⇒ Object

Deprecated.

Use ‘build_modules_in_dir` instead



217
218
219
# File 'lib/puppet_litmus/rake_helper.rb', line 217

def build_modules_in_folder(source_folder)
  build_modules_in_dir(source_folder)
end

#check_bolt_errors(result_set) ⇒ Hash

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

Parameters:

  • result_set (Array)

    result set returned by Bolt command.

Returns:

  • (Hash)

    Errors grouped by target.



311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/puppet_litmus/rake_helper.rb', line 311

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) ⇒ Boolean

Returns:

  • (Boolean)


278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/puppet_litmus/rake_helper.rb', line 278

def check_connectivity?(inventory_hash, target_node_name)
  # if we're only checking connectivity for a single node
  add_platform_field(inventory_hash, target_node_name) if target_node_name

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

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

  puts 'Connectivity check PASSED.'
  true
end

#configure_path(inventory_hash) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/puppet_litmus/rake_helper.rb', line 160

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? }.empty?
    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? }.empty?
    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) ⇒ String

Gets a string representing the operating system and version.

Parameters:

  • metadata (Hash)

    metadata to parse for operating system info

Returns:

  • (String)

    the operating system string with version info for use in provisioning.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/puppet_litmus/rake_helper.rb', line 16

def ()
  return unless .is_a?(Hash)
  return unless ['operatingsystem_support'].is_a?(Array)

  ['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) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/puppet_litmus/rake_helper.rb', line 133

def install_agent(collection, targets, inventory_hash)
  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)
  targets.each do |target|
    params = {
      'path' => '/opt/puppetlabs/bin'
    }
    node_facts = facts_from_node(inventory_hash, target)
    next unless node_facts['provisioner'] == 'vagrant'

    puts "Adding puppet agent binary to the secure_path on target #{target}."
    result = run_task('provision::fix_secure_path', target, params, config: DEFAULT_CONFIG_DATA, inventory: inventory_hash.clone)
    raise_bolt_errors(result, "Failed to add the Puppet agent binary to the secure_path on target #{target}.")
  end
  raise_bolt_errors(bolt_result, 'Installation of agent failed.')
  bolt_result
end

#install_module(inventory_hash, target_node_name, module_tar, module_repository = nil, ignore_dependencies = false) ⇒ Object

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

Parameters:

  • inventory_hash (Hash)

    the pre-loaded inventory

  • target_node_name (String)

    the name of the target where the module should be installed

  • module_tar (String)

    the filename of the module tarball to upload

  • module_repository (String) (defaults to: nil)

    the URL for the forge to use for downloading modules. Defaults to the public Forge API.

  • ignore_dependencies (Boolean) (defaults to: false)

    flag used to ignore module dependencies defaults to false.

Returns:

  • a bolt result



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/puppet_litmus/rake_helper.rb', line 230

def install_module(inventory_hash, target_node_name, module_tar, module_repository = nil, ignore_dependencies = false) # rubocop:disable Style/OptionalBooleanParameter
  # 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)
  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.casecmp('true').zero?

  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

#metadata_module_nameObject



251
252
253
254
255
256
257
258
259
# File 'lib/puppet_litmus/rake_helper.rb', line 251

def 
  require 'json'
  raise 'Could not find metadata.json' unless File.exist?(File.join(Dir.pwd, 'metadata.json'))

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

  ['name']
end

#provision(provisioner, platform, inventory_vars) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/puppet_litmus/rake_helper.rb', line 66

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?

  task_name = provisioner_task(provisioner)
  bolt_result = run_task(task_name, 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
  raise_bolt_errors(bolt_result, "provisioning of #{platform} failed.")

  bolt_result
end

#provision_list(provision_hash, key) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/puppet_litmus/rake_helper.rb', line 81

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 = []

  provision_hash[key]['images'].each do |image|
    results << provision(provisioner, image, inventory_vars)
  end
  results
end

#provisioner_task(provisioner) ⇒ Object



298
299
300
301
302
303
304
305
# File 'lib/puppet_litmus/rake_helper.rb', line 298

def provisioner_task(provisioner)
  if SUPPORTED_PROVISIONERS.include?(provisioner)
    "provision::#{provisioner}"
  else
    warn "WARNING: Unsupported provisioner '#{provisioner}', try #{SUPPORTED_PROVISIONERS.join('/')}"
    provisioner.to_s
  end
end

#raise_bolt_errors(result_set, error_msg) ⇒ Object

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

Parameters:

  • result_set (Array)

    result set returned by Bolt command.

  • error_msg (String)

    error message to raise when errors are detected. The actual errors will be appended.



330
331
332
333
334
335
336
337
338
339
# File 'lib/puppet_litmus/rake_helper.rb', line 330

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) ⇒ Object

Executes a command on the test runner.

Parameters:

  • command (String)

    command to execute.

Returns:

  • (Object)

    the standard out stream.



56
57
58
59
60
61
62
63
64
# File 'lib/puppet_litmus/rake_helper.rb', line 56

def run_local_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

#start_spinner(message) ⇒ Object



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/puppet_litmus/rake_helper.rb', line 341

def start_spinner(message)
  if (ENV['CI'] || '').casecmp('true').zero? || Gem.win_platform?
    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) ⇒ Object



359
360
361
362
363
364
365
# File 'lib/puppet_litmus/rake_helper.rb', line 359

def stop_spinner(spinner)
  if (ENV['CI'] || '').casecmp('true').zero? || Gem.win_platform?
    Thread.kill(spinner)
  else
    spinner.success
  end
end

#tear_down(node_name, inventory_hash) ⇒ Object



122
123
124
125
126
127
128
129
130
131
# File 'lib/puppet_litmus/rake_helper.rb', line 122

def tear_down(node_name, inventory_hash)
  # how do we know what provisioner to use
  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

#tear_down_nodes(targets, inventory_hash) ⇒ Object



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
# File 'lib/puppet_litmus/rake_helper.rb', line 94

def tear_down_nodes(targets, inventory_hash)
  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 local host or provisioner fact empty/not set (GH-421)
    next if node_name == 'litmus_localhost' || facts_from_node(inventory_hash, node_name)['provisioner'].nil?

    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

#uninstall_module(inventory_hash, target_node_name, module_to_remove = nil, **opts) ⇒ Object

Uninstall a module from a specified target

Parameters:

  • inventory_hash (Hash)

    the pre-loaded inventory

  • target_node_name (String)

    the name of the target where the module should be uninstalled

  • module_to_remove (String) (defaults to: nil)

    the name of the module to remove. Defaults to the module under test.

  • opts (Hash)

    additional options to pass on to ‘puppet module uninstall`



266
267
268
269
270
271
272
273
274
275
276
# File 'lib/puppet_litmus/rake_helper.rb', line 266

def uninstall_module(inventory_hash, target_node_name, module_to_remove = nil, **opts)
  include ::BoltSpec::Run
  module_name = module_to_remove || 
  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) ⇒ Object



378
379
380
381
382
383
384
385
# File 'lib/puppet_litmus/rake_helper.rb', line 378

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