cem_acpt 0.8.0 → 0.8.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 853cc9a89c0feaf798bb5efdfc0c8af47d2664121cddd3794b65a61d5dc80851
4
- data.tar.gz: 6d0d5c67e27918d7897ea567a4134249c8a81e23ea95e6a3850a65585ebdd3e0
3
+ metadata.gz: c7329475f3aa0a02e2b6f65017ed0c24b35769f31eddcff07f306297d99b6558
4
+ data.tar.gz: 346c753606378a5b022f2c0cd5e2f3db37dbc473468828075c1c6b9d1f2bf8d6
5
5
  SHA512:
6
- metadata.gz: 244c701ba966fa878c399efba4953db7be8f5977c906501f9d2da8410583ebf2c2c505c687dc03e8cd97b325f362b28472f68c089065cda21f83d01d16c15d22
7
- data.tar.gz: ae7ca222e4264ed32ed47df2745d76c8347c1b5eab94cd2f0278af581c7c7108fb006232c5bbb4d37b2455559c0826a5630498cd620d4fd85a8948b8a0856c22
6
+ metadata.gz: d3eeb3af0f675509e0e02f658656a21d66e682c2d127196155a44adedade3513f4361dd78bb818070d0bd4bb2e349782d4a475a3d1a7a51b9ab800bcc0d9b1e6
7
+ data.tar.gz: 2ac9dc43f0bd592b22053599060ffce68cccb995b32755686c0c4128cf69c2d125b155738be38a0ad91cbca7e7017109ab0721bfda4adaae4da46ab72e30a00d
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cem_acpt (0.8.0)
4
+ cem_acpt (0.8.1)
5
5
  async-http (>= 0.60, < 0.70)
6
6
  bcrypt_pbkdf (>= 1.0, < 2.0)
7
7
  deep_merge (>= 1.2, < 2.0)
@@ -4,6 +4,7 @@ require 'json'
4
4
  require 'open3'
5
5
  require 'stringio'
6
6
  require_relative '../../logging'
7
+ require_relative '../../utils/shell'
7
8
 
8
9
  module CemAcpt
9
10
  module Provision
@@ -81,34 +82,10 @@ module CemAcpt
81
82
  end
82
83
 
83
84
  def run_cmd(cmd, opts = {}, env = {}, suffix: '')
84
- logger.debug('CemAcpt::Provision::TerraformCmd') { "Running command with args: cmd = \"#{cmd}\", opts = \"#{opts}\", env = \"#{env}\", suffix = \"#{suffix}\"" }
85
- val, outerr = execute(format_cmd(cmd, opts, suffix), environment(env))
86
- raise "Error running command: #{cmd}\n#{outerr}" unless val.success?
87
-
88
- outerr
89
- end
90
-
91
- def execute(cmd, env)
92
- logger.debug('CemAcpt::Provision::TerraformCmd') { "Executing command: #{cmd}" }
93
- io_outerr = StringIO.new
94
- val = Open3.popen2e(env, cmd) do |stdin, outerr, wait_thr|
95
- stdin.close
96
- outerr.sync = true
97
- output_thread = Thread.new do
98
- while (line = outerr.readline_nonblock)
99
- logger << line
100
- io_outerr.write(line) unless line.chomp.empty?
101
- end
102
- rescue IO::WaitReadable
103
- retry
104
- rescue EOFError
105
- # Do nothing
106
- end
107
- wait_thr.join
108
- output_thread.exit
109
- wait_thr.value
110
- end
111
- [val, io_outerr.string]
85
+ cmd = format_cmd(cmd, opts, suffix)
86
+ env = environment(env)
87
+ logger.debug('CemAcpt::Provision::TerraformCmd') { "Running command \"#{cmd}\" with environment \"#{env}\"" }
88
+ CemAcpt::Utils::Shell.run_cmd(cmd, env, output: logger)
112
89
  end
113
90
 
114
91
  def chdir(opts = {})
@@ -123,14 +100,10 @@ module CemAcpt
123
100
  end
124
101
 
125
102
  def which_terraform
126
- exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
127
- ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
128
- exts.each do |ext|
129
- exe = File.join(path, "terraform#{ext}")
130
- return exe if File.executable?(exe) && !File.directory?(exe)
131
- end
132
- end
133
- raise 'terraform not found in PATH, make sure it is installed'
103
+ path = CemAcpt::Utils::Shell.which('terraform')
104
+ raise 'terraform not found in PATH, make sure it is installed' if path.nil?
105
+
106
+ path
134
107
  end
135
108
 
136
109
  def format_cmd(cmd, opts = {}, suffix = '')
@@ -36,7 +36,13 @@ module CemAcpt
36
36
  terraform_init
37
37
  terraform_plan(formatted_vars, DEFAULT_PLAN_NAME)
38
38
  terraform_apply(DEFAULT_PLAN_NAME)
39
- JSON.parse(terraform_output('instance_name_ip', json: true))
39
+ begin
40
+ output = terraform_output('instance_name_ip', json: true)
41
+ JSON.parse(output)
42
+ rescue JSON::ParserError => e
43
+ logger.error('CemAcpt::Provision::Terraform') { "Error parsing Terraform output: #{output}" }
44
+ raise e
45
+ end
40
46
  end
41
47
 
42
48
  def destroy
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fileutils'
3
4
  require 'securerandom'
4
5
  require_relative 'action_result'
5
6
  require_relative 'goss'
@@ -8,7 +9,6 @@ require_relative 'platform'
8
9
  require_relative 'provision'
9
10
  require_relative 'test_data'
10
11
  require_relative 'utils'
11
- require_relative 'utils/winrm_runner'
12
12
  require_relative 'version'
13
13
  require_relative 'test_runner/log_formatter'
14
14
 
@@ -30,6 +30,7 @@ module CemAcpt
30
30
  @results = []
31
31
  @http_statuses = []
32
32
  @provisioned = false
33
+ @destroyed = false
33
34
  end
34
35
 
35
36
  def inspect
@@ -48,7 +49,6 @@ module CemAcpt
48
49
  Dir.chdir(module_dir)
49
50
  logger.start_ci_group("CemAcpt v#{CemAcpt::VERSION} run started at #{@start_time}")
50
51
  logger.info('CemAcpt::TestRunner') { "Using module directory: #{module_dir}..." }
51
- keep_terminal_alive
52
52
  @run_data[:private_key], @run_data[:public_key], @run_data[:known_hosts] = new_ephemeral_ssh_keys
53
53
  logger.info('CemAcpt::TestRunner') { 'Created ephemeral SSH key pair...' }
54
54
  @run_data[:module_package_path] = build_module_package
@@ -74,7 +74,7 @@ module CemAcpt
74
74
  # instance_names_ips. It contains the username, password, and ip of the
75
75
  # windows node, as well as the test name that will be run on that node.
76
76
  login_info = CemAcpt::Utils.get_windows_login_info(k, v)
77
- win_node = CemAcpt::Utils::WinRMRunner::WinNode.new(login_info, @run_data[:module_package_path].split('/').last)
77
+ win_node = CemAcpt::Utils::WinRMRunner::WinNode.new(login_info, @run_data[:win_remote_module_name])
78
78
  win_node.run
79
79
  end
80
80
  end
@@ -85,43 +85,37 @@ module CemAcpt
85
85
  logger.error('CemAcpt::TestRunner') { 'Run failed due to error...' }
86
86
  @results << ActionResult.new(e)
87
87
  ensure
88
+ logger.end_ci_group
88
89
  clean_up
89
90
  process_test_results
90
91
  Dir.chdir(@old_dir) if @old_dir
91
92
  @results
92
93
  end
93
94
 
94
- def clean_up(trap_context = false)
95
- logger.end_ci_group
96
- kill_keep_terminal_alive unless trap_context
97
- cleanup_bucket # Clean up bucket if we're testing the cem_windows module
98
-
99
- return no_destroy if config.get('no_destroy_nodes')
95
+ def clean_up(_trap_context = false)
96
+ logger.start_ci_group("CemAcpt v#{CemAcpt::VERSION} run finished at #{Time.now}")
97
+ logger.debug('CemAcpt::TestRunner') { "Starting clean up, provisioned: #{@provisioned}, destroyed: #{@destroyed}" }
100
98
 
101
- clean_ephemeral_ssh_keys
102
- destroy_test_nodes if @provisioned && !@destroyed
99
+ if config.get('no_destroy_nodes')
100
+ logger.warn('CemAcpt::TestRunner') { 'Not destroying test nodes because no-destroy-nodes is set...' }
101
+ @provisioner&.show
102
+ logger.info('CemAcpt') { "Test SSH Keys:\n Private Key: #{@run_data[:private_key]}\n Public Key:#{@run_data[:public_key]}" }
103
+ else
104
+ cleanup_bucket # Clean up bucket if we're testing the cem_windows module
105
+ clean_ephemeral_ssh_keys
106
+ destroy_test_nodes
107
+ end
103
108
  rescue StandardError => e
104
109
  logger.verbose('CemAcpt::TestRunner') { "Error cleaning up: #{e}" }
105
110
  logger.verbose('CemAcpt::TestRunner') { e.backtrace.join("\n") }
111
+ ensure
112
+ logger.end_ci_group
106
113
  end
107
114
 
108
115
  private
109
116
 
110
117
  attr_reader :config
111
118
 
112
- # @return [Thread] The thread that keeps the terminal alive
113
- def keep_terminal_alive
114
- return unless config.ci?
115
-
116
- @keep_terminal_alive ||= CemAcpt::Utils::Terminal.keep_terminal_alive
117
- end
118
-
119
- def kill_keep_terminal_alive
120
- return if @trap_context
121
-
122
- keep_terminal_alive&.kill
123
- end
124
-
125
119
  # @return [String] The path to the module package
126
120
  def build_module_package
127
121
  if config.get('tests').first.include? 'windows'
@@ -163,23 +157,22 @@ module CemAcpt
163
157
  end
164
158
 
165
159
  def destroy_test_nodes
166
- return no_destroy if config.get('no_destroy_nodes')
167
-
168
- logger.info('CemAcpt::TestRunner') { 'Destroying test nodes...' }
169
- logger.start_ci_group("CemAcpt v#{CemAcpt::VERSION} run finished at #{Time.now}")
170
- @provisioner&.destroy
171
- @destroyed = true
172
- ensure
173
- logger.end_ci_group
160
+ logger.info('CemAcpt::TestRunner') { 'Destroying test nodes if necessary...' }
161
+ if !@provisioned
162
+ logger.warn('CemAcpt::TestRunner') { 'Test nodes not provisioned, nothing to destroy...' }
163
+ elsif @destroyed
164
+ logger.warn('CemAcpt::TestRunner') { 'Test nodes already destroyed, not destroying...' }
165
+ else
166
+ logger.info('CemAcpt::TestRunner') { 'Test nodes are provisioned and not destroyed, destroying...' }
167
+ @provisioner&.destroy
168
+ @destroyed = true
169
+ end
174
170
  end
175
171
 
176
172
  def no_destroy
177
- logger.warn('CemAcpt::TestRunner') { 'Not destroying test nodes...' }
178
- logger.start_ci_group("CemAcpt v#{CemAcpt::VERSION} run finished at #{Time.now}")
173
+ logger.warn('CemAcpt::TestRunner') { 'Not destroying test nodes because no-destroy-nodes is set...' }
179
174
  @provisioner&.show
180
175
  logger.info('CemAcpt') { "Test SSH Keys:\n Private Key: #{@run_data[:private_key]}\n Public Key:#{@run_data[:public_key]}" }
181
- ensure
182
- logger.end_ci_group
183
176
  end
184
177
 
185
178
  def run_tests(hosts, only_actions, except_actions)
@@ -248,38 +241,25 @@ module CemAcpt
248
241
  # This should only be done once per cem_acpt run. It's important to update the module_package_path
249
242
  # in the run_data to reflect the new module path if we do end up changing the module name
250
243
  def upload_module_to_bucket
251
- curr_module_path = @run_data[:module_package_path].split('/')[0..-2].join('/')
252
- curr_module_name = @run_data[:module_package_path].split('/')[-1]
253
-
254
- logger.debug('CemAcpt') { 'Checking if there duplicate cem_windows module in the bucket already...' }
255
- # Checking if the module already exists in the bucket. Gcloud will return an empty string if the module
256
- # doesn't exist in the bucket.
257
- attempts = 0
258
- until `gcloud storage ls gs://win_cem_acpt/#{curr_module_name}`.empty? do
259
- raise "Failed to rename duplicate module in GCloud" if attempts >= 3
260
-
261
- logger.debug('CemAcpt') { 'Duplicate cem_windows module found. Renaming cem_windows module...' }
262
- # Rename the cem_windows module
263
- curr_module_name = SecureRandom.uuid << curr_module_name
264
- `mv #{@run_data[:module_package_path]} #{curr_module_path}/#{curr_module_name}`
265
- # update the module_package_path in the run_data
266
- @run_data[:module_package_path] = File.join(curr_module_path, curr_module_name)
267
- attempts += 1
268
- end
244
+ @run_data[:win_remote_module_name] = SecureRandom.uuid << File.split(@run_data[:module_package_path]).last
245
+ @run_data[:win_remote_module_path] = File.join('gs://win_cem_acpt', @run_data[:win_remote_module_name])
269
246
  # Upload the module from the local host to the bucket
270
- logger.info('CemAcpt') { 'Transporting cem_windows module...' }
271
- gcloud_cmd = `gcloud storage cp #{@run_data[:module_package_path]} gs://win_cem_acpt`
272
- logger.debug('CemAcpt') { "Uploaded cem_windows module to bucket: #{gcloud_cmd}" }
247
+ logger.info('CemAcpt') { "Uploading #{@run_data[:module_pakage_path]} to #{@run_data[:win_remote_module_path]}..." }
248
+ CemAcpt::Utils::Shell.run_cmd("gcloud storage cp #{@run_data[:module_package_path]} #{@run_data[:win_remote_module_path]}")
249
+ logger.debug('CemAcpt') { 'Successfully uploaded module' }
273
250
  end
274
251
 
275
252
  # We have to clean up our gcp bucket after we're done testing. This will limit the duplicated
276
253
  # modules existing in the bucket.
277
254
  def cleanup_bucket
278
- # Cleanup the module from the bucket
279
- cleanup_cmd = `gcloud storage rm gs://win_cem_acpt/#{@run_data[:module_package_path].split('/')[-1]}`
280
- logger.debug('CemAcpt') { "Removed module from bucket: #{cleanup_cmd}" }
281
- rescue StandardError
282
- logger.info('CemAcpt') { 'No module to clean up in the bucket. You might not be running test for cem_windows' }
255
+ if @run_data[:win_remote_module_path]
256
+ logger.info('CemAcpt::TestRunner') { "Cleaning up bucket #{@run_data[:win_remote_module_path]}..." }
257
+ # Cleanup the module from the bucket
258
+ cleanup_cmd = CemAcpt::Utils::Shell.run_cmd("gcloud storage rm #{@run_data[:win_remote_module_path]}", raise_on_error: false)
259
+ logger.debug('CemAcpt::TestRunner') { "Removed module from bucket: #{cleanup_cmd}" } unless cleanup_cmd.nil? || cleanup_cmd.empty?
260
+ else
261
+ logger.info('CemAcpt::TestRunner') { 'No module to clean up in bucket...' }
262
+ end
283
263
  end
284
264
  end
285
265
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'stringio'
5
+
6
+ module CemAcpt
7
+ # Error class for shell commands
8
+ class ShellCommandError < StandardError; end
9
+
10
+ module Utils
11
+ # Generic utilities for running local shell commands
12
+ module Shell
13
+ # Runs a command in a subshell and returns the Process::Status
14
+ # and the string output of the command.
15
+ # @param cmd [String] The command to run
16
+ # @param env [Hash] A hash of environment variables to set
17
+ # @param output [IO] An IO object that implements #:<< to write the output of the
18
+ # command to in real time. Typically this is a Logger object. Defaults to $stdout.
19
+ # If the object responds to #:debug, the command will be logged at the debug level.
20
+ # @param raise_on_fail [Boolean] Whether to raise an error if the command fails
21
+ # @return [String] The string output of the command
22
+ def self.run_cmd(cmd, env = {}, output: $stdout, raise_on_fail: true)
23
+ io_outerr = StringIO.new
24
+ if output.respond_to?(:debug)
25
+ output.debug('CemAcpt::Utils::Shell') { "Running command:\n\t#{cmd}\nWith environment:\n\t#{env}" }
26
+ else
27
+ output << "Running command:\n\t#{cmd}\nWith environment:\n\t#{env}\n"
28
+ end
29
+ val = Open3.popen2e(env, cmd) do |stdin, outerr, wait_thr|
30
+ stdin.close
31
+ outerr.sync = true
32
+ output_thread = Thread.new do
33
+ while (line = outerr.readline_nonblock)
34
+ output << line if output
35
+ io_outerr.write(line) unless line.chomp.empty?
36
+ end
37
+ rescue IO::WaitReadable
38
+ begin
39
+ IO.select([outerr])
40
+ retry
41
+ rescue IOError
42
+ # outerr closed, won't retry
43
+ end
44
+ rescue EOFError
45
+ # outerr closed, won't retry
46
+ end
47
+ wait_thr.join
48
+ output_thread.exit
49
+ wait_thr.value
50
+ end
51
+ io_string = io_outerr.string
52
+ raise CemAcpt::ShellCommandError, "Error running command: #{cmd}\n#{io_string}" if raise_on_fail && !val.success?
53
+
54
+ io_string
55
+ end
56
+
57
+ # Mimics the behavior of the `which` command.
58
+ # @param cmd [String] The command to find
59
+ # @return [String] The path to the command
60
+ # @return [nil] If the command is not found
61
+ def self.which(cmd)
62
+ return cmd if File.executable?(cmd) && !File.directory?(cmd)
63
+
64
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
65
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
66
+ exts.each do |ext|
67
+ exe = File.join(path, "#{cmd}#{ext}")
68
+ return exe if File.executable?(exe) && !File.directory?(exe)
69
+ end
70
+ end
71
+ nil
72
+ end
73
+
74
+ # IO monkey patch for non-blocking readline
75
+ class ::IO
76
+ def readline_nonblock
77
+ rlnb = []
78
+ rnlb << read_nonblock(1) while rlnb[-1] != "\n"
79
+ rlnb.join
80
+ rescue IO::WaitReadable => blocking
81
+ raise blocking if rlnb.empty?
82
+
83
+ rlnb.join
84
+ rescue EOFError
85
+ rlnb.join
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'open3'
3
+ require_relative 'shell'
4
4
  require_relative '../logging'
5
5
 
6
6
  module CemAcpt
@@ -32,9 +32,7 @@ module CemAcpt
32
32
  delete(key_name) # Delete existing keys with same name
33
33
  cmd = new_keygen_cmd(key_name, **options)
34
34
  logger.debug('CemAcpt::Utils::SSH::Keygen') { "Creating SSH key with command: #{cmd}" }
35
- _stdout, stderr, status = Open3.capture3(cmd)
36
- raise "Failed to create SSH key! #{stderr}" unless status.success?
37
-
35
+ CemAcpt::Utils::Shell.run_cmd(cmd, output: logger)
38
36
  key_paths(key_name)
39
37
  end
40
38
 
@@ -53,20 +51,11 @@ module CemAcpt
53
51
 
54
52
  private
55
53
 
56
- FIND_BIN_PATH_COMMANDS = [
57
- "#{ENV['SHELL']} -c 'command -v ssh-keygen'",
58
- "#{ENV['SHELL']} -c 'which ssh-keygen'",
59
- ].freeze
60
-
61
- def find_bin_path(find_cmd = FIND_BIN_PATH_COMMANDS.first)
62
- bin_path, stderr, status = Open3.capture3(find_cmd)
63
- raise "Cannot find ssh-keygen with command #{find_cmd}: #{stderr}" unless status.success?
64
-
65
- bin_path.chomp
66
- rescue StandardError => e
67
- return find_bin_path(FIND_BIN_PATH_COMMANDS.last) unless FIND_BIN_PATH_COMMANDS.last == find_cmd
54
+ def find_bin_path
55
+ bin_path = CemAcpt::Utils::Shell.which('ssh-keygen')
56
+ raise 'Cannot find command ssh-keygen in PATH, make sure it is installed' if bin_path.nil?
68
57
 
69
- raise e
58
+ bin_path
70
59
  end
71
60
 
72
61
  def new_keygen_cmd(key_name, **options)
@@ -100,12 +89,10 @@ module CemAcpt
100
89
  end
101
90
 
102
91
  def self.ssh_keygen
103
- bin_path = `#{ENV['SHELL']} -c 'command -v ssh-keygen'`.chomp
104
- raise 'Cannot find ssh-keygen! Install it and verify PATH' unless bin_path
92
+ bin_path = CemAcpt::Utils::Shell.which('ssh-keygen')
93
+ raise 'Cannot find command ssh-keygen in PATH, make sure it is installed' if bin_path.nil?
105
94
 
106
95
  bin_path
107
- rescue StandardError => e
108
- raise "Cannot find ssh-keygen! Install it and verify PATH. Orignal error: #{e}"
109
96
  end
110
97
 
111
98
  def self.default_keydir
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'utils/puppet'
4
+ require_relative 'utils/shell'
4
5
  require_relative 'utils/ssh'
5
6
  require_relative 'utils/terminal'
7
+ require_relative 'utils/winrm_runner'
6
8
  require_relative 'logging'
7
9
 
8
10
  module CemAcpt
@@ -19,17 +21,33 @@ module CemAcpt
19
21
  FileUtils.rm_f(package_file)
20
22
  `cd #{module_dir} && touch puppetlabs-cem_windows.tar.gz && tar -czf puppetlabs-cem_windows.tar.gz --exclude=puppetlabs-cem_windows.tar.gz *`
21
23
  logger.info('CemAcpt') { "Windows module packaged at #{package_file}" }
22
- return package_file
24
+ package_file
23
25
  end
24
26
 
25
27
  def reset_password_readiness_polling(instance_name)
26
- result = %x(echo Y | gcloud compute reset-windows-password #{instance_name} --zone=us-west1-b)
27
- while result.empty?
28
+ attempts = 0
29
+ last_error = nil
30
+ result = nil
31
+ begin
32
+ result = CemAcpt::Utils::Shell.run_cmd("echo Y | gcloud compute reset-windows-password #{instance_name} --zone=us-west1-b")
33
+ rescue StandardError => e
34
+ logger.debug('CemAcpt::Utils') { "Error polling for password readiness: #{e}" }
35
+ last_error = e
36
+ end
37
+ while result.nil? || result.empty?
38
+ raise "Instance not ready for password reset. Last error: #{last_error}" if attempts >= 60 # 10 minutes
39
+
28
40
  logger.info('CemAcpt') { "Waiting for instance #{instance_name} to be ready for password reset..." }
29
41
  sleep 10
30
- result = %x(echo Y | gcloud compute reset-windows-password #{instance_name} --zone=us-west1-b)
42
+ begin
43
+ result = CemAcpt::Utils::Shell.run_cmd("echo Y | gcloud compute reset-windows-password #{instance_name} --zone=us-west1-b")
44
+ rescue StandardError => e
45
+ logger.debug('CemAcpt::Utils') { "Error polling for password readiness: #{e}" }
46
+ last_error = e
47
+ end
48
+ attempts += 1
31
49
  end
32
- return result
50
+ result
33
51
  end
34
52
 
35
53
  def get_windows_login_info(instance_name, hash_of_instance)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CemAcpt
4
- VERSION = '0.8.0'
4
+ VERSION = '0.8.1'
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.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - puppetlabs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-30 00:00:00.000000000 Z
11
+ date: 2023-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-http
@@ -237,6 +237,7 @@ files:
237
237
  - lib/cem_acpt/test_runner/log_formatter/goss_action_response.rb
238
238
  - lib/cem_acpt/utils.rb
239
239
  - lib/cem_acpt/utils/puppet.rb
240
+ - lib/cem_acpt/utils/shell.rb
240
241
  - lib/cem_acpt/utils/ssh.rb
241
242
  - lib/cem_acpt/utils/terminal.rb
242
243
  - lib/cem_acpt/utils/winrm_runner.rb