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.
- 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/image_builder/provision_commands.rb +43 -0
- 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 +2 -2
- 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...' }
|
@@ -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
|
|
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
|
@@ -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
|
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
|