cem_acpt 0.2.4 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/spec.yml +30 -0
- data/Gemfile +4 -3
- data/Gemfile.lock +95 -43
- data/README.md +144 -83
- data/cem_acpt.gemspec +12 -7
- data/exe/cem_acpt +41 -7
- data/lib/cem_acpt/config.rb +340 -0
- data/lib/cem_acpt/core_extensions.rb +17 -61
- data/lib/cem_acpt/goss/api/action_response.rb +175 -0
- data/lib/cem_acpt/goss/api.rb +83 -0
- data/lib/cem_acpt/goss.rb +8 -0
- data/lib/cem_acpt/image_name_builder.rb +0 -9
- data/lib/cem_acpt/logging/formatter.rb +97 -0
- data/lib/cem_acpt/logging.rb +168 -142
- data/lib/cem_acpt/platform/base.rb +26 -37
- data/lib/cem_acpt/platform/gcp.rb +48 -62
- data/lib/cem_acpt/platform.rb +30 -28
- data/lib/cem_acpt/provision/terraform/linux.rb +47 -0
- data/lib/cem_acpt/provision/terraform/os_data.rb +72 -0
- data/lib/cem_acpt/provision/terraform/windows.rb +22 -0
- data/lib/cem_acpt/provision/terraform.rb +193 -0
- data/lib/cem_acpt/provision.rb +20 -0
- data/lib/cem_acpt/puppet_helpers.rb +0 -1
- data/lib/cem_acpt/test_data.rb +23 -13
- data/lib/cem_acpt/test_runner/log_formatter/goss_action_response.rb +104 -0
- data/lib/cem_acpt/test_runner/log_formatter.rb +10 -0
- data/lib/cem_acpt/test_runner.rb +170 -3
- data/lib/cem_acpt/utils/puppet.rb +29 -0
- data/lib/cem_acpt/utils/ssh.rb +197 -0
- data/lib/cem_acpt/utils/terminal.rb +27 -0
- data/lib/cem_acpt/utils.rb +4 -138
- data/lib/cem_acpt/version.rb +1 -1
- data/lib/cem_acpt.rb +73 -23
- data/lib/terraform/gcp/linux/goss/puppet_idempotent.yaml +10 -0
- data/lib/terraform/gcp/linux/goss/puppet_noop.yaml +12 -0
- data/lib/terraform/gcp/linux/main.tf +191 -0
- data/lib/terraform/gcp/linux/systemd/goss-acpt.service +8 -0
- data/lib/terraform/gcp/linux/systemd/goss-idempotent.service +8 -0
- data/lib/terraform/gcp/linux/systemd/goss-noop.service +8 -0
- data/lib/terraform/gcp/windows/.keep +0 -0
- data/sample_config.yaml +22 -21
- metadata +151 -51
- data/lib/cem_acpt/bootstrap/bootstrapper.rb +0 -206
- data/lib/cem_acpt/bootstrap/operating_system/rhel_family.rb +0 -129
- data/lib/cem_acpt/bootstrap/operating_system.rb +0 -17
- data/lib/cem_acpt/bootstrap.rb +0 -12
- data/lib/cem_acpt/context.rb +0 -153
- data/lib/cem_acpt/platform/base/cmd.rb +0 -71
- data/lib/cem_acpt/platform/gcp/cmd.rb +0 -353
- data/lib/cem_acpt/platform/gcp/compute.rb +0 -332
- data/lib/cem_acpt/platform/vmpooler.rb +0 -24
- data/lib/cem_acpt/rspec_utils.rb +0 -242
- data/lib/cem_acpt/shared_objects.rb +0 -537
- data/lib/cem_acpt/spec_helper_acceptance.rb +0 -184
- data/lib/cem_acpt/test_runner/run_handler.rb +0 -187
- data/lib/cem_acpt/test_runner/runner.rb +0 -210
- data/lib/cem_acpt/test_runner/runner_result.rb +0 -103
@@ -1,129 +0,0 @@
|
|
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
|
@@ -1,17 +0,0 @@
|
|
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
|
data/lib/cem_acpt/bootstrap.rb
DELETED
@@ -1,12 +0,0 @@
|
|
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
|
data/lib/cem_acpt/context.rb
DELETED
@@ -1,153 +0,0 @@
|
|
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
|
@@ -1,71 +0,0 @@
|
|
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
|