cem_acpt 0.8.7 → 0.9.0
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/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,315 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module CemAcpt
|
6
|
+
module Bolt
|
7
|
+
module Cmd
|
8
|
+
# Wraps the output of a Bolt command
|
9
|
+
class Output
|
10
|
+
attr_reader :cmd_output, :items, :target_count, :elapsed_time, :error_obj
|
11
|
+
|
12
|
+
def initialize(cmd_output, strict: true, **item_defaults)
|
13
|
+
@original_cmd_output = cmd_output
|
14
|
+
@strict = strict
|
15
|
+
@item_defaults = item_defaults.transform_keys(&:to_s)
|
16
|
+
init_cmd_output_and_error_obj(cmd_output)
|
17
|
+
@target_count = @cmd_output['target_count'] || 0
|
18
|
+
@elapsed_time = @cmd_output['elapsed_time'] || 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def action
|
22
|
+
items&.map(&:action)&.uniq || [@item_defaults['action']] || ['unknown']
|
23
|
+
end
|
24
|
+
|
25
|
+
def error
|
26
|
+
return nil unless error?
|
27
|
+
|
28
|
+
items.find(&:error?)&.error || error_obj
|
29
|
+
end
|
30
|
+
|
31
|
+
def error?
|
32
|
+
!error_obj.nil? || items.any?(&:error?)
|
33
|
+
end
|
34
|
+
|
35
|
+
def success?
|
36
|
+
!error?
|
37
|
+
end
|
38
|
+
|
39
|
+
def status
|
40
|
+
return 0 if success?
|
41
|
+
|
42
|
+
1
|
43
|
+
end
|
44
|
+
|
45
|
+
def summary
|
46
|
+
@summary ||= [
|
47
|
+
"status: #{success? ? 'passed' : 'failed'}",
|
48
|
+
"items total: #{items.length}",
|
49
|
+
"items succeeded: #{items.count(&:success?)}",
|
50
|
+
"items failed: #{items.count(&:error?)}",
|
51
|
+
"target count: #{target_count}",
|
52
|
+
"elapsed time: #{elapsed_time}",
|
53
|
+
].join(', ')
|
54
|
+
end
|
55
|
+
|
56
|
+
def summary?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def inspect
|
61
|
+
"#<#{self.class}:#{object_id} #{self}>"
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_h
|
65
|
+
@cmd_output
|
66
|
+
end
|
67
|
+
|
68
|
+
def results
|
69
|
+
@results ||= new_results
|
70
|
+
end
|
71
|
+
|
72
|
+
def results?
|
73
|
+
!results.empty?
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
return error.to_s if error?
|
78
|
+
|
79
|
+
JSON.pretty_generate(@cmd_output)
|
80
|
+
end
|
81
|
+
|
82
|
+
def ==(other)
|
83
|
+
return false unless other.is_a?(self.class)
|
84
|
+
return false unless to_h == other.to_h
|
85
|
+
|
86
|
+
items.zip(other.items).all? { |a, b| a == b }
|
87
|
+
end
|
88
|
+
alias eql? ==
|
89
|
+
|
90
|
+
# Exists solely for Bolt tests
|
91
|
+
def copy_with_new_items(new_items = [])
|
92
|
+
new_self = self.class.new(original_cmd_output, strict: @strict, **@item_defaults)
|
93
|
+
new_self.items = new_items
|
94
|
+
new_self
|
95
|
+
end
|
96
|
+
|
97
|
+
def method_missing(method, *args, **kwargs, &block)
|
98
|
+
if @cmd_output.respond_to?(method)
|
99
|
+
@cmd_output.send(method, *args, **kwargs, &block)
|
100
|
+
elsif error.respond_to?(method)
|
101
|
+
error.send(method, *args, **kwargs, &block)
|
102
|
+
else
|
103
|
+
super
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def respond_to_missing?(method, include_private = false)
|
108
|
+
@cmd_output.respond_to?(method, include_private) || error.respond_to?(method, include_private) || super
|
109
|
+
end
|
110
|
+
|
111
|
+
protected
|
112
|
+
|
113
|
+
attr_reader :original_cmd_output
|
114
|
+
attr_writer :cmd_output, :items, :target_count, :elapsed_time, :error_obj
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def init_cmd_output_and_error_obj(cmd_out)
|
119
|
+
case cmd_out
|
120
|
+
when String
|
121
|
+
init_cmd_output_and_error_obj_from_str(cmd_out)
|
122
|
+
when StandardError
|
123
|
+
@error_obj = cmd_out
|
124
|
+
@cmd_output = ruby_error_to_cmd_output_hash(cmd_out)
|
125
|
+
else
|
126
|
+
raise ArgumentError, "cmd_out must be a String or StandardError, got #{cmd_out.class}"
|
127
|
+
end
|
128
|
+
ensure
|
129
|
+
@items = (@cmd_output['items'] || []).map { |item| OutputItem.new(item) }
|
130
|
+
if @items.empty? && @error_obj.nil? && @strict
|
131
|
+
err = RuntimeError.new("Cannot set results, no error or items found for cmd_output:\n#{cmd_output}")
|
132
|
+
@error_obj = err
|
133
|
+
@items = ruby_error_to_cmd_output_hash(err)['items'].map { |item| OutputItem.new(item) }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def init_cmd_output_and_error_obj_from_str(cmd_out)
|
138
|
+
@cmd_output = JSON.parse(cmd_out)
|
139
|
+
if @cmd_output.key?('_error') || @cmd_output.key?('error')
|
140
|
+
err_hash = (@cmd_output['_error'] || @cmd_output['error'])
|
141
|
+
@cmd_output['items'] ||= []
|
142
|
+
@cmd_output['items'] << {
|
143
|
+
'value' => {
|
144
|
+
'_error' => err_hash,
|
145
|
+
},
|
146
|
+
}
|
147
|
+
end
|
148
|
+
rescue JSON::ParserError => e
|
149
|
+
@error_obj = e
|
150
|
+
@cmd_output = ruby_error_to_cmd_output_hash(e)
|
151
|
+
end
|
152
|
+
|
153
|
+
def new_results
|
154
|
+
return new_error_results if items.empty?
|
155
|
+
|
156
|
+
items
|
157
|
+
end
|
158
|
+
|
159
|
+
def new_error_results
|
160
|
+
[error]
|
161
|
+
end
|
162
|
+
|
163
|
+
def ruby_error_to_cmd_output_hash(error)
|
164
|
+
error_kind = [
|
165
|
+
'cem_acpt',
|
166
|
+
error.class.name.split('::').join('.'),
|
167
|
+
].join('.')
|
168
|
+
details = { 'exit_code' => 1 }
|
169
|
+
details['backtrace'] = error.backtrace if error.backtrace
|
170
|
+
{
|
171
|
+
'items' => [
|
172
|
+
{
|
173
|
+
'value' => {
|
174
|
+
'_error' => {
|
175
|
+
'kind' => error_kind,
|
176
|
+
'msg' => error.to_s,
|
177
|
+
'issue_code' => 'CEM_ACPT_ERROR',
|
178
|
+
'details' => {
|
179
|
+
'exit_code' => 1,
|
180
|
+
'backtrace' => error.backtrace,
|
181
|
+
},
|
182
|
+
},
|
183
|
+
},
|
184
|
+
},
|
185
|
+
],
|
186
|
+
}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Represents a single item in the output of a Bolt command
|
191
|
+
class OutputItem
|
192
|
+
ATTR_DEFVAL = 'unknown'
|
193
|
+
|
194
|
+
attr_reader :target, :action, :object, :status, :value
|
195
|
+
|
196
|
+
def initialize(item_hash, **item_defaults)
|
197
|
+
@item_hash = item_hash
|
198
|
+
@item_defaults = item_defaults.transform_keys(&:to_s)
|
199
|
+
@target = item_hash['target'] || @item_defaults['target'] || ATTR_DEFVAL
|
200
|
+
@action = item_hash['action'] || @item_defaults['action'] || ATTR_DEFVAL
|
201
|
+
@object = item_hash['object'] || @item_defaults['object'] || ATTR_DEFVAL
|
202
|
+
@status = item_hash['status'] || 'failure'
|
203
|
+
@value = item_hash['value'] || {}
|
204
|
+
end
|
205
|
+
|
206
|
+
def error?
|
207
|
+
!success?
|
208
|
+
end
|
209
|
+
|
210
|
+
def error
|
211
|
+
return unless error?
|
212
|
+
|
213
|
+
@error ||= new_error
|
214
|
+
end
|
215
|
+
|
216
|
+
def success?
|
217
|
+
status == 'success'
|
218
|
+
end
|
219
|
+
|
220
|
+
def output
|
221
|
+
return nil if error?
|
222
|
+
|
223
|
+
value['_output'] || value['output'] || value
|
224
|
+
end
|
225
|
+
|
226
|
+
def to_h
|
227
|
+
@item_hash
|
228
|
+
end
|
229
|
+
|
230
|
+
def to_s
|
231
|
+
"#<#{self.class}:#{object_id.to_s(16)} #{target},#{action},#{object},#{status}>"
|
232
|
+
end
|
233
|
+
|
234
|
+
def inspect
|
235
|
+
to_s
|
236
|
+
end
|
237
|
+
|
238
|
+
def ==(other)
|
239
|
+
return false unless other.is_a?(self.class)
|
240
|
+
|
241
|
+
to_h == other.to_h
|
242
|
+
end
|
243
|
+
alias eql? ==
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
def new_error
|
248
|
+
if value.is_a?(Hash) && (value.key?('_error') || value.key?('error'))
|
249
|
+
OutputError.new(value['_error'] || value['error'])
|
250
|
+
else
|
251
|
+
OutputError.new(
|
252
|
+
{
|
253
|
+
'kind' => 'cem_acpt.unknown',
|
254
|
+
'msg' => value,
|
255
|
+
'details' => { 'exit_code' => 1 },
|
256
|
+
},
|
257
|
+
)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Represents a Bolt error value
|
263
|
+
class OutputError
|
264
|
+
attr_accessor :kind, :issue_code, :msg, :details
|
265
|
+
|
266
|
+
def initialize(error_hash)
|
267
|
+
@error_hash = error_hash
|
268
|
+
@kind = error_hash['kind']
|
269
|
+
@issue_code = error_hash['issue_code'] || 'OTHER_ERROR'
|
270
|
+
@msg = error_hash['msg']
|
271
|
+
@details = error_hash['details']
|
272
|
+
end
|
273
|
+
|
274
|
+
def error?
|
275
|
+
true
|
276
|
+
end
|
277
|
+
|
278
|
+
def success?
|
279
|
+
false
|
280
|
+
end
|
281
|
+
|
282
|
+
def status
|
283
|
+
'failure'
|
284
|
+
end
|
285
|
+
|
286
|
+
def exit_code
|
287
|
+
details['exit_code'] || 1
|
288
|
+
end
|
289
|
+
|
290
|
+
def backtrace
|
291
|
+
details['backtrace'] || []
|
292
|
+
end
|
293
|
+
|
294
|
+
def to_s
|
295
|
+
"issue code: #{issue_code}, kind: #{kind}, message: #{msg}"
|
296
|
+
end
|
297
|
+
|
298
|
+
def inspect
|
299
|
+
"#<#{self.class.name}(#{self.class.object_id})#{self}>"
|
300
|
+
end
|
301
|
+
|
302
|
+
def to_h
|
303
|
+
@error_hash
|
304
|
+
end
|
305
|
+
|
306
|
+
def ==(other)
|
307
|
+
return false unless other.is_a?(self.class)
|
308
|
+
|
309
|
+
to_h == other.to_h
|
310
|
+
end
|
311
|
+
alias eql? ==
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module CemAcpt
|
6
|
+
module Bolt
|
7
|
+
module Cmd
|
8
|
+
# Base class for task commands
|
9
|
+
class TaskBase < Base
|
10
|
+
attr_accessor :task_name
|
11
|
+
attr_reader :sub_command, :item_defaults
|
12
|
+
option :module_path, '--modulepath', config_path: 'bolt.module_path', default: Dir.pwd
|
13
|
+
option :log_level, '--log-level', config_path: 'bolt.log_level', default: 'warn'
|
14
|
+
option :clear_cache, '--clear-cache', config_path: 'bolt.clear_cache', default: false, bool_flag: true
|
15
|
+
option :project, '--project', config_path: 'bolt.project.path'
|
16
|
+
|
17
|
+
def initialize(config, sub_command = nil, task_name = nil)
|
18
|
+
@config = config
|
19
|
+
@sub_command = sub_command
|
20
|
+
@task_name = task_name
|
21
|
+
@item_defaults = {
|
22
|
+
'action' => 'task',
|
23
|
+
'object' => @task_name,
|
24
|
+
}
|
25
|
+
super()
|
26
|
+
end
|
27
|
+
|
28
|
+
def command_family
|
29
|
+
'task'
|
30
|
+
end
|
31
|
+
|
32
|
+
def cmd
|
33
|
+
join_array([bolt_bin, 'task', sub_command, task_name, options])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Runs the Bolt task show command
|
38
|
+
class TaskShow < TaskBase
|
39
|
+
def initialize(config, task_name = nil, project: nil)
|
40
|
+
super(config, 'show', task_name)
|
41
|
+
@project = project.is_a?(String) ? project : project&.path
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Runs the Bolt task run command
|
46
|
+
class TaskRun < TaskBase
|
47
|
+
option :inventory, '--inventoryfile', config_path: 'bolt.inventory_path'
|
48
|
+
option :targets, '--targets', config_path: 'bolt.targets', default: 'nix'
|
49
|
+
supports_params
|
50
|
+
|
51
|
+
def initialize(config, task_name = nil, inventory: nil, project: nil)
|
52
|
+
super(config, 'run', task_name)
|
53
|
+
@inventory = inventory.is_a?(String) ? inventory : inventory&.path
|
54
|
+
@project = project.is_a?(String) ? project : project&.path
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'cmd/task'
|
4
|
+
|
5
|
+
module CemAcpt
|
6
|
+
module Bolt
|
7
|
+
# Namespace for all Bolt command classes
|
8
|
+
module Cmd
|
9
|
+
# Represents the output of a Bolt command
|
10
|
+
# @param cmd_output [String, StandardError] the output of a Bolt command or an error
|
11
|
+
# @param strict [Boolean] whether to raise an error if the output does not match the expected format
|
12
|
+
# @param item_defaults [Hash] default values for the item.
|
13
|
+
# @option item_defaults [String] :action The action that was performed (ex: 'task')
|
14
|
+
# @option item_defaults [String] :object The object that the action was performed on (ex. The Bolt task name)
|
15
|
+
# @options item_defaults [String] :target The IP address of the target that the action was performed on
|
16
|
+
# @return [Output] a new Output object for the given command output
|
17
|
+
def self.new_output(cmd_output, strict: true, **item_defaults)
|
18
|
+
Output.new(cmd_output, strict: strict, **item_defaults)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CemAcpt
|
4
|
+
module Bolt
|
5
|
+
# Class for general Bolt errors. Can also wrap other errors.
|
6
|
+
class BoltActionError < StandardError
|
7
|
+
attr_reader :original_error, :bolt_action, :bolt_object
|
8
|
+
|
9
|
+
def initialize(msg = 'Bolt error occured', original_error = nil, bolt_action = nil, bolt_object = nil)
|
10
|
+
@original_error = original_error
|
11
|
+
@bolt_action = bolt_action
|
12
|
+
@bolt_object = bolt_object
|
13
|
+
unless @original_error.nil?
|
14
|
+
set_backtrace(@original_error.backtrace)
|
15
|
+
msg = "#{msg}: #{@original_error}"
|
16
|
+
end
|
17
|
+
super(msg)
|
18
|
+
end
|
19
|
+
|
20
|
+
def bolt_target
|
21
|
+
@bolt_target ||= @bolt_object&.target
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_h
|
25
|
+
{
|
26
|
+
bolt_action: bolt_action,
|
27
|
+
bolt_object: bolt_object,
|
28
|
+
original_error: original_error,
|
29
|
+
message: message,
|
30
|
+
backtrace: backtrace,
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Class for Bolt project errors. Can also wrap other errors.
|
36
|
+
class BoltProjectError < BoltActionError
|
37
|
+
def initialize(msg = 'Bolt project error occured', original_error = nil, *_args)
|
38
|
+
super(msg, original_error, 'project', 'bolt-project.yaml')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Class for Bolt inventory errors. Can also wrap other errors.
|
43
|
+
class BoltInventoryError < BoltActionError
|
44
|
+
def initialize(msg = 'Bolt inventory error occured', original_error = nil, *_args)
|
45
|
+
super(msg, original_error, 'inventory', 'inventory.yaml')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../utils/shell'
|
4
|
+
|
5
|
+
module CemAcpt
|
6
|
+
module Bolt
|
7
|
+
# Module containing helper methods for Bolt
|
8
|
+
module Helpers
|
9
|
+
BOLT_PROJECT_FILE = 'bolt-project.yaml'
|
10
|
+
INVENTORY_FILE = 'inventory.yaml'
|
11
|
+
|
12
|
+
def load_object_test(bolt_test_data, bolt_object)
|
13
|
+
return { params: {} } unless bolt_test_data
|
14
|
+
|
15
|
+
bolt_test_data[bolt_object].transform || { params: {} }
|
16
|
+
end
|
17
|
+
|
18
|
+
def new_bolt_project_hash(module_name, config)
|
19
|
+
{
|
20
|
+
'name' => module_name,
|
21
|
+
'analytics' => false,
|
22
|
+
}.merge(config.get('bolt.project')&.transform_keys(&:to_s) || {})
|
23
|
+
end
|
24
|
+
|
25
|
+
def new_inventory_hash(hosts, private_key, config)
|
26
|
+
{
|
27
|
+
'groups' => [
|
28
|
+
{
|
29
|
+
'name' => 'nix',
|
30
|
+
'targets' => hosts,
|
31
|
+
'config' => {
|
32
|
+
'transport' => 'ssh',
|
33
|
+
'ssh' => {
|
34
|
+
'connect-timeout' => 60,
|
35
|
+
'disconnect-timeout' => 60,
|
36
|
+
'host-key-check' => false,
|
37
|
+
'private-key' => private_key || '~/.ssh/id_rsa',
|
38
|
+
'run-as' => 'root',
|
39
|
+
'tmpdir' => '/var/tmp', # /tmp is usually noexec
|
40
|
+
}.merge(config.get('bolt.transport.ssh')&.transform_keys(&:to_s) || {}),
|
41
|
+
},
|
42
|
+
},
|
43
|
+
],
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def bolt_bin
|
48
|
+
@bolt_bin ||= CemAcpt::Utils::Shell.which('bolt', raise_if_not_found: true)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'errors'
|
4
|
+
require_relative 'yaml_file'
|
5
|
+
|
6
|
+
module CemAcpt
|
7
|
+
module Bolt
|
8
|
+
# Provides an abstraction for the Bolt inventory file
|
9
|
+
class Inventory < YamlFile
|
10
|
+
attr_reader :config, :hosts, :private_key
|
11
|
+
|
12
|
+
def initialize(config, hosts = [], private_key = nil)
|
13
|
+
path = config.get('bolt.inventory_path') || 'inventory.yaml'
|
14
|
+
super(path)
|
15
|
+
@config = config
|
16
|
+
@hosts = hosts
|
17
|
+
@private_key = private_key
|
18
|
+
@hash = new_inventory_hash(hosts, private_key, config)
|
19
|
+
end
|
20
|
+
|
21
|
+
def hosts=(hosts)
|
22
|
+
return if @hosts == hosts
|
23
|
+
|
24
|
+
@hosts = hosts
|
25
|
+
@hash = new_inventory_hash(hosts, @private_key, @config)
|
26
|
+
end
|
27
|
+
|
28
|
+
def private_key=(private_key)
|
29
|
+
return if @private_key == private_key
|
30
|
+
|
31
|
+
@private_key = private_key
|
32
|
+
@hash = new_inventory_hash(@hosts, private_key, @config)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def new_inventory_hash(hosts, private_key, config)
|
38
|
+
{
|
39
|
+
'groups' => [
|
40
|
+
{
|
41
|
+
'name' => 'nix',
|
42
|
+
'targets' => hosts,
|
43
|
+
'config' => {
|
44
|
+
'transport' => 'ssh',
|
45
|
+
'ssh' => {
|
46
|
+
'connect-timeout' => 60,
|
47
|
+
'disconnect-timeout' => 60,
|
48
|
+
'host-key-check' => false,
|
49
|
+
'private-key' => private_key || '~/.ssh/id_rsa',
|
50
|
+
'run-as' => 'root',
|
51
|
+
'tmpdir' => '/var/tmp', # /tmp is usually noexec
|
52
|
+
}.merge(config.get('bolt.transport.ssh')&.transform_keys(&:to_s) || {}),
|
53
|
+
},
|
54
|
+
},
|
55
|
+
],
|
56
|
+
}
|
57
|
+
rescue StandardError => e
|
58
|
+
raise CemAcpt::Bolt::InventoryError.new('Error creating Bolt inventory hash', e)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'errors'
|
4
|
+
require_relative 'yaml_file'
|
5
|
+
|
6
|
+
module CemAcpt
|
7
|
+
module Bolt
|
8
|
+
# Provides an abstraction for the Bolt project file / config
|
9
|
+
class Project < YamlFile
|
10
|
+
attr_reader :config, :module_name
|
11
|
+
|
12
|
+
def initialize(config, module_name)
|
13
|
+
path = config.get('bolt.project.path') || 'bolt-project.yaml'
|
14
|
+
super(path)
|
15
|
+
@config = config
|
16
|
+
@module_name = module_name
|
17
|
+
@hash = new_bolt_project_hash(module_name, config)
|
18
|
+
end
|
19
|
+
|
20
|
+
def latest_saved?
|
21
|
+
# We consider the project file to be up to date if it is subset of the contents on disk
|
22
|
+
# or equal to the contents on disk
|
23
|
+
(@hash == @saved_hash) || lte_to_disk?
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def new_bolt_project_hash(module_name, config)
|
29
|
+
{
|
30
|
+
'name' => module_name,
|
31
|
+
'analytics' => false,
|
32
|
+
}.merge(config.get('bolt.project')&.transform_keys(&:to_s) || {})
|
33
|
+
rescue StandardError => e
|
34
|
+
raise CemAcpt::Bolt::BoltProjectError.new('Error creating Bolt project hash', e)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require_relative '../utils/finalizer_queue'
|
5
|
+
|
6
|
+
module CemAcpt
|
7
|
+
module Bolt
|
8
|
+
# Class that holds the results of the entire Bolt test suite.
|
9
|
+
class SummaryResults < CemAcpt::Utils::FinalizerQueue
|
10
|
+
alias all to_a
|
11
|
+
|
12
|
+
def error
|
13
|
+
require_finalized(binding)
|
14
|
+
return unless error?
|
15
|
+
|
16
|
+
@error ||= find { |r| !r.success? }
|
17
|
+
end
|
18
|
+
|
19
|
+
def error?
|
20
|
+
require_finalized(binding)
|
21
|
+
!success?
|
22
|
+
end
|
23
|
+
|
24
|
+
def success?
|
25
|
+
require_finalized(binding)
|
26
|
+
@success ||= all?(&:success?)
|
27
|
+
end
|
28
|
+
|
29
|
+
def success_count
|
30
|
+
require_finalized(binding)
|
31
|
+
@success_count ||= count(&:success?)
|
32
|
+
end
|
33
|
+
|
34
|
+
def failure_count
|
35
|
+
require_finalized(binding)
|
36
|
+
@failure_count ||= length - success_count
|
37
|
+
end
|
38
|
+
|
39
|
+
def status
|
40
|
+
require_finalized(binding)
|
41
|
+
success? ? 0 : 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def status_str
|
45
|
+
require_finalized(binding)
|
46
|
+
success? ? 'passed' : 'failed'
|
47
|
+
end
|
48
|
+
|
49
|
+
def summary
|
50
|
+
require_finalized(binding)
|
51
|
+
@summary ||= [
|
52
|
+
"status: #{status_str}",
|
53
|
+
"tests total: #{length}",
|
54
|
+
"tests succeeded: #{success_count}",
|
55
|
+
"tests failed: #{failure_count}",
|
56
|
+
].join(', ')
|
57
|
+
end
|
58
|
+
|
59
|
+
def summary?
|
60
|
+
require_finalized(binding)
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
return "#<#{self.class}:#{object_id} unfinalized>" unless finalized?
|
66
|
+
|
67
|
+
"#<#{self.class}:#{object_id} #{self}>"
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_h
|
71
|
+
require_finalized(binding)
|
72
|
+
{ 'summary' => summary, 'status' => status, 'results' => map(&:to_h) }
|
73
|
+
end
|
74
|
+
|
75
|
+
def action
|
76
|
+
require_finalized(binding)
|
77
|
+
map { |rs| rs.results.map(&:action).uniq }.flatten.uniq
|
78
|
+
end
|
79
|
+
|
80
|
+
def results
|
81
|
+
require_finalized(binding)
|
82
|
+
@results ||= map(&:results).flatten
|
83
|
+
end
|
84
|
+
|
85
|
+
def results?
|
86
|
+
require_finalized(binding)
|
87
|
+
!results.empty?
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s
|
91
|
+
require_finalized(binding)
|
92
|
+
JSON.pretty_generate(to_h)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|