test-kitchen-rsync 3.0.0.pre.1
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 +7 -0
- data/Gemfile +21 -0
- data/LICENSE +15 -0
- data/Rakefile +53 -0
- data/bin/zl-kitchen +11 -0
- data/lib/kitchen/base64_stream.rb +48 -0
- data/lib/kitchen/chef_utils_wiring.rb +40 -0
- data/lib/kitchen/cli.rb +413 -0
- data/lib/kitchen/collection.rb +52 -0
- data/lib/kitchen/color.rb +63 -0
- data/lib/kitchen/command/action.rb +41 -0
- data/lib/kitchen/command/console.rb +54 -0
- data/lib/kitchen/command/diagnose.rb +84 -0
- data/lib/kitchen/command/doctor.rb +39 -0
- data/lib/kitchen/command/exec.rb +37 -0
- data/lib/kitchen/command/list.rb +148 -0
- data/lib/kitchen/command/login.rb +39 -0
- data/lib/kitchen/command/package.rb +32 -0
- data/lib/kitchen/command/sink.rb +50 -0
- data/lib/kitchen/command/test.rb +47 -0
- data/lib/kitchen/command.rb +207 -0
- data/lib/kitchen/config.rb +344 -0
- data/lib/kitchen/configurable.rb +616 -0
- data/lib/kitchen/data_munger.rb +1024 -0
- data/lib/kitchen/diagnostic.rb +138 -0
- data/lib/kitchen/driver/base.rb +133 -0
- data/lib/kitchen/driver/dummy.rb +105 -0
- data/lib/kitchen/driver/exec.rb +70 -0
- data/lib/kitchen/driver/proxy.rb +70 -0
- data/lib/kitchen/driver/ssh_base.rb +351 -0
- data/lib/kitchen/driver.rb +40 -0
- data/lib/kitchen/errors.rb +243 -0
- data/lib/kitchen/generator/init.rb +254 -0
- data/lib/kitchen/instance.rb +726 -0
- data/lib/kitchen/lazy_hash.rb +148 -0
- data/lib/kitchen/lifecycle_hook/base.rb +78 -0
- data/lib/kitchen/lifecycle_hook/local.rb +53 -0
- data/lib/kitchen/lifecycle_hook/remote.rb +39 -0
- data/lib/kitchen/lifecycle_hooks.rb +92 -0
- data/lib/kitchen/loader/yaml.rb +377 -0
- data/lib/kitchen/logger.rb +422 -0
- data/lib/kitchen/logging.rb +52 -0
- data/lib/kitchen/login_command.rb +49 -0
- data/lib/kitchen/metadata_chopper.rb +49 -0
- data/lib/kitchen/platform.rb +64 -0
- data/lib/kitchen/plugin.rb +76 -0
- data/lib/kitchen/plugin_base.rb +60 -0
- data/lib/kitchen/provisioner/base.rb +269 -0
- data/lib/kitchen/provisioner/chef/berkshelf.rb +116 -0
- data/lib/kitchen/provisioner/chef/common_sandbox.rb +350 -0
- data/lib/kitchen/provisioner/chef/policyfile.rb +163 -0
- data/lib/kitchen/provisioner/chef_apply.rb +121 -0
- data/lib/kitchen/provisioner/chef_base.rb +705 -0
- data/lib/kitchen/provisioner/chef_infra.rb +167 -0
- data/lib/kitchen/provisioner/chef_solo.rb +82 -0
- data/lib/kitchen/provisioner/chef_zero.rb +12 -0
- data/lib/kitchen/provisioner/dummy.rb +75 -0
- data/lib/kitchen/provisioner/shell.rb +157 -0
- data/lib/kitchen/provisioner.rb +42 -0
- data/lib/kitchen/rake_tasks.rb +80 -0
- data/lib/kitchen/shell_out.rb +90 -0
- data/lib/kitchen/ssh.rb +289 -0
- data/lib/kitchen/state_file.rb +112 -0
- data/lib/kitchen/suite.rb +48 -0
- data/lib/kitchen/thor_tasks.rb +63 -0
- data/lib/kitchen/transport/base.rb +236 -0
- data/lib/kitchen/transport/dummy.rb +78 -0
- data/lib/kitchen/transport/exec.rb +145 -0
- data/lib/kitchen/transport/ssh.rb +579 -0
- data/lib/kitchen/transport/winrm.rb +546 -0
- data/lib/kitchen/transport.rb +40 -0
- data/lib/kitchen/util.rb +229 -0
- data/lib/kitchen/verifier/base.rb +243 -0
- data/lib/kitchen/verifier/busser.rb +275 -0
- data/lib/kitchen/verifier/dummy.rb +75 -0
- data/lib/kitchen/verifier/shell.rb +99 -0
- data/lib/kitchen/verifier.rb +39 -0
- data/lib/kitchen/version.rb +20 -0
- data/lib/kitchen/which.rb +26 -0
- data/lib/kitchen.rb +152 -0
- data/lib/vendor/hash_recursive_merge.rb +79 -0
- data/support/busser_install_command.ps1 +14 -0
- data/support/busser_install_command.sh +21 -0
- data/support/chef-client-fail-if-update-handler.rb +15 -0
- data/support/chef_base_init_command.ps1 +18 -0
- data/support/chef_base_init_command.sh +1 -0
- data/support/chef_base_install_command.ps1 +85 -0
- data/support/chef_base_install_command.sh +229 -0
- data/support/download_helpers.sh +109 -0
- data/support/dummy-validation.pem +27 -0
- data/templates/driver/CHANGELOG.md.erb +3 -0
- data/templates/driver/Gemfile.erb +3 -0
- data/templates/driver/README.md.erb +64 -0
- data/templates/driver/Rakefile.erb +21 -0
- data/templates/driver/driver.rb.erb +23 -0
- data/templates/driver/gemspec.erb +29 -0
- data/templates/driver/gitignore.erb +17 -0
- data/templates/driver/license_apachev2.erb +15 -0
- data/templates/driver/license_lgplv3.erb +16 -0
- data/templates/driver/license_mit.erb +22 -0
- data/templates/driver/license_reserved.erb +5 -0
- data/templates/driver/tailor.erb +4 -0
- data/templates/driver/travis.yml.erb +11 -0
- data/templates/driver/version.rb.erb +12 -0
- data/templates/init/chefignore.erb +2 -0
- data/templates/init/kitchen.yml.erb +18 -0
- data/test-kitchen.gemspec +52 -0
- metadata +528 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Salim Afiune (<salim@afiunemaya.com.mx>)
|
|
3
|
+
# Author:: Matt Wrock (<matt@mattwrock.com>)
|
|
4
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
5
|
+
#
|
|
6
|
+
# Copyright (C) 2014, Salim Afiune
|
|
7
|
+
#
|
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
# you may not use this file except in compliance with the License.
|
|
10
|
+
# You may obtain a copy of the License at
|
|
11
|
+
#
|
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
#
|
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
# See the License for the specific language governing permissions and
|
|
18
|
+
# limitations under the License.
|
|
19
|
+
|
|
20
|
+
require "rbconfig" unless defined?(RbConfig)
|
|
21
|
+
require "uri" unless defined?(URI)
|
|
22
|
+
require_relative "../../kitchen"
|
|
23
|
+
require "winrm" unless defined?(WinRM::Connection)
|
|
24
|
+
|
|
25
|
+
module Kitchen
|
|
26
|
+
module Transport
|
|
27
|
+
# Wrapped exception for any internally raised WinRM-related errors.
|
|
28
|
+
#
|
|
29
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
30
|
+
class WinrmFailed < TransportFailed; end
|
|
31
|
+
|
|
32
|
+
# A Transport which uses WinRM to execute commands and transfer files.
|
|
33
|
+
#
|
|
34
|
+
# @author Matt Wrock <matt@mattwrock.com>
|
|
35
|
+
# @author Salim Afiune <salim@afiunemaya.com.mx>
|
|
36
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
37
|
+
class Winrm < Kitchen::Transport::Base
|
|
38
|
+
kitchen_transport_api_version 1
|
|
39
|
+
|
|
40
|
+
plugin_version Kitchen::VERSION
|
|
41
|
+
|
|
42
|
+
default_config :username, "administrator"
|
|
43
|
+
default_config :password, nil
|
|
44
|
+
default_config :elevated, false
|
|
45
|
+
default_config :rdp_port, 3389
|
|
46
|
+
default_config :connection_retries, 5
|
|
47
|
+
default_config :connection_retry_sleep, 1
|
|
48
|
+
default_config :operation_timeout, 60
|
|
49
|
+
default_config :receive_timeout, 70
|
|
50
|
+
default_config :max_wait_until_ready, 600
|
|
51
|
+
default_config :winrm_transport, :negotiate
|
|
52
|
+
default_config :scheme do |transport|
|
|
53
|
+
transport[:winrm_transport] == :ssl ? "https" : "http"
|
|
54
|
+
end
|
|
55
|
+
default_config :port do |transport|
|
|
56
|
+
transport[:winrm_transport] == :ssl ? 5986 : 5985
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def finalize_config!(instance)
|
|
60
|
+
super
|
|
61
|
+
|
|
62
|
+
config[:winrm_transport] = config[:winrm_transport].to_sym
|
|
63
|
+
|
|
64
|
+
self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# (see Base#connection)
|
|
68
|
+
def connection(state, &block)
|
|
69
|
+
options = connection_options(config.to_hash.merge(state))
|
|
70
|
+
|
|
71
|
+
if @connection && @connection_options == options
|
|
72
|
+
reuse_connection(&block)
|
|
73
|
+
else
|
|
74
|
+
create_new_connection(options, &block)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# A Connection instance can be generated and re-generated, given new
|
|
79
|
+
# connection details such as connection port, hostname, credentials, etc.
|
|
80
|
+
# This object is responsible for carrying out the actions on the remote
|
|
81
|
+
# host such as executing commands, transferring files, etc.
|
|
82
|
+
#
|
|
83
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
84
|
+
class Connection < Kitchen::Transport::Base::Connection
|
|
85
|
+
# (see Base::Connection#initialize)
|
|
86
|
+
def initialize(config = {})
|
|
87
|
+
super(config)
|
|
88
|
+
@unelevated_session = nil
|
|
89
|
+
@elevated_session = nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# (see Base::Connection#close)
|
|
93
|
+
def close
|
|
94
|
+
@unelevated_session.close if @unelevated_session
|
|
95
|
+
@elevated_session.close if @elevated_session
|
|
96
|
+
ensure
|
|
97
|
+
@unelevated_session = nil
|
|
98
|
+
@elevated_session = nil
|
|
99
|
+
@file_transporter = nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# (see Base::Connection#execute)
|
|
103
|
+
def execute(command)
|
|
104
|
+
return if command.nil?
|
|
105
|
+
|
|
106
|
+
logger.debug("[WinRM] #{self} (#{command})")
|
|
107
|
+
|
|
108
|
+
exit_code, stderr = execute_with_exit_code(command)
|
|
109
|
+
|
|
110
|
+
if logger.debug? && exit_code == 0
|
|
111
|
+
log_stderr_on_warn(stderr)
|
|
112
|
+
elsif exit_code != 0
|
|
113
|
+
log_stderr_on_warn(stderr)
|
|
114
|
+
raise Transport::WinrmFailed.new(
|
|
115
|
+
"WinRM exited (#{exit_code}) for command: [#{command}]",
|
|
116
|
+
exit_code
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# (see Base::Connection#login_command)
|
|
122
|
+
def login_command
|
|
123
|
+
case RbConfig::CONFIG["host_os"]
|
|
124
|
+
when /darwin/
|
|
125
|
+
login_command_for_mac
|
|
126
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
|
127
|
+
login_command_for_windows
|
|
128
|
+
when /linux/
|
|
129
|
+
login_command_for_linux
|
|
130
|
+
else
|
|
131
|
+
raise ActionFailed, "Remote login not supported in #{self.class} " \
|
|
132
|
+
"from host OS '#{RbConfig::CONFIG["host_os"]}'."
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# (see Base::Connection#upload)
|
|
137
|
+
def upload(locals, remote)
|
|
138
|
+
file_transporter.upload(locals, remote)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# (see Base::Connection#download)
|
|
142
|
+
def download(remotes, local)
|
|
143
|
+
# ensure the parent dir of the local target exists
|
|
144
|
+
FileUtils.mkdir_p(File.dirname(local))
|
|
145
|
+
|
|
146
|
+
Array(remotes).each do |remote|
|
|
147
|
+
file_manager.download(remote, local)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @return [Winrm::FileManager] a file transporter
|
|
152
|
+
# @api private
|
|
153
|
+
def file_manager
|
|
154
|
+
@file_manager ||= WinRM::FS::FileManager.new(connection)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# (see Base::Connection#wait_until_ready)
|
|
158
|
+
def wait_until_ready
|
|
159
|
+
delay = 3
|
|
160
|
+
unelevated_session(
|
|
161
|
+
retry_limit: max_wait_until_ready / delay,
|
|
162
|
+
retry_delay: delay
|
|
163
|
+
)
|
|
164
|
+
execute(PING_COMMAND.dup)
|
|
165
|
+
rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH => e
|
|
166
|
+
retries ||= connection_retries.to_i
|
|
167
|
+
raise e if (retries -= 1) < 0
|
|
168
|
+
|
|
169
|
+
logger.debug("[WinRM] PING_COMMAND failed. Retrying...")
|
|
170
|
+
logger.debug("#{e.class}::#{e.message}")
|
|
171
|
+
sleep(connection_retry_sleep.to_i)
|
|
172
|
+
retry
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
PING_COMMAND = "Write-Host '[WinRM] Established\n'".freeze
|
|
178
|
+
|
|
179
|
+
RESCUE_EXCEPTIONS_ON_ESTABLISH = [
|
|
180
|
+
Errno::EACCES, Errno::EALREADY, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
|
|
181
|
+
Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH, Errno::EPIPE,
|
|
182
|
+
OpenSSL::SSL::SSLError, WinRM::WinRMHTTPTransportError
|
|
183
|
+
].freeze
|
|
184
|
+
|
|
185
|
+
# @return [Integer] how many times to retry when failing to execute
|
|
186
|
+
# a command or transfer files
|
|
187
|
+
# @api private
|
|
188
|
+
attr_reader :connection_retries
|
|
189
|
+
|
|
190
|
+
# @return [Float] how many seconds to wait before attempting a retry
|
|
191
|
+
# when failing to execute a command or transfer files
|
|
192
|
+
# @api private
|
|
193
|
+
attr_reader :connection_retry_sleep
|
|
194
|
+
|
|
195
|
+
# @return [String] display name for the associated instance
|
|
196
|
+
# @api private
|
|
197
|
+
attr_reader :instance_name
|
|
198
|
+
|
|
199
|
+
# @return [String] local path to the root of the project
|
|
200
|
+
# @api private
|
|
201
|
+
attr_reader :kitchen_root
|
|
202
|
+
|
|
203
|
+
# @return [Integer] how many times to retry when invoking
|
|
204
|
+
# `#wait_until_ready` before failing
|
|
205
|
+
# @api private
|
|
206
|
+
attr_reader :max_wait_until_ready
|
|
207
|
+
|
|
208
|
+
# @return [Integer] the TCP port number to use when connection to the
|
|
209
|
+
# remote WinRM host
|
|
210
|
+
# @api private
|
|
211
|
+
attr_reader :rdp_port
|
|
212
|
+
|
|
213
|
+
# @return [Boolean] whether to use winrm-elevated for running commands
|
|
214
|
+
# @api private
|
|
215
|
+
attr_reader :elevated
|
|
216
|
+
|
|
217
|
+
# Writes an RDP document to the local file system.
|
|
218
|
+
#
|
|
219
|
+
# @param opts [Hash] file options
|
|
220
|
+
# @option opts [true,false] :mac whether or not the document is for a
|
|
221
|
+
# Mac system
|
|
222
|
+
# @api private
|
|
223
|
+
def create_rdp_doc(opts = {})
|
|
224
|
+
content = Util.outdent!(<<-RDP)
|
|
225
|
+
full address:s:#{URI.parse(options[:endpoint]).host}:#{rdp_port}
|
|
226
|
+
prompt for credentials:i:1
|
|
227
|
+
username:s:#{options[:user]}
|
|
228
|
+
RDP
|
|
229
|
+
content.prepend("drivestoredirect:s:*\n") if opts[:mac]
|
|
230
|
+
|
|
231
|
+
File.open(rdp_doc_path, "wb") { |f| f.write(content) }
|
|
232
|
+
|
|
233
|
+
if logger.debug?
|
|
234
|
+
debug("Creating RDP document for #{instance_name} (#{rdp_doc_path})")
|
|
235
|
+
debug("------------")
|
|
236
|
+
IO.read(rdp_doc_path).each_line { |l| debug(l.chomp.to_s) }
|
|
237
|
+
debug("------------")
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Execute a Powershell script over WinRM and return the command's
|
|
242
|
+
# exit code and standard error.
|
|
243
|
+
#
|
|
244
|
+
# @param command [String] Powershell script to execute
|
|
245
|
+
# @return [[Integer,String]] an array containing the exit code of the
|
|
246
|
+
# script and the standard error stream
|
|
247
|
+
# @api private
|
|
248
|
+
def execute_with_exit_code(command)
|
|
249
|
+
if elevated
|
|
250
|
+
session = elevated_session
|
|
251
|
+
command = "$env:temp='#{unelevated_temp_dir}';#{command}"
|
|
252
|
+
else
|
|
253
|
+
session = unelevated_session
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
begin
|
|
257
|
+
response = session.run(command) do |stdout, _|
|
|
258
|
+
logger << stdout if stdout
|
|
259
|
+
end
|
|
260
|
+
[response.exitcode, response.stderr]
|
|
261
|
+
ensure
|
|
262
|
+
close
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def unelevated_temp_dir
|
|
267
|
+
@unelevated_temp_dir ||= unelevated_session.run("$env:temp").stdout.chomp
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# @return [Winrm::FileTransporter] a file transporter
|
|
271
|
+
# @api private
|
|
272
|
+
def file_transporter
|
|
273
|
+
@file_transporter ||= WinRM::FS::Core::FileTransporter.new(unelevated_session)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# (see Base#init_options)
|
|
277
|
+
def init_options(options)
|
|
278
|
+
super
|
|
279
|
+
@instance_name = @options.delete(:instance_name)
|
|
280
|
+
@kitchen_root = @options.delete(:kitchen_root)
|
|
281
|
+
@rdp_port = @options.delete(:rdp_port)
|
|
282
|
+
@connection_retries = @options.delete(:connection_retries)
|
|
283
|
+
@connection_retry_sleep = @options.delete(:connection_retry_sleep)
|
|
284
|
+
@operation_timeout = @options.delete(:operation_timeout)
|
|
285
|
+
@receive_timeout = @options.delete(:receive_timeout)
|
|
286
|
+
@max_wait_until_ready = @options.delete(:max_wait_until_ready)
|
|
287
|
+
@elevated = @options.delete(:elevated)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Logs formatted standard error output at the warning level.
|
|
291
|
+
#
|
|
292
|
+
# @param stderr [String] standard error output
|
|
293
|
+
# @api private
|
|
294
|
+
def log_stderr_on_warn(stderr)
|
|
295
|
+
error_regexp = /<S S=\"Error\">/
|
|
296
|
+
|
|
297
|
+
if error_regexp.match(stderr)
|
|
298
|
+
stderr
|
|
299
|
+
.split(error_regexp)[1..-2]
|
|
300
|
+
.map! { |line| line.sub(%r{_x000D__x000A_</S>}, "").rstrip }
|
|
301
|
+
.each { |line| logger.warn(line) }
|
|
302
|
+
else
|
|
303
|
+
stderr
|
|
304
|
+
.split("\r\n")
|
|
305
|
+
.each { |line| logger.warn(line) }
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Builds a `LoginCommand` for use by Linux-based platforms.
|
|
310
|
+
#
|
|
311
|
+
# @return [LoginCommand] a login command
|
|
312
|
+
# @api private
|
|
313
|
+
def login_command_for_linux
|
|
314
|
+
xfreerdp = Util.command_exists? "xfreerdp"
|
|
315
|
+
unless xfreerdp
|
|
316
|
+
raise WinrmFailed, "xfreerdp binary not found. Please install freerdp2-x11 on Debian-based systems or freerdp on Redhat-based systems."
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
args = %W{/u:#{options[:user]}}
|
|
320
|
+
args += %W{/p:#{options[:password]}} if options.key?(:password)
|
|
321
|
+
args += %W{/v:#{URI.parse(options[:endpoint]).host}:#{rdp_port}}
|
|
322
|
+
args += %W{/cert-tofu} # always accept certificate
|
|
323
|
+
|
|
324
|
+
LoginCommand.new(xfreerdp, args)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Builds a `LoginCommand` for use by Mac-based platforms.
|
|
328
|
+
#
|
|
329
|
+
# @return [LoginCommand] a login command
|
|
330
|
+
# @api private
|
|
331
|
+
def login_command_for_mac
|
|
332
|
+
create_rdp_doc(mac: true)
|
|
333
|
+
|
|
334
|
+
LoginCommand.new("open", rdp_doc_path)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Builds a `LoginCommand` for use by Windows-based platforms.
|
|
338
|
+
#
|
|
339
|
+
# @return [LoginCommand] a login command
|
|
340
|
+
# @api private
|
|
341
|
+
def login_command_for_windows
|
|
342
|
+
create_rdp_doc
|
|
343
|
+
|
|
344
|
+
LoginCommand.new("mstsc", rdp_doc_path)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# @return [String] path to the local RDP document
|
|
348
|
+
# @api private
|
|
349
|
+
def rdp_doc_path
|
|
350
|
+
File.join(kitchen_root, ".kitchen", "#{instance_name}.rdp")
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Establishes a remote shell session, or establishes one when invoked
|
|
354
|
+
# the first time.
|
|
355
|
+
#
|
|
356
|
+
# @param retry_options [Hash] retry options for the initial connection
|
|
357
|
+
# @return [Winrm::Shells::Powershell] the command shell session
|
|
358
|
+
# @api private
|
|
359
|
+
def unelevated_session(retry_options = {})
|
|
360
|
+
@unelevated_session ||= connection(retry_options).shell(:powershell)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Creates an elevated session for running commands via a scheduled task
|
|
364
|
+
#
|
|
365
|
+
# @return [Winrm::Shells::Elevated] the elevated shell
|
|
366
|
+
# @api private
|
|
367
|
+
def elevated_session(retry_options = {})
|
|
368
|
+
@elevated_session ||= connection(retry_options).shell(:elevated).tap do |shell|
|
|
369
|
+
shell.username = options[:elevated_username]
|
|
370
|
+
shell.password = options[:elevated_password]
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Creates a winrm Connection instance
|
|
375
|
+
#
|
|
376
|
+
# @param retry_options [Hash] retry options for the initial connection
|
|
377
|
+
# @return [Winrm::Connection] the winrm connection
|
|
378
|
+
# @api private
|
|
379
|
+
def connection(retry_options = {})
|
|
380
|
+
@connection ||= begin
|
|
381
|
+
opts = {
|
|
382
|
+
retry_limit: connection_retries.to_i,
|
|
383
|
+
retry_delay: connection_retry_sleep.to_i,
|
|
384
|
+
}.merge(retry_options)
|
|
385
|
+
|
|
386
|
+
::WinRM::Connection.new(options.merge(opts)).tap do |conn|
|
|
387
|
+
conn.logger = logger
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# String representation of object, reporting its connection details and
|
|
393
|
+
# configuration.
|
|
394
|
+
#
|
|
395
|
+
# @api private
|
|
396
|
+
def to_s
|
|
397
|
+
"<#{options.inspect}>"
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
private
|
|
402
|
+
|
|
403
|
+
WINRM_SPEC_VERSION = ["~> 2.0"].freeze
|
|
404
|
+
WINRM_FS_SPEC_VERSION = ["~> 1.0"].freeze
|
|
405
|
+
WINRM_ELEVATED_SPEC_VERSION = ["~> 1.0"].freeze
|
|
406
|
+
|
|
407
|
+
# Builds the hash of options needed by the Connection object on
|
|
408
|
+
# construction.
|
|
409
|
+
#
|
|
410
|
+
# @param data [Hash] merged configuration and mutable state data
|
|
411
|
+
# @return [Hash] hash of connection options
|
|
412
|
+
# @api private
|
|
413
|
+
def connection_options(data)
|
|
414
|
+
endpoint = URI::Generic.build(
|
|
415
|
+
scheme: data.fetch(:scheme),
|
|
416
|
+
host: data.fetch(:hostname),
|
|
417
|
+
port: data.fetch(:port),
|
|
418
|
+
path: "/wsman"
|
|
419
|
+
).to_s
|
|
420
|
+
|
|
421
|
+
elevated_password = data[:password]
|
|
422
|
+
elevated_password = data[:elevated_password] if data.key?(:elevated_password)
|
|
423
|
+
|
|
424
|
+
opts = {
|
|
425
|
+
instance_name: instance.name,
|
|
426
|
+
kitchen_root: data[:kitchen_root],
|
|
427
|
+
logger: logger,
|
|
428
|
+
endpoint: endpoint,
|
|
429
|
+
user: data[:username],
|
|
430
|
+
password: data[:password],
|
|
431
|
+
rdp_port: data[:rdp_port],
|
|
432
|
+
connection_retries: data[:connection_retries],
|
|
433
|
+
connection_retry_sleep: data[:connection_retry_sleep],
|
|
434
|
+
operation_timeout: data[:operation_timeout],
|
|
435
|
+
receive_timeout: data[:receive_timeout],
|
|
436
|
+
max_wait_until_ready: data[:max_wait_until_ready],
|
|
437
|
+
transport: data[:winrm_transport],
|
|
438
|
+
elevated: data[:elevated],
|
|
439
|
+
elevated_username: data[:elevated_username] || data[:username],
|
|
440
|
+
elevated_password: elevated_password,
|
|
441
|
+
}
|
|
442
|
+
opts.merge!(additional_transport_args(opts[:transport]))
|
|
443
|
+
opts
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def additional_transport_args(transport_type)
|
|
447
|
+
case transport_type.to_sym
|
|
448
|
+
when :ssl, :negotiate
|
|
449
|
+
{
|
|
450
|
+
no_ssl_peer_verification: true,
|
|
451
|
+
disable_sspi: false,
|
|
452
|
+
basic_auth_only: false,
|
|
453
|
+
}
|
|
454
|
+
when :plaintext
|
|
455
|
+
{
|
|
456
|
+
disable_sspi: true,
|
|
457
|
+
basic_auth_only: true,
|
|
458
|
+
}
|
|
459
|
+
else
|
|
460
|
+
{}
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Creates a new WinRM Connection instance and save it for potential
|
|
465
|
+
# future reuse.
|
|
466
|
+
#
|
|
467
|
+
# @param options [Hash] conneciton options
|
|
468
|
+
# @return [Ssh::Connection] a WinRM Connection instance
|
|
469
|
+
# @api private
|
|
470
|
+
def create_new_connection(options, &block)
|
|
471
|
+
if @connection
|
|
472
|
+
logger.debug("[WinRM] shutting previous connection #{@connection}")
|
|
473
|
+
@connection.close
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
@connection_options = options
|
|
477
|
+
@connection = Kitchen::Transport::Winrm::Connection.new(options, &block)
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# (see Base#load_needed_dependencies!)
|
|
481
|
+
def load_needed_dependencies!
|
|
482
|
+
super
|
|
483
|
+
load_with_rescue!("winrm", WINRM_SPEC_VERSION.dup)
|
|
484
|
+
load_with_rescue!("winrm-fs", WINRM_FS_SPEC_VERSION.dup)
|
|
485
|
+
load_with_rescue!("winrm-elevated", WINRM_ELEVATED_SPEC_VERSION.dup) if config[:elevated]
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def load_with_rescue!(gem_name, spec_version)
|
|
489
|
+
logger.debug("#{gem_name} requested," \
|
|
490
|
+
" loading #{gem_name} gem (#{spec_version})")
|
|
491
|
+
attempt_load = false
|
|
492
|
+
gem gem_name, spec_version
|
|
493
|
+
silence_warnings { attempt_load = require gem_name }
|
|
494
|
+
if attempt_load
|
|
495
|
+
logger.debug("#{gem_name} is loaded.")
|
|
496
|
+
else
|
|
497
|
+
logger.debug("#{gem_name} was already loaded.")
|
|
498
|
+
end
|
|
499
|
+
rescue LoadError => e
|
|
500
|
+
message = fail_to_load_gem_message(gem_name,
|
|
501
|
+
spec_version)
|
|
502
|
+
logger.fatal(message)
|
|
503
|
+
raise UserError,
|
|
504
|
+
"Could not load or activate #{gem_name}. (#{e.message})"
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def fail_to_load_gem_message(name, version = nil)
|
|
508
|
+
version_cmd = "--version '#{version}'" if version
|
|
509
|
+
version_file = "', '#{version}"
|
|
510
|
+
|
|
511
|
+
"The `#{name}` gem is missing and must" \
|
|
512
|
+
" be installed or cannot be properly activated. Run" \
|
|
513
|
+
" `gem install #{name} #{version_cmd}`" \
|
|
514
|
+
" or add the following to your Gemfile if you are using Bundler:" \
|
|
515
|
+
" `gem '#{name} #{version_file}'`."
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def host_os_windows?
|
|
519
|
+
case RbConfig::CONFIG["host_os"]
|
|
520
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
|
521
|
+
true
|
|
522
|
+
else
|
|
523
|
+
false
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# Return the last saved WinRM connection instance.
|
|
528
|
+
#
|
|
529
|
+
# @return [Winrm::Connection] a WinRM Connection instance
|
|
530
|
+
# @api private
|
|
531
|
+
def reuse_connection
|
|
532
|
+
logger.debug("[WinRM] reusing existing connection #{@connection}")
|
|
533
|
+
yield @connection if block_given?
|
|
534
|
+
@connection
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def silence_warnings
|
|
538
|
+
old_verbose = $VERBOSE
|
|
539
|
+
$VERBOSE = nil
|
|
540
|
+
yield
|
|
541
|
+
ensure
|
|
542
|
+
$VERBOSE = old_verbose
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Salim Afiune (<salim@afiunemaya.com.mx>)
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2014, Salim Afiune
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
|
|
18
|
+
require_relative "plugin"
|
|
19
|
+
|
|
20
|
+
module Kitchen
|
|
21
|
+
# A transport is responsible for the communication with an instance,
|
|
22
|
+
# that is remote comands and other actions such as file transfer,
|
|
23
|
+
# login, etc.
|
|
24
|
+
#
|
|
25
|
+
# @author Salim Afiune <salim@afiunemaya.com.mx>
|
|
26
|
+
module Transport
|
|
27
|
+
# Default transport to use
|
|
28
|
+
DEFAULT_PLUGIN = "ssh".freeze
|
|
29
|
+
|
|
30
|
+
# Returns an instance of a transport given a plugin type string.
|
|
31
|
+
#
|
|
32
|
+
# @param plugin [String] a transport plugin type, to be constantized
|
|
33
|
+
# @param config [Hash] a configuration hash to initialize the transport
|
|
34
|
+
# @return [Transport::Base] a transport instance
|
|
35
|
+
# @raise [ClientError] if a transport instance could not be created
|
|
36
|
+
def self.for_plugin(plugin, config)
|
|
37
|
+
Kitchen::Plugin.load(self, plugin, config)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|