cem_win_spec 0.1.1 → 0.1.3

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: cdb91d3bd9c4220238d0712938226a34f036c4313370f0ac70b818402d67051d
4
- data.tar.gz: 6e1b4a03b910513b5d4e985f5643394834f0ff38e7af9c03ffebe24a179d1f61
3
+ metadata.gz: c59006dbfceccc0e0581630b0091722aa31a5644440bb906572c8429a654c37a
4
+ data.tar.gz: 91d3e8e17c961d90e90dd5895376418279062ace8ee3b3327d24a511b7287a98
5
5
  SHA512:
6
- metadata.gz: d36cc7f04c91d80fd494caecbe1ee33b39a71d3cde95cf71f4381ba12c232f0f4ed02b91886a89a9e4d2253d1b8ca9bc58bb8348e69d8eda97c7e5d5da22399c
7
- data.tar.gz: c3fc479c502495358843257577391242f7c7003abee9fcb73e7dfa2e456adcd9bbbd448b864b9df347096d519c6ca4ca9ce6c9a7be71582acca448710bb88807
6
+ metadata.gz: cddc71efc46c8a5a0872ee5670b6bdcff1b77904190e18e87f3fa728f2bdb8c533bef7382e8df918337b87811fc21ac04a4b0bd0f358774911a78279f9709968
7
+ data.tar.gz: d52050dd876973fadb74a801b198e88f56341ee125a96a6407594376d6155a3a02a7037576ec10f775346660a7d5fd34ee75e26966ef7011889c45ad968a9e02
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cem_win_spec (0.1.1)
4
+ cem_win_spec (0.1.3)
5
5
  parallel_tests (~> 3.4)
6
6
  puppet_forge (~> 4.1)
7
7
  tty-spinner (~> 0.9)
@@ -15,21 +15,22 @@ GEM
15
15
  builder (3.2.4)
16
16
  coderay (1.1.3)
17
17
  diff-lcs (1.5.0)
18
- erubi (1.10.0)
19
- faraday (2.3.0)
20
- faraday-net_http (~> 2.0)
18
+ erubi (1.12.0)
19
+ faraday (2.7.5)
20
+ faraday-net_http (>= 2.0, < 3.1)
21
21
  ruby2_keywords (>= 0.0.4)
22
22
  faraday-follow_redirects (0.3.0)
23
23
  faraday (>= 1, < 3)
24
- faraday-net_http (2.0.3)
24
+ faraday-net_http (3.0.2)
25
25
  ffi (1.15.5)
26
26
  gssapi (1.3.1)
27
27
  ffi (>= 1.0.1)
28
- gyoku (1.3.1)
28
+ gyoku (1.4.0)
29
29
  builder (>= 2.1.2)
30
+ rexml (~> 3.0)
30
31
  httpclient (2.8.3)
31
32
  little-plugger (1.1.4)
32
- logging (2.3.0)
33
+ logging (2.3.1)
33
34
  little-plugger (~> 1.1)
34
35
  multi_json (~> 1.14)
35
36
  method_source (1.0.0)
@@ -81,7 +82,7 @@ GEM
81
82
  ruby2_keywords (0.0.5)
82
83
  rubyntlm (0.6.3)
83
84
  rubyzip (2.3.2)
84
- semantic_puppet (1.0.4)
85
+ semantic_puppet (1.1.0)
85
86
  tty-cursor (0.7.1)
86
87
  tty-spinner (0.9.3)
87
88
  tty-cursor (~> 0.7)
@@ -103,6 +104,7 @@ GEM
103
104
 
104
105
  PLATFORMS
105
106
  x86_64-darwin-19
107
+ x86_64-darwin-20
106
108
 
107
109
  DEPENDENCIES
108
110
  cem_win_spec!
data/exe/cem-win-spec CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'optparse'
5
5
  require 'cem_win_spec'
6
+ require 'cem_win_spec/version'
6
7
 
7
8
  # This is a wrapper script for the cem_win_spec gem. It is used to run the
8
9
  # cem_win_spec gem from the command line. It is installed as part of the gem
@@ -18,6 +19,26 @@ parser = OptionParser.new do |opts|
18
19
  exit 0
19
20
  end
20
21
 
22
+ opts.on('-p', '--puppet-version [VERSION]', 'Puppet version to test against') do |version|
23
+ unless %w[7 8].include?(version)
24
+ warn "Unknown Puppet version: #{version}"
25
+ warn 'Valid versions are: 7, 8'
26
+ puts opts
27
+ exit 1
28
+ end
29
+ options[:puppet_version] = version
30
+ end
31
+
32
+ opts.on('-r', '--ruby-version [VERSION]', 'Ruby version to test against') do |version|
33
+ unless %w[2 3].include?(version)
34
+ warn "Unknown Ruby version: #{version}"
35
+ warn 'Valid versions are: 2, 3'
36
+ puts opts
37
+ exit 1
38
+ end
39
+ options[:ruby_version] = version
40
+ end
41
+
21
42
  opts.on('-o', '--operation [OPERATION]', 'Operation to perform (spec, clean_fixture_cache)') do |operation|
22
43
  unless %w[spec clean_fixture_cache].include?(operation)
23
44
  warn "Unknown operation: #{operation}"
@@ -4,6 +4,8 @@ require 'io/console'
4
4
  require_relative 'logging'
5
5
 
6
6
  module CemWinSpec
7
+ class IapTunnelStartError < StandardError; end
8
+
7
9
  # This class is used to create a tunnel to a GCP instance
8
10
  class IapTunnel
9
11
  include CemWinSpec::Logging
@@ -23,7 +25,9 @@ module CemWinSpec
23
25
  end
24
26
 
25
27
  def running?
26
- !@pid.nil?
28
+ !@pid.nil? && Process.getpgid(@pid)
29
+ rescue Errno::ESRCH
30
+ false
27
31
  end
28
32
 
29
33
  def with
@@ -41,7 +45,12 @@ module CemWinSpec
41
45
  logger.info 'Starting IAP tunnel...'
42
46
  logger.debug "Running command: #{tunnel_cmd}"
43
47
  @pid = spawn(tunnel_cmd)
44
- sleep(5)
48
+ sleep(3) # Give the tunnel a few seconds to start
49
+ Process.getpgid(@pid) # Check if the process starts successfully
50
+ logger.info "IAP tunnel started on port #{port}"
51
+ rescue Errno::ESRCH
52
+ @pid = nil
53
+ raise IapTunnelStartError, "Failed to start IAP tunnel on port #{port}: #{$ERROR_INFO}"
45
54
  end
46
55
 
47
56
  # This method stops the IAP tunnel
@@ -6,24 +6,25 @@ module CemWinSpec
6
6
  class RspecTestCmds
7
7
  DEFAULT_PATTERN = 'spec/{classes,defines}/**/*_spec.rb'
8
8
 
9
- def initialize(pattern: DEFAULT_PATTERN)
9
+ def initialize(use_bundler: true, pattern: DEFAULT_PATTERN)
10
+ @use_bundler = use_bundler
10
11
  @pattern = pattern
11
12
  end
12
13
 
13
14
  def cmd_standalone(*args)
14
- "bundle exec rake 'cem:spec_standalone#{rake_args(nil, *args)}'"
15
+ prefix "rake 'cem:spec_standalone#{rake_args(nil, *args)}'"
15
16
  end
16
17
 
17
18
  def cmd_parallel(*args)
18
- "bundle exec rake 'cem:parallel_spec_standalone#{rake_args(*args)}'"
19
+ prefix "rake 'cem:parallel_spec_standalone#{rake_args(*args)}'"
19
20
  end
20
21
 
21
22
  def cmd_chunked(*files)
22
- "bundle exec rake 'cem:parallel_spec_files#{rake_args(files.join(' '))}'"
23
+ prefix "rake 'cem:parallel_spec_files#{rake_args(files.join(' '))}'"
23
24
  end
24
25
 
25
26
  def prep_cmd
26
- 'bundle exec rake cem:win_spec_prep'
27
+ prefix 'rake cem:win_spec_prep'
27
28
  end
28
29
 
29
30
  def cmds(*args)
@@ -31,7 +32,7 @@ module CemWinSpec
31
32
  end
32
33
 
33
34
  def cleanup_cmd
34
- 'bundle exec rake cem:win_spec_clean'
35
+ prefix 'rake cem:win_spec_clean'
35
36
  end
36
37
 
37
38
  def spec_files
@@ -40,12 +41,16 @@ module CemWinSpec
40
41
 
41
42
  private
42
43
 
44
+ def prefix(cmd)
45
+ @use_bundler ? "bundle exec #{cmd}" : cmd
46
+ end
47
+
43
48
  def rake_args(*args)
44
49
  args.empty? ? '' : "[#{args.join(',')}]"
45
50
  end
46
51
 
47
52
  def rspec_cmd(file, *args)
48
- "bundle exec rake 'cem:spec_standalone#{rake_args(file, *args)}'"
53
+ prefix "rake 'cem:spec_standalone#{rake_args(file, *args)}'"
49
54
  end
50
55
  end
51
56
  end
@@ -38,7 +38,7 @@ module CemWinSpec
38
38
 
39
39
  def enable_long_paths(**opts)
40
40
  new_command('Enable long paths', **opts) do
41
- remote_exec('Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1')
41
+ remote_run('Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1')
42
42
  end
43
43
  end
44
44
 
@@ -68,7 +68,7 @@ module CemWinSpec
68
68
  module_archive_build { |a| remote_upload(a, remote_working_dir) }
69
69
  module_dir = "#{remote_working_dir}\\#{File.basename(module_archive_path, '.tar.gz')}"
70
70
  logger.debug "Module uploaded to #{module_dir}.tar.gz, extracting..."
71
- remote_exec("tar -xzf #{module_dir}.tar.gz -C #{remote_working_dir}")
71
+ remote_run("tar -xzf #{module_dir}.tar.gz -C #{remote_working_dir}")
72
72
  logger.debug "Module extracted to #{module_dir}"
73
73
  module_dir
74
74
  end
@@ -76,33 +76,33 @@ module CemWinSpec
76
76
 
77
77
  def setup_ruby(**opts)
78
78
  @setup_ruby ||= new_command('Set up ruby', **opts) do
79
- remote_exec('bundle config disable_platform_warnings true')
80
- remote_exec('bundle install')
79
+ remote_run('bundle config disable_platform_warnings true')
80
+ remote_run('bundle install')
81
81
  end
82
82
  end
83
83
 
84
84
  def rspec_prep(**opts)
85
85
  @rspec_prep ||= new_command('Prepare rspec tests', **opts) do
86
- remote_exec('bundle exec rake cem_win_spec:prep --trace')
86
+ remote_run('bundle exec rake cem_win_spec:prep --trace')
87
87
  end
88
88
  end
89
89
 
90
90
  def rspec_tests(**opts)
91
91
  @rspec_tests ||= new_command('Run rspec tests', **opts) do
92
- remote_exec(rspec_cmd_standalone('false', 'progress', 'true'))
92
+ remote_run(rspec_cmd_standalone('false', 'progress', 'true'))
93
93
  end
94
94
  end
95
95
 
96
96
  def rspec_tests_parallel(**opts)
97
97
  @rspec_tests_parallel ||= new_command('Run rspec tests in parallel', **opts) do
98
- remote_exec('bundle exec rake cem_win_spec:parallel_spec')
98
+ remote_run('bundle exec rake cem_win_spec:parallel_spec')
99
99
  end
100
100
  end
101
101
 
102
102
  def clean_up
103
103
  @clean_up ||= new_command('Cleanup') do |working_dir|
104
104
  if remote_available?
105
- remote_exec(cleanup_cmd, quiet: true)
105
+ remote_run(cleanup_cmd, quiet: true)
106
106
  else
107
107
  logger.warn 'Cleanup not available'
108
108
  end
@@ -111,7 +111,7 @@ module CemWinSpec
111
111
 
112
112
  def clean_fixture_cache(**opts)
113
113
  @clean_cache ||= new_command('Clean fixture cache', **opts) do
114
- remote_exec('bundle exec rake cem_win_spec:clean_fixture_cache')
114
+ remote_run('bundle exec rake cem_win_spec:clean_fixture_cache')
115
115
  end
116
116
  end
117
117
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CemWinSpec
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../logging'
4
+
5
+ module CemWinSpec
6
+ module WinExec
7
+ class BaseCmd
8
+ include CemWinSpec::Logging
9
+
10
+ COMMAND_SEPARATOR = '; '
11
+ PUPPET_VER_TO_RUBY_VER = {
12
+ '7' => '278',
13
+ '8' => '322',
14
+ }.freeze
15
+
16
+ attr_accessor :working_dir, :env_vars, :puppet_version
17
+
18
+ def initialize(working_dir = nil, puppet_version: nil, **env_vars)
19
+ @working_dir = working_dir
20
+ self.puppet_version = puppet_version
21
+ self.env_vars = env_vars
22
+ end
23
+
24
+ # Sets the puppet version that will be used to execute the command
25
+ # @param value [String, nil] the puppet version
26
+ def puppet_version=(value)
27
+ if value.nil?
28
+ @puppet_version = nil
29
+ return
30
+ end
31
+
32
+ ver = value.to_s
33
+ raise ArgumentError, 'puppet_version only supports major versions 7 and 8' unless ver.match?(/^(7|8).*/)
34
+ unless ver.match?(/^(7|8)(\.\d+)?\.\d+$/)
35
+ ver = "#{ver}.0"
36
+ end
37
+
38
+ @puppet_version = ver
39
+ end
40
+
41
+ # Sets the environment variables that will be used to execute the command
42
+ # @param value [Hash] the environment variables
43
+ def env_vars=(value)
44
+ raise ArgumentError, 'env_vars must be a hash' unless value.is_a?(Hash)
45
+
46
+ value['PUPPET_GEM_VERSION'] = "~> #{puppet_version}" if puppet_version
47
+ value['FACTER_GEM_VERSION'] = 'https://github.com/puppetlabs/facter#main' if puppet_version
48
+ @env_vars = value
49
+ end
50
+
51
+ def available?
52
+ raise NotImplementedError
53
+ end
54
+
55
+ def run(cmd, *_args, **_kwargs)
56
+ raise NotImplementedError
57
+ end
58
+
59
+ # Returns the ruby version that will be used to execute the command
60
+ # The ruby version is determined by the puppet version that is set
61
+ # @return [String, nil] the ruby version or nil if the puppet version is not set
62
+ def ruby_version
63
+ return nil unless puppet_version
64
+
65
+ PUPPET_VER_TO_RUBY_VER[puppet_version.split('.')[0]]
66
+ end
67
+
68
+ def command(cmd)
69
+ cmd = [cmd]
70
+ cmd.unshift(change_ruby_version_cmd) if ruby_version # executes third
71
+ env_vars.each { |k, v| cmd.unshift(set_env_var_cmd(k, v)) } if env_vars.any? # executes second
72
+ cmd.unshift(change_working_dir_cmd(working_dir)) if working_dir # executes first
73
+ cmd.join(COMMAND_SEPARATOR)
74
+ end
75
+
76
+ private
77
+
78
+ def log_command(cmd)
79
+ cmd = command(cmd)
80
+ logger.debug "Executing command:\n#{cmd.split(%r{\n|\r\n|;\s*}).map { |c| " #> #{c}" }.join("\n")}"
81
+ end
82
+
83
+ def change_ruby_version_cmd
84
+ "uru #{ruby_version}"
85
+ end
86
+
87
+ def set_env_var_cmd(key, value)
88
+ "set #{key}=\"#{value}\""
89
+ end
90
+
91
+ def change_working_dir_cmd(dir)
92
+ "cd #{dir}"
93
+ end
94
+ end
95
+ end
96
+ end
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'open3'
4
- require_relative 'base_exec'
4
+ require_relative 'base_cmd'
5
5
 
6
6
  module CemWinSpec
7
7
  module WinExec
8
8
  # Class for executing local shell commands
9
- class LocalExec < BaseExec
9
+ class LocalCmd < BaseCmd
10
10
  attr_reader :thread_results
11
11
 
12
- def initialize(working_dir = nil)
12
+ def initialize(working_dir = nil, puppet_version: nil, **env_vars)
13
13
  @ran_in_thread = false
14
14
  @thread_results = {}
15
- super(working_dir)
15
+ super(working_dir, puppet_version: puppet_version, **env_vars)
16
16
  end
17
17
 
18
18
  def available?
@@ -35,9 +35,9 @@ module CemWinSpec
35
35
  # This is useful for running commands that should not block the current process
36
36
  # and that you don't need stdout/stderr from.
37
37
  # @param cmd [String] The command to execute
38
- def bg_exec(cmd, *_args, **_kwargs)
39
- puts_cmd(cmd)
40
- Process.detach(spawn(cd_working_dir(cmd)))
38
+ def bg_run(cmd, *_args, **_kwargs)
39
+ log_command(cmd)
40
+ Process.detach(spawn(command(cmd)))
41
41
  end
42
42
 
43
43
  # Execute a command in a new thread. This works by creating a new thread in a thread group
@@ -48,20 +48,20 @@ module CemWinSpec
48
48
  # Process::Status object.
49
49
  # @param cmd [String] The command to execute
50
50
  # @return [Array] An array of [:threaded, cmd]
51
- def thread_exec(cmd, *_args, **_kwargs)
51
+ def thread_run(cmd, *_args, **_kwargs)
52
52
  @ran_in_thread = true
53
53
  th = Thread.new do
54
- puts_cmd(cmd)
55
- so, se, st = Open3.capture3(cd_working_dir(cmd))
54
+ log_command(cmd)
55
+ so, se, st = Open3.capture3(command(cmd))
56
56
  @thread_results[cmd] = [so, se, st]
57
57
  end
58
58
  thread_group.add th
59
59
  :threaded
60
60
  end
61
61
 
62
- def exec(cmd, *_args, **_kwargs)
63
- puts_cmd(cmd)
64
- Open3.capture3(cd_working_dir(cmd))
62
+ def run(cmd, *_args, **_kwargs)
63
+ log_command(cmd)
64
+ Open3.capture3(command(cmd))
65
65
  end
66
66
 
67
67
  private
@@ -2,32 +2,32 @@
2
2
 
3
3
  require 'winrm'
4
4
  require 'winrm-fs'
5
- require_relative 'base_exec'
5
+ require_relative 'base_cmd'
6
6
 
7
7
  module CemWinSpec
8
8
  module WinExec
9
9
  # Class for executing PowerShell commands over WinRM
10
- class WinRMExec < BaseExec
10
+ class WinRMCmd < BaseCmd
11
11
  attr_reader :conn_opts
12
12
 
13
- def initialize(conn_opts, working_dir = nil, quiet: false)
13
+ def initialize(conn_opts, working_dir = nil, quiet: false, puppet_version: nil, **env_vars)
14
14
  @conn_opts = conn_opts
15
15
  @quiet = quiet
16
16
  @conn = new_conn(@conn_opts.to_h)
17
- super(working_dir)
17
+ super(working_dir, puppet_version: puppet_version, **env_vars)
18
18
  end
19
19
 
20
20
  def available?
21
21
  @available
22
22
  end
23
23
 
24
- def exec(cmd, *_args, **_kwargs)
25
- puts_cmd(cmd)
24
+ def run(cmd, *_args, **_kwargs)
25
+ log_command(cmd)
26
26
  shell = nil
27
27
  output = nil
28
28
  begin
29
29
  shell = conn.shell(:powershell)
30
- output = shell.run(cd_working_dir(cmd))
30
+ output = shell.run(command(cmd))
31
31
  rescue WinRM::WinRMAuthorizationError => e
32
32
  @available = false
33
33
  raise e
@@ -77,10 +77,19 @@ module CemWinSpec
77
77
  def new_conn(hash_opts)
78
78
  logger.debug "Creating connection to #{hash_opts[:endpoint]}"
79
79
  new_conn = WinRM::Connection.new(hash_opts)
80
- new_conn.logger = logger
80
+ new_conn.logger = new_conn_logger
81
81
  new_conn
82
82
  end
83
83
 
84
+ def new_conn_logger
85
+ conn_logger = logger.dup
86
+ conn_logger.level = Logger::ERROR if @quiet
87
+ conn_logger.level = Logger::DEBUG if ENV['WINRM_DEBUG'] == 'true'
88
+ conn_logger.level = Logger::INFO if conn_logger.level == Logger::DEBUG && ENV.fetch('WINRM_DEBUG', 'false') != 'true'
89
+ logger.debug "WinRM connection logger level set to #{conn_logger.level}"
90
+ conn_logger
91
+ end
92
+
84
93
  def file_manager
85
94
  @file_manager ||= WinRM::FS::FileManager.new(conn)
86
95
  end
@@ -49,7 +49,7 @@ module CemWinSpec
49
49
  end
50
50
 
51
51
  def pass
52
- @pass ||= Digest::SHA256.hexdigest(pt_pass) unless pt_pass.nil?
52
+ Digest::SHA256.hexdigest(pt_pass) unless pt_pass.nil?
53
53
  end
54
54
 
55
55
  def endpoint
@@ -57,7 +57,7 @@ module CemWinSpec
57
57
  end
58
58
 
59
59
  def opts
60
- @opts ||= new_opts(@kwargs)
60
+ @opts ||= create_new_opts(@kwargs)
61
61
  end
62
62
 
63
63
  def ==(other)
@@ -75,14 +75,14 @@ module CemWinSpec
75
75
  end
76
76
 
77
77
  def digest
78
- Digest::SHA256.hexdigest([host, port, user, @pass, opts.values].join(':'))
78
+ Digest::SHA256.hexdigest([host, port, user, pt_pass, opts.values].join(':'))
79
79
  end
80
80
 
81
81
  # Merge new options into existing options and return a new ConnectionOpts object
82
82
  # @param [Hash] new_opts
83
83
  # @return [ConnectionOpts]
84
84
  def merge(**new_opts)
85
- self.class.new(opts_merge(**new_opts))
85
+ self.class.new(**opts_merge(**new_opts))
86
86
  end
87
87
 
88
88
  def merge!(**new_opts)
@@ -139,7 +139,7 @@ module CemWinSpec
139
139
  password
140
140
  end
141
141
 
142
- def new_opts(nopts = {})
142
+ def create_new_opts(nopts = {})
143
143
  logger.debug 'Creating new connection options opts'
144
144
  new_opts_h = CONN_DEFAULTS.dup.merge(nopts)
145
145
  new_opts_h[:transport] = new_opts_h[:transport].to_sym
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../logging'
4
+ require_relative 'output'
5
+
6
+ module CemWinSpec
7
+ module WinExec
8
+ # Class for executing PowerShell commands over WinRM
9
+ class Exec
10
+ include CemWinSpec::Logging
11
+
12
+ HALTING_ERRORS = [
13
+ IapTunnelStartError,
14
+ ].freeze
15
+
16
+ attr_reader :title, :result, :reuse_tunnel, :ignore_exitcode
17
+
18
+ def initialize(title: 'Command', l_cmd: nil, r_cmd: nil, iap_tunnel: nil, ma_builder: nil, r_test_cmds: nil, &block)
19
+ add_title(title)
20
+ add_local_cmd(l_cmd) unless l_cmd.nil?
21
+ add_remote_cmd(r_cmd) unless r_cmd.nil?
22
+ add_iap_tunnel(iap_tunnel) unless iap_tunnel.nil?
23
+ add_ma_builder(ma_builder) unless ma_builder.nil?
24
+ add_rspec_test_cmds(r_test_cmds) unless r_test_cmds.nil?
25
+ add_command_block(&block) unless block.nil?
26
+ @reuse_tunnel = true
27
+ @ignore_exitcode = false
28
+ @spinner = nil
29
+ @result = nil
30
+ end
31
+
32
+ def add_title(value)
33
+ raise ArgumentError, 'title must be a string' unless value.is_a?(String)
34
+
35
+ @title = value
36
+ end
37
+
38
+ def add_local_cmd(value)
39
+ raise ArgumentError, 'local_exec must implement the #run method' unless value.respond_to?(:run)
40
+
41
+ @local_cmd = value
42
+ end
43
+
44
+ def add_remote_cmd(value)
45
+ raise ArgumentError, 'winrm_exec must implement the #run method' unless value.respond_to?(:run)
46
+
47
+ @remote_cmd = value
48
+ end
49
+
50
+ def add_iap_tunnel(value)
51
+ unless value.respond_to?(:start) && value.respond_to?(:stop) && value.respond_to?(:with)
52
+ raise ArgumentError, 'iap_tunnel must implement the #start, #stop, and #with methods'
53
+ end
54
+
55
+ @iap_tunnel = value
56
+ end
57
+
58
+ def add_ma_builder(value)
59
+ raise ArgumentError, 'ma_builder must implement the #build method' unless value.respond_to?(:build)
60
+
61
+ @ma_builder = value
62
+ end
63
+
64
+ def add_rspec_test_cmds(value)
65
+ unless value.respond_to?(:cmd_standalone) && value.respond_to?(:cmd_parallel) &&
66
+ value.respond_to?(:prep_cmd) && value.respond_to?(:cleanup_cmd)
67
+ raise ArgumentError, 'rspec_test_cmds must implement the #cmd_standalone, #cmd_parallel, #prep_cmd, and #cleanup_cmd methods'
68
+ end
69
+
70
+ @rspec_test_cmds = value
71
+ end
72
+
73
+ def add_command_block(&block)
74
+ raise ArgumentError, 'block must be a Proc' unless block.is_a?(Proc)
75
+
76
+ @block = block
77
+ end
78
+
79
+ def reuse_tunnel=(value)
80
+ raise ArgumentError, 'reuse_tunnel must be a boolean' unless [true, false].include?(value)
81
+
82
+ @reuse_tunnel = value
83
+ end
84
+ alias reuse_tunnel? reuse_tunnel
85
+
86
+ def ignore_exitcode=(value)
87
+ raise ArgumentError, 'ignore_exitcode must be a boolean' unless [true, false].include?(value)
88
+
89
+ @ignore_exitcode = value
90
+ end
91
+ alias ignore_exitcode? ignore_exitcode
92
+
93
+ def success?
94
+ @result.success? if @result.is_a?(Output)
95
+
96
+ false
97
+ end
98
+
99
+ def spinner=(value)
100
+ raise ArgumentError, 'spinner must implement #auto_spin and #stop methods' unless value.respond_to?(:auto_spin) && value.respond_to?(:stop)
101
+
102
+ @spinner = value
103
+ end
104
+
105
+ # Proxy method calls for methods that don't exist here to various other objects.
106
+ # This allows doing things like: `win_exec.remote_exec('Get-Process')` and
107
+ # calling the `exec` method on the `winrm_exec` object. This is done by
108
+ # checking method name prefixes and calling the corresponding method on the
109
+ # appropriate object. The prefix is removed from the method name before
110
+ # calling the method on the object. The supported prefixes are:
111
+ # local_ - calls the method on the @local_cmd object
112
+ # remote_ - calls the method on the @remote_cmd object
113
+ # rspec_ - calls the method on the @rspec_test_cmds object
114
+ # module_archive_ - calls the method on the @ma_builder object
115
+ def method_missing(method, *args, **kwargs, &block)
116
+ if method.to_s.start_with?('local_') # proxy to local_exec
117
+ method = method.to_s.sub('local_', '').to_sym
118
+ @local_cmd.send(method, *args, **kwargs, &block)
119
+ elsif method.to_s.start_with?('remote_') # proxy to remote_exec
120
+ method = method.to_s.sub('remote_', '').to_sym
121
+ @remote_cmd.send(method, *args, **kwargs, &block)
122
+ elsif method.to_s.start_with?('rspec_') # proxy to rspec_test_cmds
123
+ method = method.to_s.sub('rspec_', '').to_sym
124
+ @rspec_test_cmds.send(method, *args, **kwargs, &block)
125
+ elsif method.to_s.start_with?('module_archive_') # proxy to ma_builder
126
+ method = method.to_s.sub('module_archive_', '').to_sym
127
+ @ma_builder.send(method, *args, **kwargs, &block)
128
+ else
129
+ super
130
+ end
131
+ end
132
+
133
+ # Proxy respond_to? for methods that don't exist here to various other objects.
134
+ # This allows doing things like: `win_exec.respond_to?(:remote_exec)` and
135
+ # checking if the `exec` method exists on the `winrm_exec` object. This is done by
136
+ # checking method name prefixes and calling the corresponding method on the
137
+ # appropriate object. The prefix is removed from the method name before
138
+ # calling the method on the object. The supported prefixes are:
139
+ # local_ - calls the method on the @local_cmd object
140
+ # remote_ - calls the method on the @remote_cmd object
141
+ # rspec_ - calls the method on the @rspec_test_cmds object
142
+ # module_archive_ - calls the method on the @ma_builder object
143
+ def respond_to_missing?(method, include_private = false)
144
+ if method.to_s.start_with?('local_')
145
+ @local_cmd.respond_to?(method.to_s.sub('local_', '').to_sym, include_private)
146
+ elsif method.to_s.start_with?('remote_')
147
+ @remote_cmd.respond_to?(method.to_s.sub('remote_', '').to_sym, include_private)
148
+ elsif method.to_s.start_with?('rspec_')
149
+ @rspec_test_cmds.respond_to?(method.to_s.sub('rspec_', '').to_sym, include_private)
150
+ elsif method.to_s.start_with?('module_archive_')
151
+ @ma_builder.respond_to?(method.to_s.sub('module_archive_', '').to_sym, include_private)
152
+ else
153
+ super
154
+ end
155
+ end
156
+
157
+ def run(*args, **kwargs)
158
+ validate_instance_variables
159
+ logger.info "Running #{@title}"
160
+ @spinner&.auto_spin
161
+ result = run_with_tunnel { run_in_current_scope(*args, **kwargs) }
162
+ @spinner&.stop
163
+ @result = Output.new(result)
164
+ return if @result.pending_threaded?
165
+
166
+ @result.puts_combined
167
+ unless @result.success? || ignore_exitcode?
168
+ raise "Command failed with exit code #{@result.exitcode}: #{@result.stdout}\n#{@result.stderr}"
169
+ end
170
+ @result
171
+ rescue StandardError => e
172
+ raise if HALTING_ERRORS.include?(e.class)
173
+
174
+ logger.error "Error running #{@title}: #{e.message[0..100]}"
175
+ @result = Output.new(e)
176
+ @result
177
+ end
178
+
179
+ private
180
+
181
+ def run_with_tunnel(&block)
182
+ if reuse_tunnel?
183
+ @iap_tunnel.start # ensure tunnel is running
184
+ block.call
185
+ else
186
+ @iap_tunnel.stop # ensure tunnel is stopped
187
+ @iap_tunnel.with do # start tunnel for this block
188
+ block.call
189
+ end
190
+ end
191
+ end
192
+
193
+ def validate_instance_variables
194
+ %i[@title @local_cmd @remote_cmd @iap_tunnel @ma_builder @rspec_test_cmds @block].each do |var|
195
+ raise ArgumentError, "#{var} must be set" if instance_variable_get(var).nil?
196
+ end
197
+ end
198
+
199
+ # Runs the block in the current scope. This allows the block to access
200
+ # instance variables and methods from the current scope.
201
+ def run_in_current_scope(*args, **kwargs)
202
+ instance_exec(*args, **kwargs, &@block) # self is the "instance"
203
+ end
204
+
205
+ def wrap_spinner
206
+ @spinner.auto_spin if @spinner
207
+ yield if block_given?
208
+ ensure
209
+ @spinner.stop if @spinner
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require_relative '../logging'
5
+ require_relative 'connection_opts'
6
+ require_relative 'cmd/local_cmd'
7
+ require_relative 'cmd/winrm_cmd'
8
+
9
+ module CemWinSpec
10
+ module WinExec
11
+ # Factory class for creating a WinExec object
12
+ class Factory
13
+ include CemWinSpec::Logging
14
+
15
+ attr_reader :current_local_exec, :current_remote_cmd, :current_conn_opts
16
+
17
+ def initialize(iap_tunnel, ma_builder, rspec_test_cmds, **opts)
18
+ @iap_tunnel = iap_tunnel
19
+ @ma_builder = ma_builder
20
+ @rspec_test_cmds = rspec_test_cmds
21
+ @current_local_cmd = LocalCmd.new(**opts)
22
+ @current_remote_cmd = nil
23
+ @current_conn_opts = nil
24
+ @init_opts = opts
25
+ end
26
+
27
+ # Build a WinExec object
28
+ # @param title [String] Title of the WinExec object
29
+ # @param merge [Boolean] Merge the current connection options with the new options, if applicable
30
+ # @param host [String] Hostname or IP address of the remote host
31
+ # @param port [Integer] Port of the remote host
32
+ # @param user [String] Username for the remote host
33
+ # @param pass [String] Password for the remote host
34
+ # @param opts [Hash] Additional options for the WinRM connection
35
+ # @return [Exec] An Exec object
36
+ def build(title, merge: true, user: nil, pass: nil, working_dir: nil, **opts, &block)
37
+ logger.debug "Building Wexec object for #{title}"
38
+ build_conn_opts(merge: merge, user: user, pass: pass, **opts)
39
+ logger.debug 'Created ConnectionOpts'
40
+ wexec = Exec.new
41
+ wexec.add_title title
42
+ wexec.add_local_cmd @current_local_cmd
43
+ wexec.add_remote_cmd get_remote_cmd(working_dir, **@init_opts.merge(opts))
44
+ wexec.add_iap_tunnel @iap_tunnel
45
+ wexec.add_ma_builder @ma_builder
46
+ wexec.add_rspec_test_cmds @rspec_test_cmds
47
+ wexec.add_command_block(&block)
48
+ wexec.reuse_tunnel = opts[:reuse_tunnel] if opts.key?(:reuse_tunnel)
49
+ wexec.ignore_exitcode = opts[:ignore_exitcode] if opts.key?(:ignore_exitcode)
50
+ wexec.spinner = opts[:spinner] if opts.key?(:spinner)
51
+ logger.debug 'Created Wexec'
52
+ wexec
53
+ end
54
+
55
+ def local_threaded_results
56
+ logger.info 'Checking for deferred results...'
57
+ @current_local_exec.join_threads
58
+ @current_local_exec.threaded_results.each do |cmd, results|
59
+ logger.info "Deferred results for #{cmd}:"
60
+ Output.new(results).puts_combined
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def build_conn_opts(merge: true, user: nil, pass: nil, **opts)
67
+ if @current_conn_opts.nil?
68
+ logger.debug 'Creating new ConnectionOpts object'
69
+ @current_conn_opts = ConnectionOpts.new(new_host: 'localhost', new_port: @iap_tunnel.port, user: user, pass: pass, **opts)
70
+ return @current_conn_opts
71
+ end
72
+
73
+ if need_new_conn_opts?(new_host: 'localhost', new_port: @iap_tunnel.port, user: user, pass: pass, **opts)
74
+ if merge
75
+ logger.debug 'Merging ConnectionOpts with new options'
76
+ @current_conn_opts = @current_conn_opts.merge(new_host: 'localhost', new_port: @iap_tunnel.port, user: user, pass: pass, **opts)
77
+ else
78
+ logger.debug 'Creating new ConnectionOpts object with new options'
79
+ @current_conn_opts = ConnectionOpts.new(new_host: 'localhost', new_port: @iap_tunnel.port, user: user, pass: pass, **opts)
80
+ end
81
+ else
82
+ logger.debug 'Returning existing ConnectionOpts object'
83
+ end
84
+ @current_conn_opts
85
+ end
86
+
87
+ def need_new_conn_opts?(new_host: nil, new_port: nil, user: nil, pass: nil, **opts)
88
+ if new_host && new_host != @current_conn_opts.host
89
+ logger.debug "New host #{new_host} does not match current host #{@current_conn_opts.host}"
90
+ return true
91
+ end
92
+ if new_port && new_port != @current_conn_opts.port
93
+ logger.debug "New port #{new_port} does not match current port #{@current_conn_opts.port}"
94
+ return true
95
+ end
96
+ if user && user != @current_conn_opts.user
97
+ logger.debug "New user #{user} does not match current user #{@current_conn_opts.user}"
98
+ return true
99
+ end
100
+ if pass && Digest::SHA256.hexdigest(pass) != @current_conn_opts.pass
101
+ logger.debug 'New password does not match current password'
102
+ return true
103
+ end
104
+ if !opts.empty? && opts != @current_conn_opts.opts
105
+ logger.debug "New extra options #{opts} do not match current extra options #{@current_conn_opts.opts}"
106
+ return true
107
+ end
108
+
109
+ false
110
+ end
111
+
112
+ def get_remote_cmd(working_dir = nil, **opts)
113
+ if @current_winrm_cmd.nil? || @current_winrm_cmd.conn_opts.digest != @current_conn_opts.digest
114
+ logger.debug 'Creating new WinRMExec object'
115
+ @current_remote_cmd = WinRMCmd.new(@current_conn_opts, **opts)
116
+ end
117
+ logger.debug "Setting working directory to #{working_dir}" unless working_dir.nil?
118
+ @current_remote_cmd.working_dir = working_dir
119
+ logger.debug 'Returning WinRMExec object'
120
+ @current_remote_cmd
121
+ end
122
+ end
123
+ end
124
+ end
@@ -1,234 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'digest'
4
- require_relative 'iap_tunnel'
5
- require_relative 'logging'
6
- require_relative 'module_archive_builder'
7
- require_relative 'win_exec/connection_opts'
8
- require_relative 'win_exec/local_exec'
9
- require_relative 'win_exec/winrm_exec'
10
- require_relative 'win_exec/output'
11
-
12
3
  module CemWinSpec
13
4
  module WinExec
14
- # Class for executing PowerShell commands over WinRM
15
- class Exec
16
- include CemWinSpec::Logging
17
-
18
- attr_reader :title, :result, :reuse_tunnel, :ignore_exitcode
19
-
20
- def initialize(title, local_exec, winrm_exec, iap_tunnel, ma_builder, rspec_test_cmds, &block)
21
- @title = title
22
- @local_exec = local_exec
23
- @winrm_exec = winrm_exec
24
- @iap_tunnel = iap_tunnel
25
- @ma_builder = ma_builder
26
- @rspec_test_cmds = rspec_test_cmds
27
- @block = block
28
- @reuse_tunnel = true
29
- @ignore_exitcode = false
30
- @spinner = nil
31
- @result = nil
32
- end
33
-
34
- def reuse_tunnel=(value)
35
- raise ArgumentError, 'reuse_tunnel must be a boolean' unless [true, false].include?(value)
36
-
37
- @reuse_tunnel = value
38
- end
39
- alias reuse_tunnel? reuse_tunnel
40
-
41
- def ignore_exitcode=(value)
42
- raise ArgumentError, 'ignore_exitcode must be a boolean' unless [true, false].include?(value)
43
-
44
- @ignore_exitcode = value
45
- end
46
- alias ignore_exitcode? ignore_exitcode
47
-
48
- def success?
49
- @result.success? if @result.is_a?(Output)
50
-
51
- false
52
- end
53
-
54
- def spinner=(value)
55
- raise ArgumentError, 'spinner must implement #auto_spin and #stop methods' unless value.respond_to?(:auto_spin) && value.respond_to?(:stop)
56
-
57
- @spinner = value
58
- end
59
-
60
- # Proxy method calls for methods that don't exist here to various other objects.
61
- # This allows doing things like: `win_exec.remote_exec('Get-Process')` and
62
- # calling the `exec` method on the `winrm_exec` object. This is done by
63
- # checking method name prefixes and calling the corresponding method on the
64
- # appropriate object. The prefix is removed from the method name before
65
- # calling the method on the object. The supported prefixes are:
66
- # local_ - calls the method on the @local_exec object
67
- # remote_ - calls the method on the @winrm_exec object
68
- # rspec_ - calls the method on the @rspec_test_cmds object
69
- # module_archive_ - calls the method on the @ma_builder object
70
- def method_missing(method, *args, **kwargs, &block)
71
- if method.to_s.start_with?('local_') # proxy to local_exec
72
- method = method.to_s.sub('local_', '').to_sym
73
- @local_exec.send(method, *args, **kwargs, &block)
74
- elsif method.to_s.start_with?('remote_') # proxy to winrm_exec
75
- method = method.to_s.sub('remote_', '').to_sym
76
- @winrm_exec.send(method, *args, **kwargs, &block)
77
- elsif method.to_s.start_with?('rspec_') # proxy to rspec_test_cmds
78
- method = method.to_s.sub('rspec_', '').to_sym
79
- @rspec_test_cmds.send(method, *args, **kwargs, &block)
80
- elsif method.to_s.start_with?('module_archive_') # proxy to ma_builder
81
- method = method.to_s.sub('module_archive_', '').to_sym
82
- @ma_builder.send(method, *args, **kwargs, &block)
83
- else
84
- super
85
- end
86
- end
87
-
88
- # Proxy respond_to? for methods that don't exist here to various other objects.
89
- # This allows doing things like: `win_exec.respond_to?(:remote_exec)` and
90
- # checking if the `exec` method exists on the `winrm_exec` object. This is done by
91
- # checking method name prefixes and calling the corresponding method on the
92
- # appropriate object. The prefix is removed from the method name before
93
- # calling the method on the object. The supported prefixes are:
94
- # local_ - calls the method on the @local_exec object
95
- # remote_ - calls the method on the @winrm_exec object
96
- # rspec_ - calls the method on the @rspec_test_cmds object
97
- # module_archive_ - calls the method on the @ma_builder object
98
- def respond_to_missing?(method, include_private = false)
99
- if method.to_s.start_with?('local_')
100
- @local_exec.respond_to?(method.to_s.sub('local_', '').to_sym, include_private)
101
- elsif method.to_s.start_with?('remote_')
102
- @winrm_exec.respond_to?(method.to_s.sub('remote_', '').to_sym, include_private)
103
- elsif method.to_s.start_with?('rspec_')
104
- @rspec_test_cmds.respond_to?(method.to_s.sub('rspec_', '').to_sym, include_private)
105
- elsif method.to_s.start_with?('module_archive_')
106
- @ma_builder.respond_to?(method.to_s.sub('module_archive_', '').to_sym, include_private)
107
- else
108
- super
109
- end
110
- end
111
-
112
- def run(*args, **kwargs)
113
- logger.info "Running #{@title}"
114
- @spinner&.auto_spin
115
- result = if reuse_tunnel?
116
- @iap_tunnel.start # ensure tunnel is running
117
- run_in_current_scope(*args, **kwargs)
118
- else
119
- @iap_tunnel.stop # ensure tunnel is stopped
120
- @iap_tunnel.with do # start tunnel for this block
121
- run_in_current_scope(*args, **kwargs)
122
- end
123
- end
124
- @spinner&.stop
125
- @result = Output.new(result)
126
- return if @result.pending_threaded?
127
-
128
- @result.puts_combined
129
- unless @result.success? || ignore_exitcode?
130
- raise "Command failed with exit code #{@result.exitcode}: #{@result.stdout}\n#{@result.stderr}"
131
- end
132
- @result
133
- rescue StandardError => e
134
- logger.error "Error running #{@title}: #{e.message[0..100]}"
135
- @result = Output.new(e)
136
- @result
137
- end
138
-
139
- private
140
-
141
- def run_in_current_scope(*args, **kwargs)
142
- instance_exec(*args, **kwargs, &@block)
143
- end
144
-
145
- def wrap_spinner
146
- @spinner.auto_spin if @spinner
147
- yield if block_given?
148
- ensure
149
- @spinner.stop if @spinner
150
- end
151
- end
152
-
153
- # Factory class for creating a WinExec object
154
- class Factory
155
- include CemWinSpec::Logging
156
-
157
- attr_reader :current_local_exec, :current_winrm_exec, :current_conn_opts
158
-
159
- def initialize(iap_tunnel, ma_builder, rspec_test_cmds)
160
- @iap_tunnel = iap_tunnel
161
- @ma_builder = ma_builder
162
- @rspec_test_cmds = rspec_test_cmds
163
- @current_local_exec = LocalExec.new
164
- @current_winrm_exec = nil
165
- @current_conn_opts = nil
166
- end
167
-
168
- # Build a WinExec object
169
- # @param title [String] Title of the WinExec object
170
- # @param merge [Boolean] Merge the current connection options with the new options, if applicable
171
- # @param host [String] Hostname or IP address of the remote host
172
- # @param port [Integer] Port of the remote host
173
- # @param user [String] Username for the remote host
174
- # @param pass [String] Password for the remote host
175
- # @param opts [Hash] Additional options for the WinRM connection
176
- # @return [Exec] An Exec object
177
- def build(title, merge: true, user: nil, pass: nil, working_dir: nil, **opts, &block)
178
- logger.debug "Building Wexec object for #{title}"
179
- build_conn_opts(merge: merge, user: user, pass: pass, **opts)
180
- logger.debug 'Created ConnectionOpts'
181
- wexec = Exec.new(title, @current_local_exec, get_winrm_exec(working_dir), @iap_tunnel, @ma_builder, @rspec_test_cmds, &block)
182
- wexec.reuse_tunnel = opts[:reuse_tunnel] if opts.key?(:reuse_tunnel)
183
- wexec.ignore_exitcode = opts[:ignore_exitcode] if opts.key?(:ignore_exitcode)
184
- wexec.spinner = opts[:spinner] if opts.key?(:spinner)
185
- logger.debug 'Created Wexec'
186
- wexec
187
- end
188
-
189
- def local_threaded_results
190
- logger.info 'Checking for deferred results...'
191
- @current_local_exec.join_threads
192
- @current_local_exec.threaded_results.each do |cmd, results|
193
- logger.info "Deferred results for #{cmd}:"
194
- Output.new(results).puts_combined
195
- end
196
- end
197
-
198
- private
199
-
200
- def build_conn_opts(merge: true, user: nil, pass: nil, **opts)
201
- if @current_conn_opts.nil?
202
- logger.debug 'Creating new ConnectionOpts object'
203
- @current_conn_opts = ConnectionOpts.new(new_host: 'localhost', new_port: @iap_tunnel.port, user: user, pass: pass, **opts)
204
- return @current_conn_opts
205
- end
206
- opts_digest = Digest::SHA256.hexdigest(['localhost', @iap_tunnel.port, user, pass, opts].join(':'))
207
- if opts_digest != @current_conn_opts.digest
208
- if merge
209
- logger.debug 'Merging ConnectionOpts with new options'
210
- @current_conn_opts = @current_conn_opts.merge(new_host: 'localhost', new_port: @iap_tunnel.port, user: user, pass: pass, **opts)
211
- else
212
- logger.debug 'Creating new ConnectionOpts object with new options'
213
- @current_conn_opts = ConnectionOpts.new(new_host: 'localhost', new_port: @iap_tunnel.port, user: user, pass: pass, **opts)
214
- end
215
- else
216
- logger.debug 'Returning existing ConnectionOpts object'
217
- end
218
- @current_conn_opts
219
- end
220
-
221
-
222
- def get_winrm_exec(working_dir = nil)
223
- if @current_winrm_exec.nil? || @current_winrm_exec.conn_opts.digest != @current_conn_opts.digest
224
- logger.debug 'Creating new WinRMExec object'
225
- @current_winrm_exec = WinRMExec.new(@current_conn_opts)
226
- end
227
- logger.debug "Setting working directory to #{working_dir}" unless working_dir.nil?
228
- @current_winrm_exec.working_dir = working_dir
229
- logger.debug 'Returning WinRMExec object'
230
- @current_winrm_exec
231
- end
232
- end
5
+ require_relative 'win_exec/exec'
6
+ require_relative 'win_exec/factory'
233
7
  end
234
8
  end
data/lib/cem_win_spec.rb CHANGED
@@ -45,14 +45,14 @@ module CemWinSpec
45
45
  check_output!(sre_out, runner)
46
46
  module_dir = sre_out.stdout.chomp
47
47
  logger.debug "Module dir: #{module_dir}"
48
- srr_out = setup_remote_ruby(runner, module_dir, spinner)
48
+ srr_out = setup_remote_ruby(runner, module_dir, spinner, **options)
49
49
  check_output!(srr_out, runner)
50
50
  case operation
51
51
  when :spec
52
- spec_out = run_spec(runner, module_dir, spinner)
52
+ spec_out = run_spec(runner, module_dir, spinner, **options)
53
53
  check_output!(spec_out, runner)
54
54
  when :clean_fixture_cache
55
- clean_fixture_cache_out = clean_fixture_cache(runner, module_dir, spinner)
55
+ clean_fixture_cache_out = clean_fixture_cache(runner, module_dir, spinner, **options)
56
56
  check_output!(clean_fixture_cache_out, runner)
57
57
  else
58
58
  raise ArgumentError, "Unknown operation: #{operation}"
@@ -71,32 +71,34 @@ module CemWinSpec
71
71
  working_dir_out = runner.create_working_dir.run
72
72
  working_dir = working_dir_out.stdout.chomp
73
73
  logger.debug "Working dir: #{working_dir}"
74
- runner.upload_module(working_dir: working_dir).run
74
+ runner.upload_module(operation_timeout: 300, receive_timeout: 310, working_dir: working_dir).run
75
75
  end
76
76
 
77
- def self.setup_remote_ruby(runner, module_dir, spinner)
77
+ def self.setup_remote_ruby(runner, module_dir, spinner, **opts)
78
78
  runner.setup_ruby(operation_timeout: 300,
79
79
  receive_timeout: 310,
80
80
  working_dir: module_dir,
81
81
  reuse_tunnel: false,
82
- spinner: spinner).run
82
+ spinner: spinner,
83
+ **opts).run
83
84
  end
84
85
 
85
86
  # Runs RSpec tests
86
- def self.run_spec(runner, module_dir, spinner)
87
+ def self.run_spec(runner, module_dir, spinner, **opts)
87
88
  #runner.rspec_prep(working_dir: module_dir, reuse_tunnel: false, spinner: spinner).run
88
89
  runner.rspec_tests_parallel(operation_timeout: 300,
89
90
  receive_timeout: 310,
90
91
  working_dir: module_dir,
91
92
  ignore_exitcode: true,
92
93
  reuse_tunnel: false,
93
- spinner: spinner).run
94
+ spinner: spinner,
95
+ **opts).run
94
96
  end
95
97
 
96
98
  # Clean the remote fixture cache
97
99
  # @param options [Hash] Options for the test runner
98
- def self.clean_fixture_cache(runner, module_dir, spinner)
99
- runner.clean_fixture_cache(working_dir: module_dir, spinner: spinner).run
100
+ def self.clean_fixture_cache(runner, module_dir, spinner, **opts)
101
+ runner.clean_fixture_cache(working_dir: module_dir, spinner: spinner, **opts).run
100
102
  end
101
103
 
102
104
  def self.check_output!(output, runner = nil)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cem_win_spec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Heston Snodgrass
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-13 00:00:00.000000000 Z
11
+ date: 2023-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: winrm
@@ -119,16 +119,17 @@ files:
119
119
  - lib/cem_win_spec/logging/formatter.rb
120
120
  - lib/cem_win_spec/module_archive_builder.rb
121
121
  - lib/cem_win_spec/rake_tasks.rb
122
- - lib/cem_win_spec/remote_command.rb
123
122
  - lib/cem_win_spec/rspec_test_cmds.rb
124
123
  - lib/cem_win_spec/test_runner.rb
125
124
  - lib/cem_win_spec/version.rb
126
125
  - lib/cem_win_spec/win_exec.rb
127
- - lib/cem_win_spec/win_exec/base_exec.rb
126
+ - lib/cem_win_spec/win_exec/cmd/base_cmd.rb
127
+ - lib/cem_win_spec/win_exec/cmd/local_cmd.rb
128
+ - lib/cem_win_spec/win_exec/cmd/winrm_cmd.rb
128
129
  - lib/cem_win_spec/win_exec/connection_opts.rb
129
- - lib/cem_win_spec/win_exec/local_exec.rb
130
+ - lib/cem_win_spec/win_exec/exec.rb
131
+ - lib/cem_win_spec/win_exec/factory.rb
130
132
  - lib/cem_win_spec/win_exec/output.rb
131
- - lib/cem_win_spec/win_exec/winrm_exec.rb
132
133
  - sig/cem_win_spec.rbs
133
134
  homepage: https://github.com/hsnodgrass/cem_win_spec
134
135
  licenses:
@@ -152,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
153
  - !ruby/object:Gem::Version
153
154
  version: '0'
154
155
  requirements: []
155
- rubygems_version: 3.1.4
156
+ rubygems_version: 3.4.6
156
157
  signing_key:
157
158
  specification_version: 4
158
159
  summary: Write a short summary, because RubyGems requires one.
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'iap_tunnel'
4
- require_relative 'win_exec'
5
-
6
- module CemWinSpec
7
- # Class for running a command on a remote Windows host
8
- class RemoteCommand
9
- attr_reader :title, :result
10
-
11
- def initialize(title, iap_tunnel: nil, reuse_tunnel: true, winrm_opts: {}, &block)
12
- @title = title
13
- @iap_tunnel = iap_tunnel || IapTunnel.new
14
- @reuse_tunnel = reuse_tunnel
15
- @winrm_opts = winrm_opts
16
- @block = block
17
- @win_exec = WinExec.new('localhost', @iap_tunnel.port, winrm_opts: winrm_opts)
18
- @result = nil
19
- end
20
-
21
- def ran?
22
- !@result.nil?
23
- end
24
-
25
- def success?
26
- @result.is_a? WinRM::Output
27
- end
28
-
29
- def run
30
- puts "Running #{@title}"
31
- @result = if @reuse_tunnel
32
- @iap_tunnel.start # ensure tunnel is running
33
- @block.call @win_exec
34
- else
35
- @iap_tunnel.stop # ensure tunnel is stopped
36
- @iap_tunnel.with do # start tunnel for this block
37
- @block.call @win_exec
38
- end
39
- end
40
- @result
41
- rescue StandardError => e
42
- puts "Error running #{@title}: #{e}"
43
- @result = e
44
- raise @result
45
- end
46
- end
47
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../logging'
4
-
5
- module CemWinSpec
6
- module WinExec
7
- class BaseExec
8
- include CemWinSpec::Logging
9
-
10
- attr_accessor :working_dir
11
-
12
- def initialize(working_dir = nil)
13
- @working_dir = working_dir
14
- end
15
-
16
- def available?
17
- raise NotImplementedError
18
- end
19
-
20
- def exec(cmd, *_args, **_kwargs)
21
- raise NotImplementedError
22
- end
23
-
24
- private
25
-
26
- def cd_working_dir(cmd)
27
- return cmd if working_dir.nil?
28
-
29
- "cd #{working_dir}; #{cmd}"
30
- end
31
-
32
- def cmd_prefix_msg
33
- prefix = 'Executing command'
34
- working_dir.nil? ? "#{prefix}:\n" : "#{prefix} in #{working_dir}:\n"
35
- end
36
-
37
- def puts_cmd(cmd)
38
- logger.debug "#{cmd_prefix_msg}#{cmd.split(%r{\n|\r\n|;\s*}).map { |c| " #> #{c}" }.join("\n")}"
39
- end
40
- end
41
- end
42
- end