cem_acpt 0.8.7 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/spec.yml +0 -3
- data/Gemfile.lock +9 -1
- data/README.md +95 -13
- data/cem_acpt.gemspec +2 -1
- data/lib/cem_acpt/action_result.rb +8 -2
- data/lib/cem_acpt/actions.rb +153 -0
- data/lib/cem_acpt/bolt/cmd/base.rb +174 -0
- data/lib/cem_acpt/bolt/cmd/output.rb +315 -0
- data/lib/cem_acpt/bolt/cmd/task.rb +59 -0
- data/lib/cem_acpt/bolt/cmd.rb +22 -0
- data/lib/cem_acpt/bolt/errors.rb +49 -0
- data/lib/cem_acpt/bolt/helpers.rb +52 -0
- data/lib/cem_acpt/bolt/inventory.rb +62 -0
- data/lib/cem_acpt/bolt/project.rb +38 -0
- data/lib/cem_acpt/bolt/summary_results.rb +96 -0
- data/lib/cem_acpt/bolt/tasks.rb +181 -0
- data/lib/cem_acpt/bolt/tests.rb +415 -0
- data/lib/cem_acpt/bolt/yaml_file.rb +74 -0
- data/lib/cem_acpt/bolt.rb +142 -0
- data/lib/cem_acpt/cli.rb +6 -0
- data/lib/cem_acpt/config/base.rb +4 -0
- data/lib/cem_acpt/config/cem_acpt.rb +7 -1
- data/lib/cem_acpt/core_ext.rb +25 -0
- data/lib/cem_acpt/goss/api/action_response.rb +4 -0
- data/lib/cem_acpt/goss/api.rb +23 -25
- data/lib/cem_acpt/logging/formatter.rb +3 -3
- data/lib/cem_acpt/logging.rb +17 -1
- data/lib/cem_acpt/provision/terraform/linux.rb +1 -1
- data/lib/cem_acpt/test_data.rb +2 -0
- data/lib/cem_acpt/test_runner/log_formatter/base.rb +73 -0
- data/lib/cem_acpt/test_runner/log_formatter/bolt_error_formatter.rb +65 -0
- data/lib/cem_acpt/test_runner/log_formatter/bolt_output_formatter.rb +54 -0
- data/lib/cem_acpt/test_runner/log_formatter/bolt_summary_results_formatter.rb +64 -0
- data/lib/cem_acpt/test_runner/log_formatter/goss_action_response.rb +17 -30
- data/lib/cem_acpt/test_runner/log_formatter/goss_error_formatter.rb +31 -0
- data/lib/cem_acpt/test_runner/log_formatter/standard_error_formatter.rb +35 -0
- data/lib/cem_acpt/test_runner/log_formatter.rb +17 -5
- data/lib/cem_acpt/test_runner/test_results.rb +150 -0
- data/lib/cem_acpt/test_runner.rb +153 -53
- data/lib/cem_acpt/utils/files.rb +189 -0
- data/lib/cem_acpt/utils/finalizer_queue.rb +73 -0
- data/lib/cem_acpt/utils/shell.rb +13 -4
- data/lib/cem_acpt/version.rb +1 -1
- data/sample_config.yaml +13 -0
- metadata +41 -5
- 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
|
data/lib/cem_acpt/config/base.rb
CHANGED
@@ -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]+)$},
|
data/lib/cem_acpt/core_ext.rb
CHANGED
@@ -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
|
data/lib/cem_acpt/goss/api.rb
CHANGED
@@ -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
|
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
|
-
# @
|
38
|
-
# @
|
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(
|
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, '
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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...' }
|
@@ -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
|
|
data/lib/cem_acpt/logging.rb
CHANGED
@@ -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]
|
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
|
data/lib/cem_acpt/test_data.rb
CHANGED
@@ -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
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require_relative 'base'
|
5
|
+
|
6
|
+
module CemAcpt
|
7
|
+
module TestRunner
|
8
|
+
module LogFormatter
|
9
|
+
# Formats the results of a Bolt::SummaryResults object
|
10
|
+
class BoltSummaryResultsFormatter < Base
|
11
|
+
def initialize(config, instance_names_ips, subject: nil)
|
12
|
+
super(subject)
|
13
|
+
@config = config
|
14
|
+
@instance_names_ips = instance_names_ips
|
15
|
+
end
|
16
|
+
|
17
|
+
def summary(response = nil)
|
18
|
+
super(response)
|
19
|
+
[
|
20
|
+
"SUMMARY: #{test_name(log_subject)}:",
|
21
|
+
normalize_whitespace(log_subject.summary),
|
22
|
+
].join(' ')
|
23
|
+
end
|
24
|
+
|
25
|
+
def results(response = nil)
|
26
|
+
super(response)
|
27
|
+
log_subject.each_with_object([]) do |res, ary|
|
28
|
+
res.results.each do |r|
|
29
|
+
header = [
|
30
|
+
"#{success_str(r.success?).capitalize}: #{r.name}",
|
31
|
+
"action: #{r.action}",
|
32
|
+
"target: #{name_from_ip(r.target)}",
|
33
|
+
"object: #{r.object}",
|
34
|
+
"status: #{r.status}",
|
35
|
+
]
|
36
|
+
parts = [
|
37
|
+
header.join(', '),
|
38
|
+
"validation results:\n#{JSON.pretty_generate(r.failed_validation_results)}",
|
39
|
+
]
|
40
|
+
if CemAcpt::Logging.verbose?
|
41
|
+
parts << "command result:\n#{JSON.pretty_generate(r.command_result.to_h)}"
|
42
|
+
end
|
43
|
+
parts << r.error if r.error?
|
44
|
+
if r.respond_to?(:details) && !r.details&.empty?
|
45
|
+
parts << "details:\n#{JSON.pretty_generate(r.details)}\n"
|
46
|
+
end
|
47
|
+
ary << parts.join("\n")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def host_name(response = nil)
|
53
|
+
super(response)
|
54
|
+
log_subject.items.map { |i| name_from_ip(i.target) }.compact.uniq.join(', ')
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_name(response = nil)
|
58
|
+
super(response)
|
59
|
+
'Bolt tests'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|