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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/CODEOWNERS +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +93 -0
- data/README.md +150 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/cem_acpt.gemspec +39 -0
- data/exe/cem_acpt +84 -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 +153 -0
- data/lib/cem_acpt/core_extensions.rb +108 -0
- data/lib/cem_acpt/image_name_builder.rb +104 -0
- data/lib/cem_acpt/logging.rb +351 -0
- data/lib/cem_acpt/platform/base/cmd.rb +71 -0
- data/lib/cem_acpt/platform/base.rb +78 -0
- data/lib/cem_acpt/platform/gcp/cmd.rb +345 -0
- data/lib/cem_acpt/platform/gcp/compute.rb +332 -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 +39 -0
- data/lib/cem_acpt/rspec_utils.rb +242 -0
- data/lib/cem_acpt/shared_objects.rb +537 -0
- data/lib/cem_acpt/spec_helper_acceptance.rb +184 -0
- data/lib/cem_acpt/test_data.rb +146 -0
- data/lib/cem_acpt/test_runner/run_handler.rb +187 -0
- data/lib/cem_acpt/test_runner/runner.rb +210 -0
- data/lib/cem_acpt/test_runner/runner_result.rb +103 -0
- data/lib/cem_acpt/test_runner.rb +10 -0
- data/lib/cem_acpt/utils.rb +144 -0
- data/lib/cem_acpt/version.rb +5 -0
- data/lib/cem_acpt.rb +34 -0
- data/sample_config.yaml +58 -0
- metadata +218 -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,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'platform'
|
4
|
+
require_relative 'utils'
|
5
|
+
require_relative 'shared_objects'
|
6
|
+
require_relative 'test_data'
|
7
|
+
require_relative 'test_runner/run_handler'
|
8
|
+
require_relative 'logging'
|
9
|
+
|
10
|
+
module CemAcpt
|
11
|
+
# Context provides the context in which the RunHandler creates and starts Runners.
|
12
|
+
module Context
|
13
|
+
class ContextError < StandardError; end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
include CemAcpt::Logging
|
17
|
+
|
18
|
+
KEY_PATH = File.join([ENV['HOME'], '.ssh', 'acpt_test_key']).freeze
|
19
|
+
KH_PATH = File.join([ENV['HOME'], '.ssh', 'acpt_test_known_hosts']).freeze
|
20
|
+
|
21
|
+
def log(msg, level = :info)
|
22
|
+
real_msg = "CONTEXT: #{msg}"
|
23
|
+
logger.send(level, real_msg)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Builds the Puppet module package
|
27
|
+
# @param opts [Hash] config opts
|
28
|
+
# @return [String] The path to the Puppet module package
|
29
|
+
def build_module_package(opts = {})
|
30
|
+
module_dir = opts[:module_dir] || __dir__
|
31
|
+
pkg_path = CemAcpt::Utils::Puppet.build_module_package(module_dir)
|
32
|
+
log("Module package built at #{pkg_path}")
|
33
|
+
pkg_path
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates a SSH key and a SSH known hosts file for the acceptance test suite
|
37
|
+
def new_test_ssh_key
|
38
|
+
log('Creating ephemeral SSH key and known hosts file for acceptance test suites...')
|
39
|
+
@ssh_priv_key, @ssh_pub_key = CemAcpt::Utils::SSH.ephemeral_ssh_key
|
40
|
+
@ssh_known_hosts = CemAcpt::Utils::SSH.acpt_known_hosts
|
41
|
+
CemAcpt::Utils::SSH.set_ssh_file_permissions(@ssh_priv_key, @ssh_pub_key, @ssh_known_hosts)
|
42
|
+
log('Successfully created SSH files...')
|
43
|
+
log("SSH private key: #{@ssh_priv_key}", :debug)
|
44
|
+
log("SSH public key: #{@ssh_pub_key}", :debug)
|
45
|
+
log("SSH known hosts: #{@ssh_known_hosts}", :debug)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Deletes acceptance test suite SSH files
|
49
|
+
def clean_test_ssh_key
|
50
|
+
log('Deleting ephemeral ssh keys and acpt_known_hosts if they exist...')
|
51
|
+
[@ssh_priv_key, @ssh_pub_key, @ssh_known_hosts].map { |f| File.delete(f) if File.exist?(f) }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Prints a period to the terminal every 5 seconds in a single line to keep the terminal
|
55
|
+
# alive. This is used when running in CI mode. Does nothing unless the option `:CI` is
|
56
|
+
# `true`, or the environment variables CI or GITHUB_ACTION are set to a truthy value.
|
57
|
+
# @param opts [Hash] config opts
|
58
|
+
def keep_terminal_alive(opts = {})
|
59
|
+
@keep_terminal_alive = if opts[:CI] || ENV['CI'] || ENV['GITHUB_ACTION']
|
60
|
+
CemAcpt::Utils::Terminal.keep_terminal_alive
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def clean_up_test_suite(opts)
|
65
|
+
@ctx.node_inventory.clear!
|
66
|
+
@ctx.node_inventory.clean_local_files
|
67
|
+
clean_test_ssh_key unless opts[:no_ephemeral_ssh_key]
|
68
|
+
@run_handler&.destroy_test_nodes
|
69
|
+
@keep_terminal_alive&.kill
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Creates a context (CemAcpt::Context::Ctx) object for the RunHandler to create and start Runners.
|
74
|
+
# Provides the following objects for the Runners: a config object,
|
75
|
+
# the test data hash, the node inventory, and the local port allocator.
|
76
|
+
# Additionally, it creates the platform-specific node objects for each
|
77
|
+
# test suite in the test data. It then calls the provided block with
|
78
|
+
# the context objects nodes, config, test_data, and node_inventory.
|
79
|
+
# @param config_opts [Hash] the config options
|
80
|
+
def self.with(**opts)
|
81
|
+
@opts = opts
|
82
|
+
@start_time = Time.now
|
83
|
+
raise CemAcpt::Context::ContextError, 'CemAcpt::Context.with requires a block' unless block_given?
|
84
|
+
|
85
|
+
config_file = @opts[:config_file] || File.expand_path('./cem_acpt_config.yaml')
|
86
|
+
logger.info("Running acceptance test suite at #{@start_time}")
|
87
|
+
logger.debug("Config opts: #{@opts}")
|
88
|
+
logger.debug("Config file: #{config_file}")
|
89
|
+
logger.info("Using module directory: #{@opts[:module_dir]}")
|
90
|
+
keep_terminal_alive(@opts)
|
91
|
+
Dir.chdir(opts[:module_dir]) do
|
92
|
+
new_test_ssh_key unless @opts[:no_ephemeral_ssh_key]
|
93
|
+
pkg_path = build_module_package(@opts)
|
94
|
+
@ctx = CemAcpt::Context::Ctx.new(opts: @opts, config_file: config_file, module_package_path: pkg_path)
|
95
|
+
logger.debug("Created Ctx object #{@ctx}")
|
96
|
+
@run_handler = CemAcpt::TestRunner::RunHandler.new(@ctx)
|
97
|
+
logger.debug("Created RunHandler object #{@run_handler}")
|
98
|
+
yield @run_handler
|
99
|
+
end
|
100
|
+
@exit_code = @run_handler.exit_code
|
101
|
+
rescue StandardError => e
|
102
|
+
logger.fatal("Acceptance test suite encountered an error: #{e.message}")
|
103
|
+
logger.fatal(e.backtrace.join("\n"))
|
104
|
+
@exit_code = 1
|
105
|
+
ensure
|
106
|
+
clean_up_test_suite(@opts)
|
107
|
+
total_minutes = ((Time.now - @start_time) / 60).round
|
108
|
+
logger.info("Test suite finished in ~#{total_minutes} minutes")
|
109
|
+
@exit_code || 1
|
110
|
+
end
|
111
|
+
|
112
|
+
# Ctx holds the context objects for the RunHandler to create and start Runners.
|
113
|
+
class Ctx
|
114
|
+
attr_reader :config, :test_data, :module_package_path, :node_inventory, :local_port_allocator
|
115
|
+
|
116
|
+
def initialize(opts: nil, config_file: nil, module_package_path: nil)
|
117
|
+
@config = CemAcpt::Config.new.load(opts: opts, config_file: config_file)
|
118
|
+
@test_data = CemAcpt::TestData.acceptance_test_data(@config)
|
119
|
+
@module_package_path = module_package_path
|
120
|
+
@node_inventory = CemAcpt::NodeInventory.new
|
121
|
+
@local_port_allocator = CemAcpt::LocalPortAllocator.new
|
122
|
+
prep_environment
|
123
|
+
end
|
124
|
+
|
125
|
+
def nodes
|
126
|
+
return @nodes if defined?(@nodes)
|
127
|
+
|
128
|
+
raise CemAcpt::Error, 'No platform(s) specified' unless @config.has?('platform') || @config.has?('platforms')
|
129
|
+
|
130
|
+
@nodes = nodes_from_platforms
|
131
|
+
@nodes
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def prep_environment
|
137
|
+
@node_inventory.clean_local_files
|
138
|
+
end
|
139
|
+
|
140
|
+
def nodes_from_platforms
|
141
|
+
nodes = {}
|
142
|
+
if @config.has?('platform')
|
143
|
+
nodes[@config.get('platform')] = CemAcpt::Platform.use(@config.get('platform'), @config, @test_data, @local_port_allocator)
|
144
|
+
elsif @config.has?('platforms')
|
145
|
+
config.get('platforms').each do |platform|
|
146
|
+
nodes[platform] = CemAcpt::Platform.use(platform, @config, @test_data, @local_port_allocator)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
nodes
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,108 @@
|
|
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
|
+
unless deep_frozen?
|
49
|
+
@table.reduce({}) do |h, (key, value)|
|
50
|
+
fkey = key.respond_to?(:deep_freeze) ? key.deep_freeze : key
|
51
|
+
fval = value.respond_to?(:deep_freeze) ? value.deep_freeze : value
|
52
|
+
h.merge(fkey => fval)
|
53
|
+
end.freeze
|
54
|
+
@deep_frozen = true
|
55
|
+
end
|
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
|
+
dig(*path.split('.').map(&:to_sym)) || dig(*path.split('.'))
|
94
|
+
end
|
95
|
+
|
96
|
+
# Stores a value in a nested Hash using a dot-separated path
|
97
|
+
# to dig through keys.
|
98
|
+
# Example:
|
99
|
+
# hash = {a: {b: {c: 1}}}
|
100
|
+
# hash.dot_store('a.b.c', 2)
|
101
|
+
# hash #=> {a: {b: {c: 2}}}
|
102
|
+
def dot_store(path, value)
|
103
|
+
*key, last = path.split('.').map(&:to_sym)
|
104
|
+
key.inject(self, :fetch)[last] = value
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
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
|