cem_acpt 0.2.5 → 0.6.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/spec.yml +30 -0
  3. data/Gemfile +4 -3
  4. data/Gemfile.lock +95 -43
  5. data/README.md +144 -83
  6. data/cem_acpt.gemspec +12 -7
  7. data/exe/cem_acpt +41 -7
  8. data/lib/cem_acpt/config.rb +340 -0
  9. data/lib/cem_acpt/core_extensions.rb +17 -61
  10. data/lib/cem_acpt/goss/api/action_response.rb +175 -0
  11. data/lib/cem_acpt/goss/api.rb +83 -0
  12. data/lib/cem_acpt/goss.rb +8 -0
  13. data/lib/cem_acpt/image_name_builder.rb +0 -9
  14. data/lib/cem_acpt/logging/formatter.rb +97 -0
  15. data/lib/cem_acpt/logging.rb +168 -142
  16. data/lib/cem_acpt/platform/base.rb +26 -37
  17. data/lib/cem_acpt/platform/gcp.rb +48 -62
  18. data/lib/cem_acpt/platform.rb +30 -28
  19. data/lib/cem_acpt/provision/terraform/linux.rb +47 -0
  20. data/lib/cem_acpt/provision/terraform/os_data.rb +72 -0
  21. data/lib/cem_acpt/provision/terraform/windows.rb +22 -0
  22. data/lib/cem_acpt/provision/terraform.rb +193 -0
  23. data/lib/cem_acpt/provision.rb +20 -0
  24. data/lib/cem_acpt/puppet_helpers.rb +0 -1
  25. data/lib/cem_acpt/test_data.rb +23 -13
  26. data/lib/cem_acpt/test_runner/log_formatter/goss_action_response.rb +104 -0
  27. data/lib/cem_acpt/test_runner/log_formatter.rb +10 -0
  28. data/lib/cem_acpt/test_runner.rb +170 -3
  29. data/lib/cem_acpt/utils/puppet.rb +29 -0
  30. data/lib/cem_acpt/utils/ssh.rb +197 -0
  31. data/lib/cem_acpt/utils/terminal.rb +27 -0
  32. data/lib/cem_acpt/utils.rb +4 -138
  33. data/lib/cem_acpt/version.rb +1 -1
  34. data/lib/cem_acpt.rb +73 -23
  35. data/lib/terraform/gcp/linux/goss/puppet_idempotent.yaml +10 -0
  36. data/lib/terraform/gcp/linux/goss/puppet_noop.yaml +12 -0
  37. data/lib/terraform/gcp/linux/main.tf +191 -0
  38. data/lib/terraform/gcp/linux/systemd/goss-acpt.service +8 -0
  39. data/lib/terraform/gcp/linux/systemd/goss-idempotent.service +8 -0
  40. data/lib/terraform/gcp/linux/systemd/goss-noop.service +8 -0
  41. data/lib/terraform/gcp/windows/.keep +0 -0
  42. data/sample_config.yaml +22 -21
  43. metadata +151 -51
  44. data/lib/cem_acpt/bootstrap/bootstrapper.rb +0 -206
  45. data/lib/cem_acpt/bootstrap/operating_system/rhel_family.rb +0 -129
  46. data/lib/cem_acpt/bootstrap/operating_system.rb +0 -17
  47. data/lib/cem_acpt/bootstrap.rb +0 -12
  48. data/lib/cem_acpt/context.rb +0 -153
  49. data/lib/cem_acpt/platform/base/cmd.rb +0 -71
  50. data/lib/cem_acpt/platform/gcp/cmd.rb +0 -345
  51. data/lib/cem_acpt/platform/gcp/compute.rb +0 -332
  52. data/lib/cem_acpt/platform/vmpooler.rb +0 -24
  53. data/lib/cem_acpt/rspec_utils.rb +0 -242
  54. data/lib/cem_acpt/shared_objects.rb +0 -537
  55. data/lib/cem_acpt/spec_helper_acceptance.rb +0 -184
  56. data/lib/cem_acpt/test_runner/run_handler.rb +0 -187
  57. data/lib/cem_acpt/test_runner/runner.rb +0 -210
  58. 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
@@ -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
@@ -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