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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -33
- data/CHANGELOG.md +46 -1
- data/CONTRIBUTING.md +17 -0
- data/Gemfile.proxy_tests +1 -2
- data/MAINTAINERS.md +24 -0
- data/features/kitchen_init_command.feature +50 -4
- data/lib/kitchen/cli.rb +43 -5
- data/lib/kitchen/command.rb +1 -0
- data/lib/kitchen/config.rb +4 -0
- data/lib/kitchen/generator/init.rb +4 -4
- data/lib/kitchen/instance.rb +7 -0
- data/lib/kitchen/logger.rb +4 -7
- data/lib/kitchen/provisioner/chef_base.rb +2 -1
- data/lib/kitchen/provisioner/chef_solo.rb +1 -0
- data/lib/kitchen/provisioner/chef_zero.rb +5 -0
- data/lib/kitchen/ssh.rb +1 -1
- data/lib/kitchen/transport/base.rb +7 -0
- data/lib/kitchen/transport/ssh.rb +13 -6
- data/lib/kitchen/transport/winrm.rb +133 -49
- data/lib/kitchen/version.rb +1 -2
- data/spec/kitchen/collection_spec.rb +1 -1
- data/spec/kitchen/logger_spec.rb +10 -0
- data/spec/kitchen/provisioner/chef_base_spec.rb +2 -2
- data/spec/kitchen/provisioner/chef_solo_spec.rb +13 -0
- data/spec/kitchen/provisioner/chef_zero_spec.rb +13 -0
- data/spec/kitchen/ssh_spec.rb +1 -1
- data/spec/kitchen/transport/ssh_spec.rb +2 -1
- data/spec/kitchen/transport/winrm_spec.rb +178 -48
- data/support/chef_base_install_command.ps1 +19 -12
- data/templates/driver/README.md.erb +1 -1
- data/test-kitchen.gemspec +2 -1
- metadata +26 -10
@@ -143,10 +143,10 @@ module Kitchen
|
|
143
143
|
rakedoc = <<-RAKE.gsub(/^ {10}/, "")
|
144
144
|
|
145
145
|
begin
|
146
|
-
require
|
146
|
+
require 'kitchen/rake_tasks'
|
147
147
|
Kitchen::RakeTasks.new
|
148
148
|
rescue LoadError
|
149
|
-
puts
|
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
|
164
|
+
require 'kitchen/thor_tasks'
|
165
165
|
Kitchen::ThorTasks.new
|
166
166
|
rescue LoadError
|
167
|
-
puts
|
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)
|
data/lib/kitchen/instance.rb
CHANGED
@@ -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
|
data/lib/kitchen/logger.rb
CHANGED
@@ -343,13 +343,10 @@ module Kitchen
|
|
343
343
|
# @param msg [String] a message
|
344
344
|
def <<(msg)
|
345
345
|
@buffer ||= ""
|
346
|
-
|
347
|
-
|
348
|
-
@buffer
|
349
|
-
|
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
|
@@ -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
|
#
|
data/lib/kitchen/ssh.rb
CHANGED
@@ -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,
|
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
|
-
|
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
|
-
|
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
|
130
|
-
:delay
|
131
|
-
:message
|
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[
|
303
|
-
args += %W[
|
304
|
-
args += %W[
|
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
|
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
|
427
|
-
:kitchen_root
|
428
|
-
:logger
|
429
|
-
:
|
430
|
-
:
|
431
|
-
:
|
432
|
-
:
|
433
|
-
:
|
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
|
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!
|
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
|