test-kitchen 1.7.0 → 1.7.1.dev
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 +4 -4
- data/.cane +8 -8
- data/.gitattributes +3 -0
- data/.github/ISSUE_TEMPLATE.md +55 -55
- data/.gitignore +28 -28
- data/.kitchen.ci.yml +23 -23
- data/.kitchen.proxy.yml +27 -27
- data/.rubocop.yml +3 -3
- data/.travis.yml +70 -70
- data/.yardopts +3 -3
- data/Berksfile +3 -3
- data/CHANGELOG.md +1090 -1083
- data/CONTRIBUTING.md +14 -14
- data/Gemfile +19 -19
- data/Gemfile.proxy_tests +4 -4
- data/Guardfile +42 -42
- data/LICENSE +15 -15
- data/MAINTAINERS.md +23 -23
- data/README.md +135 -135
- data/Rakefile +61 -61
- data/appveyor.yml +44 -44
- data/features/kitchen_action_commands.feature +164 -164
- data/features/kitchen_command.feature +16 -16
- data/features/kitchen_console_command.feature +34 -34
- data/features/kitchen_defaults.feature +38 -38
- data/features/kitchen_diagnose_command.feature +96 -96
- data/features/kitchen_driver_create_command.feature +64 -64
- data/features/kitchen_driver_discover_command.feature +25 -25
- data/features/kitchen_help_command.feature +16 -16
- data/features/kitchen_init_command.feature +274 -274
- data/features/kitchen_list_command.feature +104 -104
- data/features/kitchen_login_command.feature +62 -62
- data/features/kitchen_sink_command.feature +30 -30
- data/features/kitchen_test_command.feature +88 -88
- data/features/step_definitions/gem_steps.rb +36 -36
- data/features/step_definitions/git_steps.rb +5 -5
- data/features/step_definitions/output_steps.rb +5 -5
- data/features/support/env.rb +75 -75
- data/lib/kitchen.rb +150 -150
- data/lib/kitchen/base64_stream.rb +55 -55
- data/lib/kitchen/cli.rb +419 -419
- data/lib/kitchen/collection.rb +55 -55
- data/lib/kitchen/color.rb +65 -65
- data/lib/kitchen/command.rb +185 -185
- data/lib/kitchen/command/action.rb +45 -45
- data/lib/kitchen/command/console.rb +58 -58
- data/lib/kitchen/command/diagnose.rb +92 -92
- data/lib/kitchen/command/driver_discover.rb +105 -105
- data/lib/kitchen/command/exec.rb +41 -41
- data/lib/kitchen/command/list.rb +119 -119
- data/lib/kitchen/command/login.rb +43 -43
- data/lib/kitchen/command/sink.rb +54 -54
- data/lib/kitchen/command/test.rb +51 -51
- data/lib/kitchen/config.rb +322 -322
- data/lib/kitchen/configurable.rb +529 -529
- data/lib/kitchen/data_munger.rb +959 -959
- data/lib/kitchen/diagnostic.rb +141 -141
- data/lib/kitchen/driver.rb +56 -56
- data/lib/kitchen/driver/base.rb +134 -134
- data/lib/kitchen/driver/dummy.rb +108 -108
- data/lib/kitchen/driver/proxy.rb +72 -72
- data/lib/kitchen/driver/ssh_base.rb +357 -357
- data/lib/kitchen/errors.rb +229 -229
- data/lib/kitchen/generator/driver_create.rb +177 -177
- data/lib/kitchen/generator/init.rb +296 -296
- data/lib/kitchen/instance.rb +662 -662
- data/lib/kitchen/lazy_hash.rb +142 -142
- data/lib/kitchen/loader/yaml.rb +349 -349
- data/lib/kitchen/logger.rb +423 -423
- data/lib/kitchen/logging.rb +56 -56
- data/lib/kitchen/login_command.rb +52 -52
- data/lib/kitchen/metadata_chopper.rb +52 -52
- data/lib/kitchen/platform.rb +67 -67
- data/lib/kitchen/provisioner.rb +54 -54
- data/lib/kitchen/provisioner/base.rb +236 -236
- data/lib/kitchen/provisioner/chef/berkshelf.rb +114 -114
- data/lib/kitchen/provisioner/chef/common_sandbox.rb +322 -322
- data/lib/kitchen/provisioner/chef/librarian.rb +112 -112
- data/lib/kitchen/provisioner/chef_apply.rb +124 -124
- data/lib/kitchen/provisioner/chef_base.rb +341 -341
- data/lib/kitchen/provisioner/chef_solo.rb +88 -88
- data/lib/kitchen/provisioner/chef_zero.rb +245 -245
- data/lib/kitchen/provisioner/dummy.rb +79 -79
- data/lib/kitchen/provisioner/shell.rb +138 -138
- data/lib/kitchen/rake_tasks.rb +63 -63
- data/lib/kitchen/shell_out.rb +93 -93
- data/lib/kitchen/ssh.rb +276 -276
- data/lib/kitchen/state_file.rb +120 -120
- data/lib/kitchen/suite.rb +51 -51
- data/lib/kitchen/thor_tasks.rb +66 -66
- data/lib/kitchen/transport.rb +54 -54
- data/lib/kitchen/transport/base.rb +176 -176
- data/lib/kitchen/transport/dummy.rb +79 -79
- data/lib/kitchen/transport/ssh.rb +364 -364
- data/lib/kitchen/transport/winrm.rb +486 -486
- data/lib/kitchen/util.rb +147 -147
- data/lib/kitchen/verifier.rb +55 -55
- data/lib/kitchen/verifier/base.rb +235 -235
- data/lib/kitchen/verifier/busser.rb +277 -277
- data/lib/kitchen/verifier/dummy.rb +79 -79
- data/lib/kitchen/verifier/shell.rb +101 -101
- data/lib/kitchen/version.rb +21 -21
- data/lib/vendor/hash_recursive_merge.rb +82 -82
- data/spec/kitchen/base64_stream_spec.rb +77 -77
- data/spec/kitchen/cli_spec.rb +56 -56
- data/spec/kitchen/collection_spec.rb +80 -80
- data/spec/kitchen/color_spec.rb +54 -54
- data/spec/kitchen/config_spec.rb +408 -408
- data/spec/kitchen/configurable_spec.rb +1095 -1095
- data/spec/kitchen/data_munger_spec.rb +2694 -2694
- data/spec/kitchen/diagnostic_spec.rb +129 -129
- data/spec/kitchen/driver/base_spec.rb +121 -121
- data/spec/kitchen/driver/dummy_spec.rb +199 -199
- data/spec/kitchen/driver/proxy_spec.rb +138 -138
- data/spec/kitchen/driver/ssh_base_spec.rb +1115 -1115
- data/spec/kitchen/driver_spec.rb +112 -112
- data/spec/kitchen/errors_spec.rb +309 -309
- data/spec/kitchen/instance_spec.rb +1419 -1419
- data/spec/kitchen/lazy_hash_spec.rb +117 -117
- data/spec/kitchen/loader/yaml_spec.rb +774 -774
- data/spec/kitchen/logger_spec.rb +429 -429
- data/spec/kitchen/logging_spec.rb +59 -59
- data/spec/kitchen/login_command_spec.rb +68 -68
- data/spec/kitchen/metadata_chopper_spec.rb +82 -82
- data/spec/kitchen/platform_spec.rb +89 -89
- data/spec/kitchen/provisioner/base_spec.rb +386 -386
- data/spec/kitchen/provisioner/chef_apply_spec.rb +136 -136
- data/spec/kitchen/provisioner/chef_base_spec.rb +1161 -1161
- data/spec/kitchen/provisioner/chef_solo_spec.rb +557 -557
- data/spec/kitchen/provisioner/chef_zero_spec.rb +1001 -1001
- data/spec/kitchen/provisioner/dummy_spec.rb +99 -99
- data/spec/kitchen/provisioner/shell_spec.rb +566 -566
- data/spec/kitchen/provisioner_spec.rb +107 -107
- data/spec/kitchen/shell_out_spec.rb +150 -150
- data/spec/kitchen/ssh_spec.rb +693 -693
- data/spec/kitchen/state_file_spec.rb +129 -129
- data/spec/kitchen/suite_spec.rb +62 -62
- data/spec/kitchen/transport/base_spec.rb +89 -89
- data/spec/kitchen/transport/ssh_spec.rb +1255 -1255
- data/spec/kitchen/transport/winrm_spec.rb +1143 -1143
- data/spec/kitchen/transport_spec.rb +112 -112
- data/spec/kitchen/util_spec.rb +165 -165
- data/spec/kitchen/verifier/base_spec.rb +362 -362
- data/spec/kitchen/verifier/busser_spec.rb +610 -610
- data/spec/kitchen/verifier/dummy_spec.rb +99 -99
- data/spec/kitchen/verifier/shell_spec.rb +160 -160
- data/spec/kitchen/verifier_spec.rb +120 -120
- data/spec/kitchen_spec.rb +114 -114
- data/spec/spec_helper.rb +85 -85
- data/spec/support/powershell_max_size_spec.rb +40 -40
- data/support/busser_install_command.ps1 +14 -14
- data/support/busser_install_command.sh +14 -14
- data/support/chef-client-zero.rb +77 -77
- data/support/chef_base_init_command.ps1 +18 -18
- data/support/chef_base_init_command.sh +2 -2
- data/support/chef_base_install_command.ps1 +85 -85
- data/support/chef_base_install_command.sh +229 -229
- data/support/chef_zero_prepare_command_legacy.ps1 +9 -9
- data/support/chef_zero_prepare_command_legacy.sh +10 -10
- data/support/download_helpers.sh +109 -109
- data/support/dummy-validation.pem +27 -27
- data/templates/driver/CHANGELOG.md.erb +3 -3
- data/templates/driver/Gemfile.erb +3 -3
- data/templates/driver/README.md.erb +64 -64
- data/templates/driver/Rakefile.erb +21 -21
- data/templates/driver/driver.rb.erb +23 -23
- data/templates/driver/gemspec.erb +29 -29
- data/templates/driver/gitignore.erb +17 -17
- data/templates/driver/license_apachev2.erb +15 -15
- data/templates/driver/license_lgplv3.erb +16 -16
- data/templates/driver/license_mit.erb +22 -22
- data/templates/driver/license_reserved.erb +5 -5
- data/templates/driver/tailor.erb +4 -4
- data/templates/driver/travis.yml.erb +11 -11
- data/templates/driver/version.rb.erb +12 -12
- data/templates/init/chefignore.erb +1 -1
- data/templates/init/kitchen.yml.erb +18 -18
- data/test-kitchen.gemspec +62 -62
- data/test/integration/default/default_spec.rb +3 -3
- data/testing_windows.md +37 -37
- metadata +5 -4
|
@@ -1,486 +1,486 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Author:: Salim Afiune (<salim@afiunemaya.com.mx>)
|
|
4
|
-
# Author:: Matt Wrock (<matt@mattwrock.com>)
|
|
5
|
-
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
6
|
-
#
|
|
7
|
-
# Copyright (C) 2014, Salim Afiune
|
|
8
|
-
#
|
|
9
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
-
# you may not use this file except in compliance with the License.
|
|
11
|
-
# You may obtain a copy of the License at
|
|
12
|
-
#
|
|
13
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
-
#
|
|
15
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
16
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
-
# See the License for the specific language governing permissions and
|
|
19
|
-
# limitations under the License.
|
|
20
|
-
|
|
21
|
-
require "rbconfig"
|
|
22
|
-
require "uri"
|
|
23
|
-
|
|
24
|
-
require "kitchen"
|
|
25
|
-
|
|
26
|
-
module Kitchen
|
|
27
|
-
module Transport
|
|
28
|
-
# Wrapped exception for any internally raised WinRM-related errors.
|
|
29
|
-
#
|
|
30
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
31
|
-
class WinrmFailed < TransportFailed; end
|
|
32
|
-
|
|
33
|
-
# A Transport which uses WinRM to execute commands and transfer files.
|
|
34
|
-
#
|
|
35
|
-
# @author Matt Wrock <matt@mattwrock.com>
|
|
36
|
-
# @author Salim Afiune <salim@afiunemaya.com.mx>
|
|
37
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
38
|
-
class Winrm < Kitchen::Transport::Base
|
|
39
|
-
kitchen_transport_api_version 1
|
|
40
|
-
|
|
41
|
-
plugin_version Kitchen::VERSION
|
|
42
|
-
|
|
43
|
-
default_config :username, "administrator"
|
|
44
|
-
default_config :password, nil
|
|
45
|
-
default_config :rdp_port, 3389
|
|
46
|
-
default_config :connection_retries, 5
|
|
47
|
-
default_config :connection_retry_sleep, 1
|
|
48
|
-
default_config :max_wait_until_ready, 600
|
|
49
|
-
default_config :winrm_transport, :negotiate
|
|
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
|
-
config[:winrm_transport] = config[:winrm_transport].to_sym
|
|
62
|
-
|
|
63
|
-
self
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# (see Base#connection)
|
|
67
|
-
def connection(state, &block)
|
|
68
|
-
options = connection_options(config.to_hash.merge(state))
|
|
69
|
-
|
|
70
|
-
if @connection && @connection_options == options
|
|
71
|
-
reuse_connection(&block)
|
|
72
|
-
else
|
|
73
|
-
create_new_connection(options, &block)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# A Connection instance can be generated and re-generated, given new
|
|
78
|
-
# connection details such as connection port, hostname, credentials, etc.
|
|
79
|
-
# This object is responsible for carrying out the actions on the remote
|
|
80
|
-
# host such as executing commands, transferring files, etc.
|
|
81
|
-
#
|
|
82
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
83
|
-
class Connection < Kitchen::Transport::Base::Connection
|
|
84
|
-
# (see Base::Connection#close)
|
|
85
|
-
def close
|
|
86
|
-
return if @session.nil?
|
|
87
|
-
|
|
88
|
-
session.close
|
|
89
|
-
ensure
|
|
90
|
-
@session = nil
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# (see Base::Connection#execute)
|
|
94
|
-
def execute(command)
|
|
95
|
-
return if command.nil?
|
|
96
|
-
logger.debug("[WinRM] #{self} (#{command})")
|
|
97
|
-
|
|
98
|
-
if command.length > MAX_COMMAND_SIZE
|
|
99
|
-
command = run_from_file_command(command)
|
|
100
|
-
end
|
|
101
|
-
exit_code, stderr = execute_with_exit_code(command)
|
|
102
|
-
|
|
103
|
-
if logger.debug? && exit_code == 0
|
|
104
|
-
log_stderr_on_warn(stderr)
|
|
105
|
-
elsif exit_code != 0
|
|
106
|
-
log_stderr_on_warn(stderr)
|
|
107
|
-
raise Transport::WinrmFailed,
|
|
108
|
-
"WinRM exited (#{exit_code}) for command: [#{command}]"
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# (see Base::Connection#login_command)
|
|
113
|
-
def login_command
|
|
114
|
-
case RbConfig::CONFIG["host_os"]
|
|
115
|
-
when /darwin/
|
|
116
|
-
login_command_for_mac
|
|
117
|
-
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
|
118
|
-
login_command_for_windows
|
|
119
|
-
when /linux/
|
|
120
|
-
login_command_for_linux
|
|
121
|
-
else
|
|
122
|
-
fail ActionFailed, "Remote login not supported in #{self.class} " \
|
|
123
|
-
"from host OS '#{RbConfig::CONFIG["host_os"]}'."
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# (see Base::Connection#upload)
|
|
128
|
-
def upload(locals, remote)
|
|
129
|
-
file_transporter.upload(locals, remote)
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# (see Base::Connection#wait_until_ready)
|
|
133
|
-
def wait_until_ready
|
|
134
|
-
delay = 3
|
|
135
|
-
session(
|
|
136
|
-
:retry_limit => max_wait_until_ready / delay,
|
|
137
|
-
:retry_delay => delay
|
|
138
|
-
)
|
|
139
|
-
execute(PING_COMMAND.dup)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
private
|
|
143
|
-
|
|
144
|
-
PING_COMMAND = "Write-Host '[WinRM] Established\n'".freeze
|
|
145
|
-
|
|
146
|
-
# Maximum string to send to the transport to execute. WinRM has an 8000 character
|
|
147
|
-
# command line limit. The original command string is coverted to a base 64 encoded
|
|
148
|
-
# UTF-16 string which will double the string size.
|
|
149
|
-
MAX_COMMAND_SIZE = 3000
|
|
150
|
-
|
|
151
|
-
# @return [Integer] how many times to retry when failing to execute
|
|
152
|
-
# a command or transfer files
|
|
153
|
-
# @api private
|
|
154
|
-
attr_reader :connection_retries
|
|
155
|
-
|
|
156
|
-
# @return [Float] how many seconds to wait before attempting a retry
|
|
157
|
-
# when failing to execute a command or transfer files
|
|
158
|
-
# @api private
|
|
159
|
-
attr_reader :connection_retry_sleep
|
|
160
|
-
|
|
161
|
-
# @return [String] the endpoint URL of the remote WinRM host
|
|
162
|
-
# @api private
|
|
163
|
-
attr_reader :endpoint
|
|
164
|
-
|
|
165
|
-
# @return [String] display name for the associated instance
|
|
166
|
-
# @api private
|
|
167
|
-
attr_reader :instance_name
|
|
168
|
-
|
|
169
|
-
# @return [String] local path to the root of the project
|
|
170
|
-
# @api private
|
|
171
|
-
attr_reader :kitchen_root
|
|
172
|
-
|
|
173
|
-
# @return [Integer] how many times to retry when invoking
|
|
174
|
-
# `#wait_until_ready` before failing
|
|
175
|
-
# @api private
|
|
176
|
-
attr_reader :max_wait_until_ready
|
|
177
|
-
|
|
178
|
-
# @return [Integer] the TCP port number to use when connection to the
|
|
179
|
-
# remote WinRM host
|
|
180
|
-
# @api private
|
|
181
|
-
attr_reader :rdp_port
|
|
182
|
-
|
|
183
|
-
# @return [Symbol] the transport strategy to use when constructing a
|
|
184
|
-
# `WinRM::WinRMWebService`
|
|
185
|
-
# @api private
|
|
186
|
-
attr_reader :winrm_transport
|
|
187
|
-
|
|
188
|
-
# Writes an RDP document to the local file system.
|
|
189
|
-
#
|
|
190
|
-
# @param opts [Hash] file options
|
|
191
|
-
# @option opts [true,false] :mac whether or not the document is for a
|
|
192
|
-
# Mac system
|
|
193
|
-
# @api private
|
|
194
|
-
def create_rdp_doc(opts = {})
|
|
195
|
-
content = Util.outdent!(<<-RDP)
|
|
196
|
-
full address:s:#{URI.parse(endpoint).host}:#{rdp_port}
|
|
197
|
-
prompt for credentials:i:1
|
|
198
|
-
username:s:#{options[:user]}
|
|
199
|
-
RDP
|
|
200
|
-
content.prepend("drivestoredirect:s:*\n") if opts[:mac]
|
|
201
|
-
|
|
202
|
-
File.open(rdp_doc_path, "wb") { |f| f.write(content) }
|
|
203
|
-
|
|
204
|
-
if logger.debug?
|
|
205
|
-
debug("Creating RDP document for #{instance_name} (#{rdp_doc_path})")
|
|
206
|
-
debug("------------")
|
|
207
|
-
IO.read(rdp_doc_path).each_line { |l| debug("#{l.chomp}") }
|
|
208
|
-
debug("------------")
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
# Execute a Powershell script over WinRM and return the command's
|
|
213
|
-
# exit code and standard error.
|
|
214
|
-
#
|
|
215
|
-
# @param command [String] Powershell script to execute
|
|
216
|
-
# @return [[Integer,String]] an array containing the exit code of the
|
|
217
|
-
# script and the standard error stream
|
|
218
|
-
# @api private
|
|
219
|
-
def execute_with_exit_code(command)
|
|
220
|
-
response = session.run_powershell_script(command) do |stdout, _|
|
|
221
|
-
logger << stdout if stdout
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
[response[:exitcode], response.stderr]
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
# @return [Winrm::FileTransporter] a file transporter
|
|
228
|
-
# @api private
|
|
229
|
-
def file_transporter
|
|
230
|
-
@file_transporter ||= WinRM::FS::Core::FileTransporter.new(session)
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
# (see Base#init_options)
|
|
234
|
-
def init_options(options)
|
|
235
|
-
super
|
|
236
|
-
@instance_name = @options.delete(:instance_name)
|
|
237
|
-
@kitchen_root = @options.delete(:kitchen_root)
|
|
238
|
-
@endpoint = @options.delete(:endpoint)
|
|
239
|
-
@rdp_port = @options.delete(:rdp_port)
|
|
240
|
-
@winrm_transport = @options.delete(:winrm_transport)
|
|
241
|
-
@connection_retries = @options.delete(:connection_retries)
|
|
242
|
-
@connection_retry_sleep = @options.delete(:connection_retry_sleep)
|
|
243
|
-
@max_wait_until_ready = @options.delete(:max_wait_until_ready)
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
# Logs formatted standard error output at the warning level.
|
|
247
|
-
#
|
|
248
|
-
# @param stderr [String] standard error output
|
|
249
|
-
# @api private
|
|
250
|
-
def log_stderr_on_warn(stderr)
|
|
251
|
-
error_regexp = /<S S=\"Error\">/
|
|
252
|
-
|
|
253
|
-
if error_regexp.match(stderr)
|
|
254
|
-
stderr.
|
|
255
|
-
split(error_regexp)[1..-2].
|
|
256
|
-
map! { |line| line.sub(/_x000D__x000A_<\/S>/, "").rstrip }.
|
|
257
|
-
each { |line| logger.warn(line) }
|
|
258
|
-
else
|
|
259
|
-
stderr.
|
|
260
|
-
split("\r\n").
|
|
261
|
-
each { |line| logger.warn(line) }
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
# Builds a `LoginCommand` for use by Linux-based platforms.
|
|
266
|
-
#
|
|
267
|
-
# TODO: determine whether or not `desktop` exists
|
|
268
|
-
#
|
|
269
|
-
# @return [LoginCommand] a login command
|
|
270
|
-
# @api private
|
|
271
|
-
def login_command_for_linux
|
|
272
|
-
args = %W[-u #{options[:user]}]
|
|
273
|
-
args += %W[-p #{options[:pass]}] if options.key?(:pass)
|
|
274
|
-
args += %W[#{URI.parse(endpoint).host}:#{rdp_port}]
|
|
275
|
-
|
|
276
|
-
LoginCommand.new("rdesktop", args)
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
# Builds a `LoginCommand` for use by Mac-based platforms.
|
|
280
|
-
#
|
|
281
|
-
# @return [LoginCommand] a login command
|
|
282
|
-
# @api private
|
|
283
|
-
def login_command_for_mac
|
|
284
|
-
create_rdp_doc(:mac => true)
|
|
285
|
-
|
|
286
|
-
LoginCommand.new("open", rdp_doc_path)
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
# Builds a `LoginCommand` for use by Windows-based platforms.
|
|
290
|
-
#
|
|
291
|
-
# @return [LoginCommand] a login command
|
|
292
|
-
# @api private
|
|
293
|
-
def login_command_for_windows
|
|
294
|
-
create_rdp_doc
|
|
295
|
-
|
|
296
|
-
LoginCommand.new("mstsc", rdp_doc_path)
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
# @return [String] path to the local RDP document
|
|
300
|
-
# @api private
|
|
301
|
-
def rdp_doc_path
|
|
302
|
-
File.join(kitchen_root, ".kitchen", "#{instance_name}.rdp")
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
# Establishes a remote shell session, or establishes one when invoked
|
|
306
|
-
# the first time.
|
|
307
|
-
#
|
|
308
|
-
# @param retry_options [Hash] retry options for the initial connection
|
|
309
|
-
# @return [Winrm::CommandExecutor] the command executor session
|
|
310
|
-
# @api private
|
|
311
|
-
def session(retry_options = {})
|
|
312
|
-
@session ||= begin
|
|
313
|
-
opts = {
|
|
314
|
-
:retry_limit => connection_retries.to_i,
|
|
315
|
-
:retry_delay => connection_retry_sleep.to_i
|
|
316
|
-
}.merge(retry_options)
|
|
317
|
-
|
|
318
|
-
service_args = [endpoint, winrm_transport, options.merge(opts)]
|
|
319
|
-
@service = ::WinRM::WinRMWebService.new(*service_args)
|
|
320
|
-
@service.logger = logger
|
|
321
|
-
@service.create_executor
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
# String representation of object, reporting its connection details and
|
|
326
|
-
# configuration.
|
|
327
|
-
#
|
|
328
|
-
# @api private
|
|
329
|
-
def to_s
|
|
330
|
-
"#{winrm_transport}::#{endpoint}<#{options.inspect}>"
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
# takes a long (greater than 3000 characters) command and saves it to a
|
|
334
|
-
# file and uploads it to the test instance.
|
|
335
|
-
#
|
|
336
|
-
# @param command [String] a long command to be saved and uploaded
|
|
337
|
-
# @return [String] a command that executes the uploaded script
|
|
338
|
-
# @api private
|
|
339
|
-
def run_from_file_command(command)
|
|
340
|
-
temp_dir = Dir.mktmpdir("kitchen-long-script")
|
|
341
|
-
begin
|
|
342
|
-
script_name = "#{instance_name}-long_script.ps1"
|
|
343
|
-
script_path = File.join(temp_dir, script_name)
|
|
344
|
-
|
|
345
|
-
File.open(script_path, "wb") do |file|
|
|
346
|
-
file.write(command)
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
target_path = File.join("$env:TEMP", script_name)
|
|
350
|
-
upload(script_path, target_path)
|
|
351
|
-
|
|
352
|
-
%{powershell -ExecutionPolicy Bypass -File "#{target_path}"}
|
|
353
|
-
ensure
|
|
354
|
-
FileUtils.rmtree(temp_dir)
|
|
355
|
-
end
|
|
356
|
-
end
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
private
|
|
360
|
-
|
|
361
|
-
WINRM_SPEC_VERSION = ["~> 1.6"].freeze
|
|
362
|
-
WINRM_FS_SPEC_VERSION = ["~> 0.4.0"].freeze
|
|
363
|
-
|
|
364
|
-
# Builds the hash of options needed by the Connection object on
|
|
365
|
-
# construction.
|
|
366
|
-
#
|
|
367
|
-
# @param data [Hash] merged configuration and mutable state data
|
|
368
|
-
# @return [Hash] hash of connection options
|
|
369
|
-
# @api private
|
|
370
|
-
def connection_options(data)
|
|
371
|
-
opts = {
|
|
372
|
-
:instance_name => instance.name,
|
|
373
|
-
:kitchen_root => data[:kitchen_root],
|
|
374
|
-
:logger => logger,
|
|
375
|
-
:endpoint => data[:endpoint_template] % data,
|
|
376
|
-
:user => data[:username],
|
|
377
|
-
:pass => data[:password],
|
|
378
|
-
:rdp_port => data[:rdp_port],
|
|
379
|
-
:connection_retries => data[:connection_retries],
|
|
380
|
-
:connection_retry_sleep => data[:connection_retry_sleep],
|
|
381
|
-
:max_wait_until_ready => data[:max_wait_until_ready],
|
|
382
|
-
:winrm_transport => data[:winrm_transport]
|
|
383
|
-
}
|
|
384
|
-
opts.merge!(additional_transport_args(opts[:winrm_transport]))
|
|
385
|
-
opts
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
def additional_transport_args(transport_type)
|
|
389
|
-
case transport_type.to_sym
|
|
390
|
-
when :ssl, :negotiate
|
|
391
|
-
{
|
|
392
|
-
:no_ssl_peer_verification => true,
|
|
393
|
-
:disable_sspi => false,
|
|
394
|
-
:basic_auth_only => false
|
|
395
|
-
}
|
|
396
|
-
when :plaintext
|
|
397
|
-
{
|
|
398
|
-
:disable_sspi => true,
|
|
399
|
-
:basic_auth_only => true
|
|
400
|
-
}
|
|
401
|
-
else
|
|
402
|
-
{}
|
|
403
|
-
end
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
# Creates a new WinRM Connection instance and save it for potential
|
|
407
|
-
# future reuse.
|
|
408
|
-
#
|
|
409
|
-
# @param options [Hash] conneciton options
|
|
410
|
-
# @return [Ssh::Connection] a WinRM Connection instance
|
|
411
|
-
# @api private
|
|
412
|
-
def create_new_connection(options, &block)
|
|
413
|
-
if @connection
|
|
414
|
-
logger.debug("[WinRM] shutting previous connection #{@connection}")
|
|
415
|
-
@connection.close
|
|
416
|
-
end
|
|
417
|
-
|
|
418
|
-
@connection_options = options
|
|
419
|
-
@connection = Kitchen::Transport::Winrm::Connection.new(options, &block)
|
|
420
|
-
end
|
|
421
|
-
|
|
422
|
-
# (see Base#load_needed_dependencies!)
|
|
423
|
-
def load_needed_dependencies!
|
|
424
|
-
super
|
|
425
|
-
load_with_rescue!("winrm", WINRM_SPEC_VERSION.dup)
|
|
426
|
-
load_with_rescue!("winrm-fs", WINRM_FS_SPEC_VERSION.dup)
|
|
427
|
-
end
|
|
428
|
-
|
|
429
|
-
def load_with_rescue!(gem_name, spec_version)
|
|
430
|
-
logger.debug("#{gem_name} requested," \
|
|
431
|
-
" loading #{gem_name} gem (#{spec_version})")
|
|
432
|
-
attempt_load = false
|
|
433
|
-
gem gem_name, spec_version
|
|
434
|
-
silence_warnings { attempt_load = require gem_name }
|
|
435
|
-
if attempt_load
|
|
436
|
-
logger.debug("#{gem_name} is loaded.")
|
|
437
|
-
else
|
|
438
|
-
logger.debug("#{gem_name} was already loaded.")
|
|
439
|
-
end
|
|
440
|
-
rescue LoadError => e
|
|
441
|
-
message = fail_to_load_gem_message(gem_name,
|
|
442
|
-
spec_version)
|
|
443
|
-
logger.fatal(message)
|
|
444
|
-
raise UserError,
|
|
445
|
-
"Could not load or activate #{gem_name}. (#{e.message})"
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
def fail_to_load_gem_message(name, version = nil)
|
|
449
|
-
version_cmd = "--version '#{version}'" if version
|
|
450
|
-
version_file = "', '#{version}"
|
|
451
|
-
|
|
452
|
-
"The `#{name}` gem is missing and must" \
|
|
453
|
-
" be installed or cannot be properly activated. Run" \
|
|
454
|
-
" `gem install #{name} #{version_cmd}`" \
|
|
455
|
-
" or add the following to your Gemfile if you are using Bundler:" \
|
|
456
|
-
" `gem '#{name} #{version_file}'`."
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
def host_os_windows?
|
|
460
|
-
case RbConfig::CONFIG["host_os"]
|
|
461
|
-
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
|
462
|
-
true
|
|
463
|
-
else
|
|
464
|
-
false
|
|
465
|
-
end
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
# Return the last saved WinRM connection instance.
|
|
469
|
-
#
|
|
470
|
-
# @return [Winrm::Connection] a WinRM Connection instance
|
|
471
|
-
# @api private
|
|
472
|
-
def reuse_connection
|
|
473
|
-
logger.debug("[WinRM] reusing existing connection #{@connection}")
|
|
474
|
-
yield @connection if block_given?
|
|
475
|
-
@connection
|
|
476
|
-
end
|
|
477
|
-
|
|
478
|
-
def silence_warnings
|
|
479
|
-
old_verbose, $VERBOSE = $VERBOSE, nil
|
|
480
|
-
yield
|
|
481
|
-
ensure
|
|
482
|
-
$VERBOSE = old_verbose
|
|
483
|
-
end
|
|
484
|
-
end
|
|
485
|
-
end
|
|
486
|
-
end
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Author:: Salim Afiune (<salim@afiunemaya.com.mx>)
|
|
4
|
+
# Author:: Matt Wrock (<matt@mattwrock.com>)
|
|
5
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
6
|
+
#
|
|
7
|
+
# Copyright (C) 2014, Salim Afiune
|
|
8
|
+
#
|
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
+
# you may not use this file except in compliance with the License.
|
|
11
|
+
# You may obtain a copy of the License at
|
|
12
|
+
#
|
|
13
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
+
#
|
|
15
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
16
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
+
# See the License for the specific language governing permissions and
|
|
19
|
+
# limitations under the License.
|
|
20
|
+
|
|
21
|
+
require "rbconfig"
|
|
22
|
+
require "uri"
|
|
23
|
+
|
|
24
|
+
require "kitchen"
|
|
25
|
+
|
|
26
|
+
module Kitchen
|
|
27
|
+
module Transport
|
|
28
|
+
# Wrapped exception for any internally raised WinRM-related errors.
|
|
29
|
+
#
|
|
30
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
31
|
+
class WinrmFailed < TransportFailed; end
|
|
32
|
+
|
|
33
|
+
# A Transport which uses WinRM to execute commands and transfer files.
|
|
34
|
+
#
|
|
35
|
+
# @author Matt Wrock <matt@mattwrock.com>
|
|
36
|
+
# @author Salim Afiune <salim@afiunemaya.com.mx>
|
|
37
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
38
|
+
class Winrm < Kitchen::Transport::Base
|
|
39
|
+
kitchen_transport_api_version 1
|
|
40
|
+
|
|
41
|
+
plugin_version Kitchen::VERSION
|
|
42
|
+
|
|
43
|
+
default_config :username, "administrator"
|
|
44
|
+
default_config :password, nil
|
|
45
|
+
default_config :rdp_port, 3389
|
|
46
|
+
default_config :connection_retries, 5
|
|
47
|
+
default_config :connection_retry_sleep, 1
|
|
48
|
+
default_config :max_wait_until_ready, 600
|
|
49
|
+
default_config :winrm_transport, :negotiate
|
|
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
|
+
config[:winrm_transport] = config[:winrm_transport].to_sym
|
|
62
|
+
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# (see Base#connection)
|
|
67
|
+
def connection(state, &block)
|
|
68
|
+
options = connection_options(config.to_hash.merge(state))
|
|
69
|
+
|
|
70
|
+
if @connection && @connection_options == options
|
|
71
|
+
reuse_connection(&block)
|
|
72
|
+
else
|
|
73
|
+
create_new_connection(options, &block)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# A Connection instance can be generated and re-generated, given new
|
|
78
|
+
# connection details such as connection port, hostname, credentials, etc.
|
|
79
|
+
# This object is responsible for carrying out the actions on the remote
|
|
80
|
+
# host such as executing commands, transferring files, etc.
|
|
81
|
+
#
|
|
82
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
83
|
+
class Connection < Kitchen::Transport::Base::Connection
|
|
84
|
+
# (see Base::Connection#close)
|
|
85
|
+
def close
|
|
86
|
+
return if @session.nil?
|
|
87
|
+
|
|
88
|
+
session.close
|
|
89
|
+
ensure
|
|
90
|
+
@session = nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# (see Base::Connection#execute)
|
|
94
|
+
def execute(command)
|
|
95
|
+
return if command.nil?
|
|
96
|
+
logger.debug("[WinRM] #{self} (#{command})")
|
|
97
|
+
|
|
98
|
+
if command.length > MAX_COMMAND_SIZE
|
|
99
|
+
command = run_from_file_command(command)
|
|
100
|
+
end
|
|
101
|
+
exit_code, stderr = execute_with_exit_code(command)
|
|
102
|
+
|
|
103
|
+
if logger.debug? && exit_code == 0
|
|
104
|
+
log_stderr_on_warn(stderr)
|
|
105
|
+
elsif exit_code != 0
|
|
106
|
+
log_stderr_on_warn(stderr)
|
|
107
|
+
raise Transport::WinrmFailed,
|
|
108
|
+
"WinRM exited (#{exit_code}) for command: [#{command}]"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# (see Base::Connection#login_command)
|
|
113
|
+
def login_command
|
|
114
|
+
case RbConfig::CONFIG["host_os"]
|
|
115
|
+
when /darwin/
|
|
116
|
+
login_command_for_mac
|
|
117
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
|
118
|
+
login_command_for_windows
|
|
119
|
+
when /linux/
|
|
120
|
+
login_command_for_linux
|
|
121
|
+
else
|
|
122
|
+
fail ActionFailed, "Remote login not supported in #{self.class} " \
|
|
123
|
+
"from host OS '#{RbConfig::CONFIG["host_os"]}'."
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# (see Base::Connection#upload)
|
|
128
|
+
def upload(locals, remote)
|
|
129
|
+
file_transporter.upload(locals, remote)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# (see Base::Connection#wait_until_ready)
|
|
133
|
+
def wait_until_ready
|
|
134
|
+
delay = 3
|
|
135
|
+
session(
|
|
136
|
+
:retry_limit => max_wait_until_ready / delay,
|
|
137
|
+
:retry_delay => delay
|
|
138
|
+
)
|
|
139
|
+
execute(PING_COMMAND.dup)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
PING_COMMAND = "Write-Host '[WinRM] Established\n'".freeze
|
|
145
|
+
|
|
146
|
+
# Maximum string to send to the transport to execute. WinRM has an 8000 character
|
|
147
|
+
# command line limit. The original command string is coverted to a base 64 encoded
|
|
148
|
+
# UTF-16 string which will double the string size.
|
|
149
|
+
MAX_COMMAND_SIZE = 3000
|
|
150
|
+
|
|
151
|
+
# @return [Integer] how many times to retry when failing to execute
|
|
152
|
+
# a command or transfer files
|
|
153
|
+
# @api private
|
|
154
|
+
attr_reader :connection_retries
|
|
155
|
+
|
|
156
|
+
# @return [Float] how many seconds to wait before attempting a retry
|
|
157
|
+
# when failing to execute a command or transfer files
|
|
158
|
+
# @api private
|
|
159
|
+
attr_reader :connection_retry_sleep
|
|
160
|
+
|
|
161
|
+
# @return [String] the endpoint URL of the remote WinRM host
|
|
162
|
+
# @api private
|
|
163
|
+
attr_reader :endpoint
|
|
164
|
+
|
|
165
|
+
# @return [String] display name for the associated instance
|
|
166
|
+
# @api private
|
|
167
|
+
attr_reader :instance_name
|
|
168
|
+
|
|
169
|
+
# @return [String] local path to the root of the project
|
|
170
|
+
# @api private
|
|
171
|
+
attr_reader :kitchen_root
|
|
172
|
+
|
|
173
|
+
# @return [Integer] how many times to retry when invoking
|
|
174
|
+
# `#wait_until_ready` before failing
|
|
175
|
+
# @api private
|
|
176
|
+
attr_reader :max_wait_until_ready
|
|
177
|
+
|
|
178
|
+
# @return [Integer] the TCP port number to use when connection to the
|
|
179
|
+
# remote WinRM host
|
|
180
|
+
# @api private
|
|
181
|
+
attr_reader :rdp_port
|
|
182
|
+
|
|
183
|
+
# @return [Symbol] the transport strategy to use when constructing a
|
|
184
|
+
# `WinRM::WinRMWebService`
|
|
185
|
+
# @api private
|
|
186
|
+
attr_reader :winrm_transport
|
|
187
|
+
|
|
188
|
+
# Writes an RDP document to the local file system.
|
|
189
|
+
#
|
|
190
|
+
# @param opts [Hash] file options
|
|
191
|
+
# @option opts [true,false] :mac whether or not the document is for a
|
|
192
|
+
# Mac system
|
|
193
|
+
# @api private
|
|
194
|
+
def create_rdp_doc(opts = {})
|
|
195
|
+
content = Util.outdent!(<<-RDP)
|
|
196
|
+
full address:s:#{URI.parse(endpoint).host}:#{rdp_port}
|
|
197
|
+
prompt for credentials:i:1
|
|
198
|
+
username:s:#{options[:user]}
|
|
199
|
+
RDP
|
|
200
|
+
content.prepend("drivestoredirect:s:*\n") if opts[:mac]
|
|
201
|
+
|
|
202
|
+
File.open(rdp_doc_path, "wb") { |f| f.write(content) }
|
|
203
|
+
|
|
204
|
+
if logger.debug?
|
|
205
|
+
debug("Creating RDP document for #{instance_name} (#{rdp_doc_path})")
|
|
206
|
+
debug("------------")
|
|
207
|
+
IO.read(rdp_doc_path).each_line { |l| debug("#{l.chomp}") }
|
|
208
|
+
debug("------------")
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Execute a Powershell script over WinRM and return the command's
|
|
213
|
+
# exit code and standard error.
|
|
214
|
+
#
|
|
215
|
+
# @param command [String] Powershell script to execute
|
|
216
|
+
# @return [[Integer,String]] an array containing the exit code of the
|
|
217
|
+
# script and the standard error stream
|
|
218
|
+
# @api private
|
|
219
|
+
def execute_with_exit_code(command)
|
|
220
|
+
response = session.run_powershell_script(command) do |stdout, _|
|
|
221
|
+
logger << stdout if stdout
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
[response[:exitcode], response.stderr]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# @return [Winrm::FileTransporter] a file transporter
|
|
228
|
+
# @api private
|
|
229
|
+
def file_transporter
|
|
230
|
+
@file_transporter ||= WinRM::FS::Core::FileTransporter.new(session)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# (see Base#init_options)
|
|
234
|
+
def init_options(options)
|
|
235
|
+
super
|
|
236
|
+
@instance_name = @options.delete(:instance_name)
|
|
237
|
+
@kitchen_root = @options.delete(:kitchen_root)
|
|
238
|
+
@endpoint = @options.delete(:endpoint)
|
|
239
|
+
@rdp_port = @options.delete(:rdp_port)
|
|
240
|
+
@winrm_transport = @options.delete(:winrm_transport)
|
|
241
|
+
@connection_retries = @options.delete(:connection_retries)
|
|
242
|
+
@connection_retry_sleep = @options.delete(:connection_retry_sleep)
|
|
243
|
+
@max_wait_until_ready = @options.delete(:max_wait_until_ready)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Logs formatted standard error output at the warning level.
|
|
247
|
+
#
|
|
248
|
+
# @param stderr [String] standard error output
|
|
249
|
+
# @api private
|
|
250
|
+
def log_stderr_on_warn(stderr)
|
|
251
|
+
error_regexp = /<S S=\"Error\">/
|
|
252
|
+
|
|
253
|
+
if error_regexp.match(stderr)
|
|
254
|
+
stderr.
|
|
255
|
+
split(error_regexp)[1..-2].
|
|
256
|
+
map! { |line| line.sub(/_x000D__x000A_<\/S>/, "").rstrip }.
|
|
257
|
+
each { |line| logger.warn(line) }
|
|
258
|
+
else
|
|
259
|
+
stderr.
|
|
260
|
+
split("\r\n").
|
|
261
|
+
each { |line| logger.warn(line) }
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Builds a `LoginCommand` for use by Linux-based platforms.
|
|
266
|
+
#
|
|
267
|
+
# TODO: determine whether or not `desktop` exists
|
|
268
|
+
#
|
|
269
|
+
# @return [LoginCommand] a login command
|
|
270
|
+
# @api private
|
|
271
|
+
def login_command_for_linux
|
|
272
|
+
args = %W[-u #{options[:user]}]
|
|
273
|
+
args += %W[-p #{options[:pass]}] if options.key?(:pass)
|
|
274
|
+
args += %W[#{URI.parse(endpoint).host}:#{rdp_port}]
|
|
275
|
+
|
|
276
|
+
LoginCommand.new("rdesktop", args)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Builds a `LoginCommand` for use by Mac-based platforms.
|
|
280
|
+
#
|
|
281
|
+
# @return [LoginCommand] a login command
|
|
282
|
+
# @api private
|
|
283
|
+
def login_command_for_mac
|
|
284
|
+
create_rdp_doc(:mac => true)
|
|
285
|
+
|
|
286
|
+
LoginCommand.new("open", rdp_doc_path)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Builds a `LoginCommand` for use by Windows-based platforms.
|
|
290
|
+
#
|
|
291
|
+
# @return [LoginCommand] a login command
|
|
292
|
+
# @api private
|
|
293
|
+
def login_command_for_windows
|
|
294
|
+
create_rdp_doc
|
|
295
|
+
|
|
296
|
+
LoginCommand.new("mstsc", rdp_doc_path)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# @return [String] path to the local RDP document
|
|
300
|
+
# @api private
|
|
301
|
+
def rdp_doc_path
|
|
302
|
+
File.join(kitchen_root, ".kitchen", "#{instance_name}.rdp")
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Establishes a remote shell session, or establishes one when invoked
|
|
306
|
+
# the first time.
|
|
307
|
+
#
|
|
308
|
+
# @param retry_options [Hash] retry options for the initial connection
|
|
309
|
+
# @return [Winrm::CommandExecutor] the command executor session
|
|
310
|
+
# @api private
|
|
311
|
+
def session(retry_options = {})
|
|
312
|
+
@session ||= begin
|
|
313
|
+
opts = {
|
|
314
|
+
:retry_limit => connection_retries.to_i,
|
|
315
|
+
:retry_delay => connection_retry_sleep.to_i
|
|
316
|
+
}.merge(retry_options)
|
|
317
|
+
|
|
318
|
+
service_args = [endpoint, winrm_transport, options.merge(opts)]
|
|
319
|
+
@service = ::WinRM::WinRMWebService.new(*service_args)
|
|
320
|
+
@service.logger = logger
|
|
321
|
+
@service.create_executor
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# String representation of object, reporting its connection details and
|
|
326
|
+
# configuration.
|
|
327
|
+
#
|
|
328
|
+
# @api private
|
|
329
|
+
def to_s
|
|
330
|
+
"#{winrm_transport}::#{endpoint}<#{options.inspect}>"
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# takes a long (greater than 3000 characters) command and saves it to a
|
|
334
|
+
# file and uploads it to the test instance.
|
|
335
|
+
#
|
|
336
|
+
# @param command [String] a long command to be saved and uploaded
|
|
337
|
+
# @return [String] a command that executes the uploaded script
|
|
338
|
+
# @api private
|
|
339
|
+
def run_from_file_command(command)
|
|
340
|
+
temp_dir = Dir.mktmpdir("kitchen-long-script")
|
|
341
|
+
begin
|
|
342
|
+
script_name = "#{instance_name}-long_script.ps1"
|
|
343
|
+
script_path = File.join(temp_dir, script_name)
|
|
344
|
+
|
|
345
|
+
File.open(script_path, "wb") do |file|
|
|
346
|
+
file.write(command)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
target_path = File.join("$env:TEMP", script_name)
|
|
350
|
+
upload(script_path, target_path)
|
|
351
|
+
|
|
352
|
+
%{powershell -ExecutionPolicy Bypass -File "#{target_path}"}
|
|
353
|
+
ensure
|
|
354
|
+
FileUtils.rmtree(temp_dir)
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
private
|
|
360
|
+
|
|
361
|
+
WINRM_SPEC_VERSION = ["~> 1.6"].freeze
|
|
362
|
+
WINRM_FS_SPEC_VERSION = ["~> 0.4.0"].freeze
|
|
363
|
+
|
|
364
|
+
# Builds the hash of options needed by the Connection object on
|
|
365
|
+
# construction.
|
|
366
|
+
#
|
|
367
|
+
# @param data [Hash] merged configuration and mutable state data
|
|
368
|
+
# @return [Hash] hash of connection options
|
|
369
|
+
# @api private
|
|
370
|
+
def connection_options(data)
|
|
371
|
+
opts = {
|
|
372
|
+
:instance_name => instance.name,
|
|
373
|
+
:kitchen_root => data[:kitchen_root],
|
|
374
|
+
:logger => logger,
|
|
375
|
+
:endpoint => data[:endpoint_template] % data,
|
|
376
|
+
:user => data[:username],
|
|
377
|
+
:pass => data[:password],
|
|
378
|
+
:rdp_port => data[:rdp_port],
|
|
379
|
+
:connection_retries => data[:connection_retries],
|
|
380
|
+
:connection_retry_sleep => data[:connection_retry_sleep],
|
|
381
|
+
:max_wait_until_ready => data[:max_wait_until_ready],
|
|
382
|
+
:winrm_transport => data[:winrm_transport]
|
|
383
|
+
}
|
|
384
|
+
opts.merge!(additional_transport_args(opts[:winrm_transport]))
|
|
385
|
+
opts
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def additional_transport_args(transport_type)
|
|
389
|
+
case transport_type.to_sym
|
|
390
|
+
when :ssl, :negotiate
|
|
391
|
+
{
|
|
392
|
+
:no_ssl_peer_verification => true,
|
|
393
|
+
:disable_sspi => false,
|
|
394
|
+
:basic_auth_only => false
|
|
395
|
+
}
|
|
396
|
+
when :plaintext
|
|
397
|
+
{
|
|
398
|
+
:disable_sspi => true,
|
|
399
|
+
:basic_auth_only => true
|
|
400
|
+
}
|
|
401
|
+
else
|
|
402
|
+
{}
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Creates a new WinRM Connection instance and save it for potential
|
|
407
|
+
# future reuse.
|
|
408
|
+
#
|
|
409
|
+
# @param options [Hash] conneciton options
|
|
410
|
+
# @return [Ssh::Connection] a WinRM Connection instance
|
|
411
|
+
# @api private
|
|
412
|
+
def create_new_connection(options, &block)
|
|
413
|
+
if @connection
|
|
414
|
+
logger.debug("[WinRM] shutting previous connection #{@connection}")
|
|
415
|
+
@connection.close
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
@connection_options = options
|
|
419
|
+
@connection = Kitchen::Transport::Winrm::Connection.new(options, &block)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# (see Base#load_needed_dependencies!)
|
|
423
|
+
def load_needed_dependencies!
|
|
424
|
+
super
|
|
425
|
+
load_with_rescue!("winrm", WINRM_SPEC_VERSION.dup)
|
|
426
|
+
load_with_rescue!("winrm-fs", WINRM_FS_SPEC_VERSION.dup)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def load_with_rescue!(gem_name, spec_version)
|
|
430
|
+
logger.debug("#{gem_name} requested," \
|
|
431
|
+
" loading #{gem_name} gem (#{spec_version})")
|
|
432
|
+
attempt_load = false
|
|
433
|
+
gem gem_name, spec_version
|
|
434
|
+
silence_warnings { attempt_load = require gem_name }
|
|
435
|
+
if attempt_load
|
|
436
|
+
logger.debug("#{gem_name} is loaded.")
|
|
437
|
+
else
|
|
438
|
+
logger.debug("#{gem_name} was already loaded.")
|
|
439
|
+
end
|
|
440
|
+
rescue LoadError => e
|
|
441
|
+
message = fail_to_load_gem_message(gem_name,
|
|
442
|
+
spec_version)
|
|
443
|
+
logger.fatal(message)
|
|
444
|
+
raise UserError,
|
|
445
|
+
"Could not load or activate #{gem_name}. (#{e.message})"
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def fail_to_load_gem_message(name, version = nil)
|
|
449
|
+
version_cmd = "--version '#{version}'" if version
|
|
450
|
+
version_file = "', '#{version}"
|
|
451
|
+
|
|
452
|
+
"The `#{name}` gem is missing and must" \
|
|
453
|
+
" be installed or cannot be properly activated. Run" \
|
|
454
|
+
" `gem install #{name} #{version_cmd}`" \
|
|
455
|
+
" or add the following to your Gemfile if you are using Bundler:" \
|
|
456
|
+
" `gem '#{name} #{version_file}'`."
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def host_os_windows?
|
|
460
|
+
case RbConfig::CONFIG["host_os"]
|
|
461
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
|
462
|
+
true
|
|
463
|
+
else
|
|
464
|
+
false
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# Return the last saved WinRM connection instance.
|
|
469
|
+
#
|
|
470
|
+
# @return [Winrm::Connection] a WinRM Connection instance
|
|
471
|
+
# @api private
|
|
472
|
+
def reuse_connection
|
|
473
|
+
logger.debug("[WinRM] reusing existing connection #{@connection}")
|
|
474
|
+
yield @connection if block_given?
|
|
475
|
+
@connection
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def silence_warnings
|
|
479
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
|
480
|
+
yield
|
|
481
|
+
ensure
|
|
482
|
+
$VERBOSE = old_verbose
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
end
|