cem_acpt 0.2.5 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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