cem_win_spec 0.1.2 → 0.1.4

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: b8fcf2f514c6b40b1c2df94ceb0c7fd4c53c6470b5cd603281dac2303b014bdd
4
- data.tar.gz: 459a0859428ec4c326b570b7e436ed17b4637d6ae326fe991b8de1ff08ba50f4
3
+ metadata.gz: b8483cd4acfa3729cf4bd453fe3ef09a67c71ddd3dcee46f5dc3986e755ddf07
4
+ data.tar.gz: a4ef098f7125bf68579dd1b5902d5cb553ccfcbef355d8bd0e4f1d8900eae395
5
5
  SHA512:
6
- metadata.gz: 0d0e040d58de283421fed1ed9fb22d15c6f19b72f89d9fcd31cffadea277e352f4ae8e0d2c46a7b57c39756c71a2fcd9100a889344934280559ad90c65e5d908
7
- data.tar.gz: 8655104a3e2415b478bd9ded995dcdcc6043b0e3b46fc177bcc42e78bfeff872dd89b538ded90c055cfd72574c997eb5f9839cbdca1c89a186c9cb0b7eb8c5ab
6
+ metadata.gz: 8a1a521b044cc4c14a26efd6b8936f662dc1d2b173918337e49b591fe1178f6357aff317cd7dab4ae167688eea2304b75dc89fde8273bb8e7dcc3d22b8df8d33
7
+ data.tar.gz: a31a1631d32ca72d34f276920de7a9f22f34175afac7306e64f7158ec309cc66ce0e80aab6b3ac430627f170291b9e442fd7516a7231b242c059a39836f5e3e1
data/Gemfile.lock CHANGED
@@ -1,10 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cem_win_spec (0.1.2)
4
+ cem_win_spec (0.1.4)
5
5
  parallel_tests (~> 3.4)
6
6
  puppet_forge (~> 4.1)
7
- tty-spinner (~> 0.9)
8
7
  winrm (~> 2.3)
9
8
  winrm-fs (~> 1.3)
10
9
 
@@ -15,21 +14,22 @@ GEM
15
14
  builder (3.2.4)
16
15
  coderay (1.1.3)
17
16
  diff-lcs (1.5.0)
18
- erubi (1.10.0)
19
- faraday (2.3.0)
20
- faraday-net_http (~> 2.0)
17
+ erubi (1.12.0)
18
+ faraday (2.7.5)
19
+ faraday-net_http (>= 2.0, < 3.1)
21
20
  ruby2_keywords (>= 0.0.4)
22
21
  faraday-follow_redirects (0.3.0)
23
22
  faraday (>= 1, < 3)
24
- faraday-net_http (2.0.3)
23
+ faraday-net_http (3.0.2)
25
24
  ffi (1.15.5)
26
25
  gssapi (1.3.1)
27
26
  ffi (>= 1.0.1)
28
- gyoku (1.3.1)
27
+ gyoku (1.4.0)
29
28
  builder (>= 2.1.2)
29
+ rexml (~> 3.0)
30
30
  httpclient (2.8.3)
31
31
  little-plugger (1.1.4)
32
- logging (2.3.0)
32
+ logging (2.3.1)
33
33
  little-plugger (~> 1.1)
34
34
  multi_json (~> 1.14)
35
35
  method_source (1.0.0)
@@ -81,10 +81,7 @@ GEM
81
81
  ruby2_keywords (0.0.5)
82
82
  rubyntlm (0.6.3)
83
83
  rubyzip (2.3.2)
84
- semantic_puppet (1.0.4)
85
- tty-cursor (0.7.1)
86
- tty-spinner (0.9.3)
87
- tty-cursor (~> 0.7)
84
+ semantic_puppet (1.1.0)
88
85
  unicode-display_width (2.1.0)
89
86
  winrm (2.3.6)
90
87
  builder (>= 2.1.2)
@@ -102,7 +99,9 @@ GEM
102
99
  winrm (~> 2.0)
103
100
 
104
101
  PLATFORMS
102
+ arm64-darwin-22
105
103
  x86_64-darwin-19
104
+ x86_64-darwin-20
106
105
 
107
106
  DEPENDENCIES
108
107
  cem_win_spec!
data/cem_win_spec.gemspec CHANGED
@@ -30,7 +30,6 @@ Gem::Specification.new do |spec|
30
30
 
31
31
  spec.add_dependency "winrm", "~> 2.3"
32
32
  spec.add_dependency "winrm-fs", "~> 1.3"
33
- spec.add_dependency "tty-spinner", "~> 0.9"
34
33
  spec.add_dependency "puppet_forge", "~> 4.1"
35
34
  spec.add_dependency "parallel_tests", "~> 3.4"
36
35
  spec.add_development_dependency "pry"
data/exe/cem-win-spec CHANGED
@@ -19,6 +19,26 @@ parser = OptionParser.new do |opts|
19
19
  exit 0
20
20
  end
21
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
+
22
42
  opts.on('-o', '--operation [OPERATION]', 'Operation to perform (spec, clean_fixture_cache)') do |operation|
23
43
  unless %w[spec clean_fixture_cache].include?(operation)
24
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,13 +38,13 @@ 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
 
45
45
  def enable_symlinks(**opts)
46
46
  @enable_symlinks ||= new_command('Enable symlinks', **opts) do
47
- remote_exec('fsutil behavior set SymlinkEvaluation L2L:1 R2R:1 L2R:1 R2L:1')
47
+ remote_run('fsutil behavior set SymlinkEvaluation L2L:1 R2R:1 L2R:1 R2L:1')
48
48
  end
49
49
  end
50
50
 
@@ -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,38 @@ 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(
80
+ [
81
+ 'bundle config disable_platform_warnings true',
82
+ 'bundle config set --local without \'local_development\'',
83
+ 'bundle install',
84
+ ],
85
+ )
81
86
  end
82
87
  end
83
88
 
84
89
  def rspec_prep(**opts)
85
90
  @rspec_prep ||= new_command('Prepare rspec tests', **opts) do
86
- remote_exec('bundle exec rake cem_win_spec:prep --trace')
91
+ remote_run('bundle exec rake cem_win_spec:prep --trace')
87
92
  end
88
93
  end
89
94
 
90
95
  def rspec_tests(**opts)
91
96
  @rspec_tests ||= new_command('Run rspec tests', **opts) do
92
- remote_exec(rspec_cmd_standalone('false', 'progress', 'true'))
97
+ remote_run(rspec_cmd_standalone('false', 'progress', 'true'))
93
98
  end
94
99
  end
95
100
 
96
101
  def rspec_tests_parallel(**opts)
97
102
  @rspec_tests_parallel ||= new_command('Run rspec tests in parallel', **opts) do
98
- remote_exec('bundle exec rake cem_win_spec:parallel_spec')
103
+ remote_run('bundle exec rake cem_win_spec:parallel_spec')
99
104
  end
100
105
  end
101
106
 
102
107
  def clean_up
103
108
  @clean_up ||= new_command('Cleanup') do |working_dir|
104
109
  if remote_available?
105
- remote_exec(cleanup_cmd, quiet: true)
110
+ remote_run(cleanup_cmd, quiet: true)
106
111
  else
107
112
  logger.warn 'Cleanup not available'
108
113
  end
@@ -111,7 +116,7 @@ module CemWinSpec
111
116
 
112
117
  def clean_fixture_cache(**opts)
113
118
  @clean_cache ||= new_command('Clean fixture cache', **opts) do
114
- remote_exec('bundle exec rake cem_win_spec:clean_fixture_cache')
119
+ remote_run('bundle exec rake cem_win_spec:clean_fixture_cache')
115
120
  end
116
121
  end
117
122
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CemWinSpec
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.4"
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,36 @@
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
+ cmd = cmd.join('; ') if cmd.is_a?(Array)
26
+ log_command(cmd)
26
27
  shell = nil
27
28
  output = nil
28
29
  begin
29
30
  shell = conn.shell(:powershell)
30
- output = shell.run(cd_working_dir(cmd))
31
+ output = shell.run(command(cmd)) do |stdout, stderr|
32
+ logger << stdout unless stdout.nil? || stdout.empty?
33
+ logger << stderr unless stderr.nil? || stderr.empty?
34
+ end
31
35
  rescue WinRM::WinRMAuthorizationError => e
32
36
  @available = false
33
37
  raise e
@@ -77,10 +81,19 @@ module CemWinSpec
77
81
  def new_conn(hash_opts)
78
82
  logger.debug "Creating connection to #{hash_opts[:endpoint]}"
79
83
  new_conn = WinRM::Connection.new(hash_opts)
80
- new_conn.logger = logger
84
+ new_conn.logger = new_conn_logger
81
85
  new_conn
82
86
  end
83
87
 
88
+ def new_conn_logger
89
+ conn_logger = logger.dup
90
+ conn_logger.level = Logger::ERROR if @quiet
91
+ conn_logger.level = Logger::DEBUG if ENV['WINRM_DEBUG'] == 'true'
92
+ conn_logger.level = Logger::INFO if conn_logger.level == Logger::DEBUG && ENV.fetch('WINRM_DEBUG', 'false') != 'true'
93
+ logger.debug "WinRM connection logger level set to #{conn_logger.level}"
94
+ conn_logger
95
+ end
96
+
84
97
  def file_manager
85
98
  @file_manager ||= WinRM::FS::FileManager.new(conn)
86
99
  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