cem_acpt 0.2.6-universal-java-17

Sign up to get free protection for your applications and to get access to all the features.
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