cem_acpt 0.3.3-universal-java-17 → 0.3.4-universal-java-17

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50d9f549fee6436b9f857dbdf313dcab94b223e988c2cbfeadd8999d86f436f0
4
- data.tar.gz: 62a556754fe568b9d3bbc28b7381b3657c7aa0db872e5405bb392a1b9cef061d
3
+ metadata.gz: c289fcb52a1115630ffece04c465e5a8d945bdefdcc99fbf73a800398d2f2bc6
4
+ data.tar.gz: aca0f41efe831bf5bb8dc4399cd1a2d771f7bb72fb1ee13eeccb79d59bd45183
5
5
  SHA512:
6
- metadata.gz: bf027699bcf399a68a6b845ef4191b4b912736f6eb52708e35a0393e78fce74f3c0299ee627bbfe80ec06a8ef9be0e5b030db6a1ab99a678943c8ebdf21112f6
7
- data.tar.gz: eb7448394e6be3719a735cf30adedae0aefb0ea8a6144523300c2e57da61bdbb3dfbf76a394b3c2014cc78a8283a6c565f2f311481951d09f1211454f22add74
6
+ metadata.gz: 65dd7b8c5023304f0c5f9fd371a64cfb157567004d6f93ebdadc76bbf99f50f7483fab575f73fb9c07256cbb0db688ad4d8c10a6b644a8f7670a3441a54e866e
7
+ data.tar.gz: 2504bc3dc1ef1ac1a09c16d00c3cd99b08127ccd31f2b9da134f31ffb2dc19730eda580ec893def0a9b183bd4198c5aa8263ea735f50c0b2c21156a5f8f7124d
@@ -0,0 +1,36 @@
1
+ name: Unit Tests
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ pull_request:
6
+ types:
7
+ - opened
8
+ - synchronize
9
+ branches:
10
+ - main
11
+ tags:
12
+ - v.*
13
+
14
+ jobs:
15
+ tests:
16
+ name: RSpec tests
17
+ runs-on: ubuntu-20.04
18
+ steps:
19
+ - name: Checkout Source
20
+ uses: actions/checkout@v3
21
+
22
+ - name: Set up Java 17
23
+ uses: actions/setup-java@v3
24
+ with:
25
+ distribution: 'adopt-hotspot'
26
+ java-version: '17'
27
+
28
+ - name: Set up JRuby
29
+ uses: ruby/setup-ruby@v1
30
+ with:
31
+ ruby-version: jruby-9.3.3.0
32
+ bundler-cache: true
33
+
34
+ - name: Run RSpec
35
+ run: |
36
+ bundle exec rake spec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cem_acpt (0.3.3-universal-java-17)
4
+ cem_acpt (0.3.4-universal-java-17)
5
5
  concurrent-ruby (>= 1.1, < 2.0)
6
6
  deep_merge (>= 1.2, < 2.0)
7
7
  ed25519 (>= 1.2, < 2.0)
@@ -13,10 +13,13 @@ GEM
13
13
  remote: https://rubygems.org/
14
14
  specs:
15
15
  ast (2.4.2)
16
+ coderay (1.1.3)
16
17
  concurrent-ruby (1.1.10)
17
18
  deep_merge (1.2.2)
18
19
  diff-lcs (1.5.0)
19
20
  ed25519 (1.3.0-java)
21
+ ffi (1.15.5-java)
22
+ method_source (1.0.0)
20
23
  minitar (0.9)
21
24
  multi_json (1.15.0)
22
25
  net-scp (4.0.0)
@@ -27,6 +30,10 @@ GEM
27
30
  parser (3.1.2.0)
28
31
  ast (~> 2.4.1)
29
32
  pathspec (1.0.0)
33
+ pry (0.14.2-java)
34
+ coderay (~> 1.1)
35
+ method_source (~> 1.0)
36
+ spoon (~> 0.0)
30
37
  puppet-modulebuilder (0.3.0)
31
38
  minitar (~> 0.9)
32
39
  pathspec (>= 0.2.1, < 2.0.0)
@@ -73,6 +80,8 @@ GEM
73
80
  net-ssh (>= 2.7)
74
81
  net-telnet (= 0.1.1)
75
82
  sfl
83
+ spoon (0.0.6)
84
+ ffi
76
85
  unicode-display_width (2.2.0)
77
86
 
78
87
  PLATFORMS
@@ -80,9 +89,10 @@ PLATFORMS
80
89
 
81
90
  DEPENDENCIES
82
91
  cem_acpt!
92
+ pry
83
93
  rake (>= 12.0)
84
94
  rspec (>= 3.0)
85
95
  rubocop
86
96
 
87
97
  BUNDLED WITH
88
- 2.3.25
98
+ 2.3.15
data/cem_acpt.gemspec CHANGED
@@ -34,4 +34,5 @@ Gem::Specification.new do |spec|
34
34
  spec.add_runtime_dependency 'puppet-modulebuilder', '>= 0.0.1'
35
35
  spec.add_runtime_dependency 'serverspec-cem-acpt'
36
36
  spec.add_development_dependency 'rubocop'
37
+ spec.add_development_dependency 'pry'
37
38
  end
@@ -4,6 +4,7 @@ module CemAcpt::Platform::Gcp
4
4
  require 'json'
5
5
  require 'open3'
6
6
  require_relative File.join(__dir__, '..', 'base', 'cmd.rb')
7
+ require_relative File.join(__dir__, '..', 'utils', 'linux.rb')
7
8
 
8
9
  # This class provides methods to run gcloud commands. It allows for default values to be
9
10
  # set for the project, zone, and user and can also find these values from the local config.
@@ -59,6 +60,10 @@ module CemAcpt::Platform::Gcp
59
60
  @local_port ||= rand(49_512..65_535)
60
61
  end
61
62
 
63
+ def os_release
64
+ @os_release || :not_set
65
+ end
66
+
62
67
  def ssh_key
63
68
  return @ssh_key unless @ssh_key.nil?
64
69
 
@@ -173,12 +178,38 @@ module CemAcpt::Platform::Gcp
173
178
  logger.debug('Restarting SSH service')
174
179
  gcloud_ssh(instance_name, 'sudo systemctl restart sshd', ignore_command_errors: true)
175
180
  logger.info("SSH connection to #{instance_name} is ready")
181
+ logger.debug('Getting OS release')
182
+ osr = gcloud_ssh(instance_name, 'sudo cat /etc/os-release')
183
+ @os_release = CemAcpt::Platform::Utils::Linux::OSRelease.new(osr)
184
+ logger.debug("OS release: #{@os_release.to_h}")
176
185
  true
177
186
  rescue StandardError => e
178
187
  logger.debug("SSH connection to #{instance_name} failed: #{e}")
179
188
  false
180
189
  end
181
190
 
191
+ def dnf_automatic_success?(instance_name, opts: {})
192
+ ssh_options = ssh_opts(instance_name: instance_name, opts: opts)
193
+ logger.debug("Checking dnf-automatic success on #{instance_name} with options #{ssh_options}")
194
+ gcloud_ssh(instance_name, 'sudo systemctl restart dnf-automatic.timer')
195
+ true
196
+ rescue StandardError => e
197
+ logger.error("DNF automatic updates on #{instance_name} failed: #{e}")
198
+ false
199
+ end
200
+
201
+ def rpm_db_check_success?(instance_name, pkgmgr, opts: {})
202
+ ssh_options = ssh_opts(instance_name: instance_name, opts: opts)
203
+ logger.debug("Checking #{pkgmgr} rpm db on #{instance_name} with options #{ssh_options}")
204
+ gcloud_ssh(instance_name, "sudo #{pkgmgr} upgrade -y rpm glibc")
205
+ gcloud_ssh(instance_name, "sudo rm -f /var/lib/rpm/.rpm.lock")
206
+ gcloud_ssh(instance_name, "sudo #{pkgmgr} upgrade -y #{pkgmgr}")
207
+ true
208
+ rescue StandardError => e
209
+ logger.error("#{pkgmgr} rpm db check on #{instance_name} failed: #{e}")
210
+ false
211
+ end
212
+
182
213
  # This function spawns a background thread to run a GCP IAP tunnel, run the given
183
214
  # code block, then kill the thread. The code block will be yielded ssh_opts that
184
215
  # are used to configure SSH connections over the IAP tunnel. The IAP tunnel is
@@ -236,6 +236,7 @@ module CemAcpt::Platform::Gcp
236
236
  node_data: data,
237
237
  transport: :ssh,
238
238
  ssh_opts: opts,
239
+ os_release: os_release.to_h,
239
240
  }
240
241
  end
241
242
 
@@ -260,6 +261,29 @@ module CemAcpt::Platform::Gcp
260
261
  false
261
262
  end
262
263
 
264
+ def dnf_automatic_success?
265
+ if os_release.name.match?(%r{^Red Hat.*}) && os_release.version.match?(%r{^(8|9).*})
266
+ logger.debug("Checking dnf-automatic on #{name} with platform #{info[:os_release][:platform]}")
267
+ @cmd.dnf_automatic_success?(name)
268
+ else
269
+ true
270
+ end
271
+ end
272
+
273
+ def rpm_db_check_success?
274
+ if os_release.name.match?(%r{^Red Hat.*})
275
+ logger.debug("Checking rpmdb on #{name} with platform #{info[:os_release][:platform]}")
276
+ pkgmgr = os_release.version.match?(%r{^(8|9).*}) ? 'dnf' : 'yum'
277
+ @cmd.rpm_db_check_success?(name, pkgmgr)
278
+ else
279
+ true
280
+ end
281
+ end
282
+
283
+ def os_release
284
+ @cmd.os_release
285
+ end
286
+
263
287
  def destroy
264
288
  @cmd.delete_instance(name)
265
289
  end
@@ -35,6 +35,18 @@ module Platform
35
35
  @instance.ready?
36
36
  end
37
37
 
38
+ # Returns true if dnf_automatic ran successfully on the GCP instance
39
+ def dnf_automatic_success?
40
+ logger.debug("Checking if dnf_automatic ran successfully on #{node_name}...")
41
+ @instance.dnf_automatic_success?
42
+ end
43
+
44
+ # Returns true if rpm_db_check ran successfully on the GCP instance
45
+ def rpm_db_check_success?
46
+ logger.debug("Checking if rpm_db_check ran successfully on #{node_name}...")
47
+ @instance.rpm_db_check_success?
48
+ end
49
+
38
50
  # Runs the test suite against the GCP instance. Must be given a block.
39
51
  # If necessary, can pass information into the block to be used in the test suite.
40
52
  def run_tests(&block)
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CemAcpt
4
+ module Platform
5
+ module Utils
6
+ module Linux
7
+ class OSRelease
8
+ attr_reader :properties
9
+
10
+ def initialize(os_release)
11
+ @os_release = parse_string(os_release)
12
+ @properties = []
13
+ define_properties(@os_release)
14
+ end
15
+
16
+ def to_h
17
+ @os_release
18
+ end
19
+
20
+ private
21
+
22
+ def parse_string(os_release)
23
+ os_release = os_release.split("\n")
24
+ os_release = os_release.map { |line| line.split('=', 2) }.reject { |l| l.length != 2 }
25
+ os_release.map! { |k, v| [k.downcase, v.delete_prefix('"').delete_suffix('"')] }
26
+ os_release.to_h
27
+ end
28
+
29
+ def define_properties(hsh)
30
+ hsh.each do |k, v|
31
+ @properties << k
32
+ self.class.send(:define_method, k) { v }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -5,6 +5,8 @@ require 'English'
5
5
  require_relative '../logging'
6
6
  require_relative '../rspec_utils'
7
7
  require_relative 'runner_result'
8
+ require_relative 'runner_workflow_builder'
9
+ #require_relative 'workflow'
8
10
 
9
11
  module CemAcpt
10
12
  module TestRunner
@@ -23,6 +25,8 @@ module CemAcpt
23
25
 
24
26
  class RunnerProvisionError < RunnerStepError; end
25
27
 
28
+
29
+
26
30
  # Runner is a class that runs a single acceptance test suite on a single node.
27
31
  # It is responsible for managing the lifecycle of the test suite and
28
32
  # reporting the results back to the main thread. Runner objects are created
@@ -32,6 +36,8 @@ module CemAcpt
32
36
 
33
37
  attr_reader :node, :node_exists, :run_result
34
38
 
39
+ MAX_PROVISION_ATTEMPTS = 3
40
+
35
41
  # @param node [String] the name of the node to run the acceptance test suite on
36
42
  # @param ctx [CemAcpt::RunnerCtx] a cem_acpt Ctx (context) object
37
43
  # @param module_pkg_path [Concurrent::IVar] the path to the module package
@@ -42,34 +48,33 @@ module CemAcpt
42
48
  @debug_mode = @context.config.debug_mode?
43
49
  @node_inventory = @context.node_inventory
44
50
  @module_pkg_path = @context.module_package_path
51
+ @provision_attempts = 0
52
+ @provision_start_time = nil
45
53
  @node_exists = false
46
54
  @run_result = CemAcpt::TestRunner::RunnerResult.new(@node, debug: @debug_mode)
47
- @completed_steps = []
48
55
  validate!
49
56
  end
50
57
 
51
- def run_step(step_sym)
52
- send(step_sym)
53
- @completed_steps << step_sym
54
- rescue StandardError => e
55
- err = CemAcpt::TestRunner::RunnerStepError.new(step_sym, e)
56
- step_error_logging(err)
57
- @run_result.from_error(err)
58
- destroy unless step_sym == :destroy
59
- end
60
-
61
58
  # Executes test suite steps
62
59
  def start
63
60
  async_info("Starting test suite for #{@node.node_name}", log_prefix('RUNNER'))
64
- run_step(:provision)
65
- run_step(:bootstrap)
66
- run_step(:run_tests)
67
- run_step(:destroy)
68
- true
61
+ @workflow = new_workflow
62
+ @workflow.run
63
+ if @workflow.success?
64
+ @run_result = @workflow.last_result
65
+ @workflow.completed_steps.each do |s|
66
+ async_info("Step '#{s.name}' completed successfully", log_prefix('RUNNER'))
67
+ end
68
+ true
69
+ else
70
+ @run_result = @workflow.last_error
71
+ step_error_logging(@workflow.last_error)
72
+ false
73
+ end
69
74
  rescue StandardError => e
70
75
  step_error_logging(e)
76
+ @run_result = CemAcpt::TestRunner::RunnerResult.new(@node, debug: @debug_mode)
71
77
  @run_result.from_error(e)
72
- destroy
73
78
  end
74
79
 
75
80
  # Checks for failures in the test results.
@@ -81,6 +86,24 @@ module CemAcpt
81
86
 
82
87
  private
83
88
 
89
+ # Builds a new workflow for the runner
90
+ # @return [CemAcpt::TestRunner::Workflow::Manager] the new workflow
91
+ def new_workflow
92
+ builder = RunnerWorkflowBuilder.new(@node, config: @context.config)
93
+ builder.add_provision
94
+ builder.add_sleep(time: 30)
95
+ builder.add_wait_for_node_ssh
96
+ builder.add_check_dnf_automatic
97
+ builder.add_check_rpm_db
98
+ builder.add_save_node_to_inventory(node_inventory: @node_inventory, platform: @platform)
99
+ builder.add_check_for_module_package_path(module_pkg_path: @module_pkg_path)
100
+ builder.add_install_module(module_pkg_path: @module_pkg_path)
101
+ builder.add_check_node_inventory_file(node_inventory: @node_inventory)
102
+ builder.add_run_tests(rspec_opts: rspec_opts, rspec_cmd: CemAcpt::RSpecUtils::Command, run_result: @run_result)
103
+ builder.add_clean_up
104
+ builder.workflow
105
+ end
106
+
84
107
  def step_error_logging(err)
85
108
  prefix = err.respond_to?(:step) ? log_prefix(err.step.capitalize) : log_prefix('RUNNER')
86
109
  fatal_msg = ["runner failed: #{err.message}"]
@@ -94,93 +117,6 @@ module CemAcpt
94
117
  "#{prefix}: #{@node.test_data[:test_name]}:"
95
118
  end
96
119
 
97
- # Provisions the node for the acceptance test suite.
98
- def provision
99
- async_info("Provisioning #{@node.node_name}...", log_prefix('PROVISION'))
100
- start_time = Time.now
101
- @node.provision
102
- @node_exists = true
103
- max_retries = 60 # equals 300 seconds because we check every five seconds
104
- until @node.ready?
105
- if max_retries <= 0
106
- async_fatal("Node #{@node.node_name} failed to provision", log_prefix('PROVISION'))
107
- raise CemAcpt::TestRunner::RunnerProvisionError, "Provisioning timed out for node #{@node.node_name}"
108
- end
109
-
110
- async_info("Waiting for #{@node.node_name} to be ready for remote connections...", log_prefix('PROVISION'))
111
- max_retries -= 1
112
- sleep(5)
113
- end
114
- async_info("Node #{@node.node_name} is ready...", log_prefix('PROVISION'))
115
- node_desc = {
116
- test_data: @node.test_data,
117
- platform: @platform,
118
- local_port: @node.local_port,
119
- }.merge(@node.node)
120
- @node_inventory.add(@node.node_name, node_desc)
121
- @node_inventory.save
122
- async_info("Node #{@node.node_name} provisioned in #{Time.now - start_time} seconds", log_prefix('PROVISION'))
123
- end
124
-
125
- # Bootstraps the node for the acceptance test suite. Currently, this
126
- # just uploads and installs the module package.
127
- def bootstrap
128
- async_info("Bootstrapping #{@node.node_name}...", log_prefix('BOOTSTRAP'))
129
- until File.exist?(@module_pkg_path)
130
- async_debug("Waiting for module package #{@module_pkg_path} to exist...", log_prefix('BOOTSTRAP'))
131
- sleep(1)
132
- end
133
- async_info("Installing module package #{@module_pkg_path}...", log_prefix('BOOTSTRAP'))
134
- @node.install_puppet_module_package(@module_pkg_path)
135
- end
136
-
137
- # Runs the acceptance test suite via rspec.
138
- def run_tests
139
- attempts = 0
140
- until File.exist?(@node_inventory.save_file_path)
141
- raise 'Node inventory file not found' if (attempts += 1) > 3
142
-
143
- sleep(1)
144
- end
145
- async_info("Running test #{@node.test_data[:test_name]} on node #{@node.node_name}...", log_prefix('RSPEC'))
146
- @node.run_tests do |cmd_env|
147
- cmd_opts = rspec_opts
148
- cmd_opts.env = cmd_opts.env.merge(cmd_env) if cmd_env
149
- # Documentation format gets logged in real time, JSON file is read after the fact
150
- begin
151
- @rspec_cmd = CemAcpt::RSpecUtils::Command.new(cmd_opts)
152
- @rspec_cmd.execute(pty: false, log_prefix: log_prefix('RSPEC'))
153
- @run_result.from_json_file(cmd_opts.format[:json])
154
- rescue Errno::EIO => e
155
- async_error("failed to run rspec: #{@node.test_data[:test_name]}: #{$ERROR_INFO}", log_prefix('RSPEC'))
156
- @run_result.from_error(e)
157
- rescue StandardError => e
158
- async_error("failed to run rspec: #{@node.test_data[:test_name]}: #{e.message}", log_prefix('RSPEC'))
159
- async_debug("Backtrace:\n#{e.backtrace}", log_prefix('RSPEC'))
160
- @run_result.from_error(e)
161
- end
162
- end
163
- async_info("Tests completed with exit code: #{@run_result.exit_status}", log_prefix('RSPEC'))
164
- end
165
-
166
- # Destroys the node for the acceptance test suite.
167
- def destroy
168
- kill_spec_pty_if_exists
169
- if @context.config.get('no_destroy_nodes')
170
- async_info("Not destroying node #{@node.node_name} because 'no_destroy_nodes' is set to true",
171
- log_prefix('DESTROY'))
172
- else
173
- async_info("Destroying #{@node.node_name}...", log_prefix('DESTROY'))
174
- @node.destroy
175
- @node_exists = false
176
- async_info("Node #{@node.node_name} destroyed successfully", log_prefix('DESTROY'))
177
- end
178
- end
179
-
180
- def kill_spec_pty_if_exists
181
- @rspec_cmd&.kill_pty
182
- end
183
-
184
120
  # Validates the runner configuration.
185
121
  def validate!
186
122
  raise 'No node provided' unless @node
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../logging'
4
+ require_relative 'workflow'
5
+
6
+ module CemAcpt
7
+ module TestRunner
8
+ # RunnerWorkflowBuilder builds a workflow for a TestRunner
9
+ # @!attribute [r] workflow
10
+ # @return [CemAcpt::TestRunner::Workflow::Manager] Workflow object
11
+ class RunnerWorkflowBuilder
12
+ include CemAcpt::LoggingAsync
13
+
14
+ attr_reader :workflow
15
+
16
+ # @param node [CemAcpt::Platform::Base] Initialized node object
17
+ # @param config [Hash] Context config hash
18
+ def initialize(node, config)
19
+ @node = node
20
+ @workflow = CemAcpt::TestRunner::Workflow::Manager.new(workflow_manager_opts(config))
21
+ @config = config
22
+ end
23
+
24
+ def add_sleep(**kwargs)
25
+ opts = {
26
+ node: @node,
27
+ time: kwargs[:time] || 10,
28
+ retryable: false,
29
+ }
30
+ @workflow.add_step(:sleep, **opts) do |s|
31
+ log_info("sleeping for #{s.opts[:time]} seconds", s)
32
+ sleep(s.opts[:time])
33
+ end
34
+ end
35
+
36
+ def add_provision(**kwargs)
37
+ opts = {
38
+ node: @node,
39
+ retryable: kwargs[:retryable] || false,
40
+ }
41
+ @workflow.add_step(:provision, **opts) do |s|
42
+ s.opts[:node].provision
43
+ s.opts[:node]
44
+ end
45
+ end
46
+
47
+ def add_wait_for_node_ssh(**kwargs)
48
+ opts = {
49
+ node: @node,
50
+ retryable: kwargs[:retryable] || true,
51
+ retry_delay: kwargs[:retry_delay] || 30,
52
+ retry_max: kwargs[:retry_max] || 10,
53
+ }
54
+ @workflow.add_step(:wait_for_node_ssh, **opts) do |s|
55
+ unless s.opts[:node].ready?
56
+ raise "wait_for_node_ssh timed out for node #{s.opts[:node].node_name}"
57
+ end
58
+ s.opts[:node]
59
+ end
60
+ end
61
+
62
+ def add_check_dnf_automatic(**kwargs)
63
+ opts = {
64
+ node: @node,
65
+ retryable: kwargs[:retryable] || true,
66
+ retry_delay: kwargs[:retry_delay] || 30,
67
+ retry_workflow_on_fail: kwargs[:retry_workflow_on_fail] || true,
68
+ }
69
+ @workflow.add_step(:check_dnf_automatic, **opts) do |s|
70
+ unless s.opts[:node].dnf_automatic_success?
71
+ raise "dnf_automatic failed on node #{s.opts[:node].node_name}"
72
+ end
73
+ s.opts[:node]
74
+ end
75
+ end
76
+
77
+ def add_check_rpm_db(**kwargs)
78
+ opts = {
79
+ node: @node,
80
+ retryable: kwargs[:retryable] || true,
81
+ retry_delay: kwargs[:retry_delay] || 30,
82
+ retry_workflow_on_fail: kwargs[:retry_workflow_on_fail] || true,
83
+ }
84
+ @workflow.add_step(:rpm_db_check, **opts) do |s|
85
+ unless s.opts[:node].rpm_db_check_success?
86
+ raise "rpm_db_check failed on node #{s.opts[:node].node_name}"
87
+ end
88
+ s.opts[:node]
89
+ end
90
+ end
91
+
92
+ def add_save_node_to_inventory(node_inventory:, platform:, **kwargs)
93
+ opts = {
94
+ node: @node,
95
+ platform: platform,
96
+ node_inventory: node_inventory,
97
+ retryable: kwargs[:retryable] || false,
98
+ }
99
+ @workflow.add_step(:save_node_to_inventory, **opts) do |s|
100
+ node_desc = {
101
+ test_data: s.opts[:node].test_data,
102
+ platform: s.opts[:platform],
103
+ local_port: s.opts[:node].local_port,
104
+ }.merge(s.opts[:node].node)
105
+ s.opts[:node_inventory].add(s.opts[:node].node_name, node_desc)
106
+ s.opts[:node_inventory].save
107
+ log_info("node #{s.opts[:node_name]} saved to inventory", s)
108
+ s.opts[:node_inventory]
109
+ end
110
+ end
111
+
112
+ def add_check_for_module_package_path(module_pkg_path:, **kwargs)
113
+ opts = {
114
+ module_pkg_path: module_pkg_path,
115
+ retryable: kwargs[:retryable] || true,
116
+ retry_delay: kwargs[:retry_delay] || 30,
117
+ }
118
+ @workflow.add_step(:check_for_module_package_path, **opts) do |s|
119
+ unless File.exist?(s.opts[:module_pkg_path])
120
+ raise "module package #{s.opts[:module_pkg_path]} does not exist"
121
+ end
122
+ s.opts[:module_pkg_path]
123
+ end
124
+ end
125
+
126
+ def add_install_module(module_pkg_path:, **kwargs)
127
+ opts = {
128
+ node: @node,
129
+ module_pkg_path: module_pkg_path,
130
+ retryable: kwargs[:retryable] || true,
131
+ retry_delay: kwargs[:retry_delay] || 60,
132
+ }
133
+ @workflow.add_step(:bootstrap, **opts) do |s|
134
+ s.opts[:node].install_puppet_module_package(s.opts[:module_pkg_path])
135
+ s.opts[:node]
136
+ end
137
+ end
138
+
139
+ def add_check_node_inventory_file(node_inventory:, **kwargs)
140
+ opts = {
141
+ ni_file_path: node_inventory.save_file_path,
142
+ retryable: kwargs[:retryable] || true,
143
+ retry_max: kwargs[:retry_max] || 60,
144
+ retry_delay: kwargs[:retry_delay] || 5,
145
+ }
146
+ @workflow.add_step(:check_node_inventory_file, **opts) do |s|
147
+ unless File.exist?(s.opts[:ni_file_path])
148
+ raise "node inventory file #{s.opts[:ni_file_path]} not found"
149
+ end
150
+ s.opts[:ni_file_path]
151
+ end
152
+ end
153
+
154
+ def add_run_tests(rspec_opts:, rspec_cmd:, run_result:, **kwargs)
155
+ opts = {
156
+ node: @node,
157
+ rspec_opts: rspec_opts,
158
+ rspec_cmd: rspec_cmd,
159
+ run_result: run_result,
160
+ retryable: kwargs[:retryable] || false,
161
+ }
162
+ @workflow.add_step(:run_tests, **opts) do |s|
163
+ s.opts[:node].run_tests do |cmd_env|
164
+ cmd_opts = s.opts[:rspec_opts].dup
165
+ cmd_opts.env = cmd_opts.env.merge(cmd_env) if cmd_env
166
+ rspec_cmd = s.opts[:rspec_cmd].new(cmd_opts)
167
+ run_result = s.opts[:run_result].dup
168
+ begin
169
+ rspec_cmd.execute(pty: false, log_prefix: "RSPEC: #{@node.test_data[:test_name]}")
170
+ run_result.from_json_file(cmd_opts.format[:json])
171
+ rescue Errno::EIO => e
172
+ log_error("failed to run rspec: #{@node.test_data[:test_name]}: #{$ERROR_INFO}", s)
173
+ run_result.from_error(e)
174
+ rescue StandardError => e
175
+ log_error("failed to run rspec: #{@node.test_data[:test_name]}: #{e.message}", s)
176
+ log_debug(e.backtrace.join("\n"), s)
177
+ run_result.from_error(e)
178
+ end
179
+ end
180
+ run_result
181
+ end
182
+ end
183
+
184
+ def add_clean_up(force: false, **kwargs)
185
+ opts = {
186
+ node: @node,
187
+ config: @config[:config],
188
+ force: force,
189
+ retryable: kwargs[:retryable] || false,
190
+ }
191
+ @workflow.add_step(:clean_up, **opts) do |s|
192
+ if !force && s.opts[:config].get('no_destroy_nodes')
193
+ log_info("not destroying node #{s.opts[:node].node_name} because 'no_destroy_nodes' is set to true", s)
194
+ else
195
+ log_info("destroying node #{s.opts[:node].node_name}", s)
196
+ s.opts[:node].destroy
197
+ log_info("node #{s.opts[:node].node_name} destroyed", s)
198
+ end
199
+ end
200
+ end
201
+
202
+ private
203
+
204
+ def log_msg(msg, step)
205
+ "Step '#{step.name}' on #{@node.node_name}: #{msg}"
206
+ end
207
+
208
+ def log_debug(msg, step)
209
+ async_debug(log_msg(msg, step))
210
+ end
211
+
212
+ def log_info(msg, step)
213
+ async_info(log_msg(msg, step))
214
+ end
215
+
216
+ def log_warn(msg, step)
217
+ async_warn(log_msg(msg, step))
218
+ end
219
+
220
+ def log_error(msg, step)
221
+ async_error(log_msg(msg, step))
222
+ end
223
+
224
+ def log_fatal(msg, step)
225
+ async_fatal(log_msg(msg, step))
226
+ end
227
+
228
+ def workflow_manager_opts(config)
229
+ {
230
+ retry_max: config[:workflow_retry_max] || 3,
231
+ retry_delay: config[:workflow_retry_delay] || 0,
232
+ ignore_failures: config[:workflow_ignore_failures] || false,
233
+ raise_on_fail: config[:workflow_raise_on_fail] || true,
234
+ }
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../logging'
4
+ require_relative 'step'
5
+
6
+ module CemAcpt
7
+ module TestRunner
8
+ module Workflow
9
+ # Manager is a class that manages how steps in a workflow are executed.
10
+ # @!attribute [r] completed_steps
11
+ # @return [Array<Step>] The steps that have been completed
12
+ # @!attribute [r] last_error
13
+ # @return [Exception, nil] The last error that occurred, if any
14
+ # @!attribute [r] last_result
15
+ # @return [Any] The last result that occurred, if any
16
+ # @!attribute [r] retry_max
17
+ # @return [Integer] The maximum number of workflow runs to attempt before failing. Default: 3
18
+ # @!attribute [r] retry_delay
19
+ # @return [Integer] The number of seconds to wait between workflow runs. Default: 0
20
+ # @!attribute [r] steps
21
+ # @return [Array<Step>] The steps that are part of this workflow
22
+ class Manager
23
+ include CemAcpt::LoggingAsync
24
+
25
+ attr_reader :completed_steps, :last_error, :last_result, :retry_max, :retry_delay, :steps
26
+
27
+ # @param [Hash] opts The options to create a new workflow manager with
28
+ # @option opts [Integer] :retry_max The maximum number of workflow runs to attempt before failing. Default: 3
29
+ # @option opts [Integer] :retry_delay The number of seconds to wait between workflow runs. Default: 0
30
+ # @option opts [Boolean] :ignore_failures Whether to ignore Step failures and continue to the next step. Default: false
31
+ # @option opts [Boolean] :raise_on_fail Whether to raise an exception when a Step fails. Default: true
32
+ def initialize(**opts)
33
+ @retry_max = opts[:retry_max] || 3
34
+ @retry_delay = opts[:retry_delay] || 0
35
+ @ignore_failures = opts[:ignore_failures] || false
36
+ @raise_on_fail = opts[:raise_on_fail] || true
37
+ @steps = []
38
+ @workflow_runs = 0
39
+ @last_error = nil
40
+ @last_result = nil
41
+ @completed_steps = []
42
+ end
43
+
44
+ # Add a step to the workflow. Steps can be named anything, but if the step name is :clean_up,
45
+ # it will be run at the end of the workflow, regardless of the order it was added, and it's
46
+ # output will not be saved as the last_result. Additionally, the :clean_up step will be run
47
+ # even if the workflow fails.
48
+ # @param [Symbol] name The name of the step
49
+ # @param [Hash] kwargs The keyword arguments to pass to the step
50
+ # @param [Proc] block The block to pass to the step
51
+ # @yieldparam [Step] step The step that was added. This is passed to the block.
52
+ def add_step(name, **kwargs, &block)
53
+ raise ArgumentError, 'name must be a Symbol' unless name.is_a?(Symbol)
54
+
55
+ step_name = if @steps.any? { |step| step.name == name }
56
+ "#{name}_#{@steps.length}".to_sym
57
+ else
58
+ name
59
+ end
60
+ step = Step.new(step_name, **kwargs, &block)
61
+ @steps << StepState.new(step, @steps.length, **kwargs)
62
+ end
63
+
64
+ # Run the workflow
65
+ def run
66
+ @workflow_runs += 1
67
+ @completed_steps = []
68
+ @steps.each do |step|
69
+ next if step.name == :clean_up
70
+
71
+ result = step.run
72
+ handle_result(step, result)
73
+ end
74
+ ensure
75
+ clean_up
76
+ end
77
+
78
+ # Whether the workflow has completed successfully
79
+ def success?
80
+ (@completed_steps.length == @steps.length) && @completed_steps.none? { |step| step.failed? }
81
+ end
82
+
83
+ # @return [Boolean] Whether to ignore failures and continue to the next step
84
+ def ignore_failures?
85
+ @ignore_failures
86
+ end
87
+
88
+ # @return [Boolean] Whether to raise an exception when a step fails
89
+ def raise_on_fail?
90
+ @raise_on_fail
91
+ end
92
+
93
+ private
94
+
95
+ # Handles the result of a step
96
+ # @param [Step] step The step that was run
97
+ # @param [Object] result The result of the step
98
+ # @raise [StandardError] If the step failed and the workflow is not configured to ignore failures
99
+ def handle_result(step, result)
100
+ @last_result = result unless step.name == :clean_up # Don't overwrite the last result with the clean_up step
101
+ case result
102
+ when :retry_workflow
103
+ async_warn("Step '#{step.name}' failed and requested a workflow retry")
104
+ async_debug("Step '#{step.name}' failed with error: #{step.last_error.message}")
105
+ async_debug("Step '#{step.name}' failed with error: #{step.last_error.backtrace.join("\n")}")
106
+ @last_error = step.last_error
107
+ retry_workflow
108
+ when :fail
109
+ async_warn("Step '#{step.name}' failed")
110
+ async_debug(step.last_error.message)
111
+ async_debug(step.last_error.backtrace.join("\n"))
112
+ @last_error = step.last_error
113
+ if ignore_failures?
114
+ async_warn("Ignoring failure of step '#{step.name}'")
115
+ @completed_steps << step
116
+ else
117
+ async_error("Workflow failed with error: #{@last_error.message}")
118
+ raise @last_error
119
+ end
120
+ else
121
+ async_info("Step '#{step.name}' succeeded")
122
+ async_debug("Step '#{step.name}' returned: #{result}")
123
+ @completed_steps << step
124
+ end
125
+ end
126
+
127
+ # Retries the workflow if it is retryable
128
+ # @raise [StandardError] If the workflow is not retryable or has exceeded the maximum number of retries
129
+ def retry_workflow
130
+ if @workflow_runs < @retry_max
131
+ async_info("Retrying workflow (attempt #{@workflow_runs + 1} of #{@retry_max})")
132
+ sleep @retry_delay if @retry_delay > 0
133
+ clean_up
134
+ run
135
+ else
136
+ async_fatal('Workflow is not retryable or has exceeded the maximum number of retries')
137
+ raise @last_error
138
+ end
139
+ end
140
+
141
+ # Adds a default clean_up step if one is not defined
142
+ # @return [Step] The default clean_up step
143
+ def default_clean_up
144
+ add_step(:clean_up) do
145
+ async_info('No clean_up step defined, skipping')
146
+ true
147
+ end
148
+ @steps.last
149
+ end
150
+
151
+ # Runs the clean_up step
152
+ def clean_up
153
+ cleanup_step = @steps.find { |step| step.name == :clean_up } || default_clean_up
154
+ result = cleanup_step.run
155
+ handle_result(cleanup_step, result)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../logging'
4
+
5
+ module CemAcpt
6
+ module TestRunner
7
+ module Workflow
8
+ # Error used to wrap fatal errors raised in Runner steps
9
+ # @!attribute [r] step
10
+ # @return [Step] The step that raised the error
11
+ class StepError < StandardError
12
+ attr_reader :step
13
+
14
+ def initialize(step, err)
15
+ @step = step
16
+ @original_error = err
17
+ super err
18
+ set_backtrace err.backtrace if err.respond_to?(:backtrace)
19
+ end
20
+ end
21
+
22
+ # Step is a class that defines a single step in a Workflow.
23
+ # Step objects are created by the Workflow class and are not intended to be created directly.
24
+ # @!attribute [r] name
25
+ # @return [Symbol] The name of the step
26
+ # @!attribute [r] opts
27
+ # @return [Hash] The options passed to the step
28
+ # @!attribute [r] result
29
+ # @return [Object] The result of the step
30
+ class Step
31
+ include CemAcpt::LoggingAsync
32
+
33
+ attr_reader :name, :opts, :result
34
+
35
+ # @param name [Symbol] The name of the step
36
+ # @param opts [Hash] The options passed to the step
37
+ # @param block [Proc] The block to execute when the step is run
38
+ # @yieldparam step [Step] This step object is yielded to the block
39
+ def initialize(name, **opts, &block)
40
+ @name = name
41
+ @opts = opts
42
+ @block = block
43
+ @result = :not_run
44
+ @failed = false
45
+ @log_prefix = opts[:log_prefix] || "#{@name.upcase}:"
46
+ end
47
+
48
+ # @return [Boolean] True if the step has been run and failed
49
+ def failed?
50
+ @failed
51
+ end
52
+
53
+ # Run the step. This calls and executes the block passed to the constructor.
54
+ # @param log_prefix [String] The prefix to use when logging the step name
55
+ # @return [Object] The result of the step
56
+ def run(_log_prefix = 'Running step')
57
+ async_info(log_msg('Starting step'))
58
+ @result = @block.call(self)
59
+ async_debug(log_msg('SUCCESS'))
60
+ @result
61
+ rescue StandardError => e
62
+ async_debug(log_msg("FAILED: #{e.message}"))
63
+ @result = StepError.new(@name, e)
64
+ @failed = true
65
+ @result
66
+ ensure
67
+ Thread.pass # Be kind to the scheduler
68
+ end
69
+
70
+ private
71
+
72
+ def log_msg(msg)
73
+ [@log_prefix, msg].join(' ')
74
+ end
75
+ end
76
+
77
+ # StepState is a class that holds the state of a Step.
78
+ # StepState objects are created by the Workflow class and are not intended to be created directly.
79
+ # @!attribute [r] step
80
+ # @return [Step] The step object
81
+ # @!attribute [r] position
82
+ # @return [Integer] The position of the step in the workflow
83
+ # @!attribute [r] opts
84
+ # @return [Hash] The options passed to the step
85
+ # @!attribute [r] run_count
86
+ # @return [Integer] The number of times the step has been run
87
+ # @!attribute [r] results
88
+ # @return [Array<Object>] The results of all runs of the step
89
+ # @!attribute [r] last_error
90
+ # @return [StepError] The last error raised by the step
91
+ # @!attribute [r] last_result
92
+ # @return [Object] The result of the last run of the step
93
+ class StepState
94
+ attr_reader :step, :position, :opts, :run_count, :results, :last_error, :last_result
95
+
96
+ # @param step [Step] The step object
97
+ # @param position [Integer] The position of the step in the workflow
98
+ # @param opts [Hash] The options passed to the step
99
+ def initialize(step, position, **opts)
100
+ @step = step
101
+ @position = position
102
+ @retryable = opts[:retryable] || false
103
+ @retry_max = opts[:retry_max] || 3
104
+ @retry_delay = opts[:retry_delay] || 0
105
+ @retry_workflow_on_fail = opts[:retry_workflow_on_fail] || false
106
+ @run_count = 0
107
+ @results = []
108
+ @last_error = nil
109
+ @last_result = nil
110
+ end
111
+
112
+ # Proxy any methods not defined in this class to the Step
113
+ def method_missing(method, *args, &block)
114
+ if @step.respond_to?(method)
115
+ @step.send(method, *args, &block)
116
+ else
117
+ super
118
+ end
119
+ end
120
+
121
+ # Proxy any methods not defined in this class to the Step
122
+ def respond_to_missing?(method, include_private = false)
123
+ @step.respond_to?(method) || super
124
+ end
125
+
126
+ # @return [Object] The result of the last run of the step
127
+ def result
128
+ @last_result || :not_run
129
+ end
130
+
131
+ # @return [Boolean] If the step is retryable
132
+ def retryable?
133
+ @retryable
134
+ end
135
+
136
+ # @return [Boolean] If the workflow should be retried if the step fails
137
+ def retry_workflow_on_fail?
138
+ @retry_workflow_on_fail
139
+ end
140
+
141
+ # @return [Boolean] True if the step has been run and failed
142
+ def failed?
143
+ @results.last.is_a?(StepError)
144
+ end
145
+
146
+ # Run the step. This wraps the Step#run method and handles updating the state of the step.
147
+ # @param log_prefix [String] The prefix to use when logging the step name
148
+ # @return [Object] The result of the step
149
+ def run(log_prefix = 'Running step')
150
+ @run_count += 1
151
+ @last_result = @step.run(log_prefix)
152
+ @results << @last_result
153
+ if @last_result.is_a?(StepError)
154
+ handle_error(@last_result)
155
+ end
156
+ @last_result
157
+ end
158
+
159
+ private
160
+
161
+ # Handle the error raised by the step
162
+ # @param result [StepError] The result of the step
163
+ def handle_error(result)
164
+ @last_error = result
165
+ if retry_workflow_on_fail?
166
+ @last_result = :retry_workflow
167
+ elsif retry?
168
+ retry_step
169
+ else
170
+ @last_result = :fail
171
+ end
172
+ end
173
+
174
+ # @return [Boolean] True if the step should be retried
175
+ def retry?
176
+ @retryable && @run_count < @retry_max
177
+ end
178
+
179
+ # Retry running the step
180
+ def retry_step
181
+ sleep @retry_delay if @retry_delay > 0
182
+ run("Retrying step (attempt #{@run_count} of #{@retry_max})")
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../logging'
4
+ require_relative 'workflow/manager'
5
+ require_relative 'workflow/step'
6
+
7
+ module CemAcpt
8
+ module TestRunner
9
+ # Namespace for workflow classes
10
+ module Workflow; end
11
+ end
12
+ end
13
+
14
+ # module CemAcpt
15
+ # module TestRunner
16
+
17
+
18
+ # # Workflow is a class that manages how steps are executed.
19
+ # class Workflow
20
+ # include CemAcpt::LoggingAsync
21
+
22
+ # attr_reader :steps, :groups, :group_retry_max, :completed_steps
23
+
24
+ # def initialize(init_steps: [], **opts)
25
+ # raise ArgumentError, 'init_steps must be an Array' unless init_steps.is_a?(Array)
26
+ # raise ArgumentError, 'init_steps must be an Array of Step objects' unless init_steps.all? { |step| step.is_a?(Step) }
27
+
28
+ # @steps = init_steps
29
+ # @groups = @steps.map(&:group).compact.uniq
30
+ # @group_retry_max = opts[:group_retry_max] || 3
31
+ # @group_tries = {}
32
+ # @last_error = nil
33
+ # @completed_steps = []
34
+ # end
35
+
36
+ # def add_step(step, order = nil)
37
+ # raise ArgumentError, 'step must be a Step object' unless step.is_a?(Step)
38
+
39
+ # unless order.nil?
40
+ # @steps.insert(order, step)
41
+ # else
42
+ # @steps << step
43
+ # end
44
+ # end
45
+
46
+ # def add_step_with(name, order = nil, **kwargs, &block)
47
+ # raise ArgumentError, 'order must be an Integer' unless order.is_a?(Integer) || order.nil?
48
+ # raise ArgumentError, 'name must be a Symbol' unless name.is_a?(Symbol)
49
+
50
+ # unless order.nil?
51
+ # @steps.insert(order, Step.new(name, **kwargs, &block))
52
+ # else
53
+ # @steps << Step.new(name, **kwargs, &block)
54
+ # end
55
+ # end
56
+
57
+ # def run
58
+ # current_run_groups = []
59
+ # @steps.each do |step|
60
+ # next if step.name == :clean_up
61
+
62
+ # group_run_increment(step, current_run_groups)
63
+ # step.run
64
+ # @completed_steps << step
65
+ # end
66
+ # @completed_steps
67
+ # rescue StandardError => e
68
+ # async_error("Workflow failed with error: #{e}")
69
+ # @last_error = e
70
+ # raise e
71
+ # ensure
72
+ # clean_up
73
+ # end
74
+
75
+ # def success?
76
+ # (@completed_steps.length == @steps.length) && @completed_steps.none? { |step| step.result.is_a?(StepError) }
77
+ # end
78
+
79
+ # private
80
+
81
+ # def group_run_increment(step, current_run_groups = [])
82
+ # if step.grouped? && !current_run_groups.include?(step.group)
83
+ # current_run_groups << step.group
84
+ # if @group_tries[step.group].nil? || @group_tries[step.group].zero?
85
+ # @group_tries[step.group] = 1
86
+ # elsif @group_tries[step.group] >= @group_retry_max
87
+ # raise StepError.new(step, @last_error)
88
+ # else
89
+ # @group_tries[step.group] += 1
90
+ # end
91
+ # end
92
+ # end
93
+
94
+ # def run_step(step)
95
+ # result = step.run
96
+ # #handle_result(step, result)
97
+ # @completed_steps << step
98
+ # result
99
+ # end
100
+
101
+ # def handle_result(step, result)
102
+ # if result.is_a?(StepError)
103
+ # handle_error(step, result)
104
+ # else
105
+ # async_info("Step '#{step.name}' completed successfully")
106
+ # end
107
+ # end
108
+
109
+ # def handle_error(step, err)
110
+ # if step.retryable? && step.runs < @retry_max
111
+ # async_info("Retrying step '#{step.name}' (attempt #{step.runs + 1} of #{@retry_max})")
112
+ # sleep step.retry_delay if step.retry_delay > 0
113
+ # run_step(step)
114
+ # else
115
+ # async_debug("Step '#{step.name}' is not retryable or has exceeded the maximum number of retries")
116
+ # raise err if step.raise_on_fail?
117
+ # end
118
+ # end
119
+
120
+ # def default_clean_up
121
+ # Step.new(:clean_up, retryable: false, raise_on_fail: true) do
122
+ # async_info('No clean_up step defined, skipping')
123
+ # end
124
+ # end
125
+
126
+ # def clean_up
127
+ # cleanup_step = @steps.find { |step| step.name == :clean_up } || default_clean_up
128
+ # unless cleanup_step.nil?
129
+ # cleanup_step.retryable = false # clean_up steps should not be retried
130
+ # cleanup_step.raise_on_fail = true # clean_up steps should always raise on failure
131
+ # run_step(cleanup_step)
132
+ # end
133
+ # end
134
+ # end
135
+
136
+ # # Step is a class that defines a single step in a Workflow.
137
+ # class Step
138
+ # include CemAcpt::LoggingAsync
139
+
140
+ # attr_reader :group, :name, :opts, :result, :retry_delay, :retry_max, :runs
141
+
142
+ # def initialize(name, **opts, &logic)
143
+ # @name = name
144
+ # @logic = logic
145
+ # @group = opts[:group] || nil
146
+ # @retryable = opts[:retryable] || false
147
+ # @retry_delay = opts[:retry_delay] || 0
148
+ # @retry_max = opts[:retry_max] || 3
149
+ # @retry_group_on_fail = opts[:retry_group_on_fail] || false
150
+ # @raise_on_fail = opts[:raise_on_fail] || true
151
+ # @runs = 0
152
+ # @opts = opts
153
+ # @result = :not_run
154
+ # end
155
+
156
+ # def grouped?
157
+ # !@group.nil?
158
+ # end
159
+
160
+ # def retryable?
161
+ # @retryable
162
+ # end
163
+
164
+ # def retry_group_on_fail?
165
+ # @retry_group_on_fail
166
+ # end
167
+
168
+ # def raise_on_fail?
169
+ # @raise_on_fail
170
+ # end
171
+
172
+ # def retryable=(val)
173
+ # raise ArgumentError, 'retryable must be a Boolean' unless val.is_a?(TrueClass) || val.is_a?(FalseClass)
174
+
175
+ # @retryable = val
176
+ # end
177
+
178
+ # def retry_delay=(val)
179
+ # raise ArgumentError, 'retry_delay must be an Integer' unless val.is_a?(Integer)
180
+
181
+ # @retry_delay = val
182
+ # end
183
+
184
+ # def retry_group_on_fail=(val)
185
+ # raise ArgumentError, 'retry_group_on_fail must be a Boolean' unless val.is_a?(TrueClass) || val.is_a?(FalseClass)
186
+
187
+ # @retry_group_on_fail = val
188
+ # end
189
+
190
+ # def raise_on_fail=(val)
191
+ # raise ArgumentError, 'raise_on_fail must be a Boolean' unless val.is_a?(TrueClass) || val.is_a?(FalseClass)
192
+
193
+ # @raise_on_fail = val
194
+ # end
195
+
196
+ # def run
197
+ # begin
198
+ # @runs += 1
199
+ # async_info("Running step #{@name}...") if @runs == 1
200
+ # @result = @logic.call(@opts)
201
+ # rescue StandardError => e
202
+ # if retryable? && @runs < @retry_max
203
+ # sleep @retry_delay if @retry_delay > 0
204
+ # async_info("Retrying step #{@name} (attempt #{@runs + 1} of #{@retry_max})")
205
+ # run
206
+ # else
207
+ # @result = CemAcpt::TestRunner::StepError.new(@name, e)
208
+ # raise @result if raise_on_fail?
209
+ # end
210
+ # end
211
+ # @result
212
+ # end
213
+ # end
214
+ # end
215
+ # end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CemAcpt
4
- VERSION = '0.3.3'
4
+ VERSION = '0.3.4'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cem_acpt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: universal-java-17
6
6
  authors:
7
7
  - puppetlabs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-14 00:00:00.000000000 Z
11
+ date: 2023-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -132,6 +132,20 @@ dependencies:
132
132
  - - ">="
133
133
  - !ruby/object:Gem::Version
134
134
  version: '0'
135
+ - !ruby/object:Gem::Dependency
136
+ requirement: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ name: pry
142
+ prerelease: false
143
+ type: :development
144
+ version_requirements: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
135
149
  description: Litmus-like library focusing on CEM Acceptance Tests
136
150
  email:
137
151
  - abide-team@puppet.com
@@ -140,6 +154,7 @@ executables:
140
154
  extensions: []
141
155
  extra_rdoc_files: []
142
156
  files:
157
+ - ".github/workflows/spec.yml"
143
158
  - ".gitignore"
144
159
  - ".rspec"
145
160
  - CODEOWNERS
@@ -167,6 +182,7 @@ files:
167
182
  - lib/cem_acpt/platform/gcp.rb
168
183
  - lib/cem_acpt/platform/gcp/cmd.rb
169
184
  - lib/cem_acpt/platform/gcp/compute.rb
185
+ - lib/cem_acpt/platform/utils/linux.rb
170
186
  - lib/cem_acpt/platform/vmpooler.rb
171
187
  - lib/cem_acpt/puppet_helpers.rb
172
188
  - lib/cem_acpt/rspec_utils.rb
@@ -177,6 +193,10 @@ files:
177
193
  - lib/cem_acpt/test_runner/run_handler.rb
178
194
  - lib/cem_acpt/test_runner/runner.rb
179
195
  - lib/cem_acpt/test_runner/runner_result.rb
196
+ - lib/cem_acpt/test_runner/runner_workflow_builder.rb
197
+ - lib/cem_acpt/test_runner/workflow.rb
198
+ - lib/cem_acpt/test_runner/workflow/manager.rb
199
+ - lib/cem_acpt/test_runner/workflow/step.rb
180
200
  - lib/cem_acpt/utils.rb
181
201
  - lib/cem_acpt/version.rb
182
202
  - sample_config.yaml