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.
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 -353
  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
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'json'
5
+ require 'ruby-terraform'
6
+ require_relative '../logging'
7
+ require_relative 'terraform/linux'
8
+ require_relative 'terraform/windows'
9
+
10
+ module CemAcpt
11
+ module Provision
12
+ class Terraform
13
+ DEFAULT_PLAN_NAME = 'testplan.tfplan'
14
+ include CemAcpt::Logging
15
+
16
+ attr_reader :environment, :working_dir
17
+
18
+ def initialize(config, provision_data)
19
+ @config = config
20
+ @provision_data = provision_data
21
+ @backend = new_backend(@provision_data[:test_data].first[:test_name])
22
+ @environment = new_environment(@config)
23
+ @working_dir = nil
24
+ end
25
+
26
+ def provision
27
+ logger.info('Terraform') { 'Provisioning nodes...' }
28
+ @working_dir = new_working_dir
29
+ validate_working_dir!
30
+
31
+ terraform_configure_logging
32
+ terraform_init
33
+ terraform_plan(formatted_vars, DEFAULT_PLAN_NAME)
34
+ terraform_apply(DEFAULT_PLAN_NAME)
35
+ JSON.parse(terraform_output('instance_name_ip', json: true))
36
+ end
37
+
38
+ def destroy
39
+ terraform_destroy(formatted_vars)
40
+ logger.verbose('Terraform') { "Deleting old working directory #{working_dir}" }
41
+ FileUtils.rm_rf(working_dir)
42
+ @working_dir = nil
43
+ end
44
+
45
+ private
46
+
47
+ def terraform
48
+ @terraform ||= RubyTerraform
49
+ end
50
+
51
+ def terraform_configure_logging
52
+ terraform.configure do |c|
53
+ c.logger = logger
54
+ c.stdout = c.logger
55
+ c.stderr = c.logger
56
+ end
57
+ end
58
+
59
+ def terraform_init
60
+ logger.debug('Terraform') { 'Initializing Terraform' }
61
+ terraform.init({ chdir: working_dir, input: false, no_color: true }, { environment: environment })
62
+ end
63
+
64
+ def terraform_plan(vars, plan_name = DEFAULT_PLAN_NAME)
65
+ logger.debug('Terraform') { "Creating Terraform plan '#{plan_name}'" }
66
+ logger.verbose('Terraform') { "Using vars:\n#{JSON.pretty_generate(vars)}" }
67
+ terraform.plan(
68
+ {
69
+ chdir: working_dir,
70
+ input: false,
71
+ no_color: true,
72
+ plan: plan_name,
73
+ vars: vars,
74
+ },
75
+ {
76
+ environment: environment,
77
+ },
78
+ )
79
+ end
80
+
81
+ def terraform_apply(plan_name = DEFAULT_PLAN_NAME)
82
+ logger.debug('Terraform') { "Running Terraform apply with the plan #{plan_name}" }
83
+ terraform.apply({ chdir: working_dir, input: false, no_color: true, plan: plan_name }, { environment: environment })
84
+ end
85
+
86
+ def terraform_output(name, json: true)
87
+ logger.debug('Terraform') { "Getting Terraform output #{name}" }
88
+ terraform.output({ chdir: working_dir, no_color: true, json: json, name: name }, { environment: environment })
89
+ end
90
+
91
+ def terraform_destroy(vars)
92
+ logger.debug('Terraform') { 'Destroying Terraform resources' }
93
+ terraform.destroy(
94
+ {
95
+ chdir: working_dir,
96
+ auto_approve: true,
97
+ input: false,
98
+ no_color: true,
99
+ vars: vars,
100
+ },
101
+ {
102
+ environment: environment,
103
+ },
104
+ )
105
+ end
106
+
107
+ def new_backend(test_name)
108
+ if CemAcpt::Provision::Linux.use_for?(test_name)
109
+ logger.info('Terraform') { 'Using Linux backend' }
110
+ logger.verbose('Terraform') { "Creating backend with provision_data:\n#{JSON.pretty_generate(@provision_data)}" }
111
+ CemAcpt::Provision::Linux.new(@config, @provision_data)
112
+ elsif CemAcpt::Provision::Windows.use_for?(test_name)
113
+ logger.info('Terraform') { 'Using Windows backend' }
114
+ logger.verbose('Terraform') { "Creating backend with provision_data:\n#{JSON.pretty_generate(@provision_data)}" }
115
+ CemAcpt::Provision::Windows.new(@config, @provision_data)
116
+ else
117
+ err_msg = [
118
+ "Test name #{test_name} does not match any known OS.",
119
+ "Known OSes are: #{CemAcpt::Provision::Linux.valid_names.join(', ')}",
120
+ "and #{CemAcpt::Provision::Windows.valid_names.join(', ')}.",
121
+ "Known versions are: #{CemAcpt::Provision::Linux.valid_versions.join(', ')}",
122
+ ", and #{CemAcpt::Provision::Windows.valid_versions.join(', ')}."
123
+ ].join(' ')
124
+ logger.error('Terraform') { err_msg }
125
+ raise ArgumentError, err_msg
126
+ end
127
+ end
128
+
129
+ def new_environment(config)
130
+ env = (config.get('terraform.environment') || {})
131
+ env['CLOUDSDK_PYTHON_SITEPACKAGES'] = '1' # This is needed for gcloud to use numpy
132
+ logger.verbose('Terraform') { "Using environment:\n#{JSON.pretty_generate(env)}" }
133
+ env
134
+ end
135
+
136
+ def new_working_dir
137
+ logger.debug('Terraform') { "Creating new working directory" }
138
+ base_dir = File.join(@config.get('terraform.dir'), @config.get('platform.name'))
139
+ logger.verbose('Terraform') { "Base directory defined as #{base_dir}" }
140
+ @backend.base_provision_directory = base_dir
141
+ logger.verbose('Terraform') { 'Base directory set in backend' }
142
+ work_dir = File.join(@config.get('terraform.dir'), "test_#{Time.now.to_i.to_s}")
143
+ logger.verbose('Terraform') { "Working directory defined as #{work_dir}" }
144
+ logger.verbose('Terraform') { "Copying backend provision directory #{@backend.provision_directory} to working directory" }
145
+ FileUtils.cp_r(@backend.provision_directory, work_dir)
146
+ logger.verbose('Terraform') { "Copied provision directory #{@backend.provision_directory} to #{work_dir}" }
147
+ FileUtils.cp(@provision_data[:module_package_path], work_dir)
148
+ logger.verbose('Terraform') { "Copied module package #{@provision_data[:module_package_path]} to #{work_dir}" }
149
+ work_dir
150
+ rescue StandardError => e
151
+ logger.error('Terraform') { 'Error creating working directory' }
152
+ raise e
153
+ end
154
+
155
+ def validate_working_dir!
156
+ logger.debug('Terraform') { "Validating working directory #{working_dir}" }
157
+ logger.verbose('Terraform') { "Content of #{working_dir}:\n#{Dir.glob(File.join(working_dir, '*')).join("\n")}" }
158
+ raise "Terraform working directory #{working_dir} does not exist" unless File.directory?(working_dir)
159
+ raise "Terraform working directory #{working_dir} does not contain a Terraform file" unless Dir.glob(File.join(working_dir, '*.tf')).any?
160
+ logger.info('Terraform') { "Using working directory: #{working_dir}" }
161
+ rescue StandardError => e
162
+ logger.error('Terraform') { 'Error validating working directory' }
163
+ raise e
164
+ end
165
+
166
+ def provision_node_data
167
+ node_data = @provision_data[:nodes].each_with_object({}) do |node, h|
168
+ h[node.node_name] = node.node_data.merge({
169
+ goss_file: node.test_data[:goss_file],
170
+ puppet_manifest: node.test_data[:puppet_manifest],
171
+ provision_dir_source: @backend.provision_directory,
172
+ provision_dir_dest: @backend.destination_provision_directory,
173
+ provision_commands: @backend.provision_commands,
174
+ })
175
+ end
176
+ node_data.to_json
177
+ rescue StandardError => e
178
+ logger.error('Terraform') { 'Error creating node data' }
179
+ raise e
180
+ end
181
+
182
+ def formatted_vars
183
+ @provision_data[:nodes].first.platform_data.merge({
184
+ puppet_module_package: @provision_data[:module_package_path],
185
+ node_data: provision_node_data,
186
+ })
187
+ rescue StandardError => e
188
+ logger.error('Terraform') { 'Error creating formatted vars' }
189
+ raise e
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'logging'
4
+ require_relative 'provision/terraform'
5
+
6
+ module CemAcpt
7
+ module Provision
8
+ include CemAcpt::Logging
9
+
10
+ def self.new_provisioner(config, provision_data)
11
+ case config.get('provisioner')
12
+ when 'terraform'
13
+ logger.debug('Provision') { 'Using Terraform provisioner' }
14
+ CemAcpt::Provision::Terraform.new(config, provision_data)
15
+ else
16
+ raise ArgumentError, "Unknown provisioner #{config.get('provisioner')}"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -28,7 +28,6 @@ module CemAcpt
28
28
 
29
29
  # Validates module metadata by raising exception if invalid
30
30
  _metadata = builder.metadata
31
- logger.debug("Metadata for module #{builder.release_name} is valid")
32
31
 
33
32
  # Builds the module package
34
33
  logger.info("Building module package for #{builder.release_name}")
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
3
4
  require_relative 'core_extensions'
4
5
  require_relative 'logging'
5
6
 
@@ -19,28 +20,38 @@ module CemAcpt
19
20
  include CemAcpt::Logging
20
21
  using CemAcpt::CoreExtensions::ExtendedHash
21
22
 
22
- attr_reader :module_dir, :acceptance_tests
23
+ attr_reader :acpt_test_dir, :acceptance_tests
23
24
 
24
25
  # Initializes a new Fetcher object.
25
26
  # @param config [CemAcpt::Config] the config object
26
27
  def initialize(config)
27
28
  @config = config
28
- @module_dir = config.get('module_dir')
29
- @acceptance_tests = find_acceptance_tests(config.get('module_dir'))
29
+ @acpt_test_dir = Pathname(File.join(@config.get('module_dir'), 'spec', 'acceptance'))
30
+ find_acceptance_tests!
30
31
  end
31
32
 
32
33
  # Extracts, formats, and returns a test data hash.
33
34
  # @return [Array<Hash>] an array of test data hashes
34
35
  def acceptance_test_data
35
36
  logger.info 'Gathering acceptance test data...'
36
- acceptance_tests.each_with_object([]) do |t, a|
37
- logger.debug("Processing #{t}...")
38
- test_name = File.basename(t, '_spec.rb')
37
+ raise "No 'tests' entry found in config" unless @config.has?('tests')
38
+
39
+ @config.get('tests').each_with_object([]) do |test_name, a|
40
+ test_dir = acceptance_tests.find { |f| File.basename(f) == test_name }
41
+ raise "Test directory not found for test #{test_name}" unless test_dir
42
+
43
+ goss_file = File.expand_path(File.join(test_dir, 'goss.yaml'))
44
+ puppet_manifest = File.expand_path(File.join(test_dir, 'manifest.pp'))
45
+ raise "Goss file not found for test #{test_name}" unless File.exist?(goss_file)
46
+ raise "Puppet manifest not found for test #{test_name}" unless File.exist?(puppet_manifest)
47
+
48
+ logger.debug("Complete test directory found for test #{test_name}: #{test_dir}")
39
49
  test_data = {
40
50
  test_name: test_name,
41
- test_file: File.expand_path(t),
51
+ test_dir: File.expand_path(test_dir),
52
+ goss_file: goss_file,
53
+ puppet_manifest: puppet_manifest,
42
54
  }
43
- next unless @config.has?('tests') && @config.get('tests').include?(test_name)
44
55
 
45
56
  process_for_each(test_data).each do |test_data_i|
46
57
  process_static_vars(test_data_i)
@@ -56,12 +67,11 @@ module CemAcpt
56
67
 
57
68
  # Locates acceptance tests in the module directory.
58
69
  # @return [Array<String>] the list of acceptance test paths
59
- def find_acceptance_tests(module_dir)
60
- tests = Dir.glob(File.join(module_dir, 'spec', 'acceptance', '*_spec.rb'))
61
- raise 'No acceptance tests found' if tests.empty?
70
+ def find_acceptance_tests!
71
+ @acceptance_tests = acpt_test_dir.children.select { |f| f.directory? && File.exist?(File.join(f, 'goss.yaml')) }.map(&:to_s)
72
+ raise 'No acceptance tests found' if @acceptance_tests.empty?
62
73
 
63
- logger.info "Found #{tests.size} acceptance tests"
64
- tests
74
+ logger.info "Found #{@acceptance_tests.size} acceptance tests"
65
75
  end
66
76
 
67
77
  # Processes a for_each statement in the test data config.
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CemAcpt
4
+ module TestRunner
5
+ module LogFormatter
6
+ # Formats the results of a Goss action
7
+ class GossActionResponse
8
+ INDENT = ' '
9
+
10
+ def initialize(config, instance_names_ips)
11
+ @config = config
12
+ @instance_names_ips = instance_names_ips
13
+ end
14
+
15
+ def inspect
16
+ to_s
17
+ end
18
+
19
+ def to_s
20
+ "#<#{self.class.name}:0x#{object_id.to_s(16)}>"
21
+ end
22
+
23
+ def summary(response)
24
+ new_summary_message(response)
25
+ end
26
+
27
+ def results(response)
28
+ new_results_message(response.results)
29
+ end
30
+
31
+ def host_name(response)
32
+ name_from_ip(response.host)
33
+ end
34
+
35
+ def test_name(response)
36
+ test_from_ip(response.host)
37
+ end
38
+
39
+ private
40
+
41
+ def normalize_whitespace(str)
42
+ raise ArgumentError, 'str must be a String' unless str.is_a?(String)
43
+
44
+ str.gsub(%r{(\n|\r|\t)}, ' ').gsub(%r{\s{2,}}, ' ').strip
45
+ end
46
+
47
+ def success_str(success)
48
+ success ? 'passed' : 'failed'
49
+ end
50
+
51
+ def name_from_ip(ip)
52
+ @instance_names_ips.each do |name, val|
53
+ return name if val['ip'] == ip
54
+ end
55
+ ip
56
+ end
57
+
58
+ def test_from_ip(ip)
59
+ @instance_names_ips.each do |_name, val|
60
+ return val['test_name'] if val['ip'] == ip
61
+ end
62
+ '<unknown>'
63
+ end
64
+
65
+ def new_summary_message(response)
66
+ msg = [
67
+ "SUMMARY: #{success_str(response.success?).capitalize}: Test #{test_from_ip(response.host)}:",
68
+ "#{normalize_whitespace(response.summary.summary_line)}:",
69
+ "Action '#{response.action}' on host #{name_from_ip(response.host)}",
70
+ ].join(' ')
71
+ return msg unless @config.debug?
72
+
73
+ [
74
+ msg,
75
+ "HTTP Status: #{response.http_status}",
76
+ "Failed percentage: #{response.summary.failed_percentage}",
77
+ ].join("\n#{INDENT}")
78
+ end
79
+
80
+ def new_results_message(results)
81
+ results.map { |r| new_result_message(r) }
82
+ end
83
+
84
+ def new_result_message(result)
85
+ return "Error: #{result.error}" unless result.err.nil?
86
+
87
+ status = result.skipped? ? 'Skipped' : success_str(result.success?).capitalize
88
+ msg = [
89
+ "#{status}:",
90
+ normalize_whitespace(result.summary_line),
91
+ ]
92
+ return msg.join(' ') unless @config.debug?
93
+
94
+ [
95
+ msg.join(' '),
96
+ "Duration: #{result.duration.to_s}",
97
+ "Expected: #{result.expected}",
98
+ "Found: #{result.found}",
99
+ ].join("\n#{INDENT}")
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'log_formatter/goss_action_response'
4
+
5
+ module CemAcpt
6
+ module TestRunner
7
+ # Holds classes for formatting test runner results
8
+ module LogFormatter; end
9
+ end
10
+ end
@@ -1,10 +1,177 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'core_extensions'
4
+ require_relative 'goss'
5
+ require_relative 'logging'
6
+ require_relative 'platform'
7
+ require_relative 'provision'
8
+ require_relative 'test_data'
9
+ require_relative 'utils'
10
+ require_relative 'version'
11
+ require_relative 'test_runner/log_formatter'
12
+
3
13
  module CemAcpt
4
14
  # Namespace for all Runner-related classes and modules
5
15
  module TestRunner
6
- require_relative 'test_runner/run_handler'
7
- require_relative 'test_runner/runner'
8
- require_relative 'test_runner/runner_result'
16
+ # Holds all the Runner related code
17
+ class Runner
18
+ include CemAcpt::Logging
19
+
20
+ attr_reader :duration, :exit_code
21
+
22
+ def initialize(config)
23
+ @config = config
24
+ @run_data = {}
25
+ @duration = 0
26
+ @exit_code = 0
27
+ @results = nil
28
+ @http_statuses = []
29
+ end
30
+
31
+ def inspect
32
+ to_s
33
+ end
34
+
35
+ def to_s
36
+ "#<#{self.class.name}:0x#{object_id.to_s(16)}>"
37
+ end
38
+
39
+ def run
40
+ @run_data = {}
41
+ @start_time = Time.now
42
+ logger.info('CemAcpt') { "Starting CemAcpt v#{CemAcpt::VERSION}..." }
43
+ logger.info('CemAcpt') { "Test suite started at #{@start_time}..." }
44
+ logger.info('CemAcpt') { "Using module directory: #{config.get('module_dir')}..." }
45
+ Dir.chdir(config.get('module_dir')) do
46
+ keep_terminal_alive
47
+ @run_data[:priv_key], @run_data[:pub_key], @run_data[:known_hosts] = new_ephemeral_ssh_keys
48
+ logger.info('CemAcpt') { 'Created ephemeral SSH key pair...' }
49
+ @run_data[:module_package_path] = build_module_package
50
+ logger.info('CemAcpt') { "Created module package: #{@run_data[:module_package_path]}..." }
51
+ @run_data[:test_data] = new_test_data
52
+ logger.info('CemAcpt') { 'Created test data...' }
53
+ logger.verbose('CemAcpt') { "Test data: #{@run_data[:test_data]}" }
54
+ @run_data[:nodes] = new_node_data
55
+ logger.info('CemAcpt') { 'Created node data...' }
56
+ logger.verbose('CemAcpt') { "Node data: #{@run_data[:nodes]}" }
57
+ @instance_names_ips = provision_test_nodes
58
+ logger.info('CemAcpt') { 'Provisioned test nodes...' }
59
+ logger.debug('CemAcpt') { "Instance names and IPs: #{@instance_names_ips}" }
60
+ @results = run_tests(@instance_names_ips.map { |_, v| v['ip'] },
61
+ config.get('actions.only'),
62
+ config.get('actions.except'))
63
+ end
64
+ ensure
65
+ clean_up
66
+ process_test_results
67
+ end
68
+
69
+ def clean_up(trap_context = false)
70
+ unless trap_context
71
+ kill_keep_terminal_alive
72
+ end
73
+ clean_ephemeral_ssh_keys
74
+ destroy_test_nodes
75
+ end
76
+
77
+ private
78
+
79
+ attr_reader :config
80
+
81
+ # @return [Thread] The thread that keeps the terminal alive
82
+ def keep_terminal_alive
83
+ return unless config.ci?
84
+
85
+ @keep_terminal_alive ||= CemAcpt::Utils::Terminal.keep_terminal_alive
86
+ end
87
+
88
+ def kill_keep_terminal_alive
89
+ return if @trap_context
90
+
91
+ keep_terminal_alive&.kill
92
+ end
93
+
94
+ # @return [String] The path to the module package
95
+ def build_module_package
96
+ CemAcpt::Utils::Puppet.build_module_package(config.get('module_dir'))
97
+ end
98
+
99
+ # @return [Array<String>] The paths to the ssh private key, public key, and known hosts file
100
+ def new_ephemeral_ssh_keys
101
+ return [nil, nil, nil] if config.get('no_ephemeral_ssh_key')
102
+
103
+ CemAcpt::Utils::SSH::Ephemeral.create
104
+ end
105
+
106
+ def clean_ephemeral_ssh_keys
107
+ return if config.get('no_ephemeral_ssh_key')
108
+
109
+ CemAcpt::Utils::SSH::Ephemeral.clean
110
+ end
111
+
112
+ def new_test_data
113
+ CemAcpt::TestData.acceptance_test_data(config)
114
+ end
115
+
116
+ def new_node_data
117
+ CemAcpt::Platform.use(config.get('platform.name'), config, @run_data[:test_data])
118
+ end
119
+
120
+ def provision_test_nodes
121
+ @provisioner = CemAcpt::Provision.new_provisioner(config, @run_data)
122
+ @provisioner.provision
123
+ end
124
+
125
+ def destroy_test_nodes
126
+ @provisioner&.destroy
127
+ end
128
+
129
+ def run_tests(hosts, only_actions, except_actions)
130
+ only_actions = [] if only_actions.nil?
131
+ except_actions = [] if except_actions.nil?
132
+ CemAcpt::Goss::Api.run_actions_async(hosts, only: only_actions, except: except_actions)
133
+ end
134
+
135
+ def result_log_formatter
136
+ @result_log_formatter ||= LogFormatter::GossActionResponse.new(config, @instance_names_ips)
137
+ end
138
+
139
+ def process_test_results
140
+ if @results.nil?
141
+ logger.error('CemAcpt') { 'No test results to process' }
142
+ @exit_code = 1
143
+ else
144
+ until @results.empty?
145
+ result = @results.pop
146
+ @http_statuses << result.http_status
147
+ log_test_result(result)
148
+ end
149
+ if @http_statuses.empty?
150
+ logger.error('CemAcpt') { 'No test results to process' }
151
+ @exit_code = 1
152
+ else
153
+ @exit_code = @http_statuses.any? { |s| s.to_i != 200 } ? 1 : 0
154
+ end
155
+ end
156
+ @duration = Time.now - @start_time
157
+ logger.info('CemAcpt') { "Test suite finished after ~#{duration.round} seconds." }
158
+ end
159
+
160
+ def log_test_result(result)
161
+ logger.with_ci_group("Test results for #{result_log_formatter.test_name(result)}") do
162
+ logger.info(result_log_formatter.summary(result))
163
+ formatted_results = result_log_formatter.results(result)
164
+ formatted_results.each do |r|
165
+ if r.start_with?('Passed:')
166
+ logger.verbose { r }
167
+ elsif r.start_with?('Skipped:')
168
+ logger.info { r }
169
+ else
170
+ logger.error { r }
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
9
176
  end
10
177
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppet/modulebuilder'
4
+ require 'fileutils'
5
+
6
+ module CemAcpt
7
+ module Utils
8
+ # Puppet-related utilities
9
+ module Puppet
10
+ # Builds a Puppet module package.
11
+ # @param module_dir [String] Path to the module directory. If target_dir
12
+ # is specified as a relative path, it will be relative to the module dir.
13
+ # @param target_dir [String] Path to the target directory where the package
14
+ # will be built. This defaults to the relative path 'pkg/'.
15
+ # @param should_log [Boolean] Whether or not to log the build process.
16
+ # @return [String] Path to the built package.
17
+ def self.build_module_package(module_dir, target_dir = nil, should_log: false)
18
+ builder_logger = should_log ? logger : nil
19
+ builder = ::Puppet::Modulebuilder::Builder.new(::File.expand_path(module_dir), target_dir, builder_logger)
20
+
21
+ # Validates module metadata by raising exception if invalid
22
+ _metadata = builder.metadata
23
+
24
+ # Builds the module package
25
+ builder.build
26
+ end
27
+ end
28
+ end
29
+ end