cem_acpt 0.1.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 +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
|