cem_acpt 0.8.8 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/spec.yml +0 -3
  3. data/Gemfile.lock +9 -1
  4. data/README.md +95 -13
  5. data/cem_acpt.gemspec +2 -1
  6. data/lib/cem_acpt/action_result.rb +8 -2
  7. data/lib/cem_acpt/actions.rb +153 -0
  8. data/lib/cem_acpt/bolt/cmd/base.rb +174 -0
  9. data/lib/cem_acpt/bolt/cmd/output.rb +315 -0
  10. data/lib/cem_acpt/bolt/cmd/task.rb +59 -0
  11. data/lib/cem_acpt/bolt/cmd.rb +22 -0
  12. data/lib/cem_acpt/bolt/errors.rb +49 -0
  13. data/lib/cem_acpt/bolt/helpers.rb +52 -0
  14. data/lib/cem_acpt/bolt/inventory.rb +62 -0
  15. data/lib/cem_acpt/bolt/project.rb +38 -0
  16. data/lib/cem_acpt/bolt/summary_results.rb +96 -0
  17. data/lib/cem_acpt/bolt/tasks.rb +181 -0
  18. data/lib/cem_acpt/bolt/tests.rb +415 -0
  19. data/lib/cem_acpt/bolt/yaml_file.rb +74 -0
  20. data/lib/cem_acpt/bolt.rb +142 -0
  21. data/lib/cem_acpt/cli.rb +6 -0
  22. data/lib/cem_acpt/config/base.rb +4 -0
  23. data/lib/cem_acpt/config/cem_acpt.rb +7 -1
  24. data/lib/cem_acpt/core_ext.rb +25 -0
  25. data/lib/cem_acpt/goss/api/action_response.rb +4 -0
  26. data/lib/cem_acpt/goss/api.rb +23 -25
  27. data/lib/cem_acpt/image_builder/provision_commands.rb +43 -0
  28. data/lib/cem_acpt/logging/formatter.rb +3 -3
  29. data/lib/cem_acpt/logging.rb +17 -1
  30. data/lib/cem_acpt/provision/terraform/linux.rb +2 -2
  31. data/lib/cem_acpt/test_data.rb +2 -0
  32. data/lib/cem_acpt/test_runner/log_formatter/base.rb +73 -0
  33. data/lib/cem_acpt/test_runner/log_formatter/bolt_error_formatter.rb +65 -0
  34. data/lib/cem_acpt/test_runner/log_formatter/bolt_output_formatter.rb +54 -0
  35. data/lib/cem_acpt/test_runner/log_formatter/bolt_summary_results_formatter.rb +64 -0
  36. data/lib/cem_acpt/test_runner/log_formatter/goss_action_response.rb +17 -30
  37. data/lib/cem_acpt/test_runner/log_formatter/goss_error_formatter.rb +31 -0
  38. data/lib/cem_acpt/test_runner/log_formatter/standard_error_formatter.rb +35 -0
  39. data/lib/cem_acpt/test_runner/log_formatter.rb +17 -5
  40. data/lib/cem_acpt/test_runner/test_results.rb +150 -0
  41. data/lib/cem_acpt/test_runner.rb +153 -53
  42. data/lib/cem_acpt/utils/files.rb +189 -0
  43. data/lib/cem_acpt/utils/finalizer_queue.rb +73 -0
  44. data/lib/cem_acpt/utils/shell.rb +13 -4
  45. data/lib/cem_acpt/version.rb +1 -1
  46. data/sample_config.yaml +13 -0
  47. metadata +41 -5
  48. data/lib/cem_acpt/test_runner/log_formatter/error_formatter.rb +0 -33
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'json'
5
+ require 'yaml'
6
+ require_relative 'logging'
7
+ require_relative 'bolt/cmd'
8
+ require_relative 'bolt/helpers'
9
+ require_relative 'bolt/inventory'
10
+ require_relative 'bolt/project'
11
+ require_relative 'bolt/summary_results'
12
+ require_relative 'bolt/tasks'
13
+ require_relative 'bolt/tests'
14
+ require_relative 'utils/files'
15
+ require_relative 'utils/puppet'
16
+ require_relative 'utils/shell'
17
+
18
+ module CemAcpt
19
+ # Holds all the Bolt related code
20
+ module Bolt
21
+ # Class for running Bolt tests
22
+ class TestRunner
23
+ include CemAcpt::Logging
24
+ include CemAcpt::Bolt::Helpers
25
+
26
+ attr_reader :hosts, :results, :run_data
27
+
28
+ def initialize(config, run_data: {}, hosts: [])
29
+ @config = config
30
+ @run_data = run_data
31
+ @tests = nil
32
+ @hosts = hosts
33
+ @module_name = File.basename(Dir.pwd).gsub(%r{puppetlabs-}, '')
34
+ @bolt_project_setup = false
35
+ @inventory = CemAcpt::Bolt::Inventory.new(config, hosts, run_data[:private_key])
36
+ @project = CemAcpt::Bolt::Project.new(config, @module_name)
37
+ @results = CemAcpt::Bolt::SummaryResults.new
38
+ end
39
+
40
+ def tests
41
+ @tests ||= CemAcpt::Bolt::Tests::TestList.new(@config, @run_data, @inventory, @project)
42
+ end
43
+
44
+ def setup!
45
+ return if setup?
46
+
47
+ @project.save! unless @project.latest_saved?
48
+ @inventory.save! unless @inventory.latest_saved?
49
+ tests.setup! unless tests.setup?
50
+ end
51
+
52
+ def setup?
53
+ @project.latest_saved? && @inventory.latest_saved? && tests.setup?
54
+ end
55
+
56
+ def teardown!
57
+ logger.debug('CemAcpt::Bolt') { 'Tearing down Bolt project...' }
58
+ @inventory.delete!
59
+ @project.delete!
60
+ end
61
+
62
+ def hosts=(hosts)
63
+ @hosts = hosts
64
+ @inventory.hosts = hosts
65
+ end
66
+
67
+ def run_data=(run_data)
68
+ @run_data = run_data
69
+ @inventory.private_key = run_data[:private_key]
70
+ end
71
+
72
+ def run(threaded: true)
73
+ raise BoltActionError, 'Cannot run Bolt tests, no hosts specified' if hosts.nil? || hosts.empty?
74
+
75
+ logger.info('CemAcpt::Bolt') { "Preparing to run Bolt tests for module #{@module_name}..." }
76
+ if threaded
77
+ run_in_context { |t| run_threaded(t) }
78
+ else
79
+ run_in_context { |t| run_synchronous(t) }
80
+ end
81
+ rescue StandardError => e
82
+ errmsg = "Error running Bolt tests for module #{@module_name}"
83
+ wrapped_error = CemAcpt::Bolt::BoltActionError.new(errmsg, e, "#{self.class.name}#run")
84
+ logger.error('CemAcpt::Bolt') { errmsg }
85
+ logger.debug('CemAcpt::Bolt') { "Backtrace:\n#{e.backtrace.join("\n")}" }
86
+ @results << CemAcpt::Bolt::Tests::TestResult.new(
87
+ 'Bolt test runner error',
88
+ nil,
89
+ nil,
90
+ error: wrapped_error,
91
+ )
92
+ ensure
93
+ @results.finalize!
94
+ @results
95
+ end
96
+
97
+ private
98
+
99
+ def max_threads
100
+ @max_threads ||= @config.get('bolt.max_threads') || 5
101
+ end
102
+
103
+ def run_in_context
104
+ raise 'No block given' unless block_given?
105
+
106
+ setup! unless setup?
107
+ yield @tests
108
+ ensure
109
+ teardown! if setup?
110
+ end
111
+
112
+ def run_threaded(tests_to_run = @tests)
113
+ test_groups = tests_to_run.split_into_groups(max_threads)
114
+ logger.info('CemAcpt::Bolt') do
115
+ "Running #{tests_to_run.length} Bolt test(s) in #{test_groups.length} group(s) against #{hosts.length} host(s)..."
116
+ end
117
+ threads = []
118
+ test_groups.each do |test_group|
119
+ threads << Thread.new do
120
+ test_group.each do |t|
121
+ t.run
122
+ result = t.result
123
+ logger.debug('CemAcpt::Bolt') { "Bolt test result:\n#{result}" }
124
+ @results << result
125
+ end
126
+ end
127
+ end
128
+ threads.each(&:join)
129
+ end
130
+
131
+ def run_synchronous(tests_to_run = @tests)
132
+ logger.info('CemAcpt::Bolt') do
133
+ "Running #{tests_to_run.length} Bolt test(s) against #{hosts.length} host(s)..."
134
+ end
135
+ tests_to_run.to_a.each do |t|
136
+ t.run
137
+ @results << t.result
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
data/lib/cem_acpt/cli.rb CHANGED
@@ -35,6 +35,12 @@ module CemAcpt
35
35
  opts.on('-t', '--tests TESTS', 'Set tests. Example: -t "test1,test2"') do |t|
36
36
  options[:tests] = t.split(',')
37
37
  end
38
+
39
+ opts.on('-B', '--no-bolt-tests', 'Do not run Bolt tests') do
40
+ options[:bolt] ||= {}
41
+ options[:bolt][:tests] ||= {}
42
+ options[:bolt][:tests][:enabled] = false
43
+ end
38
44
  when :cem_acpt_image
39
45
  opts.on('--dry-run', 'Logs the information for the images that would be created. Does not create images.') do
40
46
  options[:dry_run] = true
@@ -47,6 +47,10 @@ module CemAcpt
47
47
  load(opts: opts, config_file: config_file)
48
48
  end
49
49
 
50
+ def inspect
51
+ "#<#{self.class}:#{object_id.to_s(16)}>"
52
+ end
53
+
50
54
  # @return [String] The prefix for environment variables
51
55
  def env_var_prefix
52
56
  'CEM_ACPT'
@@ -8,6 +8,7 @@ module CemAcpt
8
8
  class CemAcpt < Base
9
9
  VALID_KEYS = %i[
10
10
  actions
11
+ bolt
11
12
  image_name_builder
12
13
  module_dir
13
14
  node_data
@@ -19,6 +20,11 @@ module CemAcpt
19
20
  def defaults
20
21
  {
21
22
  actions: {},
23
+ bolt: {
24
+ tests: {
25
+ enabled: true,
26
+ },
27
+ },
22
28
  ci_mode: false,
23
29
  config_file: nil,
24
30
  image_name_builder: {
@@ -38,7 +44,7 @@ module CemAcpt
38
44
  quiet: false,
39
45
  test_data: {
40
46
  for_each: {
41
- collection: %w[puppet7],
47
+ collection: %w[puppet7 puppet8],
42
48
  },
43
49
  vars: {},
44
50
  name_pattern_vars: %r{^(?<framework>[a-z]+)_(?<image_fam>[a-z0-9-]+)_(?<firewall>[a-z]+)_(?<framework_vars>[-_a-z0-9]+)$},
@@ -63,4 +63,29 @@ module CemAcpt::CoreExt
63
63
  alias dset dot_store
64
64
  end
65
65
  end
66
+
67
+ # Refines the Array class with some convenience methods.
68
+ module ExtendedArray
69
+ refine Array do
70
+ # Splits the array into the given number of groups.
71
+ # If the array cannot be evenly split, the last group(s)
72
+ # will contain the remainder.
73
+ # Example:
74
+ # [1, 2, 3, 4, 5].split_into_groups(2) #=> [[1, 2, 3], [4, 5]]
75
+ # [1, 2, 3, 4, 5].split_into_groups(3) #=> [[1, 2, 3], [4], [5]]
76
+ def split_into_groups(num_groups)
77
+ group_size = size / num_groups
78
+ leftovers = size % num_groups
79
+
80
+ groups = []
81
+ start = 0
82
+ num_groups.times do |i|
83
+ length = group_size + ((leftovers > 0 && leftovers > i) ? 1 : 0)
84
+ groups << slice(start, length)
85
+ start += length
86
+ end
87
+ groups
88
+ end
89
+ end
90
+ end
66
91
  end
@@ -39,6 +39,10 @@ module CemAcpt
39
39
  status == 200
40
40
  end
41
41
 
42
+ def error?
43
+ !success?
44
+ end
45
+
42
46
  def results
43
47
  @results ||= @body['results'].map { |r| ActionResponseResult.new(r) }
44
48
  end
@@ -11,17 +11,17 @@ module CemAcpt
11
11
  module Goss
12
12
  # Holds methods for interacting with the Goss API running on a test node.
13
13
  module Api
14
+ # The actions that can be run against the Goss API. The key is the action
15
+ # name and the value is the port/endpoint of the action.
16
+ ACTIONS = {
17
+ acpt: '8080/acpt',
18
+ idempotent: '8081/idempotent',
19
+ noop: '8082/noop',
20
+ }.freeze
21
+
14
22
  class << self
15
23
  include CemAcpt::Logging
16
24
 
17
- # The actions that can be run against the Goss API. The key is the action
18
- # name and the value is the port/endpoint of the action.
19
- ACTIONS = {
20
- acpt: '8080/acpt',
21
- idempotent: '8081/idempotent',
22
- noop: '8082/noop',
23
- }.freeze
24
-
25
25
  # Create a URI for the specified action against the specified host.
26
26
  # @param host [String] The host to run the action against. This should be
27
27
  # a public IP address or a DNS-resolvable name.
@@ -32,31 +32,30 @@ module CemAcpt
32
32
  end
33
33
 
34
34
  # Run the specified actions against the specified hosts asynchronously.
35
- # @param hosts [Array<String>] The hosts to run the actions against. Each
35
+ # @param context [Hash] The context for the test run.
36
+ # @option context [Array<String>] :hosts The hosts to run the actions against. Each
36
37
  # host should be a public IP address or a DNS-resolvable name.
37
- # @param results [Queue] The queue to push the results to.
38
- # @param only [Array<Symbol>] The actions to run.
39
- # @param except [Array<Symbol>] The actions to skip.
38
+ # @option context [Queue] :results The queue to push the results to.
39
+ # @option [Array<String>] :actions The actions to run.
40
40
  # @return [Queue] The queue of results.
41
- def run_actions_async(hosts, results: Queue.new, only: [], except: [])
41
+ def run_actions_async(context = {})
42
+ actions = context[:actions] || []
43
+ hosts = context[:hosts] || []
44
+ results = context[:results] || Queue.new
42
45
  raise ArgumentError, 'hosts must be an Array' unless hosts.is_a?(Array)
43
46
  raise ArgumentError, 'results must be a Queue-like object implementing #<<' unless results.respond_to?(:<<)
44
- raise ArgumentError, 'only must be an Array' unless except.is_a?(Array)
45
- raise ArgumentError, 'except must be an Array' unless except.is_a?(Array)
46
- logger.info('CemAcpt::Goss::Api') { "Running test actions against #{hosts.size} host(s)..." }
47
- only.map!(&:to_sym)
48
- except.map!(&:to_sym)
49
- only_specified = !only.empty?
50
- except_specified = !except.empty?
47
+ raise ArgumentError, 'actions must be an Array' unless actions.is_a?(Array)
48
+
49
+ actions.map!(&:to_sym)
50
+ logger.info('CemAcpt::Goss::Api') do
51
+ "Running test actions #{actions.join(', ')} against #{hosts.size} host(s)..."
52
+ end
51
53
  task = Async do
52
54
  internet = Async::HTTP::Internet.new
53
55
  barrier = Async::Barrier.new
54
56
  barrier.async do
55
57
  hosts.each do |host|
56
- ACTIONS.each_key do |action|
57
- next if only_specified && !only.include?(action)
58
- next if except_specified && except.include?(action)
59
-
58
+ actions.each do |action|
60
59
  results << run_action(internet, host, action)
61
60
  end
62
61
  end
@@ -64,7 +63,6 @@ module CemAcpt
64
63
  barrier.wait
65
64
  ensure
66
65
  internet&.close
67
- results.close if results.respond_to?(:close)
68
66
  end
69
67
  task.wait
70
68
  logger.info('CemAcpt::Goss::Api') { 'Finished running test actions, returning results...' }
@@ -74,6 +74,48 @@ module CemAcpt
74
74
  alias to_a provision_commands
75
75
  end
76
76
 
77
+ class DebianFamily
78
+ def initialize(config, image_name, base_image, os_major_version, puppet_version)
79
+ @config = config
80
+ @image_name = image_name
81
+ @base_image = base_image
82
+ @os_major_version = os_major_version
83
+ @puppet_version = puppet_version
84
+ end
85
+
86
+ def default_provision_commands
87
+ [install_curl_command, enable_puppet_repository_command, upgrade_packages_command, install_puppet_agent_command]
88
+ end
89
+
90
+ def provision_commands
91
+ commands_from_config = @config.get("images.#{@image_name}.provision_commands") || []
92
+ (default_provision_commands + commands_from_config).compact
93
+ end
94
+ alias to_a provision_commands
95
+
96
+ private
97
+
98
+ def upgrade_packages_command
99
+ 'sudo apt-get update && sudo apt-get upgrade -y'
100
+ end
101
+
102
+ def puppet_platform_repository_url
103
+ "https://apt.puppet.com/puppet#{@puppet_version}-release-focal.deb"
104
+ end
105
+
106
+ def enable_puppet_repository_command
107
+ "wget #{puppet_platform_repository_url} && sudo dpkg -i puppet#{@puppet_version}-release-focal.deb"
108
+ end
109
+
110
+ def install_puppet_agent_command
111
+ 'sudo apt-get install -y puppet-agent'
112
+ end
113
+
114
+ # The latest Google-provided Ubuntu images do not have curl installed by default
115
+ def install_curl_command
116
+ 'sudo apt-get install -y curl'
117
+ end
118
+ end
77
119
  class << self
78
120
  # Map of OS to class that holds the provision commands for that OS
79
121
  OS_CLASS_MAP = {
@@ -82,6 +124,7 @@ module CemAcpt
82
124
  'alma' => 'EnterpriseLinuxFamily',
83
125
  'oel' => 'EnterpriseLinuxFamily',
84
126
  'rocky' => 'EnterpriseLinuxFamily',
127
+ 'ubuntu' => 'DebianFamily',
85
128
  'windows' => 'WindowsFamily',
86
129
  }.freeze
87
130
 
@@ -18,7 +18,7 @@ module CemAcpt
18
18
  def all
19
19
  @all ||= [FileFormatter.new, JSONFormatter.new, TextFormatter.new, GithubActionFormatter.new]
20
20
  end
21
- end
21
+ end
22
22
 
23
23
  class FileFormatter
24
24
  attr_reader :log_format
@@ -26,10 +26,10 @@ module CemAcpt
26
26
  def initialize
27
27
  @log_format = :file
28
28
  end
29
-
29
+
30
30
  def get
31
31
  proc do |severity, datetime, progname, msg|
32
- format(severity, datetime, progname, msg)
32
+ self.format(severity, datetime, progname, msg)
33
33
  end
34
34
  end
35
35
 
@@ -73,7 +73,7 @@ module CemAcpt
73
73
 
74
74
  def initialize(logdev, shift_age = 0, shift_size = 1_048_576, **kwargs)
75
75
  @config_hash = { logdev: logdev, shift_age: shift_age, shift_size: shift_size, **kwargs }
76
- @ci_mode = (kwargs[:formatter].downcase.to_sym == :github_action || !ENV['GITHUB_ACTIONS'].nil? || !ENV['CI'].nil?)
76
+ @ci_mode = (kwargs[:formatter]&.downcase&.to_sym == :github_action || !ENV['GITHUB_ACTIONS'].nil? || !ENV['CI'].nil?)
77
77
  kwargs[:formatter] = CemAcpt::Logging::Formatter.for(kwargs[:formatter]) if kwargs[:formatter]
78
78
  super(logdev, shift_age, shift_size, **kwargs)
79
79
  @raw_logdev = logdev # Used for logging from trap context
@@ -93,6 +93,10 @@ module CemAcpt
93
93
  debug(progname, &block)
94
94
  end
95
95
 
96
+ def verbose?
97
+ @verbose
98
+ end
99
+
96
100
  def debug(progname = nil, &block)
97
101
  severity = 'debug'
98
102
  return log_trap_context(severity, progname, &block) if log_trap_context?(severity)
@@ -243,6 +247,10 @@ module CemAcpt
243
247
  @logger.level = LEVEL_MAP[level.downcase]
244
248
  end
245
249
 
250
+ def verbose?
251
+ logger.is_a?(CemAcpt::Logging::MultiLogger) ? logger.verbose?.all? : logger.verbose?
252
+ end
253
+
246
254
  # Shows the current log format style if set, or the default if not.
247
255
  # @return [Symbol] the current log format style
248
256
  def current_log_format
@@ -332,6 +340,10 @@ module CemAcpt
332
340
  CemAcpt::Logging.new_log_level(level)
333
341
  end
334
342
 
343
+ def verbose?
344
+ CemAcpt::Logging.verbose?
345
+ end
346
+
335
347
  def current_log_format
336
348
  CemAcpt::Logging.current_log_format
337
349
  end
@@ -369,6 +381,10 @@ module CemAcpt
369
381
  CemAcpt::Logging.new_log_level(level)
370
382
  end
371
383
 
384
+ def verbose?
385
+ CemAcpt::Logging.verbose?
386
+ end
387
+
372
388
  def current_log_format
373
389
  CemAcpt::Logging.current_log_format
374
390
  end
@@ -7,11 +7,11 @@ module CemAcpt
7
7
  # Class provides methods for gathering provision data for Linux nodes
8
8
  class Linux < OsData
9
9
  def self.valid_names
10
- %w[centos rhel oel alma rocky]
10
+ %w[centos rhel oel alma rocky ubuntu]
11
11
  end
12
12
 
13
13
  def self.valid_versions
14
- %w[7 8 9]
14
+ %w[7 8 9 2004]
15
15
  end
16
16
 
17
17
  def systemd_files
@@ -45,6 +45,7 @@ module CemAcpt
45
45
  raise "Goss file not found for test #{test_name}" unless File.exist?(goss_file)
46
46
  raise "Puppet manifest not found for test #{test_name}" unless File.exist?(puppet_manifest)
47
47
 
48
+ bolt_test = File.expand_path(File.join(test_dir, 'bolt.yaml'))
48
49
  logger.debug('CemAcpt::TestData') { "Complete test directory found for test #{test_name}: #{test_dir}" }
49
50
  test_data = {
50
51
  test_name: test_name,
@@ -52,6 +53,7 @@ module CemAcpt
52
53
  goss_file: goss_file,
53
54
  puppet_manifest: puppet_manifest,
54
55
  }
56
+ test_data[:bolt_test] = bolt_test if File.exist?(bolt_test)
55
57
 
56
58
  process_for_each(test_data).each do |test_data_i|
57
59
  process_static_vars(test_data_i)
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CemAcpt
4
+ module TestRunner
5
+ module LogFormatter
6
+ # Base class for log formatters
7
+ class Base
8
+ INDENT = ' '
9
+
10
+ attr_accessor :subject
11
+
12
+ def initialize(subject = nil)
13
+ @subject = subject
14
+ end
15
+
16
+ def inspect
17
+ to_s
18
+ end
19
+
20
+ def to_s
21
+ "#<#{self.class.name}:0x#{object_id.to_s(16)}>"
22
+ end
23
+
24
+ def summary(response = nil)
25
+ register_response(response)
26
+ end
27
+
28
+ def results(response = nil)
29
+ register_response(response)
30
+ end
31
+
32
+ def host_name(response = nil)
33
+ register_response(response)
34
+ end
35
+
36
+ def test_name(response = nil)
37
+ register_response(response)
38
+ end
39
+
40
+ private
41
+
42
+ def register_response(response = nil)
43
+ raise ArgumentError, 'either subject or response must be provided' if @subject.nil? && response.nil?
44
+
45
+ @response = response
46
+ end
47
+
48
+ def log_subject
49
+ @response || @subject
50
+ end
51
+
52
+ def normalize_whitespace(str)
53
+ raise ArgumentError, 'str must be a String' unless str.is_a?(String)
54
+
55
+ str.gsub(%r{(\n|\r|\t)}, ' ').gsub(%r{\s{2,}}, ' ').strip
56
+ end
57
+
58
+ def success_str(success)
59
+ success ? 'passed' : 'failed'
60
+ end
61
+
62
+ def name_from_ip(ip)
63
+ raise 'class does not provide instance_names_ips instance variable' if @instance_names_ips.nil?
64
+
65
+ @instance_names_ips.each do |name, val|
66
+ return name if val['ip'] == ip
67
+ end
68
+ ip
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module CemAcpt
6
+ module TestRunner
7
+ module LogFormatter
8
+ class BoltErrorFormatter < Base
9
+ def initialize(subject: nil)
10
+ super(subject)
11
+ end
12
+
13
+ def summary(response = nil)
14
+ super(response)
15
+ "Error: #{log_subject.summary}"
16
+ end
17
+
18
+ def results(response = nil)
19
+ super(response)
20
+ log_subject.results.map do |res|
21
+ format_failed_result(res)
22
+ end
23
+ end
24
+
25
+ def host_name(response = nil)
26
+ super(response)
27
+ "Error: #{log_subject.error.class.name}"
28
+ end
29
+
30
+ def test_name(response = nil)
31
+ super(response)
32
+ "Error: #{log_subject.error.class.name}"
33
+ end
34
+
35
+ private
36
+
37
+ def format_failed_result(res)
38
+ prefix = [
39
+ 'Failed',
40
+ "action #{res.action}",
41
+ "object #{res.object}",
42
+ "target #{res.target}",
43
+ ].join(': ')
44
+ return prefix unless res.error?
45
+
46
+ error_parts = [
47
+ "error type #{res.error.class.name}",
48
+ normalize_whitespace(res.error.to_s),
49
+ ]
50
+ if res.error.respond_to?(:details) && res.error.details
51
+ error_parts << "details #{res.error.details}"
52
+ end
53
+ if res.error.respond_to?(:original_error) && res.error.original_error
54
+ error_parts << "original error #{res.error.original_error.class.name}"
55
+ error_parts << "original message #{res.error.original_error.message}"
56
+ end
57
+ if res.error.respond_to?(:backtrace) && !res.error.backtrace.empty?
58
+ error_parts << "backtrace:\n#{res.error.backtrace.join("\n#{INDENT}#{INDENT}")}"
59
+ end
60
+ "#{prefix}\n#{INDENT}#{error_parts.join("\n#{INDENT}")}"
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module CemAcpt
6
+ module TestRunner
7
+ module LogFormatter
8
+ # Formats the results of a Bolt action
9
+ class BoltOutputFormatter < Base
10
+ def initialize(config, instance_names_ips, subject: nil)
11
+ super(subject)
12
+ @config = config
13
+ @instance_names_ips = instance_names_ips
14
+ end
15
+
16
+ def summary(response = nil)
17
+ super(response)
18
+ [
19
+ "SUMMARY: #{test_name(log_subject)}:",
20
+ normalize_whitespace(log_subject.summary),
21
+ ].join(' ')
22
+ end
23
+
24
+ def results(response = nil)
25
+ super(response)
26
+ log_subject.results.map do |res|
27
+ prefix = success_str(res.success?).capitalize
28
+ parts = [
29
+ prefix,
30
+ "action #{res.action}",
31
+ "host(s) #{host_name(log_subject)}",
32
+ "object #{res.object}",
33
+ "status #{res.status}",
34
+ ]
35
+ parts << res.error if res.error?
36
+ parts.join(': ')
37
+ end
38
+ end
39
+
40
+ def host_name(response = nil)
41
+ super(response)
42
+ log_subject.items.map { |i| name_from_ip(i.target) }.compact.uniq.join(', ')
43
+ end
44
+
45
+ def test_name(response = nil)
46
+ super(response)
47
+ test_type = (log_subject.items.find { |i| i.action != 'unknown' }&.action || 'unknown')
48
+ test_objects = log_subject.items.map(&:object).uniq.join(', ')
49
+ "Bolt #{test_type} test for: #{test_objects}"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end