cem_acpt 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/CODEOWNERS +1 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +68 -0
- data/README.md +146 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/cem_acpt.gemspec +37 -0
- data/exe/cem_acpt +58 -0
- data/lib/cem_acpt/bootstrap/bootstrapper.rb +206 -0
- data/lib/cem_acpt/bootstrap/operating_system/rhel_family.rb +129 -0
- data/lib/cem_acpt/bootstrap/operating_system.rb +17 -0
- data/lib/cem_acpt/bootstrap.rb +12 -0
- data/lib/cem_acpt/context.rb +60 -0
- data/lib/cem_acpt/core_extensions.rb +111 -0
- data/lib/cem_acpt/image_name_builder.rb +104 -0
- data/lib/cem_acpt/logging.rb +193 -0
- data/lib/cem_acpt/platform/base/cmd.rb +65 -0
- data/lib/cem_acpt/platform/base.rb +78 -0
- data/lib/cem_acpt/platform/gcp/cmd.rb +313 -0
- data/lib/cem_acpt/platform/gcp/compute.rb +327 -0
- data/lib/cem_acpt/platform/gcp.rb +85 -0
- data/lib/cem_acpt/platform/vmpooler.rb +24 -0
- data/lib/cem_acpt/platform.rb +103 -0
- data/lib/cem_acpt/puppet_helpers.rb +38 -0
- data/lib/cem_acpt/runner.rb +304 -0
- data/lib/cem_acpt/shared_objects.rb +416 -0
- data/lib/cem_acpt/spec_helper_acceptance.rb +176 -0
- data/lib/cem_acpt/test_data.rb +157 -0
- data/lib/cem_acpt/utils.rb +70 -0
- data/lib/cem_acpt/version.rb +5 -0
- data/lib/cem_acpt.rb +27 -0
- data/sample_config.yaml +58 -0
- metadata +195 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Currently unused in this implementation.
|
4
|
+
module CemAcpt::Bootstrap::OperatingSystem
|
5
|
+
# This module holds methods used by Bootstrap for RHEL-family operating systems
|
6
|
+
module RhelFamily
|
7
|
+
def puppet_agent_repo
|
8
|
+
"https://yum.puppet.com/#{@collection}-release-el-#{@major_version}.noarch.rpm"
|
9
|
+
end
|
10
|
+
|
11
|
+
def package_manager
|
12
|
+
return 'dnf' if @major_version.to_i >= 8
|
13
|
+
|
14
|
+
'yum'
|
15
|
+
end
|
16
|
+
|
17
|
+
def rvm_deps
|
18
|
+
[
|
19
|
+
'kernel-devel',
|
20
|
+
'gcc',
|
21
|
+
'gcc-c++',
|
22
|
+
'make',
|
23
|
+
'augeas',
|
24
|
+
'augeas-devel',
|
25
|
+
'patch',
|
26
|
+
'readline',
|
27
|
+
'readline-devel',
|
28
|
+
'zlib',
|
29
|
+
'zlib-devel',
|
30
|
+
'libffi-devel',
|
31
|
+
'openssl-devel',
|
32
|
+
'bzip2',
|
33
|
+
'autoconf',
|
34
|
+
'automake',
|
35
|
+
'libtool',
|
36
|
+
'bison',
|
37
|
+
'sqlite-devel',
|
38
|
+
]
|
39
|
+
end
|
40
|
+
|
41
|
+
def repo_installs(repos, sudo: true)
|
42
|
+
return if repos.nil? || repos.empty?
|
43
|
+
|
44
|
+
cmd = [
|
45
|
+
repo_install_cmd(repos, sudo: sudo),
|
46
|
+
repo_install_verify_cmd(repos, sudo: sudo),
|
47
|
+
].join(' && ')
|
48
|
+
"#{cmd} | sudo tee -ai #{@log_file}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def package_installs(packages, sudo: true)
|
52
|
+
return if packages.nil? || packages.empty?
|
53
|
+
|
54
|
+
packages += rvm_deps
|
55
|
+
cmd = [
|
56
|
+
package_install_cmd(packages, sudo: sudo),
|
57
|
+
package_install_verify_cmd(packages, sudo: sudo),
|
58
|
+
].join(' && ')
|
59
|
+
"#{cmd} | sudo tee -ai #{@log_file}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def service_starts(services, sudo: true)
|
63
|
+
return if services.nil? || services.empty?
|
64
|
+
|
65
|
+
cmd = [
|
66
|
+
#service_start_verify_existance_cmd(services, sudo: sudo),
|
67
|
+
service_start_cmd(services, sudo: sudo),
|
68
|
+
].join(' && ')
|
69
|
+
"#{cmd} | sudo tee -ai #{@log_file}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def repo_install_cmd(repos, sudo: true)
|
73
|
+
cmd = []
|
74
|
+
cmd << 'sudo' if sudo
|
75
|
+
cmd << 'rpm -Uv'
|
76
|
+
repos.each { |r| cmd << r }
|
77
|
+
cmd.join(' ')
|
78
|
+
end
|
79
|
+
|
80
|
+
def repo_install_verify_cmd(repos, sudo: true)
|
81
|
+
cmd = []
|
82
|
+
cmd << 'sudo' if sudo
|
83
|
+
cmd << 'rpm -q'
|
84
|
+
repos.each { |r| cmd << r }
|
85
|
+
cmd.join(' ')
|
86
|
+
end
|
87
|
+
|
88
|
+
def package_install_cmd(packages, sudo: true)
|
89
|
+
cmd = []
|
90
|
+
cmd << 'sudo' if sudo
|
91
|
+
cmd << "#{package_manager} install #{package_install_cmd_opts}"
|
92
|
+
packages.each { |p| cmd << p }
|
93
|
+
cmd << package_install_cmd_output_format
|
94
|
+
cmd.join(' ')
|
95
|
+
end
|
96
|
+
|
97
|
+
def package_install_verify_cmd(packages, sudo: true)
|
98
|
+
cmd = []
|
99
|
+
cmd << 'sudo' if sudo
|
100
|
+
cmd << "#{package_manager} list installed"
|
101
|
+
packages.each { |p| cmd << p }
|
102
|
+
cmd.join(' ')
|
103
|
+
end
|
104
|
+
|
105
|
+
def package_install_cmd_opts
|
106
|
+
'-y'
|
107
|
+
end
|
108
|
+
|
109
|
+
def package_install_cmd_output_format
|
110
|
+
'| tr "\n" "#" | sed -e \'s/# / /g\' | tr "#" "\n"'
|
111
|
+
end
|
112
|
+
|
113
|
+
def service_start_cmd(services, sudo: true)
|
114
|
+
cmd = []
|
115
|
+
cmd << 'sudo' if sudo
|
116
|
+
cmd << 'systemctl start'
|
117
|
+
services.each { |s| cmd << s }
|
118
|
+
cmd.join(' ')
|
119
|
+
end
|
120
|
+
|
121
|
+
def service_start_verify_existance_cmd(services, sudo: true)
|
122
|
+
cmd = []
|
123
|
+
cmd << 'sudo' if sudo
|
124
|
+
cmd << 'systemctl list-units --type=service'
|
125
|
+
services.each { |s| cmd << s }
|
126
|
+
cmd.join(' ')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Currently unused in this implementation.
|
4
|
+
module CemAcpt::Bootstrap::OperatingSystem
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
# Currently unused in this implementation.
|
8
|
+
def use_os(os)
|
9
|
+
case os
|
10
|
+
when %r{^(centos|rhel)$}
|
11
|
+
require_relative 'operating_system/rhel_family'
|
12
|
+
self.class.include CemAcpt::Bootstrap::OperatingSystem::RhelFamily
|
13
|
+
else
|
14
|
+
raise Error, "Operating system #{os} is not supported"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Bootstrap provides a method for bootstrapping test nodes.
|
4
|
+
# Currently unused in this implementation.
|
5
|
+
module CemAcpt::Bootstrap
|
6
|
+
require_relative 'bootstrap/bootstrapper'
|
7
|
+
|
8
|
+
def self.run(instance_name, instance_image, cmd_provider, collection: 'puppet7', repos: [], packages: [], services: [], commands: [], cmd_provider_args: {})
|
9
|
+
bootstrapper = CemAcpt::Bootstrap::Bootstrapper.new(instance_name, instance_image, cmd_provider, collection: 'puppet7', repos: [], packages: [], services: [], commands: [], cmd_provider_args: {})
|
10
|
+
bootstrapper.run
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent-ruby'
|
4
|
+
module CemAcpt
|
5
|
+
# Context provides the context in which the RunHandler creates and starts Runners.
|
6
|
+
module Context
|
7
|
+
require_relative 'platform'
|
8
|
+
require_relative 'shared_objects'
|
9
|
+
require_relative 'test_data'
|
10
|
+
require_relative 'logging'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
include CemAcpt::Logging
|
14
|
+
end
|
15
|
+
|
16
|
+
# Creates a context for the RunHandler to create and start Runners.
|
17
|
+
# Provides the following objects for the Runners: a config object,
|
18
|
+
# the test data hash, the node inventory, and the local port allocator.
|
19
|
+
# Additionally, it creates the platform-specific node objects for each
|
20
|
+
# test suite in the test data. It then calls the provided block with
|
21
|
+
# the context objects nodes, config, test_data, and node_inventory.
|
22
|
+
# @param config_opts [Hash] the config options
|
23
|
+
# @param config_file [String] the config file
|
24
|
+
def self.with(config_opts: nil, config_file: nil, &block)
|
25
|
+
logger.debug('In context with:')
|
26
|
+
logger.debug("config_opts: #{config_opts}")
|
27
|
+
logger.debug("config_file: #{config_file}")
|
28
|
+
|
29
|
+
config = CemAcpt::Config.new.load(opts: config_opts, config_file: config_file)
|
30
|
+
logger.debug("Loaded config: #{config.to_h}")
|
31
|
+
|
32
|
+
test_data = CemAcpt::TestData.acceptance_test_data(config)
|
33
|
+
logger.debug("Loaded test data: #{test_data}")
|
34
|
+
|
35
|
+
node_inventory = CemAcpt::NodeInventory.new
|
36
|
+
logger.debug("Initialized new node inventory: #{node_inventory}")
|
37
|
+
|
38
|
+
local_port_allocator = CemAcpt::LocalPortAllocator.new
|
39
|
+
logger.debug("Initialized new local port allocator: #{local_port_allocator}")
|
40
|
+
|
41
|
+
if config.has?('platform')
|
42
|
+
logger.debug("Using platform: #{config.get('platform')}")
|
43
|
+
nodes = CemAcpt::Platform.use(config.get('platform'), config, test_data, local_port_allocator)
|
44
|
+
block.call(nodes, config, test_data, node_inventory)
|
45
|
+
elsif config.has?('platforms')
|
46
|
+
config.get('platforms').each do |platform|
|
47
|
+
logger.debug("Using platform: #{platform}")
|
48
|
+
nodes = CemAcpt::Platform.use(platform, config, test_data, local_port_allocator)
|
49
|
+
block.call(nodes, config, test_data, node_inventory)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
raise CemAcpt::Error, 'No platform(s) specified'
|
53
|
+
end
|
54
|
+
rescue StandardError => e
|
55
|
+
logger.fatal("Fatal error: #{e.message}")
|
56
|
+
logger.debug(e.backtrace.join('; '))
|
57
|
+
exit(1)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This module holds extensions to Ruby and the Ruby stdlib
|
4
|
+
# Extensions related to deep_freeze were pulled from: https://gist.github.com/steakknife/1a37057b3b8539f4aca3
|
5
|
+
module CemAcpt::CoreExtensions
|
6
|
+
# DeepFreeze recursively freezes all keys and values in a hash
|
7
|
+
# Currently unused, but was used at one point and may be useful again
|
8
|
+
module DeepFreeze
|
9
|
+
# Holds deep_freeze extensions to Kernel
|
10
|
+
module Kernel
|
11
|
+
alias deep_freeze freeze
|
12
|
+
alias deep_frozen? frozen?
|
13
|
+
end
|
14
|
+
|
15
|
+
# Holds deep_freeze extensions to Enumerable
|
16
|
+
module Enumerable
|
17
|
+
def deep_freeze
|
18
|
+
unless @deep_frozen
|
19
|
+
each(&:deep_freeze)
|
20
|
+
@deep_frozen = true
|
21
|
+
end
|
22
|
+
freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
def deep_frozen?
|
26
|
+
!!@deep_frozen
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Holds deep_freeze extensions to Hash
|
31
|
+
module Hash
|
32
|
+
def deep_freeze
|
33
|
+
transform_values! do |value|
|
34
|
+
value.respond_to?(:deep_freeze) ? value.deep_freeze : value.freeze
|
35
|
+
end
|
36
|
+
freeze
|
37
|
+
@deep_frozen = true
|
38
|
+
end
|
39
|
+
|
40
|
+
def deep_frozen?
|
41
|
+
!!@deep_frozen
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Holds deep_freeze extensions to OpenStruct
|
46
|
+
module OpenStruct
|
47
|
+
def deep_freeze
|
48
|
+
return if deep_frozen?
|
49
|
+
|
50
|
+
@table.reduce({}) do |h, (key, value)|
|
51
|
+
fkey = key.respond_to?(:deep_freeze) ? key.deep_freeze : key
|
52
|
+
fval = value.respond_to?(:deep_freeze) ? value.deep_freeze : value
|
53
|
+
h.merge(fkey => fval)
|
54
|
+
end.freeze
|
55
|
+
@deep_frozen = true
|
56
|
+
end
|
57
|
+
|
58
|
+
def deep_frozen?
|
59
|
+
!!@deep_frozen
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Refines the Hash class with some convenience methods.
|
65
|
+
# Must call `using CemAcpt::CoreExtensions::HashExtensions`
|
66
|
+
# before these methods will be available.
|
67
|
+
module ExtendedHash
|
68
|
+
refine Hash do
|
69
|
+
# Formats a hash by converting all keys to symbols.
|
70
|
+
# If any value is a hash, it will be recursively
|
71
|
+
# extend and formatted.
|
72
|
+
def format!
|
73
|
+
transform_keys!(&:to_sym)
|
74
|
+
transform_values! do |value|
|
75
|
+
if value.is_a?(Hash)
|
76
|
+
value.format!
|
77
|
+
else
|
78
|
+
value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def has?(path)
|
84
|
+
!!dot_dig(path)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Digs into a Hash using a dot-separated path.
|
88
|
+
# If the path is not found, returns nil.
|
89
|
+
# Example:
|
90
|
+
# hash = {a: {b: {c: 1}}}
|
91
|
+
# hash.dot_dig('a.b.c') # => 1
|
92
|
+
def dot_dig(path)
|
93
|
+
result = dig(*path.split('.').map(&:to_sym))
|
94
|
+
return result unless result.nil?
|
95
|
+
|
96
|
+
dig(*path.split('.'))
|
97
|
+
end
|
98
|
+
|
99
|
+
# Stores a value in a nested Hash using a dot-separated path
|
100
|
+
# to dig through keys.
|
101
|
+
# Example:
|
102
|
+
# hash = {a: {b: {c: 1}}}
|
103
|
+
# hash.dot_store('a.b.c', 2)
|
104
|
+
# hash #=> {a: {b: {c: 2}}}
|
105
|
+
def dot_store(path, value)
|
106
|
+
*key, last = path.split('.').map(&:to_sym)
|
107
|
+
key.inject(self, :fetch)[last] = value
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CemAcpt
|
4
|
+
require_relative 'core_extensions'
|
5
|
+
require_relative 'logging'
|
6
|
+
|
7
|
+
# Dynamically builds an image name based on parameters specified in the
|
8
|
+
# config. The config is expected to have a key 'image_name_builder' with
|
9
|
+
# the following options:
|
10
|
+
# - 'parts' - (Required) An array of strings to be joined together to form the image name.
|
11
|
+
# If the strings begin with a '$', they will be replaced with the corresponding
|
12
|
+
# value from current test data. To specify nested keys, use '.' to separate
|
13
|
+
# the keys. Example: '$name.pattern.framework' will be replaced with the
|
14
|
+
# value of the name pattern key framework in the current test data.
|
15
|
+
# - 'join_with' - (Optional) The string to join the parts with. Defaults to ''.
|
16
|
+
# - 'character_substitutions' - (Optional) An array of of 2-item arrays. The first item
|
17
|
+
# is the character to replace, the second is the replacement.
|
18
|
+
# Example: [[' ', '-'], ['_', '-']] will replace all spaces
|
19
|
+
# and underscores with dashes.
|
20
|
+
# - 'validation_pattern' - (Optional) A regex pattern to validate the image name against.
|
21
|
+
class ImageNameBuilder
|
22
|
+
include CemAcpt::Logging
|
23
|
+
using CemAcpt::CoreExtensions::ExtendedHash
|
24
|
+
|
25
|
+
# Initializes the ImageNameBuilder.
|
26
|
+
# @param config [CemAcpt::Config] The config to use.
|
27
|
+
def initialize(config)
|
28
|
+
unless config.has?('image_name_builder')
|
29
|
+
raise ArgumentError, 'Configuration does not have an image_name_builder key'
|
30
|
+
end
|
31
|
+
|
32
|
+
@config = config.get('image_name_builder')
|
33
|
+
end
|
34
|
+
|
35
|
+
# Builds an image name based on the config. It does so in three steps:
|
36
|
+
# 1. Resolve variables in the parts array.
|
37
|
+
# 2. Join the parts together with the join_with string if specified
|
38
|
+
# and validate the image name against the validation_pattern if
|
39
|
+
# specified.
|
40
|
+
# 3. Perform any specified character substitutions on the image name.
|
41
|
+
# @param test_data [Hash] The test data to use to build the image name.
|
42
|
+
# @return [String] The image name.
|
43
|
+
def build(test_data)
|
44
|
+
logger.debug 'Building image name...'
|
45
|
+
logger.debug "Using config: #{@config.to_h}"
|
46
|
+
logger.debug "Test data: #{test_data}"
|
47
|
+
parts = resolve_parts(test_data)
|
48
|
+
logger.debug "Resolved parts: #{parts}"
|
49
|
+
image_name = create_image_name(parts)
|
50
|
+
logger.debug "Created image name: #{image_name}"
|
51
|
+
final_image_name = character_substitutions(image_name)
|
52
|
+
logger.debug "Final image name: #{final_image_name}"
|
53
|
+
final_image_name
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Resolves variables in the parts array by replacing them with the
|
59
|
+
# corresponding value from the test data.
|
60
|
+
# @param test_data [Hash] The test data to use to build the image name.
|
61
|
+
# @return [Array] The parts array with variables resolved.
|
62
|
+
def resolve_parts(test_data)
|
63
|
+
@config[:parts].each_with_object([]) do |part, ary|
|
64
|
+
logger.debug "Resolving part: #{part}"
|
65
|
+
if part.start_with?('$')
|
66
|
+
var_path = part[1..-1]
|
67
|
+
logger.debug "Resolving variable path: #{var_path}"
|
68
|
+
ary << test_data.dot_dig(var_path)
|
69
|
+
else
|
70
|
+
ary << part
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Creates an image name based on the parts array.
|
76
|
+
# @param parts [Array] The parts array to use to build the image name.
|
77
|
+
# @return [String] The image name.
|
78
|
+
def create_image_name(parts)
|
79
|
+
image_name = @config[:join_with] ? parts.join(@config[:join_with]) : parts.join
|
80
|
+
logger.debug("Final image name: #{image_name}")
|
81
|
+
|
82
|
+
if @config[:validation_pattern]
|
83
|
+
logger.debug "Validating image name: #{image_name}..."
|
84
|
+
return image_name if image_name.match?(@config[:validation_pattern])
|
85
|
+
|
86
|
+
raise "Image name #{image_name} does not match validation pattern #{@config[:validation_pattern]}"
|
87
|
+
end
|
88
|
+
image_name
|
89
|
+
end
|
90
|
+
|
91
|
+
# Performs character substitutions on the image name.
|
92
|
+
# @param name [String] The image name to perform substitutions on.
|
93
|
+
# @return [String] The image name with substitutions performed.
|
94
|
+
def character_substitutions(name)
|
95
|
+
return name unless @config[:character_substitutions]
|
96
|
+
|
97
|
+
subbed_name = name
|
98
|
+
@config[:character_substitutions].each do |char_sub|
|
99
|
+
subbed_name.gsub!(char_sub[0], char_sub[1])
|
100
|
+
end
|
101
|
+
subbed_name
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module CemAcpt
|
6
|
+
# Logging for CemAcpt
|
7
|
+
module Logging
|
8
|
+
class << self
|
9
|
+
attr_writer :logger
|
10
|
+
|
11
|
+
# Exposes a logger instance. Will either use the currently set logger or
|
12
|
+
# create a new one.
|
13
|
+
# @return [Logger]
|
14
|
+
def logger
|
15
|
+
@logger ||= Logger.new(
|
16
|
+
current_log_config[:logdev],
|
17
|
+
current_log_config[:shift_age],
|
18
|
+
current_log_config[:shift_size],
|
19
|
+
**current_log_config.reject { |k, _| %i[logdev shift_age shift_size].include?(k) }
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Shortcut method for logger.level
|
24
|
+
# @return [Logger::Severity]
|
25
|
+
def current_log_level
|
26
|
+
logger.level
|
27
|
+
end
|
28
|
+
|
29
|
+
# Shortcut method to set logger.level
|
30
|
+
# @param level [String] the log level to set
|
31
|
+
def new_log_level(level)
|
32
|
+
case level.downcase
|
33
|
+
when 'debug'
|
34
|
+
@logger.level = Logger::DEBUG
|
35
|
+
when 'info'
|
36
|
+
@logger.level = Logger::INFO
|
37
|
+
when 'warn'
|
38
|
+
@logger.level = Logger::WARN
|
39
|
+
when 'error'
|
40
|
+
@logger.level = Logger::ERROR
|
41
|
+
when 'fatal'
|
42
|
+
@logger.level = Logger::FATAL
|
43
|
+
when 'unknown'
|
44
|
+
@logger.level = Logger::UNKNOWN
|
45
|
+
else
|
46
|
+
raise "Cannot set log level #{level}: invalid level"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Shows the current log format style if set, or the default if not.
|
51
|
+
# @return [Symbol] the current log format style
|
52
|
+
def current_log_format
|
53
|
+
@current_log_format ||= :text
|
54
|
+
end
|
55
|
+
|
56
|
+
# Sets the current log format style and returns a proc to be passed to
|
57
|
+
# Logger#formatter=
|
58
|
+
# @param f [Symbol] the log format style to set
|
59
|
+
# @return [Proc] the proc to be passed to Logger#formatter=
|
60
|
+
def new_log_formatter(f)
|
61
|
+
case f.downcase.to_sym
|
62
|
+
when :json
|
63
|
+
require 'json'
|
64
|
+
@current_log_format = :json
|
65
|
+
proc do |severity, datetime, progname, msg|
|
66
|
+
{
|
67
|
+
timestamp: datetime.iso8601(3),
|
68
|
+
progname: progname,
|
69
|
+
severity: severity,
|
70
|
+
message: msg,
|
71
|
+
}.to_json + "\n"
|
72
|
+
end
|
73
|
+
when :text
|
74
|
+
@current_log_format = :text
|
75
|
+
proc do |severity, _datetime, _progname, msg|
|
76
|
+
"#{severity} - #{msg}\n"
|
77
|
+
end
|
78
|
+
else
|
79
|
+
@current_log_format = :file
|
80
|
+
proc do |severity, datetime, _progname, msg|
|
81
|
+
"[#{datetime.iso8601(3)}] #{severity}: #{msg}\n"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the current log config if set, or the default if not.
|
87
|
+
# @return [Hash] the current log config
|
88
|
+
def current_log_config
|
89
|
+
return @log_config if @log_config
|
90
|
+
|
91
|
+
formatter = new_log_formatter(current_log_format)
|
92
|
+
@log_config = {
|
93
|
+
logdev: $stdout,
|
94
|
+
shift_age: 'o',
|
95
|
+
shift_size: 1_048_576,
|
96
|
+
level: Logger::INFO,
|
97
|
+
progname: 'CemAcpt',
|
98
|
+
datetime_format: '%Y%m%dT%H%M%S%z',
|
99
|
+
formatter: formatter,
|
100
|
+
}
|
101
|
+
@log_config
|
102
|
+
end
|
103
|
+
|
104
|
+
# Creates a new log config hash and a new Logger instance using that config and sets
|
105
|
+
# the new Logger instance as the current logger. NO DEFAULT VALUES ARE SET.
|
106
|
+
# @param logdev [String, IO] the log device to use. If 'stdout' or 'stderr' are passed,
|
107
|
+
# the respective IO object will be used. Otherwise, Strings will be treated as file paths.
|
108
|
+
# If an IO object is passed, it will be used directly.
|
109
|
+
# If no logdev is passed, or an invalid logdev is passed, the default is $stdout.
|
110
|
+
# @param shift_age [String] the log rotation age
|
111
|
+
# @param shift_size [Integer] the log rotation size
|
112
|
+
# @param level [Logger::Severity] the log level
|
113
|
+
# @param formatter [Proc] the log formatter
|
114
|
+
# @param datetime_format [String] the datetime format
|
115
|
+
def new_log_config(logdev: nil, shift_age: nil, shift_size: nil, level: nil, formatter: nil, datetime_format: nil)
|
116
|
+
@log_config[:shift_age] = shift_age if shift_age
|
117
|
+
@log_config[:shift_size] = shift_size if shift_size
|
118
|
+
@log_config[:level] = level if level
|
119
|
+
@log_config[:formatter] = formatter if formatter
|
120
|
+
@log_config[:datetime_format] = datetime_format if datetime_format
|
121
|
+
case logdev
|
122
|
+
when 'stdout'
|
123
|
+
@log_config[:logdev] = $stdout
|
124
|
+
when 'stderr'
|
125
|
+
@log_config[:logdev] = $stderr
|
126
|
+
when String
|
127
|
+
@log_config[:logdev] = target
|
128
|
+
when IO
|
129
|
+
@log_config[:logdev] = logdev
|
130
|
+
else
|
131
|
+
@log_config[:logdev] = $stdout
|
132
|
+
logger.warn("Unknown log target: #{logdev.inspect}, using STDOUT")
|
133
|
+
end
|
134
|
+
@logger = Logger.new(
|
135
|
+
@log_config[:logdev],
|
136
|
+
@log_config[:shift_age],
|
137
|
+
@log_config[:shift_size],
|
138
|
+
**@log_config.reject { |k, _| %i[logdev shift_age shift_size].include?(k) },
|
139
|
+
)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Provides class method wrappers for logging methods
|
144
|
+
def self.included(base)
|
145
|
+
class << base
|
146
|
+
def logger
|
147
|
+
CemAcpt::Logging.logger
|
148
|
+
end
|
149
|
+
|
150
|
+
def current_log_level
|
151
|
+
CemAcpt::Logging.current_log_level
|
152
|
+
end
|
153
|
+
|
154
|
+
def new_log_level(level)
|
155
|
+
CemAcpt::Logging.new_log_level(level)
|
156
|
+
end
|
157
|
+
|
158
|
+
def current_log_config
|
159
|
+
CemAcpt::Logging.current_log_config
|
160
|
+
end
|
161
|
+
|
162
|
+
def new_log_config(logdev: nil, shift_age: nil, shift_size: nil, level: nil, formatter: nil, datetime_format: nil)
|
163
|
+
CemAcpt::Logging.new_log_config(logdev: logdev, shift_age: shift_age, shift_size: shift_size, level: level, formatter: formatter, datetime_format: datetime_format)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Exposes the logger instance
|
169
|
+
def logger
|
170
|
+
CemAcpt::Logging.logger
|
171
|
+
end
|
172
|
+
|
173
|
+
# Exposes the current log level
|
174
|
+
def current_log_level
|
175
|
+
CemAcpt::Logging.current_log_level
|
176
|
+
end
|
177
|
+
|
178
|
+
# Exposes setting the log level
|
179
|
+
def new_log_level(level)
|
180
|
+
CemAcpt::Logging.new_log_level(level)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Exposes the current log config
|
184
|
+
def current_log_config
|
185
|
+
CemAcpt::Logging.current_log_config
|
186
|
+
end
|
187
|
+
|
188
|
+
# Exposes setting a new log config
|
189
|
+
def new_log_config(logdev: nil, shift_age: nil, shift_size: nil, level: nil, formatter: nil, datetime_format: nil)
|
190
|
+
CemAcpt::Logging.new_log_config(logdev: logdev, shift_age: shift_age, shift_size: shift_size, level: level, formatter: formatter, datetime_format: datetime_format)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CemAcpt::Platform
|
4
|
+
require_relative File.join(__dir__, '..', '..', 'logging.rb')
|
5
|
+
|
6
|
+
# Base class for command providers. Provides an API for subclasses to implement.
|
7
|
+
class CmdBase
|
8
|
+
include CemAcpt::Logging
|
9
|
+
|
10
|
+
def initialize(*args, **kwargs); end
|
11
|
+
|
12
|
+
def local_exec(*_args, **_kwargs)
|
13
|
+
raise NotImplementedError, '#local_exec must be implemented by a subclass'
|
14
|
+
end
|
15
|
+
|
16
|
+
def ssh(_instance_name, _command, _ignore_command_errors: false, _opts: {})
|
17
|
+
raise NotImplementedError, '#ssh must be implemented by a subclass'
|
18
|
+
end
|
19
|
+
|
20
|
+
def scp_upload(_instance_name, _local, _remote, _scp_opts: {}, _opts: {})
|
21
|
+
raise NotImplementedError, '#scp_upload must be implemented by a subclass'
|
22
|
+
end
|
23
|
+
|
24
|
+
def scp_download(_instance_name, _local, _remote, _scp_opts: {}, _opts: {})
|
25
|
+
raise NotImplementedError, '#scp_download must be implemented by a subclass'
|
26
|
+
end
|
27
|
+
|
28
|
+
def ssh_ready?(_instance_name, _timeout = 300, _opts: {})
|
29
|
+
raise NotImplementedError, '#ssh_ready? must be implemented by a subclass'
|
30
|
+
end
|
31
|
+
|
32
|
+
def apply_manifest(_instance_name, _manifest, _opts: {})
|
33
|
+
raise NotImplementedError, '#create_manifest_on_node must be implemented by a subclass'
|
34
|
+
end
|
35
|
+
|
36
|
+
def run_shell(_instance_name, _command, _opts: {})
|
37
|
+
raise NotImplementedError, '#run_shell must be implemented by a subclass'
|
38
|
+
end
|
39
|
+
|
40
|
+
def trim_output(output)
|
41
|
+
if output.include?("\n")
|
42
|
+
output.split("\n").map(&:strip).reject(&:empty?).join("\n")
|
43
|
+
else
|
44
|
+
output.strip
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def with_timed_retry(timeout = 300)
|
49
|
+
return unless block_given?
|
50
|
+
|
51
|
+
last_error = nil
|
52
|
+
start_time = Time.now
|
53
|
+
while Time.now - start_time < timeout
|
54
|
+
begin
|
55
|
+
output = yield
|
56
|
+
return output
|
57
|
+
rescue StandardError => e
|
58
|
+
last_error = e
|
59
|
+
sleep(5)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
raise last_error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|