cem_acpt 0.2.6-universal-java-17

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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +3 -0
  4. data/CODEOWNERS +1 -0
  5. data/Gemfile +9 -0
  6. data/Gemfile.lock +93 -0
  7. data/README.md +150 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/cem_acpt.gemspec +39 -0
  12. data/exe/cem_acpt +84 -0
  13. data/lib/cem_acpt/bootstrap/bootstrapper.rb +206 -0
  14. data/lib/cem_acpt/bootstrap/operating_system/rhel_family.rb +129 -0
  15. data/lib/cem_acpt/bootstrap/operating_system.rb +17 -0
  16. data/lib/cem_acpt/bootstrap.rb +12 -0
  17. data/lib/cem_acpt/context.rb +153 -0
  18. data/lib/cem_acpt/core_extensions.rb +108 -0
  19. data/lib/cem_acpt/image_name_builder.rb +104 -0
  20. data/lib/cem_acpt/logging.rb +351 -0
  21. data/lib/cem_acpt/platform/base/cmd.rb +71 -0
  22. data/lib/cem_acpt/platform/base.rb +78 -0
  23. data/lib/cem_acpt/platform/gcp/cmd.rb +345 -0
  24. data/lib/cem_acpt/platform/gcp/compute.rb +332 -0
  25. data/lib/cem_acpt/platform/gcp.rb +85 -0
  26. data/lib/cem_acpt/platform/vmpooler.rb +24 -0
  27. data/lib/cem_acpt/platform.rb +103 -0
  28. data/lib/cem_acpt/puppet_helpers.rb +39 -0
  29. data/lib/cem_acpt/rspec_utils.rb +242 -0
  30. data/lib/cem_acpt/shared_objects.rb +537 -0
  31. data/lib/cem_acpt/spec_helper_acceptance.rb +184 -0
  32. data/lib/cem_acpt/test_data.rb +146 -0
  33. data/lib/cem_acpt/test_runner/run_handler.rb +187 -0
  34. data/lib/cem_acpt/test_runner/runner.rb +210 -0
  35. data/lib/cem_acpt/test_runner/runner_result.rb +103 -0
  36. data/lib/cem_acpt/test_runner.rb +10 -0
  37. data/lib/cem_acpt/utils.rb +144 -0
  38. data/lib/cem_acpt/version.rb +5 -0
  39. data/lib/cem_acpt.rb +34 -0
  40. data/sample_config.yaml +58 -0
  41. metadata +218 -0
@@ -0,0 +1,351 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module CemAcpt
6
+ # Logging for CemAcpt
7
+ module Logging
8
+ LEVEL_MAP = {
9
+ 'debug' => Logger::DEBUG,
10
+ 'info' => Logger::INFO,
11
+ 'warn' => Logger::WARN,
12
+ 'error' => Logger::ERROR,
13
+ 'fatal' => Logger::FATAL,
14
+ 'unknown' => Logger::UNKNOWN,
15
+ }
16
+
17
+ # Delegator class for when you want to log to multiple devices
18
+ # at the same time, such as STDOUT and a file.
19
+ # @param loggers [::Logger] one or more instances of Logger
20
+ class MultiLogger
21
+ def initialize(*loggers)
22
+ @loggers = loggers
23
+ end
24
+
25
+ def method_missing(m, *args, &block)
26
+ if @loggers.all? { |l| l.respond_to?(m) }
27
+ @loggers.map { |l| l.send(m, *args, &block) }
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ def respond_to_missing?(m, include_private = false)
34
+ @loggers.all? { |l| l.respond_to?(m) } || super
35
+ end
36
+ end
37
+
38
+ class << self
39
+ def new_logger(*logdevs, **configs)
40
+ new_configs = current_log_config.merge(configs.reject { |_, v| v.nil? })
41
+ if new_configs.key?(:level) && !LEVEL_MAP.values.include?(new_configs[:level])
42
+ new_configs[:level] = LEVEL_MAP[new_configs[:level].downcase]
43
+ end
44
+ loggers = logdevs.map do |dev|
45
+ logdev = dev.is_a?(String) ? $stdout : dev
46
+ logger = Logger.new(
47
+ logdev,
48
+ new_configs[:shift_age],
49
+ new_configs[:shift_size],
50
+ **new_configs.reject { |k, _| %i[logdev shift_age shift_size].include?(k) },
51
+ )
52
+ logger.reopen(dev) if dev.is_a?(String)
53
+ logger
54
+ end
55
+ @logger = CemAcpt::Logging::MultiLogger.new(*loggers)
56
+ end
57
+
58
+ # Exposes a logger instance. Will either use the currently set logger or
59
+ # create a new one.
60
+ # @return [Logger]
61
+ def logger
62
+ @logger ||= Logger.new(
63
+ current_log_config[:logdev],
64
+ current_log_config[:shift_age],
65
+ current_log_config[:shift_size],
66
+ **current_log_config.reject { |k, _| %i[logdev shift_age shift_size].include?(k) }
67
+ )
68
+ end
69
+
70
+ # Shortcut method for logger.level
71
+ # @return [Logger::Severity]
72
+ def current_log_level
73
+ logger.level
74
+ end
75
+
76
+ # Shortcut method to set logger.level
77
+ # @param level [String] the log level to set
78
+ def new_log_level(level)
79
+ raise ArgumentError, 'Log level not recognized' unless LEVEL_MAP[level.downcase]
80
+
81
+ @logger.level = LEVEL_MAP[level.downcase]
82
+ end
83
+
84
+ # Shows the current log format style if set, or the default if not.
85
+ # @return [Symbol] the current log format style
86
+ def current_log_format
87
+ @current_log_format ||= :text
88
+ end
89
+
90
+ # Sets the current log format style and returns a proc to be passed to
91
+ # Logger#formatter=
92
+ # @param f [Symbol] the log format style to set
93
+ # @return [Proc] the proc to be passed to Logger#formatter=
94
+ def new_log_formatter(f)
95
+ case f.downcase.to_sym
96
+ when :json
97
+ require 'json'
98
+ @current_log_format = :json
99
+ proc do |severity, datetime, progname, msg|
100
+ {
101
+ timestamp: datetime,
102
+ progname: progname,
103
+ severity: severity,
104
+ message: msg,
105
+ }.to_json + "\n"
106
+ end
107
+ when :text
108
+ @current_log_format = :text
109
+ proc do |severity, _datetime, _progname, msg|
110
+ "#{severity} - #{msg}\n"
111
+ end
112
+ when :github_action
113
+ @current_log_format = :github_action
114
+ sev_map = {
115
+ 'DEBUG' => '::debug::',
116
+ 'INFO' => '::notice::',
117
+ 'WARN' => '::warning::',
118
+ 'ERROR' => '::error::',
119
+ 'FATAL' => '::error::',
120
+ }
121
+ proc do |severity, _datetime, _progname, msg|
122
+ "#{sev_map[severity]}{#{msg}}\n"
123
+ end
124
+ else
125
+ @current_log_format = :file
126
+ proc do |severity, datetime, _progname, msg|
127
+ "[#{datetime}] #{severity}: #{msg}\n"
128
+ end
129
+ end
130
+ end
131
+
132
+ # Returns the current log config if set, or the default if not.
133
+ # @return [Hash] the current log config
134
+ def current_log_config
135
+ return @log_config if @log_config
136
+
137
+ formatter = new_log_formatter(current_log_format)
138
+ @log_config = {
139
+ logdev: $stdout,
140
+ shift_age: 'o',
141
+ shift_size: 1_048_576,
142
+ level: Logger::INFO,
143
+ progname: 'CemAcpt',
144
+ datetime_format: '%Y%m%dT%H%M%S%z',
145
+ formatter: formatter,
146
+ }
147
+ @log_config
148
+ end
149
+
150
+ # Creates a new log config hash and a new Logger instance using that config and sets
151
+ # the new Logger instance as the current logger. NO DEFAULT VALUES ARE SET.
152
+ # @param logdev [String, IO] the log device to use. If 'stdout' or 'stderr' are passed,
153
+ # the respective IO object will be used. Otherwise, Strings will be treated as file paths.
154
+ # If an IO object is passed, it will be used directly.
155
+ # If no logdev is passed, or an invalid logdev is passed, the default is $stdout.
156
+ # @param shift_age [String] the log rotation age
157
+ # @param shift_size [Integer] the log rotation size
158
+ # @param level [Logger::Severity] the log level
159
+ # @param formatter [Proc] the log formatter
160
+ # @param datetime_format [String] the datetime format
161
+ def new_log_config(logdev: nil, shift_age: nil, shift_size: nil, level: nil, formatter: nil, datetime_format: nil)
162
+ @log_config[:shift_age] = shift_age if shift_age
163
+ @log_config[:shift_size] = shift_size if shift_size
164
+ @log_config[:level] = level if level
165
+ @log_config[:formatter] = formatter if formatter
166
+ @log_config[:datetime_format] = datetime_format if datetime_format
167
+ case logdev
168
+ when 'stdout'
169
+ @log_config[:logdev] = $stdout
170
+ when 'stderr'
171
+ @log_config[:logdev] = $stderr
172
+ when String
173
+ @log_config[:logdev] = target
174
+ when IO
175
+ @log_config[:logdev] = logdev
176
+ else
177
+ @log_config[:logdev] = $stdout
178
+ logger.warn("Unknown log target: #{logdev.inspect}, using STDOUT")
179
+ end
180
+ @logger = Logger.new(
181
+ @log_config[:logdev],
182
+ @log_config[:shift_age],
183
+ @log_config[:shift_size],
184
+ **@log_config.reject { |k, _| %i[logdev shift_age shift_size].include?(k) },
185
+ )
186
+ end
187
+ end
188
+
189
+ # Provides class method wrappers for logging methods
190
+ def self.included(base)
191
+ class << base
192
+ def new_logger(*logdevs, **configs)
193
+ CemAcpt::Logging.new_logger(*logdevs, **configs)
194
+ end
195
+
196
+ def logger
197
+ CemAcpt::Logging.logger
198
+ end
199
+
200
+ def current_log_level
201
+ CemAcpt::Logging.current_log_level
202
+ end
203
+
204
+ def new_log_level(level)
205
+ CemAcpt::Logging.new_log_level(level)
206
+ end
207
+
208
+ def current_log_format
209
+ CemAcpt::Logging.current_log_format
210
+ end
211
+
212
+ def new_log_formatter(f)
213
+ CemAcpt::Logging.new_log_formatter(f)
214
+ end
215
+
216
+ def current_log_config
217
+ CemAcpt::Logging.current_log_config
218
+ end
219
+
220
+ def new_log_config(logdev: nil, shift_age: nil, shift_size: nil, level: nil, formatter: nil, datetime_format: nil)
221
+ CemAcpt::Logging.new_log_config(logdev: logdev, shift_age: shift_age, shift_size: shift_size, level: level, formatter: formatter, datetime_format: datetime_format)
222
+ end
223
+ end
224
+ end
225
+
226
+ def new_logger(*logdevs, **configs)
227
+ CemAcpt::Logging.new_logger(*logdevs, **configs)
228
+ end
229
+
230
+ # Exposes the logger instance
231
+ def logger
232
+ CemAcpt::Logging.logger
233
+ end
234
+
235
+ # Exposes the current log level
236
+ def current_log_level
237
+ CemAcpt::Logging.current_log_level
238
+ end
239
+
240
+ # Exposes setting the log level
241
+ def new_log_level(level)
242
+ CemAcpt::Logging.new_log_level(level)
243
+ end
244
+
245
+ def current_log_format
246
+ CemAcpt::Logging.current_log_format
247
+ end
248
+
249
+ def new_log_formatter(f)
250
+ CemAcpt::Logging.new_log_formatter(f)
251
+ end
252
+
253
+ # Exposes the current log config
254
+ def current_log_config
255
+ CemAcpt::Logging.current_log_config
256
+ end
257
+
258
+ # Exposes setting a new log config
259
+ def new_log_config(logdev: nil, shift_age: nil, shift_size: nil, level: nil, formatter: nil, datetime_format: nil)
260
+ CemAcpt::Logging.new_log_config(logdev: logdev, shift_age: shift_age, shift_size: shift_size, level: level, formatter: formatter, datetime_format: datetime_format)
261
+ end
262
+ end
263
+
264
+ module LoggingAsync
265
+ require 'concurrent-ruby'
266
+
267
+ class << self
268
+ def log_write_thread
269
+ @log_write_thread ||= Concurrent::SingleThreadExecutor.new
270
+ end
271
+
272
+ def async_debug(message, prefix = nil)
273
+ msg = prefix.nil? || prefix.empty? ? message : "#{prefix} #{message}"
274
+ CemAcpt::Logging.logger.debug(msg)
275
+ end
276
+
277
+ def async_info(message, prefix = nil)
278
+ msg = prefix.nil? || prefix.empty? ? message : "#{prefix} #{message}"
279
+ CemAcpt::Logging.logger.info(msg)
280
+ end
281
+
282
+ def async_warn(message, prefix = nil)
283
+ msg = prefix.nil? || prefix.empty? ? message : "#{prefix} #{message}"
284
+ CemAcpt::Logging.logger.warn(msg)
285
+ end
286
+
287
+ def async_error(message, prefix = nil)
288
+ msg = prefix.nil? || prefix.empty? ? message : "#{prefix} #{message}"
289
+ CemAcpt::Logging.logger.error(msg)
290
+ end
291
+
292
+ def async_fatal(message, prefix = nil)
293
+ msg = prefix.nil? || prefix.empty? ? message : "#{prefix} #{message}"
294
+ CemAcpt::Logging.logger.fatal(msg)
295
+ end
296
+ end
297
+
298
+ # Provides class method wrappers for logging methods
299
+ def self.included(base)
300
+ class << base
301
+ def log_write_thread
302
+ CemAcpt::LoggingAsync.log_write_thread
303
+ end
304
+
305
+ def async_debug(message, prefix = nil)
306
+ CemAcpt::LoggingAsync.async_debug(message, prefix)
307
+ end
308
+
309
+ def async_info(message, prefix = nil)
310
+ CemAcpt::LoggingAsync.async_info(message, prefix)
311
+ end
312
+
313
+ def async_warn(message, prefix = nil)
314
+ CemAcpt::LoggingAsync.async_warn(message, prefix)
315
+ end
316
+
317
+ def async_error(message, prefix = nil)
318
+ CemAcpt::LoggingAsync.async_error(message, prefix)
319
+ end
320
+
321
+ def async_fatal(message, prefix = nil)
322
+ CemAcpt::LoggingAsync.async_fatal(message, prefix)
323
+ end
324
+ end
325
+ end
326
+
327
+ def log_write_thread
328
+ CemAcpt::LoggingAsync.log_write_thread
329
+ end
330
+
331
+ def async_debug(message, prefix = nil)
332
+ CemAcpt::LoggingAsync.async_debug(message, prefix)
333
+ end
334
+
335
+ def async_info(message, prefix = nil)
336
+ CemAcpt::LoggingAsync.async_info(message, prefix)
337
+ end
338
+
339
+ def async_warn(message, prefix = nil)
340
+ CemAcpt::LoggingAsync.async_warn(message, prefix)
341
+ end
342
+
343
+ def async_error(message, prefix = nil)
344
+ CemAcpt::LoggingAsync.async_error(message, prefix)
345
+ end
346
+
347
+ def async_fatal(message, prefix = nil)
348
+ CemAcpt::LoggingAsync.async_fatal(message, prefix)
349
+ end
350
+ end
351
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CemAcpt::Platform
4
+ require_relative File.join(__dir__, '..', '..', 'logging.rb')
5
+
6
+ class CmdError < StandardError; end
7
+
8
+ # Base class for command providers. Provides an API for subclasses to implement.
9
+ class CmdBase
10
+ include CemAcpt::Logging
11
+
12
+ attr_reader :env
13
+
14
+ def initialize(*_args, env: {}, **_kwargs)
15
+ @env = env
16
+ end
17
+
18
+ def local_exec(*_args, **_kwargs)
19
+ raise NotImplementedError, '#local_exec must be implemented by a subclass'
20
+ end
21
+
22
+ def ssh(_instance_name, _command, _ignore_command_errors: false, _opts: {})
23
+ raise NotImplementedError, '#ssh must be implemented by a subclass'
24
+ end
25
+
26
+ def scp_upload(_instance_name, _local, _remote, _scp_opts: {}, _opts: {})
27
+ raise NotImplementedError, '#scp_upload must be implemented by a subclass'
28
+ end
29
+
30
+ def scp_download(_instance_name, _local, _remote, _scp_opts: {}, _opts: {})
31
+ raise NotImplementedError, '#scp_download must be implemented by a subclass'
32
+ end
33
+
34
+ def ssh_ready?(_instance_name, _timeout = 300, _opts: {})
35
+ raise NotImplementedError, '#ssh_ready? must be implemented by a subclass'
36
+ end
37
+
38
+ def apply_manifest(_instance_name, _manifest, _opts: {})
39
+ raise NotImplementedError, '#create_manifest_on_node must be implemented by a subclass'
40
+ end
41
+
42
+ def run_shell(_instance_name, _command, _opts: {})
43
+ raise NotImplementedError, '#run_shell must be implemented by a subclass'
44
+ end
45
+
46
+ def trim_output(output)
47
+ if output.include?("\n")
48
+ output.split("\n").map(&:strip).reject(&:empty?).join("\n")
49
+ else
50
+ output.strip
51
+ end
52
+ end
53
+
54
+ def with_timed_retry(timeout = 300)
55
+ return unless block_given?
56
+
57
+ last_error = nil
58
+ start_time = Time.now
59
+ while Time.now - start_time < timeout
60
+ begin
61
+ output = yield
62
+ return output
63
+ rescue StandardError => e
64
+ last_error = e
65
+ sleep(10)
66
+ end
67
+ end
68
+ raise last_error
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require_relative File.join(__dir__, '..', 'image_name_builder')
5
+ require_relative File.join(__dir__, '..', 'logging')
6
+
7
+ module CemAcpt::Platform
8
+ # Base class for all platform classes. This class provides an API for
9
+ # interacting with the underlying platform.
10
+ class Base
11
+ include CemAcpt::Logging
12
+
13
+ attr_reader :config, :test_data, :local_port, :node_name, :image_name
14
+
15
+ # @param conf [CemAcpt::Config] the config object.
16
+ # @param single_test_data [Hash] the test data for the current test.
17
+ # @param local_port [Integer] the local port to use for the test.
18
+ def initialize(conf, single_test_data, local_port)
19
+ raise ArgumentError, 'single_test_data must be a Hash' unless single_test_data.is_a?(Hash)
20
+
21
+ @config = conf.get('node_data')
22
+ @test_data = single_test_data
23
+ @local_port = local_port
24
+ @node_name = @test_data[:node_name] || random_node_name
25
+ @image_name = conf.has?('image_name_builder') ? image_name_builder(conf, single_test_data) : @test_data[:image_name]
26
+ end
27
+
28
+ # Node should return a hash of all data about a created node.
29
+ def node
30
+ raise NotImplementedError, '#node must be implemented by subclass'
31
+ end
32
+
33
+ # Provision a node. Will be called asynchronously.
34
+ def provision
35
+ raise NotImplementedError, '#provision must be implemented by subclass'
36
+ end
37
+
38
+ # Destroy a node. Will be called asynchronously.
39
+ def destroy
40
+ raise NotImplementedError, '#destroy must be implemented by subclass'
41
+ end
42
+
43
+ # Tests to see if a node is ready to accept connections from the test suite.
44
+ def ready?
45
+ raise NotImplementedError, '#ready? must be implemented by subclass'
46
+ end
47
+
48
+ # Upload and install a Puppet module package on the node. Blocking call.
49
+ def install_puppet_module_package(_module_pkg_path, _remote_path)
50
+ raise NotImplementedError, '#install_puppet_module_package must be implemented by subclass'
51
+ end
52
+
53
+ # Generates a random node name.
54
+ def random_node_name
55
+ "acpt-test-#{SecureRandom.hex(10)}"
56
+ end
57
+
58
+ # Builds an image name if the config specifies to use the image name builder.
59
+ def image_name_builder(conf, tdata)
60
+ @image_name_builder ||= CemAcpt::ImageNameBuilder.new(conf).build(tdata)
61
+ end
62
+
63
+ # Returns a command provider specified by the Platform module of the specific platform.
64
+ def self.command_provider
65
+ raise NotImplementedError, '#command_provider must be implemented by subclass'
66
+ end
67
+
68
+ # Applies a Puppet manifest on the given node.
69
+ def self.apply_manifest(_instance_name, _manifest, _opts = {})
70
+ raise NotImplementedError, '#apply_manifest must be implemented by subclass'
71
+ end
72
+
73
+ # Runs a shell command on the given node.
74
+ def self.run_shell(_instance_name, _cmd, _opts = {})
75
+ raise NotImplementedError, '#run_shell must be implemented by subclass'
76
+ end
77
+ end
78
+ end