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,1143 +1,1143 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Author:: Matt Wrock (<matt@mattwrock.com>)
|
|
4
|
-
#
|
|
5
|
-
# Copyright (C) 2014, Matt Wrock
|
|
6
|
-
#
|
|
7
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
# you may not use this file except in compliance with the License.
|
|
9
|
-
# You may obtain a copy of the License at
|
|
10
|
-
#
|
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
#
|
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
# See the License for the specific language governing permissions and
|
|
17
|
-
# limitations under the License.
|
|
18
|
-
|
|
19
|
-
require_relative "../../spec_helper"
|
|
20
|
-
|
|
21
|
-
require "kitchen/transport/winrm"
|
|
22
|
-
require "winrm"
|
|
23
|
-
require "winrm-fs"
|
|
24
|
-
|
|
25
|
-
module Kitchen
|
|
26
|
-
|
|
27
|
-
module Transport
|
|
28
|
-
|
|
29
|
-
class WinRMConnectionDummy < Kitchen::Transport::Winrm::Connection
|
|
30
|
-
|
|
31
|
-
attr_reader :saved_command, :remote_path, :local_path
|
|
32
|
-
|
|
33
|
-
def upload(locals, remote)
|
|
34
|
-
@saved_command = IO.read(locals)
|
|
35
|
-
@local_path = locals
|
|
36
|
-
@remote_path = remote
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
describe Kitchen::Transport::Winrm do
|
|
43
|
-
|
|
44
|
-
before do
|
|
45
|
-
RbConfig::CONFIG.stubs(:[]).with("host_os").returns("blah")
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
let(:logged_output) { StringIO.new }
|
|
49
|
-
let(:logger) { Logger.new(logged_output) }
|
|
50
|
-
let(:config) { Hash.new }
|
|
51
|
-
let(:state) { Hash.new }
|
|
52
|
-
|
|
53
|
-
let(:instance) do
|
|
54
|
-
stub(:name => "coolbeans", :logger => logger, :to_str => "instance")
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
let(:transport) do
|
|
58
|
-
t = Kitchen::Transport::Winrm.new(config)
|
|
59
|
-
# :load_winrm_s! is not cross-platform safe
|
|
60
|
-
# and gets initialized too early in the pipeline
|
|
61
|
-
t.stubs(:load_winrm_s!)
|
|
62
|
-
t.finalize_config!(instance)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
it "provisioner api_version is 1" do
|
|
66
|
-
transport.diagnose_plugin[:api_version].must_equal 1
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
it "plugin_version is set to Kitchen::VERSION" do
|
|
70
|
-
transport.diagnose_plugin[:version].must_equal Kitchen::VERSION
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
describe "default_config" do
|
|
74
|
-
|
|
75
|
-
it "sets :port to 5985 by default" do
|
|
76
|
-
transport[:port].must_equal 5985
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
it "sets :username to administrator by default" do
|
|
80
|
-
transport[:username].must_equal "administrator"
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
it "sets :password to nil by default" do
|
|
84
|
-
transport[:password].must_equal nil
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
it "sets a default :endpoint_template value" do
|
|
88
|
-
transport[:endpoint_template].
|
|
89
|
-
must_equal "http://%{hostname}:%{port}/wsman"
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
it "sets :rdp_port to 3389 by default" do
|
|
93
|
-
transport[:rdp_port].must_equal 3389
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
it "sets :connection_retries to 5 by default" do
|
|
97
|
-
transport[:connection_retries].must_equal 5
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
it "sets :connection_retry_sleep to 1 by default" do
|
|
101
|
-
transport[:connection_retry_sleep].must_equal 1
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
it "sets :max_wait_until_ready to 600 by default" do
|
|
105
|
-
transport[:max_wait_until_ready].must_equal 600
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
it "sets :winrm_transport to :negotiate" do
|
|
109
|
-
transport[:winrm_transport].must_equal :negotiate
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
describe "#connection" do
|
|
114
|
-
|
|
115
|
-
let(:klass) { Kitchen::Transport::Winrm::Connection }
|
|
116
|
-
|
|
117
|
-
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
118
|
-
def self.common_connection_specs
|
|
119
|
-
before do
|
|
120
|
-
config[:hostname] = "here"
|
|
121
|
-
config[:kitchen_root] = "/i/am/root"
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
it "returns a Kitchen::Transport::Winrm::Connection object" do
|
|
125
|
-
transport.connection(state).must_be_kind_of klass
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
it "sets :instance_name to the instance's name" do
|
|
129
|
-
klass.expects(:new).with do |hash|
|
|
130
|
-
hash[:instance_name] == "coolbeans"
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
make_connection
|
|
134
|
-
end
|
|
135
|
-
it "sets :kitchen_root to the transport's kitchen_root" do
|
|
136
|
-
klass.expects(:new).with do |hash|
|
|
137
|
-
hash[:kitchen_root] == "/i/am/root"
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
make_connection
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
it "sets the :logger to the transport's logger" do
|
|
144
|
-
klass.expects(:new).with do |hash|
|
|
145
|
-
hash[:logger] == logger
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
make_connection
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
it "sets the :winrm_transport to :negotiate" do
|
|
152
|
-
klass.expects(:new).with do |hash|
|
|
153
|
-
hash[:winrm_transport] == :negotiate
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
make_connection
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
it "sets the :disable_sspi to false" do
|
|
160
|
-
klass.expects(:new).with do |hash|
|
|
161
|
-
hash[:disable_sspi] == false
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
make_connection
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
it "sets :endpoint from data in config" do
|
|
168
|
-
config[:hostname] = "host_from_config"
|
|
169
|
-
config[:port] = "port_from_config"
|
|
170
|
-
config[:winrm_transport] = "ssl"
|
|
171
|
-
|
|
172
|
-
klass.expects(:new).with do |hash|
|
|
173
|
-
hash[:endpoint] == "https://host_from_config:port_from_config/wsman"
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
make_connection
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
it "sets :endpoint from data in state over config data" do
|
|
180
|
-
state[:hostname] = "host_from_state"
|
|
181
|
-
config[:hostname] = "host_from_config"
|
|
182
|
-
state[:port] = "port_from_state"
|
|
183
|
-
config[:port] = "port_from_config"
|
|
184
|
-
config[:winrm_transport] = "ssl"
|
|
185
|
-
|
|
186
|
-
klass.expects(:new).with do |hash|
|
|
187
|
-
hash[:endpoint] == "https://host_from_state:port_from_state/wsman"
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
make_connection
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
it "sets :user from :username in config" do
|
|
194
|
-
config[:username] = "user_from_config"
|
|
195
|
-
|
|
196
|
-
klass.expects(:new).with do |hash|
|
|
197
|
-
hash[:user] == "user_from_config"
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
make_connection
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
it "sets :user from :username in state over config data" do
|
|
204
|
-
state[:username] = "user_from_state"
|
|
205
|
-
config[:username] = "user_from_config"
|
|
206
|
-
|
|
207
|
-
klass.expects(:new).with do |hash|
|
|
208
|
-
hash[:user] == "user_from_state"
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
make_connection
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
it "sets :pass from :password in config" do
|
|
215
|
-
config[:password] = "pass_from_config"
|
|
216
|
-
|
|
217
|
-
klass.expects(:new).with do |hash|
|
|
218
|
-
hash[:pass] == "pass_from_config"
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
make_connection
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
it "sets :pass from :password in state over config data" do
|
|
225
|
-
state[:password] = "pass_from_state"
|
|
226
|
-
config[:password] = "pass_from_config"
|
|
227
|
-
|
|
228
|
-
klass.expects(:new).with do |hash|
|
|
229
|
-
hash[:pass] == "pass_from_state"
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
make_connection
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
it "sets :rdp_port from config" do
|
|
236
|
-
config[:rdp_port] = "rdp_from_config"
|
|
237
|
-
|
|
238
|
-
klass.expects(:new).with do |hash|
|
|
239
|
-
hash[:rdp_port] == "rdp_from_config"
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
make_connection
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
it "sets :rdp_port from state over config data" do
|
|
246
|
-
state[:rdp_port] = "rdp_from_state"
|
|
247
|
-
config[:rdp_port] = "rdp_from_config"
|
|
248
|
-
|
|
249
|
-
klass.expects(:new).with do |hash|
|
|
250
|
-
hash[:rdp_port] == "rdp_from_state"
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
make_connection
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
it "sets :connection_retries from config" do
|
|
257
|
-
config[:connection_retries] = "retries_from_config"
|
|
258
|
-
|
|
259
|
-
klass.expects(:new).with do |hash|
|
|
260
|
-
hash[:connection_retries] == "retries_from_config"
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
make_connection
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
it "sets :connection_retries from state over config data" do
|
|
267
|
-
state[:connection_retries] = "retries_from_state"
|
|
268
|
-
config[:connection_retries] = "retries_from_config"
|
|
269
|
-
|
|
270
|
-
klass.expects(:new).with do |hash|
|
|
271
|
-
hash[:connection_retries] == "retries_from_state"
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
make_connection
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
it "sets :connection_retry_sleep from config" do
|
|
278
|
-
config[:connection_retry_sleep] = "sleep_from_config"
|
|
279
|
-
|
|
280
|
-
klass.expects(:new).with do |hash|
|
|
281
|
-
hash[:connection_retry_sleep] == "sleep_from_config"
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
make_connection
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
it "sets :connection_retry_sleep from state over config data" do
|
|
288
|
-
state[:connection_retry_sleep] = "sleep_from_state"
|
|
289
|
-
config[:connection_retry_sleep] = "sleep_from_config"
|
|
290
|
-
|
|
291
|
-
klass.expects(:new).with do |hash|
|
|
292
|
-
hash[:connection_retry_sleep] == "sleep_from_state"
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
make_connection
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
it "sets :max_wait_until_ready from config" do
|
|
299
|
-
config[:max_wait_until_ready] = "max_from_config"
|
|
300
|
-
|
|
301
|
-
klass.expects(:new).with do |hash|
|
|
302
|
-
hash[:max_wait_until_ready] == "max_from_config"
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
make_connection
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
it "sets :max_wait_until_ready from state over config data" do
|
|
309
|
-
state[:max_wait_until_ready] = "max_from_state"
|
|
310
|
-
config[:max_wait_until_ready] = "max_from_config"
|
|
311
|
-
|
|
312
|
-
klass.expects(:new).with do |hash|
|
|
313
|
-
hash[:max_wait_until_ready] == "max_from_state"
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
make_connection
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
it "sets :winrm_transport from config data" do
|
|
320
|
-
config[:winrm_transport] = "ssl"
|
|
321
|
-
|
|
322
|
-
klass.expects(:new).with do |hash|
|
|
323
|
-
hash[:winrm_transport] == :ssl
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
make_connection
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
describe "when negotiate is set in config" do
|
|
330
|
-
before do
|
|
331
|
-
config[:winrm_transport] = "negotiate"
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
it "sets :winrm_transport to negotiate" do
|
|
335
|
-
|
|
336
|
-
klass.expects(:new).with do |hash|
|
|
337
|
-
hash[:winrm_transport] == :negotiate &&
|
|
338
|
-
hash[:disable_sspi] == false &&
|
|
339
|
-
hash[:basic_auth_only] == false
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
make_connection
|
|
343
|
-
end
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
it "returns the same connection when called again with same state" do
|
|
347
|
-
first_connection = make_connection(state)
|
|
348
|
-
second_connection = make_connection(state)
|
|
349
|
-
|
|
350
|
-
first_connection.object_id.must_equal second_connection.object_id
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
it "logs a debug message when the connection is reused" do
|
|
354
|
-
make_connection(state)
|
|
355
|
-
make_connection(state)
|
|
356
|
-
|
|
357
|
-
logged_output.string.lines.count { |l|
|
|
358
|
-
l =~ debug_line_with("[WinRM] reusing existing connection ")
|
|
359
|
-
}.must_equal 1
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
it "returns a new connection when called again if state differs" do
|
|
363
|
-
first_connection = make_connection(state)
|
|
364
|
-
second_connection = make_connection(state.merge(:port => 9000))
|
|
365
|
-
|
|
366
|
-
first_connection.object_id.wont_equal second_connection.object_id
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
it "closes first connection when a second is created" do
|
|
370
|
-
first_connection = make_connection(state)
|
|
371
|
-
first_connection.expects(:close)
|
|
372
|
-
|
|
373
|
-
make_connection(state.merge(:port => 9000))
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
it "logs a debug message a second connection is created" do
|
|
377
|
-
make_connection(state)
|
|
378
|
-
make_connection(state.merge(:port => 9000))
|
|
379
|
-
|
|
380
|
-
logged_output.string.lines.count { |l|
|
|
381
|
-
l =~ debug_line_with("[WinRM] shutting previous connection ")
|
|
382
|
-
}.must_equal 1
|
|
383
|
-
end
|
|
384
|
-
end
|
|
385
|
-
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
386
|
-
|
|
387
|
-
describe "called without a block" do
|
|
388
|
-
|
|
389
|
-
def make_connection(s = state)
|
|
390
|
-
transport.connection(s)
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
common_connection_specs
|
|
394
|
-
end
|
|
395
|
-
|
|
396
|
-
describe "called with a block" do
|
|
397
|
-
|
|
398
|
-
def make_connection(s = state)
|
|
399
|
-
transport.connection(s) do |conn|
|
|
400
|
-
conn
|
|
401
|
-
end
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
common_connection_specs
|
|
405
|
-
end
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
describe "#load_needed_dependencies" do
|
|
409
|
-
describe "winrm-fs" do
|
|
410
|
-
before do
|
|
411
|
-
# force loading of winrm-fs to get the version constant
|
|
412
|
-
require "winrm-fs"
|
|
413
|
-
end
|
|
414
|
-
|
|
415
|
-
it "logs a message to debug that code will be loaded" do
|
|
416
|
-
transport
|
|
417
|
-
|
|
418
|
-
logged_output.string.must_match debug_line_with(
|
|
419
|
-
"winrm-fs requested, loading winrm-fs gem")
|
|
420
|
-
end
|
|
421
|
-
|
|
422
|
-
it "logs a message to debug when library is initially loaded" do
|
|
423
|
-
transport = Kitchen::Transport::Winrm.new(config)
|
|
424
|
-
transport.stubs(:require).with("winrm", anything)
|
|
425
|
-
transport.stubs(:require).with("winrm-fs").returns(true)
|
|
426
|
-
transport.finalize_config!(instance)
|
|
427
|
-
|
|
428
|
-
logged_output.string.must_match(
|
|
429
|
-
/winrm-fs is loaded/
|
|
430
|
-
)
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
it "logs a message to debug when library is previously loaded" do
|
|
434
|
-
transport = Kitchen::Transport::Winrm.new(config)
|
|
435
|
-
transport.stubs(:require).with("winrm", anything)
|
|
436
|
-
transport.stubs(:require).with("winrm-fs").returns(false)
|
|
437
|
-
transport.finalize_config!(instance)
|
|
438
|
-
|
|
439
|
-
logged_output.string.must_match(
|
|
440
|
-
/winrm-fs was already loaded/
|
|
441
|
-
)
|
|
442
|
-
end
|
|
443
|
-
|
|
444
|
-
it "logs a message to fatal when libraries cannot be loaded" do
|
|
445
|
-
transport = Kitchen::Transport::Winrm.new(config)
|
|
446
|
-
transport.stubs(:require).with("winrm", anything)
|
|
447
|
-
transport.stubs(:require).with("winrm-fs").
|
|
448
|
-
raises(LoadError, "uh oh")
|
|
449
|
-
begin
|
|
450
|
-
transport.finalize_config!(instance)
|
|
451
|
-
rescue # rubocop:disable Lint/HandleExceptions
|
|
452
|
-
# we are interested in the log output, not this exception
|
|
453
|
-
end
|
|
454
|
-
|
|
455
|
-
logged_output.string.must_match fatal_line_with(
|
|
456
|
-
"The `winrm-fs` gem is missing and must be installed")
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
it "raises a UserError when libraries cannot be loaded" do
|
|
460
|
-
transport = Kitchen::Transport::Winrm.new(config)
|
|
461
|
-
transport.stubs(:require).with("winrm", anything)
|
|
462
|
-
transport.stubs(:require).with("winrm-fs").
|
|
463
|
-
raises(LoadError, "uh oh")
|
|
464
|
-
|
|
465
|
-
err = proc {
|
|
466
|
-
transport.finalize_config!(instance)
|
|
467
|
-
}.must_raise Kitchen::UserError
|
|
468
|
-
err.message.must_match(/^Could not load or activate winrm-fs\. /)
|
|
469
|
-
end
|
|
470
|
-
end
|
|
471
|
-
|
|
472
|
-
describe "winrm" do
|
|
473
|
-
it "logs a message to debug that code will be loaded" do
|
|
474
|
-
transport = Kitchen::Transport::Winrm.new(config)
|
|
475
|
-
transport.stubs(:require).with("winrm-fs", anything)
|
|
476
|
-
transport.stubs(:require)
|
|
477
|
-
transport.finalize_config!(instance)
|
|
478
|
-
|
|
479
|
-
logged_output.string.must_match debug_line_with(
|
|
480
|
-
"winrm requested, loading winrm gem")
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
it "logs a message to debug when library is initially loaded" do
|
|
484
|
-
transport = Kitchen::Transport::Winrm.new(config)
|
|
485
|
-
transport.stubs(:require).with("winrm-fs", anything)
|
|
486
|
-
transport.stubs(:require).returns(true)
|
|
487
|
-
|
|
488
|
-
transport.finalize_config!(instance)
|
|
489
|
-
|
|
490
|
-
logged_output.string.must_match(
|
|
491
|
-
/winrm is loaded/
|
|
492
|
-
)
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
it "logs a message to debug when library is previously loaded" do
|
|
496
|
-
transport = Kitchen::Transport::Winrm.new(config)
|
|
497
|
-
transport.stubs(:require).with("winrm-fs", anything)
|
|
498
|
-
transport.stubs(:require).returns(false)
|
|
499
|
-
|
|
500
|
-
transport.finalize_config!(instance)
|
|
501
|
-
|
|
502
|
-
logged_output.string.must_match(
|
|
503
|
-
/winrm was already loaded/
|
|
504
|
-
)
|
|
505
|
-
end
|
|
506
|
-
|
|
507
|
-
it "logs a message to fatal when libraries cannot be loaded" do
|
|
508
|
-
transport = Kitchen::Transport::Winrm.new(config)
|
|
509
|
-
transport.stubs(:require).with("winrm-fs", anything)
|
|
510
|
-
transport.stubs(:require).raises(LoadError, "uh oh")
|
|
511
|
-
begin
|
|
512
|
-
transport.finalize_config!(instance)
|
|
513
|
-
rescue # rubocop:disable Lint/HandleExceptions
|
|
514
|
-
# we are interested in the log output, not this exception
|
|
515
|
-
end
|
|
516
|
-
|
|
517
|
-
logged_output.string.must_match fatal_line_with(
|
|
518
|
-
"The `winrm` gem is missing and must be installed")
|
|
519
|
-
end
|
|
520
|
-
|
|
521
|
-
it "raises a UserError when libraries cannot be loaded" do
|
|
522
|
-
transport = Kitchen::Transport::Winrm.new(config)
|
|
523
|
-
transport.stubs(:require).with("winrm-fs", anything)
|
|
524
|
-
transport.stubs(:require).raises(LoadError, "uh oh")
|
|
525
|
-
|
|
526
|
-
err = proc {
|
|
527
|
-
transport.finalize_config!(instance)
|
|
528
|
-
}.must_raise Kitchen::UserError
|
|
529
|
-
err.message.must_match(/^Could not load or activate winrm\. /)
|
|
530
|
-
end
|
|
531
|
-
end
|
|
532
|
-
end
|
|
533
|
-
|
|
534
|
-
def debug_line_with(msg)
|
|
535
|
-
%r{^D, .* : #{Regexp.escape(msg)}}
|
|
536
|
-
end
|
|
537
|
-
|
|
538
|
-
def fatal_line_with(msg)
|
|
539
|
-
%r{^F, .* : #{Regexp.escape(msg)}}
|
|
540
|
-
end
|
|
541
|
-
end
|
|
542
|
-
|
|
543
|
-
describe Kitchen::Transport::Winrm::Connection do
|
|
544
|
-
|
|
545
|
-
let(:logged_output) { StringIO.new }
|
|
546
|
-
let(:logger) { Logger.new(logged_output) }
|
|
547
|
-
|
|
548
|
-
let(:options) do
|
|
549
|
-
{ :logger => logger, :user => "me", :pass => "haha",
|
|
550
|
-
:endpoint => "http://foo:5985/wsman", :winrm_transport => :plaintext,
|
|
551
|
-
:kitchen_root => "/i/am/root", :instance_name => "coolbeans",
|
|
552
|
-
:rdp_port => "rdpyeah" }
|
|
553
|
-
end
|
|
554
|
-
|
|
555
|
-
let(:info) do
|
|
556
|
-
copts = { :user => "me", :pass => "haha" }
|
|
557
|
-
"plaintext::http://foo:5985/wsman<#{copts}>"
|
|
558
|
-
end
|
|
559
|
-
|
|
560
|
-
let(:winrm_session) do
|
|
561
|
-
s = mock("winrm_session")
|
|
562
|
-
s.responds_like_instance_of(::WinRM::WinRMWebService)
|
|
563
|
-
s
|
|
564
|
-
end
|
|
565
|
-
|
|
566
|
-
let(:executor) do
|
|
567
|
-
s = mock("command_executor")
|
|
568
|
-
s.responds_like_instance_of(WinRM::CommandExecutor)
|
|
569
|
-
s
|
|
570
|
-
end
|
|
571
|
-
|
|
572
|
-
let(:connection) do
|
|
573
|
-
Kitchen::Transport::Winrm::Connection.new(options)
|
|
574
|
-
end
|
|
575
|
-
|
|
576
|
-
before do
|
|
577
|
-
WinRM::WinRMWebService.stubs(:new).returns(winrm_session)
|
|
578
|
-
winrm_session.stubs(:logger=)
|
|
579
|
-
logger.level = Logger::DEBUG
|
|
580
|
-
end
|
|
581
|
-
|
|
582
|
-
describe "#close" do
|
|
583
|
-
|
|
584
|
-
let(:response) do
|
|
585
|
-
o = WinRM::Output.new
|
|
586
|
-
o[:exitcode] = 0
|
|
587
|
-
o[:data].concat([{ :stdout => "ok\r\n" }])
|
|
588
|
-
o
|
|
589
|
-
end
|
|
590
|
-
|
|
591
|
-
before do
|
|
592
|
-
winrm_session.stubs(:create_executor).returns(executor)
|
|
593
|
-
executor.stubs(:close)
|
|
594
|
-
executor.stubs(:run_powershell_script).
|
|
595
|
-
with("doit").yields("ok\n", nil).returns(response)
|
|
596
|
-
end
|
|
597
|
-
|
|
598
|
-
it "only closes the shell once for multiple calls" do
|
|
599
|
-
executor.expects(:close).once
|
|
600
|
-
|
|
601
|
-
connection.execute("doit")
|
|
602
|
-
connection.close
|
|
603
|
-
connection.close
|
|
604
|
-
connection.close
|
|
605
|
-
end
|
|
606
|
-
end
|
|
607
|
-
|
|
608
|
-
describe "#execute" do
|
|
609
|
-
|
|
610
|
-
before do
|
|
611
|
-
winrm_session.stubs(:create_executor).returns(executor)
|
|
612
|
-
end
|
|
613
|
-
|
|
614
|
-
describe "for a successful command" do
|
|
615
|
-
|
|
616
|
-
let(:response) do
|
|
617
|
-
o = WinRM::Output.new
|
|
618
|
-
o[:exitcode] = 0
|
|
619
|
-
o[:data].concat([
|
|
620
|
-
{ :stdout => "ok\r\n" },
|
|
621
|
-
{ :stderr => "congrats\r\n" }
|
|
622
|
-
])
|
|
623
|
-
o
|
|
624
|
-
end
|
|
625
|
-
|
|
626
|
-
before do
|
|
627
|
-
executor.expects(:run_powershell_script).
|
|
628
|
-
with("doit").yields("ok\n", nil).returns(response)
|
|
629
|
-
end
|
|
630
|
-
|
|
631
|
-
it "logger displays command on debug" do
|
|
632
|
-
connection.execute("doit")
|
|
633
|
-
|
|
634
|
-
logged_output.string.must_match debug_line(
|
|
635
|
-
"[WinRM] #{info} (doit)")
|
|
636
|
-
end
|
|
637
|
-
|
|
638
|
-
it "logger captures stdout" do
|
|
639
|
-
connection.execute("doit")
|
|
640
|
-
|
|
641
|
-
logged_output.string.must_match(/^ok$/)
|
|
642
|
-
end
|
|
643
|
-
|
|
644
|
-
it "logger captures stderr on warn if logger is at debug level" do
|
|
645
|
-
logger.level = Logger::DEBUG
|
|
646
|
-
connection.execute("doit")
|
|
647
|
-
|
|
648
|
-
logged_output.string.must_match warn_line("congrats")
|
|
649
|
-
end
|
|
650
|
-
|
|
651
|
-
it "logger does not log stderr on warn if logger is below debug level" do
|
|
652
|
-
logger.level = Logger::INFO
|
|
653
|
-
connection.execute("doit")
|
|
654
|
-
|
|
655
|
-
logged_output.string.wont_match warn_line("congrats")
|
|
656
|
-
end
|
|
657
|
-
end
|
|
658
|
-
|
|
659
|
-
describe "long command" do
|
|
660
|
-
let(:command) { %{Write-Host "#{"a" * 4000}"} }
|
|
661
|
-
|
|
662
|
-
let(:connection) do
|
|
663
|
-
Kitchen::Transport::WinRMConnectionDummy.new(options)
|
|
664
|
-
end
|
|
665
|
-
|
|
666
|
-
let(:response) do
|
|
667
|
-
o = WinRM::Output.new
|
|
668
|
-
o[:exitcode] = 0
|
|
669
|
-
o[:data].concat([
|
|
670
|
-
{ :stdout => "ok\r\n" },
|
|
671
|
-
{ :stderr => "congrats\r\n" }
|
|
672
|
-
])
|
|
673
|
-
o
|
|
674
|
-
end
|
|
675
|
-
|
|
676
|
-
before do
|
|
677
|
-
executor.expects(:run_powershell_script).with(
|
|
678
|
-
%{powershell -ExecutionPolicy Bypass -File "$env:TEMP/coolbeans-long_script.ps1"}
|
|
679
|
-
).yields("ok\n", nil).returns(response)
|
|
680
|
-
end
|
|
681
|
-
|
|
682
|
-
it "uploads the long command" do
|
|
683
|
-
with_fake_fs do
|
|
684
|
-
connection.execute(command)
|
|
685
|
-
|
|
686
|
-
connection.saved_command.must_equal command
|
|
687
|
-
end
|
|
688
|
-
end
|
|
689
|
-
end
|
|
690
|
-
|
|
691
|
-
describe "for a failed command" do
|
|
692
|
-
|
|
693
|
-
let(:response) do
|
|
694
|
-
o = WinRM::Output.new
|
|
695
|
-
o[:exitcode] = 1
|
|
696
|
-
o[:data].concat([
|
|
697
|
-
{ :stderr => "#< CLIXML\r\n" },
|
|
698
|
-
{ :stderr => "<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas." },
|
|
699
|
-
{ :stderr => "microsoft.com/powershell/2004/04\"><S S=\"Error\">" },
|
|
700
|
-
{ :stderr => "doit : The term 'doit' is not recognized as the " },
|
|
701
|
-
{ :stderr => "name of a cmdlet, function, _x000D__x000A_</S>" },
|
|
702
|
-
{ :stderr => "<S S=\"Error\">script file, or operable program. " },
|
|
703
|
-
{ :stderr => "Check the spelling of" },
|
|
704
|
-
{ :stderr => "the name, or if a path _x000D__x000A_</S><S S=\"E" },
|
|
705
|
-
{ :stderr => "rror\">was included, verify that the path is corr" },
|
|
706
|
-
{ :stderr => "ect and try again._x000D__x000A_</S><S S=\"Error" },
|
|
707
|
-
{ :stderr => "\">At line:1 char:1_x000D__x000A_</S><S S=\"Error" },
|
|
708
|
-
{ :stderr => "\">+ doit_x000D__x000A_</S><S S=\"Error\">+ ~~~~_" },
|
|
709
|
-
{ :stderr => "x000D__x000A_</S><S S=\"Error\"> + CategoryInf" },
|
|
710
|
-
{ :stderr => "o : ObjectNotFound: (doit:String) [], Co" },
|
|
711
|
-
{ :stderr => "mmandNotFoun _x000D__x000A_</S><S S=\"Error\"> " },
|
|
712
|
-
{ :stderr => "dException_x000D__x000A_</S><S S=\"Error\"> + " },
|
|
713
|
-
{ :stderr => "FullyQualifiedErrorId : CommandNotFoundException_" },
|
|
714
|
-
{ :stderr => "x000D__x000A_</S><S S=\"Error\"> _x000D__x000A_</" },
|
|
715
|
-
{ :stderr => "S></Objs>" }
|
|
716
|
-
])
|
|
717
|
-
o
|
|
718
|
-
end
|
|
719
|
-
|
|
720
|
-
before do
|
|
721
|
-
executor.expects(:run_powershell_script).
|
|
722
|
-
with("doit").yields("nope\n", nil).returns(response)
|
|
723
|
-
end
|
|
724
|
-
|
|
725
|
-
# rubocop:disable Metrics/MethodLength
|
|
726
|
-
def self.common_failed_command_specs
|
|
727
|
-
it "logger displays command on debug" do
|
|
728
|
-
begin
|
|
729
|
-
connection.execute("doit")
|
|
730
|
-
rescue # rubocop:disable Lint/HandleExceptions
|
|
731
|
-
# the raise is not what is being tested here, rather its side-effect
|
|
732
|
-
end
|
|
733
|
-
|
|
734
|
-
logged_output.string.must_match debug_line(
|
|
735
|
-
"[WinRM] #{info} (doit)"
|
|
736
|
-
)
|
|
737
|
-
end
|
|
738
|
-
|
|
739
|
-
it "logger captures stdout" do
|
|
740
|
-
begin
|
|
741
|
-
connection.execute("doit")
|
|
742
|
-
rescue # rubocop:disable Lint/HandleExceptions
|
|
743
|
-
# the raise is not what is being tested here, rather its side-effect
|
|
744
|
-
end
|
|
745
|
-
|
|
746
|
-
logged_output.string.must_match(/^nope$/)
|
|
747
|
-
end
|
|
748
|
-
|
|
749
|
-
it "stderr is printed on logger warn level" do
|
|
750
|
-
begin
|
|
751
|
-
connection.execute("doit")
|
|
752
|
-
rescue # rubocop:disable Lint/HandleExceptions
|
|
753
|
-
# the raise is not what is being tested here, rather its side-effect
|
|
754
|
-
end
|
|
755
|
-
|
|
756
|
-
message = <<'MSG'.chomp!
|
|
757
|
-
doit : The term 'doit' is not recognized as the name of a cmdlet, function,
|
|
758
|
-
script file, or operable program. Check the spelling ofthe name, or if a path
|
|
759
|
-
was included, verify that the path is correct and try again.
|
|
760
|
-
At line:1 char:1
|
|
761
|
-
+ doit
|
|
762
|
-
+ ~~~~
|
|
763
|
-
+ CategoryInfo : ObjectNotFound: (doit:String) [], CommandNotFoun
|
|
764
|
-
dException
|
|
765
|
-
+ FullyQualifiedErrorId : CommandNotFoundException
|
|
766
|
-
MSG
|
|
767
|
-
|
|
768
|
-
message.lines.each do |line|
|
|
769
|
-
logged_output.string.must_match warn_line(line.chomp)
|
|
770
|
-
end
|
|
771
|
-
end
|
|
772
|
-
end
|
|
773
|
-
# rubocop:enable Metrics/MethodLength
|
|
774
|
-
|
|
775
|
-
describe "when a non-zero exit code is returned" do
|
|
776
|
-
|
|
777
|
-
common_failed_command_specs
|
|
778
|
-
|
|
779
|
-
it "raises a WinrmFailed exception" do
|
|
780
|
-
err = proc {
|
|
781
|
-
connection.execute("doit")
|
|
782
|
-
}.must_raise Kitchen::Transport::WinrmFailed
|
|
783
|
-
err.message.must_equal "WinRM exited (1) for command: [doit]"
|
|
784
|
-
end
|
|
785
|
-
end
|
|
786
|
-
end
|
|
787
|
-
|
|
788
|
-
describe "for a nil command" do
|
|
789
|
-
|
|
790
|
-
it "does not log on debug" do
|
|
791
|
-
executor.expects(:open).never
|
|
792
|
-
connection.execute(nil)
|
|
793
|
-
|
|
794
|
-
logged_output.string.must_equal ""
|
|
795
|
-
end
|
|
796
|
-
end
|
|
797
|
-
|
|
798
|
-
[
|
|
799
|
-
Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
|
|
800
|
-
Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
|
|
801
|
-
::WinRM::WinRMHTTPTransportError, ::WinRM::WinRMAuthorizationError,
|
|
802
|
-
HTTPClient::KeepAliveDisconnected, HTTPClient::ConnectTimeoutError
|
|
803
|
-
].each do |klass|
|
|
804
|
-
describe "raising #{klass}" do
|
|
805
|
-
|
|
806
|
-
before do
|
|
807
|
-
k = if klass == ::WinRM::WinRMHTTPTransportError
|
|
808
|
-
# this exception takes 2 args in its constructor, which is not stock
|
|
809
|
-
klass.new("dang", 200)
|
|
810
|
-
else
|
|
811
|
-
klass
|
|
812
|
-
end
|
|
813
|
-
|
|
814
|
-
options[:connection_retries] = 3
|
|
815
|
-
options[:connection_retry_sleep] = 7
|
|
816
|
-
winrm_session.stubs(:create_executor).raises(k)
|
|
817
|
-
end
|
|
818
|
-
|
|
819
|
-
it "reraises the #{klass} exception" do
|
|
820
|
-
proc { connection.execute("nope") }.must_raise klass
|
|
821
|
-
end
|
|
822
|
-
end
|
|
823
|
-
end
|
|
824
|
-
end
|
|
825
|
-
|
|
826
|
-
describe "#login_command" do
|
|
827
|
-
|
|
828
|
-
let(:login_command) { connection.login_command }
|
|
829
|
-
let(:args) { login_command.arguments.join(" ") }
|
|
830
|
-
let(:exec_args) { login_command.exec_args }
|
|
831
|
-
|
|
832
|
-
let(:rdp_doc) do
|
|
833
|
-
File.join(File.join(options[:kitchen_root], ".kitchen", "coolbeans.rdp"))
|
|
834
|
-
end
|
|
835
|
-
|
|
836
|
-
describe "for Mac-based workstations" do
|
|
837
|
-
|
|
838
|
-
before do
|
|
839
|
-
RbConfig::CONFIG.stubs(:[]).with("host_os").returns("darwin14")
|
|
840
|
-
end
|
|
841
|
-
|
|
842
|
-
it "returns a LoginCommand" do
|
|
843
|
-
with_fake_fs do
|
|
844
|
-
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
845
|
-
login_command.must_be_instance_of Kitchen::LoginCommand
|
|
846
|
-
end
|
|
847
|
-
end
|
|
848
|
-
|
|
849
|
-
it "creates an rdp document" do
|
|
850
|
-
actual = nil
|
|
851
|
-
with_fake_fs do
|
|
852
|
-
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
853
|
-
login_command
|
|
854
|
-
actual = IO.read(rdp_doc)
|
|
855
|
-
end
|
|
856
|
-
|
|
857
|
-
actual.must_equal Kitchen::Util.outdent!(<<-RDP)
|
|
858
|
-
drivestoredirect:s:*
|
|
859
|
-
full address:s:foo:rdpyeah
|
|
860
|
-
prompt for credentials:i:1
|
|
861
|
-
username:s:me
|
|
862
|
-
RDP
|
|
863
|
-
end
|
|
864
|
-
|
|
865
|
-
it "prints the rdp document on debug" do
|
|
866
|
-
with_fake_fs do
|
|
867
|
-
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
868
|
-
login_command
|
|
869
|
-
end
|
|
870
|
-
|
|
871
|
-
expected = Kitchen::Util.outdent!(<<-OUTPUT)
|
|
872
|
-
Creating RDP document for coolbeans (/i/am/root/.kitchen/coolbeans.rdp)
|
|
873
|
-
------------
|
|
874
|
-
drivestoredirect:s:*
|
|
875
|
-
full address:s:foo:rdpyeah
|
|
876
|
-
prompt for credentials:i:1
|
|
877
|
-
username:s:me
|
|
878
|
-
------------
|
|
879
|
-
OUTPUT
|
|
880
|
-
debug_output(logged_output.string).must_match expected
|
|
881
|
-
end
|
|
882
|
-
|
|
883
|
-
it "returns a LoginCommand which calls open on the rdp document" do
|
|
884
|
-
actual = nil
|
|
885
|
-
with_fake_fs do
|
|
886
|
-
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
887
|
-
actual = login_command
|
|
888
|
-
end
|
|
889
|
-
|
|
890
|
-
actual.exec_args.must_equal ["open", rdp_doc, {}]
|
|
891
|
-
end
|
|
892
|
-
end
|
|
893
|
-
|
|
894
|
-
describe "for Windows-based workstations" do
|
|
895
|
-
|
|
896
|
-
before do
|
|
897
|
-
RbConfig::CONFIG.stubs(:[]).with("host_os").returns("mingw32")
|
|
898
|
-
end
|
|
899
|
-
|
|
900
|
-
it "returns a LoginCommand" do
|
|
901
|
-
with_fake_fs do
|
|
902
|
-
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
903
|
-
login_command.must_be_instance_of Kitchen::LoginCommand
|
|
904
|
-
end
|
|
905
|
-
end
|
|
906
|
-
|
|
907
|
-
it "creates an rdp document" do
|
|
908
|
-
actual = nil
|
|
909
|
-
with_fake_fs do
|
|
910
|
-
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
911
|
-
login_command
|
|
912
|
-
actual = IO.read(rdp_doc)
|
|
913
|
-
end
|
|
914
|
-
|
|
915
|
-
actual.must_equal Kitchen::Util.outdent!(<<-RDP)
|
|
916
|
-
full address:s:foo:rdpyeah
|
|
917
|
-
prompt for credentials:i:1
|
|
918
|
-
username:s:me
|
|
919
|
-
RDP
|
|
920
|
-
end
|
|
921
|
-
|
|
922
|
-
it "prints the rdp document on debug" do
|
|
923
|
-
with_fake_fs do
|
|
924
|
-
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
925
|
-
login_command
|
|
926
|
-
end
|
|
927
|
-
|
|
928
|
-
expected = Kitchen::Util.outdent!(<<-OUTPUT)
|
|
929
|
-
Creating RDP document for coolbeans (/i/am/root/.kitchen/coolbeans.rdp)
|
|
930
|
-
------------
|
|
931
|
-
full address:s:foo:rdpyeah
|
|
932
|
-
prompt for credentials:i:1
|
|
933
|
-
username:s:me
|
|
934
|
-
------------
|
|
935
|
-
OUTPUT
|
|
936
|
-
debug_output(logged_output.string).must_match expected
|
|
937
|
-
end
|
|
938
|
-
|
|
939
|
-
it "returns a LoginCommand which calls mstsc on the rdp document" do
|
|
940
|
-
actual = nil
|
|
941
|
-
with_fake_fs do
|
|
942
|
-
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
943
|
-
actual = login_command
|
|
944
|
-
end
|
|
945
|
-
|
|
946
|
-
actual.exec_args.must_equal ["mstsc", rdp_doc, {}]
|
|
947
|
-
end
|
|
948
|
-
end
|
|
949
|
-
|
|
950
|
-
describe "for Linux-based workstations" do
|
|
951
|
-
|
|
952
|
-
before do
|
|
953
|
-
RbConfig::CONFIG.stubs(:[]).with("host_os").returns("linux-gnu")
|
|
954
|
-
end
|
|
955
|
-
|
|
956
|
-
it "returns a LoginCommand" do
|
|
957
|
-
login_command.must_be_instance_of Kitchen::LoginCommand
|
|
958
|
-
end
|
|
959
|
-
|
|
960
|
-
it "is an rdesktop command" do
|
|
961
|
-
login_command.command.must_equal "rdesktop"
|
|
962
|
-
args.must_match %r{ foo:rdpyeah$}
|
|
963
|
-
end
|
|
964
|
-
|
|
965
|
-
it "sets the user" do
|
|
966
|
-
args.must_match regexify("-u me ")
|
|
967
|
-
end
|
|
968
|
-
|
|
969
|
-
it "sets the pass if given" do
|
|
970
|
-
args.must_match regexify(" -p haha ")
|
|
971
|
-
end
|
|
972
|
-
|
|
973
|
-
it "won't set the pass if not given" do
|
|
974
|
-
options.delete(:pass)
|
|
975
|
-
|
|
976
|
-
args.wont_match regexify(" -p haha ")
|
|
977
|
-
end
|
|
978
|
-
end
|
|
979
|
-
|
|
980
|
-
describe "for unknown workstation platforms" do
|
|
981
|
-
|
|
982
|
-
before do
|
|
983
|
-
RbConfig::CONFIG.stubs(:[]).with("host_os").returns("cray")
|
|
984
|
-
end
|
|
985
|
-
|
|
986
|
-
it "raises an ActionFailed error" do
|
|
987
|
-
err = proc { login_command }.must_raise Kitchen::ActionFailed
|
|
988
|
-
err.message.must_equal "Remote login not supported in " \
|
|
989
|
-
"Kitchen::Transport::Winrm::Connection from host OS 'cray'."
|
|
990
|
-
end
|
|
991
|
-
end
|
|
992
|
-
end
|
|
993
|
-
|
|
994
|
-
describe "#upload" do
|
|
995
|
-
|
|
996
|
-
let(:transporter) do
|
|
997
|
-
t = mock("file_transporter")
|
|
998
|
-
t.responds_like_instance_of(WinRM::FS::Core::FileTransporter)
|
|
999
|
-
t
|
|
1000
|
-
end
|
|
1001
|
-
|
|
1002
|
-
before do
|
|
1003
|
-
winrm_session.stubs(:create_executor).returns(executor)
|
|
1004
|
-
|
|
1005
|
-
WinRM::FS::Core::FileTransporter.stubs(:new).
|
|
1006
|
-
with(executor).returns(transporter)
|
|
1007
|
-
transporter.stubs(:upload)
|
|
1008
|
-
end
|
|
1009
|
-
|
|
1010
|
-
def self.common_specs_for_upload
|
|
1011
|
-
it "builds a Winrm::FileTransporter" do
|
|
1012
|
-
WinRM::FS::Core::FileTransporter.unstub(:new)
|
|
1013
|
-
|
|
1014
|
-
WinRM::FS::Core::FileTransporter.expects(:new).
|
|
1015
|
-
with(executor).returns(transporter)
|
|
1016
|
-
|
|
1017
|
-
upload
|
|
1018
|
-
end
|
|
1019
|
-
|
|
1020
|
-
it "reuses the Winrm::FileTransporter" do
|
|
1021
|
-
WinRM::FS::Core::FileTransporter.unstub(:new)
|
|
1022
|
-
|
|
1023
|
-
WinRM::FS::Core::FileTransporter.expects(:new).
|
|
1024
|
-
with(executor).returns(transporter).once
|
|
1025
|
-
|
|
1026
|
-
upload
|
|
1027
|
-
upload
|
|
1028
|
-
upload
|
|
1029
|
-
end
|
|
1030
|
-
end
|
|
1031
|
-
|
|
1032
|
-
describe "for a file" do
|
|
1033
|
-
|
|
1034
|
-
def upload # execute every time, not lazily once
|
|
1035
|
-
connection.upload("/tmp/file.txt", "C:\\dest")
|
|
1036
|
-
end
|
|
1037
|
-
|
|
1038
|
-
common_specs_for_upload
|
|
1039
|
-
end
|
|
1040
|
-
|
|
1041
|
-
describe "for a collection of files" do
|
|
1042
|
-
|
|
1043
|
-
def upload # execute every time, not lazily once
|
|
1044
|
-
connection.upload(%W[/tmp/file1.txt /tmp/file2.txt], "C:\\dest")
|
|
1045
|
-
end
|
|
1046
|
-
|
|
1047
|
-
common_specs_for_upload
|
|
1048
|
-
end
|
|
1049
|
-
end
|
|
1050
|
-
|
|
1051
|
-
describe "#wait_until_ready" do
|
|
1052
|
-
|
|
1053
|
-
before do
|
|
1054
|
-
winrm_session.stubs(:create_executor).returns(executor)
|
|
1055
|
-
options[:max_wait_until_ready] = 300
|
|
1056
|
-
end
|
|
1057
|
-
|
|
1058
|
-
describe "when connection is successful" do
|
|
1059
|
-
|
|
1060
|
-
let(:response) do
|
|
1061
|
-
o = WinRM::Output.new
|
|
1062
|
-
o[:exitcode] = 0
|
|
1063
|
-
o[:data].concat([{ :stdout => "[WinRM] Established\r\n" }])
|
|
1064
|
-
o
|
|
1065
|
-
end
|
|
1066
|
-
|
|
1067
|
-
before do
|
|
1068
|
-
executor.expects(:run_powershell_script).
|
|
1069
|
-
with("Write-Host '[WinRM] Established\n'").returns(response)
|
|
1070
|
-
end
|
|
1071
|
-
|
|
1072
|
-
it "executes an empty command string to ensure working" do
|
|
1073
|
-
connection.wait_until_ready
|
|
1074
|
-
end
|
|
1075
|
-
end
|
|
1076
|
-
|
|
1077
|
-
describe "when connection suceeds but command fails, sad panda" do
|
|
1078
|
-
|
|
1079
|
-
let(:response) do
|
|
1080
|
-
o = WinRM::Output.new
|
|
1081
|
-
o[:exitcode] = 42
|
|
1082
|
-
o[:data].concat([{ :stderr => "Ah crap.\r\n" }])
|
|
1083
|
-
o
|
|
1084
|
-
end
|
|
1085
|
-
|
|
1086
|
-
before do
|
|
1087
|
-
executor.expects(:run_powershell_script).
|
|
1088
|
-
with("Write-Host '[WinRM] Established\n'").returns(response)
|
|
1089
|
-
end
|
|
1090
|
-
|
|
1091
|
-
it "executes an empty command string to ensure working" do
|
|
1092
|
-
err = proc {
|
|
1093
|
-
connection.wait_until_ready
|
|
1094
|
-
}.must_raise Kitchen::Transport::WinrmFailed
|
|
1095
|
-
err.message.must_equal "WinRM exited (42) for command: " \
|
|
1096
|
-
"[Write-Host '[WinRM] Established\n']"
|
|
1097
|
-
end
|
|
1098
|
-
|
|
1099
|
-
it "stderr is printed on logger warn level" do
|
|
1100
|
-
begin
|
|
1101
|
-
connection.wait_until_ready
|
|
1102
|
-
rescue # rubocop:disable Lint/HandleExceptions
|
|
1103
|
-
# the raise is not what is being tested here, rather its side-effect
|
|
1104
|
-
end
|
|
1105
|
-
|
|
1106
|
-
logged_output.string.must_match warn_line("Ah crap.\n")
|
|
1107
|
-
end
|
|
1108
|
-
end
|
|
1109
|
-
end
|
|
1110
|
-
|
|
1111
|
-
def debug_output(output)
|
|
1112
|
-
regexp = %r{^D, .* DEBUG -- : }
|
|
1113
|
-
output.lines.grep(%r{^D, .* DEBUG -- : }).map { |l| l.sub(regexp, "") }.join
|
|
1114
|
-
end
|
|
1115
|
-
|
|
1116
|
-
def debug_line(msg)
|
|
1117
|
-
%r{^D, .* : #{Regexp.escape(msg)}$}
|
|
1118
|
-
end
|
|
1119
|
-
|
|
1120
|
-
def debug_line_with(msg)
|
|
1121
|
-
%r{^D, .* : #{Regexp.escape(msg)}}
|
|
1122
|
-
end
|
|
1123
|
-
|
|
1124
|
-
def info_line(msg)
|
|
1125
|
-
%r{^I, .* : #{Regexp.escape(msg)}$}
|
|
1126
|
-
end
|
|
1127
|
-
|
|
1128
|
-
def info_line_with(msg)
|
|
1129
|
-
%r{^I, .* : #{Regexp.escape(msg)}}
|
|
1130
|
-
end
|
|
1131
|
-
|
|
1132
|
-
def regexify(string)
|
|
1133
|
-
Regexp.new(Regexp.escape(string))
|
|
1134
|
-
end
|
|
1135
|
-
|
|
1136
|
-
def warn_line(msg)
|
|
1137
|
-
%r{^W, .* : #{Regexp.escape(msg)}$}
|
|
1138
|
-
end
|
|
1139
|
-
|
|
1140
|
-
def warn_line_with(msg)
|
|
1141
|
-
%r{^W, .* : #{Regexp.escape(msg)}}
|
|
1142
|
-
end
|
|
1143
|
-
end
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Author:: Matt Wrock (<matt@mattwrock.com>)
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2014, Matt Wrock
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
|
|
19
|
+
require_relative "../../spec_helper"
|
|
20
|
+
|
|
21
|
+
require "kitchen/transport/winrm"
|
|
22
|
+
require "winrm"
|
|
23
|
+
require "winrm-fs"
|
|
24
|
+
|
|
25
|
+
module Kitchen
|
|
26
|
+
|
|
27
|
+
module Transport
|
|
28
|
+
|
|
29
|
+
class WinRMConnectionDummy < Kitchen::Transport::Winrm::Connection
|
|
30
|
+
|
|
31
|
+
attr_reader :saved_command, :remote_path, :local_path
|
|
32
|
+
|
|
33
|
+
def upload(locals, remote)
|
|
34
|
+
@saved_command = IO.read(locals)
|
|
35
|
+
@local_path = locals
|
|
36
|
+
@remote_path = remote
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe Kitchen::Transport::Winrm do
|
|
43
|
+
|
|
44
|
+
before do
|
|
45
|
+
RbConfig::CONFIG.stubs(:[]).with("host_os").returns("blah")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
let(:logged_output) { StringIO.new }
|
|
49
|
+
let(:logger) { Logger.new(logged_output) }
|
|
50
|
+
let(:config) { Hash.new }
|
|
51
|
+
let(:state) { Hash.new }
|
|
52
|
+
|
|
53
|
+
let(:instance) do
|
|
54
|
+
stub(:name => "coolbeans", :logger => logger, :to_str => "instance")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
let(:transport) do
|
|
58
|
+
t = Kitchen::Transport::Winrm.new(config)
|
|
59
|
+
# :load_winrm_s! is not cross-platform safe
|
|
60
|
+
# and gets initialized too early in the pipeline
|
|
61
|
+
t.stubs(:load_winrm_s!)
|
|
62
|
+
t.finalize_config!(instance)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "provisioner api_version is 1" do
|
|
66
|
+
transport.diagnose_plugin[:api_version].must_equal 1
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "plugin_version is set to Kitchen::VERSION" do
|
|
70
|
+
transport.diagnose_plugin[:version].must_equal Kitchen::VERSION
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe "default_config" do
|
|
74
|
+
|
|
75
|
+
it "sets :port to 5985 by default" do
|
|
76
|
+
transport[:port].must_equal 5985
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "sets :username to administrator by default" do
|
|
80
|
+
transport[:username].must_equal "administrator"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "sets :password to nil by default" do
|
|
84
|
+
transport[:password].must_equal nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "sets a default :endpoint_template value" do
|
|
88
|
+
transport[:endpoint_template].
|
|
89
|
+
must_equal "http://%{hostname}:%{port}/wsman"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "sets :rdp_port to 3389 by default" do
|
|
93
|
+
transport[:rdp_port].must_equal 3389
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "sets :connection_retries to 5 by default" do
|
|
97
|
+
transport[:connection_retries].must_equal 5
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "sets :connection_retry_sleep to 1 by default" do
|
|
101
|
+
transport[:connection_retry_sleep].must_equal 1
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "sets :max_wait_until_ready to 600 by default" do
|
|
105
|
+
transport[:max_wait_until_ready].must_equal 600
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "sets :winrm_transport to :negotiate" do
|
|
109
|
+
transport[:winrm_transport].must_equal :negotiate
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe "#connection" do
|
|
114
|
+
|
|
115
|
+
let(:klass) { Kitchen::Transport::Winrm::Connection }
|
|
116
|
+
|
|
117
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
118
|
+
def self.common_connection_specs
|
|
119
|
+
before do
|
|
120
|
+
config[:hostname] = "here"
|
|
121
|
+
config[:kitchen_root] = "/i/am/root"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "returns a Kitchen::Transport::Winrm::Connection object" do
|
|
125
|
+
transport.connection(state).must_be_kind_of klass
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "sets :instance_name to the instance's name" do
|
|
129
|
+
klass.expects(:new).with do |hash|
|
|
130
|
+
hash[:instance_name] == "coolbeans"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
make_connection
|
|
134
|
+
end
|
|
135
|
+
it "sets :kitchen_root to the transport's kitchen_root" do
|
|
136
|
+
klass.expects(:new).with do |hash|
|
|
137
|
+
hash[:kitchen_root] == "/i/am/root"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
make_connection
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "sets the :logger to the transport's logger" do
|
|
144
|
+
klass.expects(:new).with do |hash|
|
|
145
|
+
hash[:logger] == logger
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
make_connection
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "sets the :winrm_transport to :negotiate" do
|
|
152
|
+
klass.expects(:new).with do |hash|
|
|
153
|
+
hash[:winrm_transport] == :negotiate
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
make_connection
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "sets the :disable_sspi to false" do
|
|
160
|
+
klass.expects(:new).with do |hash|
|
|
161
|
+
hash[:disable_sspi] == false
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
make_connection
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "sets :endpoint from data in config" do
|
|
168
|
+
config[:hostname] = "host_from_config"
|
|
169
|
+
config[:port] = "port_from_config"
|
|
170
|
+
config[:winrm_transport] = "ssl"
|
|
171
|
+
|
|
172
|
+
klass.expects(:new).with do |hash|
|
|
173
|
+
hash[:endpoint] == "https://host_from_config:port_from_config/wsman"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
make_connection
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it "sets :endpoint from data in state over config data" do
|
|
180
|
+
state[:hostname] = "host_from_state"
|
|
181
|
+
config[:hostname] = "host_from_config"
|
|
182
|
+
state[:port] = "port_from_state"
|
|
183
|
+
config[:port] = "port_from_config"
|
|
184
|
+
config[:winrm_transport] = "ssl"
|
|
185
|
+
|
|
186
|
+
klass.expects(:new).with do |hash|
|
|
187
|
+
hash[:endpoint] == "https://host_from_state:port_from_state/wsman"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
make_connection
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it "sets :user from :username in config" do
|
|
194
|
+
config[:username] = "user_from_config"
|
|
195
|
+
|
|
196
|
+
klass.expects(:new).with do |hash|
|
|
197
|
+
hash[:user] == "user_from_config"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
make_connection
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it "sets :user from :username in state over config data" do
|
|
204
|
+
state[:username] = "user_from_state"
|
|
205
|
+
config[:username] = "user_from_config"
|
|
206
|
+
|
|
207
|
+
klass.expects(:new).with do |hash|
|
|
208
|
+
hash[:user] == "user_from_state"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
make_connection
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it "sets :pass from :password in config" do
|
|
215
|
+
config[:password] = "pass_from_config"
|
|
216
|
+
|
|
217
|
+
klass.expects(:new).with do |hash|
|
|
218
|
+
hash[:pass] == "pass_from_config"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
make_connection
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it "sets :pass from :password in state over config data" do
|
|
225
|
+
state[:password] = "pass_from_state"
|
|
226
|
+
config[:password] = "pass_from_config"
|
|
227
|
+
|
|
228
|
+
klass.expects(:new).with do |hash|
|
|
229
|
+
hash[:pass] == "pass_from_state"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
make_connection
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "sets :rdp_port from config" do
|
|
236
|
+
config[:rdp_port] = "rdp_from_config"
|
|
237
|
+
|
|
238
|
+
klass.expects(:new).with do |hash|
|
|
239
|
+
hash[:rdp_port] == "rdp_from_config"
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
make_connection
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it "sets :rdp_port from state over config data" do
|
|
246
|
+
state[:rdp_port] = "rdp_from_state"
|
|
247
|
+
config[:rdp_port] = "rdp_from_config"
|
|
248
|
+
|
|
249
|
+
klass.expects(:new).with do |hash|
|
|
250
|
+
hash[:rdp_port] == "rdp_from_state"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
make_connection
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
it "sets :connection_retries from config" do
|
|
257
|
+
config[:connection_retries] = "retries_from_config"
|
|
258
|
+
|
|
259
|
+
klass.expects(:new).with do |hash|
|
|
260
|
+
hash[:connection_retries] == "retries_from_config"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
make_connection
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it "sets :connection_retries from state over config data" do
|
|
267
|
+
state[:connection_retries] = "retries_from_state"
|
|
268
|
+
config[:connection_retries] = "retries_from_config"
|
|
269
|
+
|
|
270
|
+
klass.expects(:new).with do |hash|
|
|
271
|
+
hash[:connection_retries] == "retries_from_state"
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
make_connection
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
it "sets :connection_retry_sleep from config" do
|
|
278
|
+
config[:connection_retry_sleep] = "sleep_from_config"
|
|
279
|
+
|
|
280
|
+
klass.expects(:new).with do |hash|
|
|
281
|
+
hash[:connection_retry_sleep] == "sleep_from_config"
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
make_connection
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
it "sets :connection_retry_sleep from state over config data" do
|
|
288
|
+
state[:connection_retry_sleep] = "sleep_from_state"
|
|
289
|
+
config[:connection_retry_sleep] = "sleep_from_config"
|
|
290
|
+
|
|
291
|
+
klass.expects(:new).with do |hash|
|
|
292
|
+
hash[:connection_retry_sleep] == "sleep_from_state"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
make_connection
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
it "sets :max_wait_until_ready from config" do
|
|
299
|
+
config[:max_wait_until_ready] = "max_from_config"
|
|
300
|
+
|
|
301
|
+
klass.expects(:new).with do |hash|
|
|
302
|
+
hash[:max_wait_until_ready] == "max_from_config"
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
make_connection
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
it "sets :max_wait_until_ready from state over config data" do
|
|
309
|
+
state[:max_wait_until_ready] = "max_from_state"
|
|
310
|
+
config[:max_wait_until_ready] = "max_from_config"
|
|
311
|
+
|
|
312
|
+
klass.expects(:new).with do |hash|
|
|
313
|
+
hash[:max_wait_until_ready] == "max_from_state"
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
make_connection
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
it "sets :winrm_transport from config data" do
|
|
320
|
+
config[:winrm_transport] = "ssl"
|
|
321
|
+
|
|
322
|
+
klass.expects(:new).with do |hash|
|
|
323
|
+
hash[:winrm_transport] == :ssl
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
make_connection
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
describe "when negotiate is set in config" do
|
|
330
|
+
before do
|
|
331
|
+
config[:winrm_transport] = "negotiate"
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
it "sets :winrm_transport to negotiate" do
|
|
335
|
+
|
|
336
|
+
klass.expects(:new).with do |hash|
|
|
337
|
+
hash[:winrm_transport] == :negotiate &&
|
|
338
|
+
hash[:disable_sspi] == false &&
|
|
339
|
+
hash[:basic_auth_only] == false
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
make_connection
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it "returns the same connection when called again with same state" do
|
|
347
|
+
first_connection = make_connection(state)
|
|
348
|
+
second_connection = make_connection(state)
|
|
349
|
+
|
|
350
|
+
first_connection.object_id.must_equal second_connection.object_id
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
it "logs a debug message when the connection is reused" do
|
|
354
|
+
make_connection(state)
|
|
355
|
+
make_connection(state)
|
|
356
|
+
|
|
357
|
+
logged_output.string.lines.count { |l|
|
|
358
|
+
l =~ debug_line_with("[WinRM] reusing existing connection ")
|
|
359
|
+
}.must_equal 1
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
it "returns a new connection when called again if state differs" do
|
|
363
|
+
first_connection = make_connection(state)
|
|
364
|
+
second_connection = make_connection(state.merge(:port => 9000))
|
|
365
|
+
|
|
366
|
+
first_connection.object_id.wont_equal second_connection.object_id
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
it "closes first connection when a second is created" do
|
|
370
|
+
first_connection = make_connection(state)
|
|
371
|
+
first_connection.expects(:close)
|
|
372
|
+
|
|
373
|
+
make_connection(state.merge(:port => 9000))
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
it "logs a debug message a second connection is created" do
|
|
377
|
+
make_connection(state)
|
|
378
|
+
make_connection(state.merge(:port => 9000))
|
|
379
|
+
|
|
380
|
+
logged_output.string.lines.count { |l|
|
|
381
|
+
l =~ debug_line_with("[WinRM] shutting previous connection ")
|
|
382
|
+
}.must_equal 1
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
386
|
+
|
|
387
|
+
describe "called without a block" do
|
|
388
|
+
|
|
389
|
+
def make_connection(s = state)
|
|
390
|
+
transport.connection(s)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
common_connection_specs
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
describe "called with a block" do
|
|
397
|
+
|
|
398
|
+
def make_connection(s = state)
|
|
399
|
+
transport.connection(s) do |conn|
|
|
400
|
+
conn
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
common_connection_specs
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
describe "#load_needed_dependencies" do
|
|
409
|
+
describe "winrm-fs" do
|
|
410
|
+
before do
|
|
411
|
+
# force loading of winrm-fs to get the version constant
|
|
412
|
+
require "winrm-fs"
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
it "logs a message to debug that code will be loaded" do
|
|
416
|
+
transport
|
|
417
|
+
|
|
418
|
+
logged_output.string.must_match debug_line_with(
|
|
419
|
+
"winrm-fs requested, loading winrm-fs gem")
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
it "logs a message to debug when library is initially loaded" do
|
|
423
|
+
transport = Kitchen::Transport::Winrm.new(config)
|
|
424
|
+
transport.stubs(:require).with("winrm", anything)
|
|
425
|
+
transport.stubs(:require).with("winrm-fs").returns(true)
|
|
426
|
+
transport.finalize_config!(instance)
|
|
427
|
+
|
|
428
|
+
logged_output.string.must_match(
|
|
429
|
+
/winrm-fs is loaded/
|
|
430
|
+
)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
it "logs a message to debug when library is previously loaded" do
|
|
434
|
+
transport = Kitchen::Transport::Winrm.new(config)
|
|
435
|
+
transport.stubs(:require).with("winrm", anything)
|
|
436
|
+
transport.stubs(:require).with("winrm-fs").returns(false)
|
|
437
|
+
transport.finalize_config!(instance)
|
|
438
|
+
|
|
439
|
+
logged_output.string.must_match(
|
|
440
|
+
/winrm-fs was already loaded/
|
|
441
|
+
)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
it "logs a message to fatal when libraries cannot be loaded" do
|
|
445
|
+
transport = Kitchen::Transport::Winrm.new(config)
|
|
446
|
+
transport.stubs(:require).with("winrm", anything)
|
|
447
|
+
transport.stubs(:require).with("winrm-fs").
|
|
448
|
+
raises(LoadError, "uh oh")
|
|
449
|
+
begin
|
|
450
|
+
transport.finalize_config!(instance)
|
|
451
|
+
rescue # rubocop:disable Lint/HandleExceptions
|
|
452
|
+
# we are interested in the log output, not this exception
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
logged_output.string.must_match fatal_line_with(
|
|
456
|
+
"The `winrm-fs` gem is missing and must be installed")
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
it "raises a UserError when libraries cannot be loaded" do
|
|
460
|
+
transport = Kitchen::Transport::Winrm.new(config)
|
|
461
|
+
transport.stubs(:require).with("winrm", anything)
|
|
462
|
+
transport.stubs(:require).with("winrm-fs").
|
|
463
|
+
raises(LoadError, "uh oh")
|
|
464
|
+
|
|
465
|
+
err = proc {
|
|
466
|
+
transport.finalize_config!(instance)
|
|
467
|
+
}.must_raise Kitchen::UserError
|
|
468
|
+
err.message.must_match(/^Could not load or activate winrm-fs\. /)
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
describe "winrm" do
|
|
473
|
+
it "logs a message to debug that code will be loaded" do
|
|
474
|
+
transport = Kitchen::Transport::Winrm.new(config)
|
|
475
|
+
transport.stubs(:require).with("winrm-fs", anything)
|
|
476
|
+
transport.stubs(:require)
|
|
477
|
+
transport.finalize_config!(instance)
|
|
478
|
+
|
|
479
|
+
logged_output.string.must_match debug_line_with(
|
|
480
|
+
"winrm requested, loading winrm gem")
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
it "logs a message to debug when library is initially loaded" do
|
|
484
|
+
transport = Kitchen::Transport::Winrm.new(config)
|
|
485
|
+
transport.stubs(:require).with("winrm-fs", anything)
|
|
486
|
+
transport.stubs(:require).returns(true)
|
|
487
|
+
|
|
488
|
+
transport.finalize_config!(instance)
|
|
489
|
+
|
|
490
|
+
logged_output.string.must_match(
|
|
491
|
+
/winrm is loaded/
|
|
492
|
+
)
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
it "logs a message to debug when library is previously loaded" do
|
|
496
|
+
transport = Kitchen::Transport::Winrm.new(config)
|
|
497
|
+
transport.stubs(:require).with("winrm-fs", anything)
|
|
498
|
+
transport.stubs(:require).returns(false)
|
|
499
|
+
|
|
500
|
+
transport.finalize_config!(instance)
|
|
501
|
+
|
|
502
|
+
logged_output.string.must_match(
|
|
503
|
+
/winrm was already loaded/
|
|
504
|
+
)
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
it "logs a message to fatal when libraries cannot be loaded" do
|
|
508
|
+
transport = Kitchen::Transport::Winrm.new(config)
|
|
509
|
+
transport.stubs(:require).with("winrm-fs", anything)
|
|
510
|
+
transport.stubs(:require).raises(LoadError, "uh oh")
|
|
511
|
+
begin
|
|
512
|
+
transport.finalize_config!(instance)
|
|
513
|
+
rescue # rubocop:disable Lint/HandleExceptions
|
|
514
|
+
# we are interested in the log output, not this exception
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
logged_output.string.must_match fatal_line_with(
|
|
518
|
+
"The `winrm` gem is missing and must be installed")
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
it "raises a UserError when libraries cannot be loaded" do
|
|
522
|
+
transport = Kitchen::Transport::Winrm.new(config)
|
|
523
|
+
transport.stubs(:require).with("winrm-fs", anything)
|
|
524
|
+
transport.stubs(:require).raises(LoadError, "uh oh")
|
|
525
|
+
|
|
526
|
+
err = proc {
|
|
527
|
+
transport.finalize_config!(instance)
|
|
528
|
+
}.must_raise Kitchen::UserError
|
|
529
|
+
err.message.must_match(/^Could not load or activate winrm\. /)
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def debug_line_with(msg)
|
|
535
|
+
%r{^D, .* : #{Regexp.escape(msg)}}
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def fatal_line_with(msg)
|
|
539
|
+
%r{^F, .* : #{Regexp.escape(msg)}}
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
describe Kitchen::Transport::Winrm::Connection do
|
|
544
|
+
|
|
545
|
+
let(:logged_output) { StringIO.new }
|
|
546
|
+
let(:logger) { Logger.new(logged_output) }
|
|
547
|
+
|
|
548
|
+
let(:options) do
|
|
549
|
+
{ :logger => logger, :user => "me", :pass => "haha",
|
|
550
|
+
:endpoint => "http://foo:5985/wsman", :winrm_transport => :plaintext,
|
|
551
|
+
:kitchen_root => "/i/am/root", :instance_name => "coolbeans",
|
|
552
|
+
:rdp_port => "rdpyeah" }
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
let(:info) do
|
|
556
|
+
copts = { :user => "me", :pass => "haha" }
|
|
557
|
+
"plaintext::http://foo:5985/wsman<#{copts}>"
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
let(:winrm_session) do
|
|
561
|
+
s = mock("winrm_session")
|
|
562
|
+
s.responds_like_instance_of(::WinRM::WinRMWebService)
|
|
563
|
+
s
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
let(:executor) do
|
|
567
|
+
s = mock("command_executor")
|
|
568
|
+
s.responds_like_instance_of(WinRM::CommandExecutor)
|
|
569
|
+
s
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
let(:connection) do
|
|
573
|
+
Kitchen::Transport::Winrm::Connection.new(options)
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
before do
|
|
577
|
+
WinRM::WinRMWebService.stubs(:new).returns(winrm_session)
|
|
578
|
+
winrm_session.stubs(:logger=)
|
|
579
|
+
logger.level = Logger::DEBUG
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
describe "#close" do
|
|
583
|
+
|
|
584
|
+
let(:response) do
|
|
585
|
+
o = WinRM::Output.new
|
|
586
|
+
o[:exitcode] = 0
|
|
587
|
+
o[:data].concat([{ :stdout => "ok\r\n" }])
|
|
588
|
+
o
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
before do
|
|
592
|
+
winrm_session.stubs(:create_executor).returns(executor)
|
|
593
|
+
executor.stubs(:close)
|
|
594
|
+
executor.stubs(:run_powershell_script).
|
|
595
|
+
with("doit").yields("ok\n", nil).returns(response)
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
it "only closes the shell once for multiple calls" do
|
|
599
|
+
executor.expects(:close).once
|
|
600
|
+
|
|
601
|
+
connection.execute("doit")
|
|
602
|
+
connection.close
|
|
603
|
+
connection.close
|
|
604
|
+
connection.close
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
describe "#execute" do
|
|
609
|
+
|
|
610
|
+
before do
|
|
611
|
+
winrm_session.stubs(:create_executor).returns(executor)
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
describe "for a successful command" do
|
|
615
|
+
|
|
616
|
+
let(:response) do
|
|
617
|
+
o = WinRM::Output.new
|
|
618
|
+
o[:exitcode] = 0
|
|
619
|
+
o[:data].concat([
|
|
620
|
+
{ :stdout => "ok\r\n" },
|
|
621
|
+
{ :stderr => "congrats\r\n" }
|
|
622
|
+
])
|
|
623
|
+
o
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
before do
|
|
627
|
+
executor.expects(:run_powershell_script).
|
|
628
|
+
with("doit").yields("ok\n", nil).returns(response)
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
it "logger displays command on debug" do
|
|
632
|
+
connection.execute("doit")
|
|
633
|
+
|
|
634
|
+
logged_output.string.must_match debug_line(
|
|
635
|
+
"[WinRM] #{info} (doit)")
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
it "logger captures stdout" do
|
|
639
|
+
connection.execute("doit")
|
|
640
|
+
|
|
641
|
+
logged_output.string.must_match(/^ok$/)
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
it "logger captures stderr on warn if logger is at debug level" do
|
|
645
|
+
logger.level = Logger::DEBUG
|
|
646
|
+
connection.execute("doit")
|
|
647
|
+
|
|
648
|
+
logged_output.string.must_match warn_line("congrats")
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
it "logger does not log stderr on warn if logger is below debug level" do
|
|
652
|
+
logger.level = Logger::INFO
|
|
653
|
+
connection.execute("doit")
|
|
654
|
+
|
|
655
|
+
logged_output.string.wont_match warn_line("congrats")
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
describe "long command" do
|
|
660
|
+
let(:command) { %{Write-Host "#{"a" * 4000}"} }
|
|
661
|
+
|
|
662
|
+
let(:connection) do
|
|
663
|
+
Kitchen::Transport::WinRMConnectionDummy.new(options)
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
let(:response) do
|
|
667
|
+
o = WinRM::Output.new
|
|
668
|
+
o[:exitcode] = 0
|
|
669
|
+
o[:data].concat([
|
|
670
|
+
{ :stdout => "ok\r\n" },
|
|
671
|
+
{ :stderr => "congrats\r\n" }
|
|
672
|
+
])
|
|
673
|
+
o
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
before do
|
|
677
|
+
executor.expects(:run_powershell_script).with(
|
|
678
|
+
%{powershell -ExecutionPolicy Bypass -File "$env:TEMP/coolbeans-long_script.ps1"}
|
|
679
|
+
).yields("ok\n", nil).returns(response)
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
it "uploads the long command" do
|
|
683
|
+
with_fake_fs do
|
|
684
|
+
connection.execute(command)
|
|
685
|
+
|
|
686
|
+
connection.saved_command.must_equal command
|
|
687
|
+
end
|
|
688
|
+
end
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
describe "for a failed command" do
|
|
692
|
+
|
|
693
|
+
let(:response) do
|
|
694
|
+
o = WinRM::Output.new
|
|
695
|
+
o[:exitcode] = 1
|
|
696
|
+
o[:data].concat([
|
|
697
|
+
{ :stderr => "#< CLIXML\r\n" },
|
|
698
|
+
{ :stderr => "<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas." },
|
|
699
|
+
{ :stderr => "microsoft.com/powershell/2004/04\"><S S=\"Error\">" },
|
|
700
|
+
{ :stderr => "doit : The term 'doit' is not recognized as the " },
|
|
701
|
+
{ :stderr => "name of a cmdlet, function, _x000D__x000A_</S>" },
|
|
702
|
+
{ :stderr => "<S S=\"Error\">script file, or operable program. " },
|
|
703
|
+
{ :stderr => "Check the spelling of" },
|
|
704
|
+
{ :stderr => "the name, or if a path _x000D__x000A_</S><S S=\"E" },
|
|
705
|
+
{ :stderr => "rror\">was included, verify that the path is corr" },
|
|
706
|
+
{ :stderr => "ect and try again._x000D__x000A_</S><S S=\"Error" },
|
|
707
|
+
{ :stderr => "\">At line:1 char:1_x000D__x000A_</S><S S=\"Error" },
|
|
708
|
+
{ :stderr => "\">+ doit_x000D__x000A_</S><S S=\"Error\">+ ~~~~_" },
|
|
709
|
+
{ :stderr => "x000D__x000A_</S><S S=\"Error\"> + CategoryInf" },
|
|
710
|
+
{ :stderr => "o : ObjectNotFound: (doit:String) [], Co" },
|
|
711
|
+
{ :stderr => "mmandNotFoun _x000D__x000A_</S><S S=\"Error\"> " },
|
|
712
|
+
{ :stderr => "dException_x000D__x000A_</S><S S=\"Error\"> + " },
|
|
713
|
+
{ :stderr => "FullyQualifiedErrorId : CommandNotFoundException_" },
|
|
714
|
+
{ :stderr => "x000D__x000A_</S><S S=\"Error\"> _x000D__x000A_</" },
|
|
715
|
+
{ :stderr => "S></Objs>" }
|
|
716
|
+
])
|
|
717
|
+
o
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
before do
|
|
721
|
+
executor.expects(:run_powershell_script).
|
|
722
|
+
with("doit").yields("nope\n", nil).returns(response)
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
# rubocop:disable Metrics/MethodLength
|
|
726
|
+
def self.common_failed_command_specs
|
|
727
|
+
it "logger displays command on debug" do
|
|
728
|
+
begin
|
|
729
|
+
connection.execute("doit")
|
|
730
|
+
rescue # rubocop:disable Lint/HandleExceptions
|
|
731
|
+
# the raise is not what is being tested here, rather its side-effect
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
logged_output.string.must_match debug_line(
|
|
735
|
+
"[WinRM] #{info} (doit)"
|
|
736
|
+
)
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
it "logger captures stdout" do
|
|
740
|
+
begin
|
|
741
|
+
connection.execute("doit")
|
|
742
|
+
rescue # rubocop:disable Lint/HandleExceptions
|
|
743
|
+
# the raise is not what is being tested here, rather its side-effect
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
logged_output.string.must_match(/^nope$/)
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
it "stderr is printed on logger warn level" do
|
|
750
|
+
begin
|
|
751
|
+
connection.execute("doit")
|
|
752
|
+
rescue # rubocop:disable Lint/HandleExceptions
|
|
753
|
+
# the raise is not what is being tested here, rather its side-effect
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
message = <<'MSG'.chomp!
|
|
757
|
+
doit : The term 'doit' is not recognized as the name of a cmdlet, function,
|
|
758
|
+
script file, or operable program. Check the spelling ofthe name, or if a path
|
|
759
|
+
was included, verify that the path is correct and try again.
|
|
760
|
+
At line:1 char:1
|
|
761
|
+
+ doit
|
|
762
|
+
+ ~~~~
|
|
763
|
+
+ CategoryInfo : ObjectNotFound: (doit:String) [], CommandNotFoun
|
|
764
|
+
dException
|
|
765
|
+
+ FullyQualifiedErrorId : CommandNotFoundException
|
|
766
|
+
MSG
|
|
767
|
+
|
|
768
|
+
message.lines.each do |line|
|
|
769
|
+
logged_output.string.must_match warn_line(line.chomp)
|
|
770
|
+
end
|
|
771
|
+
end
|
|
772
|
+
end
|
|
773
|
+
# rubocop:enable Metrics/MethodLength
|
|
774
|
+
|
|
775
|
+
describe "when a non-zero exit code is returned" do
|
|
776
|
+
|
|
777
|
+
common_failed_command_specs
|
|
778
|
+
|
|
779
|
+
it "raises a WinrmFailed exception" do
|
|
780
|
+
err = proc {
|
|
781
|
+
connection.execute("doit")
|
|
782
|
+
}.must_raise Kitchen::Transport::WinrmFailed
|
|
783
|
+
err.message.must_equal "WinRM exited (1) for command: [doit]"
|
|
784
|
+
end
|
|
785
|
+
end
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
describe "for a nil command" do
|
|
789
|
+
|
|
790
|
+
it "does not log on debug" do
|
|
791
|
+
executor.expects(:open).never
|
|
792
|
+
connection.execute(nil)
|
|
793
|
+
|
|
794
|
+
logged_output.string.must_equal ""
|
|
795
|
+
end
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
[
|
|
799
|
+
Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
|
|
800
|
+
Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
|
|
801
|
+
::WinRM::WinRMHTTPTransportError, ::WinRM::WinRMAuthorizationError,
|
|
802
|
+
HTTPClient::KeepAliveDisconnected, HTTPClient::ConnectTimeoutError
|
|
803
|
+
].each do |klass|
|
|
804
|
+
describe "raising #{klass}" do
|
|
805
|
+
|
|
806
|
+
before do
|
|
807
|
+
k = if klass == ::WinRM::WinRMHTTPTransportError
|
|
808
|
+
# this exception takes 2 args in its constructor, which is not stock
|
|
809
|
+
klass.new("dang", 200)
|
|
810
|
+
else
|
|
811
|
+
klass
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
options[:connection_retries] = 3
|
|
815
|
+
options[:connection_retry_sleep] = 7
|
|
816
|
+
winrm_session.stubs(:create_executor).raises(k)
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
it "reraises the #{klass} exception" do
|
|
820
|
+
proc { connection.execute("nope") }.must_raise klass
|
|
821
|
+
end
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
describe "#login_command" do
|
|
827
|
+
|
|
828
|
+
let(:login_command) { connection.login_command }
|
|
829
|
+
let(:args) { login_command.arguments.join(" ") }
|
|
830
|
+
let(:exec_args) { login_command.exec_args }
|
|
831
|
+
|
|
832
|
+
let(:rdp_doc) do
|
|
833
|
+
File.join(File.join(options[:kitchen_root], ".kitchen", "coolbeans.rdp"))
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
describe "for Mac-based workstations" do
|
|
837
|
+
|
|
838
|
+
before do
|
|
839
|
+
RbConfig::CONFIG.stubs(:[]).with("host_os").returns("darwin14")
|
|
840
|
+
end
|
|
841
|
+
|
|
842
|
+
it "returns a LoginCommand" do
|
|
843
|
+
with_fake_fs do
|
|
844
|
+
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
845
|
+
login_command.must_be_instance_of Kitchen::LoginCommand
|
|
846
|
+
end
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
it "creates an rdp document" do
|
|
850
|
+
actual = nil
|
|
851
|
+
with_fake_fs do
|
|
852
|
+
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
853
|
+
login_command
|
|
854
|
+
actual = IO.read(rdp_doc)
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
actual.must_equal Kitchen::Util.outdent!(<<-RDP)
|
|
858
|
+
drivestoredirect:s:*
|
|
859
|
+
full address:s:foo:rdpyeah
|
|
860
|
+
prompt for credentials:i:1
|
|
861
|
+
username:s:me
|
|
862
|
+
RDP
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
it "prints the rdp document on debug" do
|
|
866
|
+
with_fake_fs do
|
|
867
|
+
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
868
|
+
login_command
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
expected = Kitchen::Util.outdent!(<<-OUTPUT)
|
|
872
|
+
Creating RDP document for coolbeans (/i/am/root/.kitchen/coolbeans.rdp)
|
|
873
|
+
------------
|
|
874
|
+
drivestoredirect:s:*
|
|
875
|
+
full address:s:foo:rdpyeah
|
|
876
|
+
prompt for credentials:i:1
|
|
877
|
+
username:s:me
|
|
878
|
+
------------
|
|
879
|
+
OUTPUT
|
|
880
|
+
debug_output(logged_output.string).must_match expected
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
it "returns a LoginCommand which calls open on the rdp document" do
|
|
884
|
+
actual = nil
|
|
885
|
+
with_fake_fs do
|
|
886
|
+
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
887
|
+
actual = login_command
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
actual.exec_args.must_equal ["open", rdp_doc, {}]
|
|
891
|
+
end
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
describe "for Windows-based workstations" do
|
|
895
|
+
|
|
896
|
+
before do
|
|
897
|
+
RbConfig::CONFIG.stubs(:[]).with("host_os").returns("mingw32")
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
it "returns a LoginCommand" do
|
|
901
|
+
with_fake_fs do
|
|
902
|
+
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
903
|
+
login_command.must_be_instance_of Kitchen::LoginCommand
|
|
904
|
+
end
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
it "creates an rdp document" do
|
|
908
|
+
actual = nil
|
|
909
|
+
with_fake_fs do
|
|
910
|
+
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
911
|
+
login_command
|
|
912
|
+
actual = IO.read(rdp_doc)
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
actual.must_equal Kitchen::Util.outdent!(<<-RDP)
|
|
916
|
+
full address:s:foo:rdpyeah
|
|
917
|
+
prompt for credentials:i:1
|
|
918
|
+
username:s:me
|
|
919
|
+
RDP
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
it "prints the rdp document on debug" do
|
|
923
|
+
with_fake_fs do
|
|
924
|
+
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
925
|
+
login_command
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
expected = Kitchen::Util.outdent!(<<-OUTPUT)
|
|
929
|
+
Creating RDP document for coolbeans (/i/am/root/.kitchen/coolbeans.rdp)
|
|
930
|
+
------------
|
|
931
|
+
full address:s:foo:rdpyeah
|
|
932
|
+
prompt for credentials:i:1
|
|
933
|
+
username:s:me
|
|
934
|
+
------------
|
|
935
|
+
OUTPUT
|
|
936
|
+
debug_output(logged_output.string).must_match expected
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
it "returns a LoginCommand which calls mstsc on the rdp document" do
|
|
940
|
+
actual = nil
|
|
941
|
+
with_fake_fs do
|
|
942
|
+
FileUtils.mkdir_p(File.dirname(rdp_doc))
|
|
943
|
+
actual = login_command
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
actual.exec_args.must_equal ["mstsc", rdp_doc, {}]
|
|
947
|
+
end
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
describe "for Linux-based workstations" do
|
|
951
|
+
|
|
952
|
+
before do
|
|
953
|
+
RbConfig::CONFIG.stubs(:[]).with("host_os").returns("linux-gnu")
|
|
954
|
+
end
|
|
955
|
+
|
|
956
|
+
it "returns a LoginCommand" do
|
|
957
|
+
login_command.must_be_instance_of Kitchen::LoginCommand
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
it "is an rdesktop command" do
|
|
961
|
+
login_command.command.must_equal "rdesktop"
|
|
962
|
+
args.must_match %r{ foo:rdpyeah$}
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
it "sets the user" do
|
|
966
|
+
args.must_match regexify("-u me ")
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
it "sets the pass if given" do
|
|
970
|
+
args.must_match regexify(" -p haha ")
|
|
971
|
+
end
|
|
972
|
+
|
|
973
|
+
it "won't set the pass if not given" do
|
|
974
|
+
options.delete(:pass)
|
|
975
|
+
|
|
976
|
+
args.wont_match regexify(" -p haha ")
|
|
977
|
+
end
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
describe "for unknown workstation platforms" do
|
|
981
|
+
|
|
982
|
+
before do
|
|
983
|
+
RbConfig::CONFIG.stubs(:[]).with("host_os").returns("cray")
|
|
984
|
+
end
|
|
985
|
+
|
|
986
|
+
it "raises an ActionFailed error" do
|
|
987
|
+
err = proc { login_command }.must_raise Kitchen::ActionFailed
|
|
988
|
+
err.message.must_equal "Remote login not supported in " \
|
|
989
|
+
"Kitchen::Transport::Winrm::Connection from host OS 'cray'."
|
|
990
|
+
end
|
|
991
|
+
end
|
|
992
|
+
end
|
|
993
|
+
|
|
994
|
+
describe "#upload" do
|
|
995
|
+
|
|
996
|
+
let(:transporter) do
|
|
997
|
+
t = mock("file_transporter")
|
|
998
|
+
t.responds_like_instance_of(WinRM::FS::Core::FileTransporter)
|
|
999
|
+
t
|
|
1000
|
+
end
|
|
1001
|
+
|
|
1002
|
+
before do
|
|
1003
|
+
winrm_session.stubs(:create_executor).returns(executor)
|
|
1004
|
+
|
|
1005
|
+
WinRM::FS::Core::FileTransporter.stubs(:new).
|
|
1006
|
+
with(executor).returns(transporter)
|
|
1007
|
+
transporter.stubs(:upload)
|
|
1008
|
+
end
|
|
1009
|
+
|
|
1010
|
+
def self.common_specs_for_upload
|
|
1011
|
+
it "builds a Winrm::FileTransporter" do
|
|
1012
|
+
WinRM::FS::Core::FileTransporter.unstub(:new)
|
|
1013
|
+
|
|
1014
|
+
WinRM::FS::Core::FileTransporter.expects(:new).
|
|
1015
|
+
with(executor).returns(transporter)
|
|
1016
|
+
|
|
1017
|
+
upload
|
|
1018
|
+
end
|
|
1019
|
+
|
|
1020
|
+
it "reuses the Winrm::FileTransporter" do
|
|
1021
|
+
WinRM::FS::Core::FileTransporter.unstub(:new)
|
|
1022
|
+
|
|
1023
|
+
WinRM::FS::Core::FileTransporter.expects(:new).
|
|
1024
|
+
with(executor).returns(transporter).once
|
|
1025
|
+
|
|
1026
|
+
upload
|
|
1027
|
+
upload
|
|
1028
|
+
upload
|
|
1029
|
+
end
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
describe "for a file" do
|
|
1033
|
+
|
|
1034
|
+
def upload # execute every time, not lazily once
|
|
1035
|
+
connection.upload("/tmp/file.txt", "C:\\dest")
|
|
1036
|
+
end
|
|
1037
|
+
|
|
1038
|
+
common_specs_for_upload
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1041
|
+
describe "for a collection of files" do
|
|
1042
|
+
|
|
1043
|
+
def upload # execute every time, not lazily once
|
|
1044
|
+
connection.upload(%W[/tmp/file1.txt /tmp/file2.txt], "C:\\dest")
|
|
1045
|
+
end
|
|
1046
|
+
|
|
1047
|
+
common_specs_for_upload
|
|
1048
|
+
end
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
describe "#wait_until_ready" do
|
|
1052
|
+
|
|
1053
|
+
before do
|
|
1054
|
+
winrm_session.stubs(:create_executor).returns(executor)
|
|
1055
|
+
options[:max_wait_until_ready] = 300
|
|
1056
|
+
end
|
|
1057
|
+
|
|
1058
|
+
describe "when connection is successful" do
|
|
1059
|
+
|
|
1060
|
+
let(:response) do
|
|
1061
|
+
o = WinRM::Output.new
|
|
1062
|
+
o[:exitcode] = 0
|
|
1063
|
+
o[:data].concat([{ :stdout => "[WinRM] Established\r\n" }])
|
|
1064
|
+
o
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
before do
|
|
1068
|
+
executor.expects(:run_powershell_script).
|
|
1069
|
+
with("Write-Host '[WinRM] Established\n'").returns(response)
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
it "executes an empty command string to ensure working" do
|
|
1073
|
+
connection.wait_until_ready
|
|
1074
|
+
end
|
|
1075
|
+
end
|
|
1076
|
+
|
|
1077
|
+
describe "when connection suceeds but command fails, sad panda" do
|
|
1078
|
+
|
|
1079
|
+
let(:response) do
|
|
1080
|
+
o = WinRM::Output.new
|
|
1081
|
+
o[:exitcode] = 42
|
|
1082
|
+
o[:data].concat([{ :stderr => "Ah crap.\r\n" }])
|
|
1083
|
+
o
|
|
1084
|
+
end
|
|
1085
|
+
|
|
1086
|
+
before do
|
|
1087
|
+
executor.expects(:run_powershell_script).
|
|
1088
|
+
with("Write-Host '[WinRM] Established\n'").returns(response)
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
it "executes an empty command string to ensure working" do
|
|
1092
|
+
err = proc {
|
|
1093
|
+
connection.wait_until_ready
|
|
1094
|
+
}.must_raise Kitchen::Transport::WinrmFailed
|
|
1095
|
+
err.message.must_equal "WinRM exited (42) for command: " \
|
|
1096
|
+
"[Write-Host '[WinRM] Established\n']"
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1099
|
+
it "stderr is printed on logger warn level" do
|
|
1100
|
+
begin
|
|
1101
|
+
connection.wait_until_ready
|
|
1102
|
+
rescue # rubocop:disable Lint/HandleExceptions
|
|
1103
|
+
# the raise is not what is being tested here, rather its side-effect
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1106
|
+
logged_output.string.must_match warn_line("Ah crap.\n")
|
|
1107
|
+
end
|
|
1108
|
+
end
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1111
|
+
def debug_output(output)
|
|
1112
|
+
regexp = %r{^D, .* DEBUG -- : }
|
|
1113
|
+
output.lines.grep(%r{^D, .* DEBUG -- : }).map { |l| l.sub(regexp, "") }.join
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
def debug_line(msg)
|
|
1117
|
+
%r{^D, .* : #{Regexp.escape(msg)}$}
|
|
1118
|
+
end
|
|
1119
|
+
|
|
1120
|
+
def debug_line_with(msg)
|
|
1121
|
+
%r{^D, .* : #{Regexp.escape(msg)}}
|
|
1122
|
+
end
|
|
1123
|
+
|
|
1124
|
+
def info_line(msg)
|
|
1125
|
+
%r{^I, .* : #{Regexp.escape(msg)}$}
|
|
1126
|
+
end
|
|
1127
|
+
|
|
1128
|
+
def info_line_with(msg)
|
|
1129
|
+
%r{^I, .* : #{Regexp.escape(msg)}}
|
|
1130
|
+
end
|
|
1131
|
+
|
|
1132
|
+
def regexify(string)
|
|
1133
|
+
Regexp.new(Regexp.escape(string))
|
|
1134
|
+
end
|
|
1135
|
+
|
|
1136
|
+
def warn_line(msg)
|
|
1137
|
+
%r{^W, .* : #{Regexp.escape(msg)}$}
|
|
1138
|
+
end
|
|
1139
|
+
|
|
1140
|
+
def warn_line_with(msg)
|
|
1141
|
+
%r{^W, .* : #{Regexp.escape(msg)}}
|
|
1142
|
+
end
|
|
1143
|
+
end
|