test-kitchen 1.5.0.rc.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -143,10 +143,10 @@ module Kitchen
143
143
  rakedoc = <<-RAKE.gsub(/^ {10}/, "")
144
144
 
145
145
  begin
146
- require "kitchen/rake_tasks"
146
+ require 'kitchen/rake_tasks'
147
147
  Kitchen::RakeTasks.new
148
148
  rescue LoadError
149
- puts ">>>>> Kitchen gem not loaded, omitting tasks" unless ENV["CI"]
149
+ puts '>>>>> Kitchen gem not loaded, omitting tasks' unless ENV['CI']
150
150
  end
151
151
  RAKE
152
152
  append_to_file(File.join(destination_root, "Rakefile"), rakedoc)
@@ -161,10 +161,10 @@ module Kitchen
161
161
  thordoc = <<-THOR.gsub(/^ {10}/, "")
162
162
 
163
163
  begin
164
- require "kitchen/thor_tasks"
164
+ require 'kitchen/thor_tasks'
165
165
  Kitchen::ThorTasks.new
166
166
  rescue LoadError
167
- puts ">>>>> Kitchen gem not loaded, omitting tasks" unless ENV["CI"]
167
+ puts '>>>>> Kitchen gem not loaded, omitting tasks' unless ENV['CI']
168
168
  end
169
169
  THOR
170
170
  append_to_file(File.join(destination_root, "Thorfile"), thordoc)
@@ -267,6 +267,13 @@ module Kitchen
267
267
  state_file.read[:last_action]
268
268
  end
269
269
 
270
+ # Clean up any per-instance resources before exiting.
271
+ #
272
+ # @return [void]
273
+ def cleanup!
274
+ @transport.cleanup! if @transport
275
+ end
276
+
270
277
  private
271
278
 
272
279
  # @return [StateFile] a state file object that can be read from or written
@@ -343,13 +343,10 @@ module Kitchen
343
343
  # @param msg [String] a message
344
344
  def <<(msg)
345
345
  @buffer ||= ""
346
- lines, _, remainder = msg.rpartition("\n")
347
- if lines.empty?
348
- @buffer << remainder
349
- else
350
- lines.insert(0, @buffer)
351
- lines.split("\n").each { |l| format_line(l.chomp) }
352
- @buffer = ""
346
+ @buffer += msg
347
+ while i = @buffer.index("\n")
348
+ format_line(@buffer[0, i].chomp)
349
+ @buffer[0, i + 1] = ""
353
350
  end
354
351
  end
355
352
 
@@ -45,6 +45,7 @@ module Kitchen
45
45
  default_config :attributes, {}
46
46
  default_config :config_path, nil
47
47
  default_config :log_file, nil
48
+ default_config :profile_ruby, false
48
49
  default_config :cookbook_files_glob, %w[
49
50
  README.* metadata.{json,rb}
50
51
  attributes/**/* definitions/**/* files/**/* libraries/**/*
@@ -132,7 +133,7 @@ module Kitchen
132
133
  # Passing "true" to mixlib-install currently breaks the windows metadata_url
133
134
  # TODO: remove this line once https://github.com/chef/mixlib-install/pull/22
134
135
  # is accepted and released
135
- version = "" if version == "true"
136
+ version = "" if version == "true" && powershell_shell?
136
137
 
137
138
  installer = Mixlib::Install.new(version, powershell_shell?, install_options)
138
139
  config[:chef_omnibus_root] = installer.root
@@ -59,6 +59,7 @@ module Kitchen
59
59
  "--json-attributes #{remote_path_join(config[:root_path], "dna.json")}"
60
60
  ]
61
61
  args << "--logfile #{config[:log_file]}" if config[:log_file]
62
+ args << "--profile-ruby" if config[:profile_ruby]
62
63
 
63
64
  prefix_command(
64
65
  wrap_shell_code(
@@ -88,6 +88,7 @@ module Kitchen
88
88
  #
89
89
  # @param args [Array<String>] array of flags
90
90
  # @api private
91
+ # rubocop:disable Metrics/CyclomaticComplexity
91
92
  def add_optional_chef_client_args!(args)
92
93
  if config[:json_attributes]
93
94
  json = remote_path_join(config[:root_path], "dna.json")
@@ -106,7 +107,11 @@ module Kitchen
106
107
  if config[:chef_zero_port]
107
108
  args << "--chef-zero-port #{config[:chef_zero_port]}"
108
109
  end
110
+ if config[:profile_ruby]
111
+ args << "--profile-ruby"
112
+ end
109
113
  end
114
+ # rubocop:enable Metrics/CyclomaticComplexity
110
115
 
111
116
  # Returns an Array of command line arguments for the chef client.
112
117
  #
@@ -192,7 +192,7 @@ module Kitchen
192
192
  rescue_exceptions = [
193
193
  Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
194
194
  Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
195
- Net::SSH::Disconnect, Net::SSH::AuthenticationFailed
195
+ Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout
196
196
  ]
197
197
  retries = options[:ssh_retries] || 3
198
198
 
@@ -58,6 +58,13 @@ module Kitchen
58
58
  raise ClientError, "#{self.class}#connection must be implemented"
59
59
  end
60
60
 
61
+ # Closes the connection, if it is still active.
62
+ #
63
+ # @return [void]
64
+ def cleanup!
65
+ # This method may be left unimplemented if that is applicable
66
+ end
67
+
61
68
  # A Connection instance can be generated and re-generated, given new
62
69
  # connection details such as connection port, hostname, credentials, etc.
63
70
  # This object is responsible for carrying out the actions on the remote
@@ -20,6 +20,7 @@ require "kitchen"
20
20
 
21
21
  require "net/ssh"
22
22
  require "net/scp"
23
+ require "timeout"
23
24
 
24
25
  module Kitchen
25
26
 
@@ -85,6 +86,15 @@ module Kitchen
85
86
  end
86
87
  end
87
88
 
89
+ # (see Base#cleanup!)
90
+ def cleanup!
91
+ if @connection
92
+ logger.debug("[SSH] shutting previous connection #{@connection}")
93
+ @connection.close
94
+ @connection = @connection_options = nil
95
+ end
96
+ end
97
+
88
98
  # A Connection instance can be generated and re-generated, given new
89
99
  # connection details such as connection port, hostname, credentials, etc.
90
100
  # This object is responsible for carrying out the actions on the remote
@@ -165,7 +175,8 @@ module Kitchen
165
175
  RESCUE_EXCEPTIONS_ON_ESTABLISH = [
166
176
  Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
167
177
  Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
168
- Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Timeout::Error
178
+ Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout,
179
+ Timeout::Error
169
180
  ].freeze
170
181
 
171
182
  # @return [Integer] how many times to retry when failing to execute
@@ -334,11 +345,7 @@ module Kitchen
334
345
  # @return [Ssh::Connection] an SSH Connection instance
335
346
  # @api private
336
347
  def create_new_connection(options, &block)
337
- if @connection
338
- logger.debug("[SSH] shutting previous connection #{@connection}")
339
- @connection.close
340
- end
341
-
348
+ cleanup!
342
349
  @connection_options = options
343
350
  @connection = Kitchen::Transport::Ssh::Connection.new(options, &block)
344
351
  end
@@ -24,9 +24,7 @@ require "uri"
24
24
  require "kitchen"
25
25
 
26
26
  module Kitchen
27
-
28
27
  module Transport
29
-
30
28
  # Wrapped exception for any internally raised WinRM-related errors.
31
29
  #
32
30
  # @author Fletcher Nichol <fnichol@nichol.ca>
@@ -38,19 +36,43 @@ module Kitchen
38
36
  # @author Salim Afiune <salim@afiunemaya.com.mx>
39
37
  # @author Fletcher Nichol <fnichol@nichol.ca>
40
38
  class Winrm < Kitchen::Transport::Base
41
-
42
39
  kitchen_transport_api_version 1
43
40
 
44
41
  plugin_version Kitchen::VERSION
45
42
 
46
- default_config :port, 5985
47
43
  default_config :username, "administrator"
48
44
  default_config :password, nil
49
- default_config :endpoint_template, "http://%{hostname}:%{port}/wsman"
50
45
  default_config :rdp_port, 3389
51
46
  default_config :connection_retries, 5
52
47
  default_config :connection_retry_sleep, 1
53
48
  default_config :max_wait_until_ready, 600
49
+ default_config :winrm_transport, "plaintext"
50
+ default_config :port do |transport|
51
+ transport[:winrm_transport] == :ssl ? 5986 : 5985
52
+ end
53
+ default_config :endpoint_template do |transport|
54
+ scheme = transport[:winrm_transport] == :ssl ? "https" : "http"
55
+ "#{scheme}://%{hostname}:%{port}/wsman"
56
+ end
57
+
58
+ def finalize_config!(instance)
59
+ super
60
+
61
+ transport = config[:winrm_transport].to_sym
62
+ config[:winrm_transport] =
63
+ case transport
64
+ when :sspinegotiate
65
+ if host_os_windows?
66
+ transport
67
+ else
68
+ :plaintext
69
+ end
70
+ else
71
+ transport
72
+ end
73
+
74
+ self
75
+ end
54
76
 
55
77
  # (see Base#connection)
56
78
  def connection(state, &block)
@@ -70,7 +92,6 @@ module Kitchen
70
92
  #
71
93
  # @author Fletcher Nichol <fnichol@nichol.ca>
72
94
  class Connection < Kitchen::Transport::Base::Connection
73
-
74
95
  # (see Base::Connection#close)
75
96
  def close
76
97
  return if @session.nil?
@@ -112,7 +133,7 @@ module Kitchen
112
133
  when /linux/
113
134
  login_command_for_linux
114
135
  else
115
- raise ActionFailed, "Remote login not supported in #{self.class} " \
136
+ fail ActionFailed, "Remote login not supported in #{self.class} " \
116
137
  "from host OS '#{RbConfig::CONFIG["host_os"]}'."
117
138
  end
118
139
  end
@@ -126,9 +147,9 @@ module Kitchen
126
147
  def wait_until_ready
127
148
  delay = 3
128
149
  session(
129
- :retries => max_wait_until_ready / delay,
130
- :delay => delay,
131
- :message => "Waiting for WinRM service on #{endpoint}, " \
150
+ :retries => max_wait_until_ready / delay,
151
+ :delay => delay,
152
+ :message => "Waiting for WinRM service on #{endpoint}, " \
132
153
  "retrying in #{delay} seconds"
133
154
  )
134
155
  execute(PING_COMMAND.dup)
@@ -145,7 +166,7 @@ module Kitchen
145
166
 
146
167
  RESCUE_EXCEPTIONS_ON_ESTABLISH = lambda do
147
168
  [
148
- Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
169
+ Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
149
170
  Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
150
171
  ::WinRM::WinRMHTTPTransportError, ::WinRM::WinRMAuthorizationError,
151
172
  HTTPClient::KeepAliveDisconnected,
@@ -299,9 +320,9 @@ module Kitchen
299
320
  # @return [LoginCommand] a login command
300
321
  # @api private
301
322
  def login_command_for_linux
302
- args = %W[ -u #{options[:user]} ]
303
- args += %W[ -p #{options[:pass]} ] if options.key?(:pass)
304
- args += %W[ #{URI.parse(endpoint).host}:#{rdp_port} ]
323
+ args = %W[-u #{options[:user]}]
324
+ args += %W[-p #{options[:pass]}] if options.key?(:pass)
325
+ args += %W[#{URI.parse(endpoint).host}:#{rdp_port}]
305
326
 
306
327
  LoginCommand.new("rdesktop", args)
307
328
  end
@@ -373,7 +394,7 @@ module Kitchen
373
394
  def session(retry_options = {})
374
395
  @session ||= establish_shell({
375
396
  :retries => connection_retries.to_i,
376
- :delay => connection_retry_sleep.to_i
397
+ :delay => connection_retry_sleep.to_i
377
398
  }.merge(retry_options))
378
399
  end
379
400
 
@@ -423,24 +444,38 @@ module Kitchen
423
444
  # @api private
424
445
  def connection_options(data)
425
446
  opts = {
426
- :instance_name => instance.name,
427
- :kitchen_root => data[:kitchen_root],
428
- :logger => logger,
429
- :winrm_transport => :plaintext,
430
- :disable_sspi => true,
431
- :basic_auth_only => true,
432
- :endpoint => data[:endpoint_template] % data,
433
- :user => data[:username],
434
- :pass => data[:password],
435
- :rdp_port => data[:rdp_port],
436
- :connection_retries => data[:connection_retries],
447
+ :instance_name => instance.name,
448
+ :kitchen_root => data[:kitchen_root],
449
+ :logger => logger,
450
+ :endpoint => data[:endpoint_template] % data,
451
+ :user => data[:username],
452
+ :pass => data[:password],
453
+ :rdp_port => data[:rdp_port],
454
+ :connection_retries => data[:connection_retries],
437
455
  :connection_retry_sleep => data[:connection_retry_sleep],
438
- :max_wait_until_ready => data[:max_wait_until_ready]
456
+ :max_wait_until_ready => data[:max_wait_until_ready],
457
+ :winrm_transport => data[:winrm_transport]
439
458
  }
440
-
459
+ opts.merge!(additional_transport_args(opts[:winrm_transport]))
441
460
  opts
442
461
  end
443
462
 
463
+ def additional_transport_args(transport_type)
464
+ case transport_type
465
+ when :ssl, :sspinegotiate
466
+ {
467
+ :no_ssl_peer_verification => true,
468
+ :disable_sspi => false,
469
+ :basic_auth_only => false
470
+ }
471
+ when :plaintext
472
+ {
473
+ :disable_sspi => true,
474
+ :basic_auth_only => true
475
+ }
476
+ end
477
+ end
478
+
444
479
  # Creates a new WinRM Connection instance and save it for potential
445
480
  # future reuse.
446
481
  #
@@ -458,41 +493,90 @@ module Kitchen
458
493
  end
459
494
 
460
495
  # (see Base#load_needed_dependencies!)
461
- def load_needed_dependencies! # rubocop:disable Metrics/MethodLength
496
+ def load_needed_dependencies!
462
497
  super
463
- spec_version = WINRM_TRANSPORT_SPEC_VERSION.dup
464
- logger.debug("Winrm Transport requested," \
465
- " loading WinRM::Transport gem (#{spec_version})")
466
- gem "winrm-transport", *spec_version
467
- first_load = require "winrm/transport/version"
468
498
  load_winrm_transport!
469
-
470
- version = ::WinRM::Transport::VERSION
471
- if first_load
472
- logger.debug("WinRM::Transport #{version} library loaded")
473
- else
474
- logger.debug("WinRM::Transport #{version} previously loaded")
475
- end
476
- rescue LoadError => e
477
- logger.fatal("The `winrm-transport' gem is missing and must" \
478
- " be installed or cannot be properly activated. Run" \
479
- " `gem install winrm-transport --version '#{spec_version}'`" \
480
- " or add the following to your Gemfile if you are using Bundler:" \
481
- " `gem 'winrm-transport', '#{spec_version}'`.")
482
- raise UserError,
483
- "Could not load or activate WinRM::Transport (#{e.message})"
499
+ load_winrm_s! if host_os_windows?
484
500
  end
485
501
 
486
502
  # Load WinRM::Transport code.
487
503
  #
488
504
  # @api private
489
505
  def load_winrm_transport!
506
+ spec_version = WINRM_TRANSPORT_SPEC_VERSION.dup
507
+ options = {
508
+ :load_msg => "Winrm Transport requested," \
509
+ " loading WinRM::Transport gem (#{spec_version})",
510
+ :success_msg => "WinRM::Transport library loaded",
511
+ :already_msg => "WinRM::Transport previously loaded",
512
+ :name => "winrm-transport",
513
+ :version => spec_version
514
+ }
515
+
516
+ load_with_rescue!(options) do
517
+ gem "winrm-transport", WINRM_TRANSPORT_SPEC_VERSION.dup
518
+ require "winrm/transport/version"
519
+ end
520
+
490
521
  silence_warnings { require "winrm" }
491
522
  require "winrm/transport/shell_closer"
492
523
  require "winrm/transport/command_executor"
493
524
  require "winrm/transport/file_transporter"
494
525
  end
495
526
 
527
+ def load_winrm_s!
528
+ options = {
529
+ :load_msg => "The winrm-s gem is being loaded" \
530
+ " to enable sspiauthentication.",
531
+ :success_msg => "winrm-s is loaded." \
532
+ " sspinegotiate auth is now available.",
533
+ :already_msg => "winrm-s was already loaded.",
534
+ :name => "winrm-s"
535
+ }
536
+
537
+ load_with_rescue!(options) { require "winrm-s" }
538
+ end
539
+
540
+ def load_with_rescue!(options = {}, &block)
541
+ logger.debug(options[:load_msg])
542
+ attempt_load = execute_block(&block)
543
+ if attempt_load
544
+ logger.debug(options[:success_msg])
545
+ else
546
+ logger.debug(options[:already_msg])
547
+ end
548
+ rescue LoadError => e
549
+ message = fail_to_load_gem_message(options[:name],
550
+ options[:version])
551
+ logger.fatal(message)
552
+ raise UserError,
553
+ "Could not load or activate #{options[:name]}. (#{e.message})"
554
+ end
555
+
556
+ def execute_block
557
+ yield if block_given?
558
+ end
559
+
560
+ def fail_to_load_gem_message(name, version = nil)
561
+ version_cmd = "--version '#{version}'" if version
562
+ version_file = "', '#{version}"
563
+
564
+ "The `#{name}` gem is missing and must" \
565
+ " be installed or cannot be properly activated. Run" \
566
+ " `gem install #{name} #{version_cmd}`" \
567
+ " or add the following to your Gemfile if you are using Bundler:" \
568
+ " `gem '#{name} #{version_file}'`."
569
+ end
570
+
571
+ def host_os_windows?
572
+ case RbConfig::CONFIG["host_os"]
573
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
574
+ true
575
+ else
576
+ false
577
+ end
578
+ end
579
+
496
580
  # Return the last saved WinRM connection instance.
497
581
  #
498
582
  # @return [Winrm::Connection] a WinRM Connection instance