test-kitchen 1.4.0.beta.2 → 1.4.0.rc.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/features/kitchen_diagnose_command.feature +32 -0
- data/lib/kitchen/cli.rb +3 -0
- data/lib/kitchen/command/diagnose.rb +6 -1
- data/lib/kitchen/configurable.rb +48 -1
- data/lib/kitchen/diagnostic.rb +29 -0
- data/lib/kitchen/driver/base.rb +25 -0
- data/lib/kitchen/driver/dummy.rb +4 -0
- data/lib/kitchen/driver/proxy.rb +3 -0
- data/lib/kitchen/instance.rb +17 -0
- data/lib/kitchen/provisioner/base.rb +30 -1
- data/lib/kitchen/provisioner/chef_base.rb +7 -3
- data/lib/kitchen/provisioner/chef_solo.rb +4 -0
- data/lib/kitchen/provisioner/chef_zero.rb +5 -1
- data/lib/kitchen/provisioner/dummy.rb +4 -0
- data/lib/kitchen/provisioner/shell.rb +5 -0
- data/lib/kitchen/shell_out.rb +6 -2
- data/lib/kitchen/transport/base.rb +25 -0
- data/lib/kitchen/transport/dummy.rb +4 -0
- data/lib/kitchen/transport/ssh.rb +22 -1
- data/lib/kitchen/transport/winrm.rb +61 -90
- data/lib/kitchen/verifier/base.rb +30 -1
- data/lib/kitchen/verifier/busser.rb +4 -0
- data/lib/kitchen/verifier/dummy.rb +4 -0
- data/lib/kitchen/version.rb +1 -1
- data/spec/kitchen/configurable_spec.rb +35 -0
- data/spec/kitchen/diagnostic_spec.rb +53 -3
- data/spec/kitchen/driver/dummy_spec.rb +8 -0
- data/spec/kitchen/driver/proxy_spec.rb +4 -0
- data/spec/kitchen/driver/ssh_base_spec.rb +4 -0
- data/spec/kitchen/instance_spec.rb +75 -0
- data/spec/kitchen/provisioner/base_spec.rb +32 -6
- data/spec/kitchen/provisioner/chef_base_spec.rb +3 -2
- data/spec/kitchen/provisioner/chef_solo_spec.rb +10 -2
- data/spec/kitchen/provisioner/chef_zero_spec.rb +24 -2
- data/spec/kitchen/provisioner/dummy_spec.rb +8 -0
- data/spec/kitchen/provisioner/shell_spec.rb +10 -0
- data/spec/kitchen/shell_out_spec.rb +7 -0
- data/spec/kitchen/transport/ssh_spec.rb +90 -1
- data/spec/kitchen/transport/winrm_spec.rb +91 -11
- data/spec/kitchen/verifier/base_spec.rb +32 -6
- data/spec/kitchen/verifier/busser_spec.rb +8 -0
- data/spec/kitchen/verifier/dummy_spec.rb +8 -0
- data/support/chef_base_install_command.sh +183 -100
- data/test-kitchen.gemspec +1 -2
- metadata +11 -48
- data/lib/kitchen/transport/winrm/command_executor.rb +0 -188
- data/lib/kitchen/transport/winrm/file_transporter.rb +0 -454
- data/lib/kitchen/transport/winrm/logging.rb +0 -50
- data/lib/kitchen/transport/winrm/template.rb +0 -74
- data/lib/kitchen/transport/winrm/tmp_zip.rb +0 -187
- data/spec/kitchen/transport/winrm/command_executor_spec.rb +0 -400
- data/spec/kitchen/transport/winrm/file_transporter_spec.rb +0 -876
- data/spec/kitchen/transport/winrm/logging_spec.rb +0 -92
- data/spec/kitchen/transport/winrm/template_spec.rb +0 -51
- data/spec/kitchen/transport/winrm/tmp_zip_spec.rb +0 -132
- data/support/check_files.ps1.erb +0 -48
- data/support/decode_files.ps1.erb +0 -62
@@ -1,50 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
#
|
3
|
-
# Author:: Fletcher (<fnichol@nichol.ca>)
|
4
|
-
#
|
5
|
-
# Copyright (C) 2015, Fletcher Nichol
|
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
|
-
module Kitchen
|
20
|
-
|
21
|
-
module Transport
|
22
|
-
|
23
|
-
class Winrm < Kitchen::Transport::Base
|
24
|
-
|
25
|
-
# Mixin to use an optionally provided logger for logging.
|
26
|
-
#
|
27
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
28
|
-
module Logging
|
29
|
-
|
30
|
-
# Logs a message on the logger at the debug level, if a logger is
|
31
|
-
# present.
|
32
|
-
#
|
33
|
-
# @param msg [String] a message to log
|
34
|
-
# @yield evaluates and uses return value as message to log. If msg
|
35
|
-
# parameter is set, it will take precedence over the block.
|
36
|
-
def debug(msg = nil, &block)
|
37
|
-
return if logger.nil? || !logger.debug?
|
38
|
-
logger.debug("[#{log_subject}] " << (msg || block.call))
|
39
|
-
end
|
40
|
-
|
41
|
-
# The subject for log messages.
|
42
|
-
#
|
43
|
-
# @return [String] log subject
|
44
|
-
def log_subject
|
45
|
-
@log_subject ||= self.class.to_s.split("::").last
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,74 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
#
|
3
|
-
# Author:: Fletcher (<fnichol@nichol.ca>)
|
4
|
-
#
|
5
|
-
# Copyright (C) 2015, Fletcher Nichol
|
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 "erb"
|
20
|
-
require "ostruct"
|
21
|
-
|
22
|
-
module Kitchen
|
23
|
-
|
24
|
-
module Transport
|
25
|
-
|
26
|
-
class Winrm < Kitchen::Transport::Base
|
27
|
-
|
28
|
-
# Wraps an ERb template which can be called multiple times with
|
29
|
-
# different binding contexts.
|
30
|
-
#
|
31
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
32
|
-
# @api private
|
33
|
-
class Template
|
34
|
-
|
35
|
-
# Initializes an ERb template using a file as the template source.
|
36
|
-
#
|
37
|
-
# @param file [String] path to an ERb template file
|
38
|
-
def initialize(file)
|
39
|
-
@erb = ERB.new(IO.read(file))
|
40
|
-
end
|
41
|
-
|
42
|
-
# Renders the template using a hash as context.
|
43
|
-
#
|
44
|
-
# @param vars [Hash] a hash used for context
|
45
|
-
# @return [String] the rendered template
|
46
|
-
def render(vars)
|
47
|
-
@erb.result(Context.for(vars))
|
48
|
-
end
|
49
|
-
alias_method :%, :render
|
50
|
-
|
51
|
-
# Internal class which wraps a binding context for rendering
|
52
|
-
# an ERb template.
|
53
|
-
#
|
54
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
55
|
-
# @api private
|
56
|
-
class Context < OpenStruct
|
57
|
-
|
58
|
-
# Creates a new binding context for a hash of data.
|
59
|
-
#
|
60
|
-
# @param vars [Hash] a hash used for context
|
61
|
-
# @return [Binding] a binding context for the given hash
|
62
|
-
def self.for(vars)
|
63
|
-
new(vars).my_binding
|
64
|
-
end
|
65
|
-
|
66
|
-
# @return [Binding] a binding context
|
67
|
-
def my_binding
|
68
|
-
binding
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
@@ -1,187 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
#
|
3
|
-
# Author:: Fletcher (<fnichol@nichol.ca>)
|
4
|
-
#
|
5
|
-
# Copyright (C) 2015, Fletcher Nichol
|
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 "delegate"
|
20
|
-
require "pathname"
|
21
|
-
require "tempfile"
|
22
|
-
require "zip"
|
23
|
-
|
24
|
-
require "kitchen/transport/winrm/logging"
|
25
|
-
|
26
|
-
module Kitchen
|
27
|
-
|
28
|
-
module Transport
|
29
|
-
|
30
|
-
class Winrm < Kitchen::Transport::Base
|
31
|
-
|
32
|
-
# A temporary Zip file for a given directory.
|
33
|
-
#
|
34
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
35
|
-
class TmpZip
|
36
|
-
|
37
|
-
include Logging
|
38
|
-
|
39
|
-
# Contructs a new Zip file for the given directory.
|
40
|
-
#
|
41
|
-
# There are 2 ways to interpret the directory path:
|
42
|
-
#
|
43
|
-
# * If the directory has no path separator terminator, then the
|
44
|
-
# directory basename will be used as the base directory in the
|
45
|
-
# resulting zip file.
|
46
|
-
# * If the directory has a path separator terminator (such as `/` or
|
47
|
-
# `\\`), then the entries under the directory will be added to the
|
48
|
-
# resulting zip file.
|
49
|
-
#
|
50
|
-
# The following emaples assume a directory tree structure of:
|
51
|
-
#
|
52
|
-
# src
|
53
|
-
# |-- alpha.txt
|
54
|
-
# |-- beta.txt
|
55
|
-
# \-- sub
|
56
|
-
# \-- charlie.txt
|
57
|
-
#
|
58
|
-
# @example Including the base directory in the zip file
|
59
|
-
#
|
60
|
-
# TmpZip.new("/path/to/src")
|
61
|
-
# # produces a zip file with entries:
|
62
|
-
# # - src/alpha.txt
|
63
|
-
# # - src/beta.txt
|
64
|
-
# # - src/sub/charlie.txt
|
65
|
-
#
|
66
|
-
# @example Excluding the base directory in the zip file
|
67
|
-
#
|
68
|
-
# TmpZip.new("/path/to/src/")
|
69
|
-
# # produces a zip file with entries:
|
70
|
-
# # - alpha.txt
|
71
|
-
# # - beta.txt
|
72
|
-
# # - sub/charlie.txt
|
73
|
-
#
|
74
|
-
# @param dir [String,Pathname,#to_s] path to the directory
|
75
|
-
# @param logger [#debug,#debug?] an optional logger/ui object that
|
76
|
-
# responds to `#debug` and `#debug?` (default `nil`)
|
77
|
-
def initialize(dir, logger = nil)
|
78
|
-
@logger = logger
|
79
|
-
@dir = Pathname.new(dir)
|
80
|
-
@method = ::Zip::Entry::DEFLATED
|
81
|
-
@compression = Zlib::BEST_COMPRESSION
|
82
|
-
@zip_io = Tempfile.open(["tmpzip-", ".zip"], :binmode => true)
|
83
|
-
write_zip
|
84
|
-
@zip_io.close
|
85
|
-
end
|
86
|
-
|
87
|
-
# @return [Pathname] path to zip file
|
88
|
-
def path
|
89
|
-
Pathname.new(zip_io.path) if zip_io.path
|
90
|
-
end
|
91
|
-
|
92
|
-
# Unlinks (deletes) the zip file from the filesystem.
|
93
|
-
def unlink
|
94
|
-
zip_io.unlink
|
95
|
-
end
|
96
|
-
|
97
|
-
private
|
98
|
-
|
99
|
-
# @return [Integer] the compression used for Zip entries. Possible
|
100
|
-
# values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION`,
|
101
|
-
# and `Zlib::NO_COMPRESSION`.
|
102
|
-
# @api private
|
103
|
-
attr_reader :compression
|
104
|
-
|
105
|
-
# @return [Pathname] the directory used to create the Zip file
|
106
|
-
# @api private
|
107
|
-
attr_reader :dir
|
108
|
-
|
109
|
-
# @return [#debug] the logger
|
110
|
-
# @api private
|
111
|
-
attr_reader :logger
|
112
|
-
|
113
|
-
# @return [Integer] compression method used for Zip entries. Possible
|
114
|
-
# values are `Zip::Entry::DEFLATED` and `Zip::Entry::STORED`.
|
115
|
-
# @api private
|
116
|
-
attr_reader :method
|
117
|
-
|
118
|
-
# @return [IO] the Zip file IO
|
119
|
-
# @api private
|
120
|
-
attr_reader :zip_io
|
121
|
-
|
122
|
-
# @return [Pathname] the path segement to be stripped off Zip entries
|
123
|
-
# @api private
|
124
|
-
def dir_strip
|
125
|
-
@dir_strip ||= if dir.to_s.end_with?("/", "\\")
|
126
|
-
Pathname.new(dir.to_s.chop)
|
127
|
-
else
|
128
|
-
dir.dirname
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
# @return [Array<Pathname] all recursive files under the base
|
133
|
-
# directory, excluding directories
|
134
|
-
# @api private
|
135
|
-
def entries
|
136
|
-
Pathname.glob(dir.join("**/*")).delete_if(&:directory?).sort
|
137
|
-
end
|
138
|
-
|
139
|
-
# (see Logging.log_subject)
|
140
|
-
# @api private
|
141
|
-
def log_subject
|
142
|
-
@log_subject ||= [self.class.to_s.split("::").last, path].join("::")
|
143
|
-
end
|
144
|
-
|
145
|
-
# Adds all file entries to the Zip output stream.
|
146
|
-
#
|
147
|
-
# @param zos [Zip::OutputStream] zip output stream
|
148
|
-
# @api private
|
149
|
-
def produce_zip_entries(zos)
|
150
|
-
entries.each do |entry|
|
151
|
-
entry_path = entry.sub("#{dir_strip}/", "")
|
152
|
-
debug { "+++ Adding #{entry_path}" }
|
153
|
-
zos.put_next_entry(entry_path, nil, nil, method, compression)
|
154
|
-
entry.open("rb") { |src| IO.copy_stream(src, zos) }
|
155
|
-
end
|
156
|
-
debug { "=== All files added." }
|
157
|
-
end
|
158
|
-
|
159
|
-
# Writes out a temporary Zip file.
|
160
|
-
#
|
161
|
-
# @api private
|
162
|
-
def write_zip
|
163
|
-
debug { "Populating files" }
|
164
|
-
Zip::OutputStream.write_buffer(NoDupIO.new(zip_io)) do |zos|
|
165
|
-
produce_zip_entries(zos)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
# Simple delegate wrapper to prevent `#dup` calls being made on IO
|
170
|
-
# objects. This is used to bypass an issue in the `Zip::Outputstream`
|
171
|
-
# constructor where an incoming IO is duplicated, leading to races
|
172
|
-
# on flushing the final stream to disk.
|
173
|
-
#
|
174
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
175
|
-
# @api private
|
176
|
-
class NoDupIO < SimpleDelegator
|
177
|
-
|
178
|
-
# @return [self] returns self and does *not* return a duplicate
|
179
|
-
# object
|
180
|
-
def dup
|
181
|
-
self
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
@@ -1,400 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
#
|
3
|
-
# Author:: Fletcher (<fnichol@nichol.ca>)
|
4
|
-
#
|
5
|
-
# Copyright (C) 2015, Fletcher Nichol
|
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"
|
22
|
-
require "kitchen/transport/winrm/command_executor"
|
23
|
-
|
24
|
-
require "base64"
|
25
|
-
require "securerandom"
|
26
|
-
require "winrm"
|
27
|
-
|
28
|
-
describe Kitchen::Transport::Winrm::CommandExecutor do
|
29
|
-
|
30
|
-
let(:logged_output) { StringIO.new }
|
31
|
-
let(:logger) { Logger.new(logged_output) }
|
32
|
-
let(:shell_id) { "shell-123" }
|
33
|
-
|
34
|
-
let(:executor) do
|
35
|
-
Kitchen::Transport::Winrm::CommandExecutor.new(service, logger)
|
36
|
-
end
|
37
|
-
|
38
|
-
let(:service) do
|
39
|
-
s = mock("winrm_service")
|
40
|
-
s.responds_like_instance_of(WinRM::WinRMWebService)
|
41
|
-
s
|
42
|
-
end
|
43
|
-
|
44
|
-
let(:version_output) do
|
45
|
-
o = WinRM::Output.new
|
46
|
-
o[:exitcode] = 0
|
47
|
-
o[:data].concat([{ :stdout => "6.3.9600.0\r\n" }])
|
48
|
-
o
|
49
|
-
end
|
50
|
-
|
51
|
-
before do
|
52
|
-
service.stubs(:open_shell).returns(shell_id)
|
53
|
-
|
54
|
-
stub_powershell_script(shell_id,
|
55
|
-
"[environment]::OSVersion.Version.tostring()", version_output)
|
56
|
-
end
|
57
|
-
|
58
|
-
describe "#close" do
|
59
|
-
|
60
|
-
it "calls service#close_shell" do
|
61
|
-
executor.open
|
62
|
-
service.expects(:close_shell).with(shell_id)
|
63
|
-
|
64
|
-
executor.close
|
65
|
-
end
|
66
|
-
|
67
|
-
it "only calls service#close_shell once for multiple calls" do
|
68
|
-
executor.open
|
69
|
-
service.expects(:close_shell).with(shell_id).once
|
70
|
-
|
71
|
-
executor.close
|
72
|
-
executor.close
|
73
|
-
executor.close
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
describe "#open" do
|
78
|
-
|
79
|
-
it "calls service#open_shell" do
|
80
|
-
service.expects(:open_shell).returns(shell_id)
|
81
|
-
|
82
|
-
executor.open
|
83
|
-
end
|
84
|
-
|
85
|
-
it "returns a shell id as a string" do
|
86
|
-
executor.open.must_equal shell_id
|
87
|
-
end
|
88
|
-
|
89
|
-
describe "for modern windows distributions" do
|
90
|
-
|
91
|
-
let(:version_output) do
|
92
|
-
o = WinRM::Output.new
|
93
|
-
o[:exitcode] = 0
|
94
|
-
o[:data].concat([{ :stdout => "6.3.9600.0\r\n" }])
|
95
|
-
o
|
96
|
-
end
|
97
|
-
|
98
|
-
it "sets #max_commands to 1500 - 2" do
|
99
|
-
executor.max_commands.must_equal nil
|
100
|
-
executor.open
|
101
|
-
|
102
|
-
executor.max_commands.must_equal(1500 - 2)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
describe "for older/legacy windows distributions" do
|
107
|
-
|
108
|
-
let(:version_output) do
|
109
|
-
o = WinRM::Output.new
|
110
|
-
o[:exitcode] = 0
|
111
|
-
o[:data].concat([{ :stdout => "6.1.8500.0\r\n" }])
|
112
|
-
o
|
113
|
-
end
|
114
|
-
|
115
|
-
it "sets #max_commands to 15 - 2" do
|
116
|
-
executor.max_commands.must_equal nil
|
117
|
-
executor.open
|
118
|
-
|
119
|
-
executor.max_commands.must_equal(15 - 2)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
describe "#run_cmd" do
|
125
|
-
|
126
|
-
describe "when #open has not been previously called" do
|
127
|
-
|
128
|
-
it "raises a WinRMError error" do
|
129
|
-
err = proc { executor.run_cmd("nope") }.must_raise WinRM::WinRMError
|
130
|
-
err.message.must_equal "#{executor.class}#open must be called " \
|
131
|
-
"before any run methods are invoked"
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
describe "when #open has been previously called" do
|
136
|
-
|
137
|
-
let(:command_id) { "command-123" }
|
138
|
-
|
139
|
-
let(:echo_output) do
|
140
|
-
o = WinRM::Output.new
|
141
|
-
o[:exitcode] = 0
|
142
|
-
o[:data].concat([
|
143
|
-
{ :stdout => "Hello\r\n" },
|
144
|
-
{ :stderr => "Psst\r\n" }
|
145
|
-
])
|
146
|
-
o
|
147
|
-
end
|
148
|
-
|
149
|
-
before do
|
150
|
-
stub_cmd(shell_id, "echo", ["Hello"], echo_output, command_id)
|
151
|
-
|
152
|
-
executor.open
|
153
|
-
end
|
154
|
-
|
155
|
-
it "calls service#run_command" do
|
156
|
-
service.expects(:run_command).with(shell_id, "echo", ["Hello"])
|
157
|
-
|
158
|
-
executor.run_cmd("echo", ["Hello"])
|
159
|
-
end
|
160
|
-
|
161
|
-
it "calls service#get_command_output to get results" do
|
162
|
-
service.expects(:get_command_output).with(shell_id, command_id)
|
163
|
-
|
164
|
-
executor.run_cmd("echo", ["Hello"])
|
165
|
-
end
|
166
|
-
|
167
|
-
it "calls service#get_command_output with a block to get results" do
|
168
|
-
blk = proc { |_, _| "something" }
|
169
|
-
service.expects(:get_command_output).with(shell_id, command_id, &blk)
|
170
|
-
|
171
|
-
executor.run_cmd("echo", ["Hello"], &blk)
|
172
|
-
end
|
173
|
-
|
174
|
-
it "returns an Output object hash" do
|
175
|
-
executor.run_cmd("echo", ["Hello"]).must_equal echo_output
|
176
|
-
end
|
177
|
-
|
178
|
-
it "runs the block in #get_command_output when given" do
|
179
|
-
io_out = StringIO.new
|
180
|
-
io_err = StringIO.new
|
181
|
-
|
182
|
-
output = executor.run_cmd("echo", ["Hello"]) do |stdout, stderr|
|
183
|
-
io_out << stdout if stdout
|
184
|
-
io_err << stderr if stderr
|
185
|
-
end
|
186
|
-
|
187
|
-
io_out.string.must_equal "Hello\r\n"
|
188
|
-
io_err.string.must_equal "Psst\r\n"
|
189
|
-
output.must_equal echo_output
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
describe "when called many times over time" do
|
194
|
-
|
195
|
-
# use a "old" version of windows with lower max_commands threshold
|
196
|
-
# to trigger quicker shell recyles
|
197
|
-
let(:version_output) do
|
198
|
-
o = WinRM::Output.new
|
199
|
-
o[:exitcode] = 0
|
200
|
-
o[:data].concat([{ :stdout => "6.1.8500.0\r\n" }])
|
201
|
-
o
|
202
|
-
end
|
203
|
-
|
204
|
-
let(:echo_output) do
|
205
|
-
o = WinRM::Output.new
|
206
|
-
o[:exitcode] = 0
|
207
|
-
o[:data].concat([{ :stdout => "Hello\r\n" }])
|
208
|
-
o
|
209
|
-
end
|
210
|
-
|
211
|
-
before do
|
212
|
-
service.stubs(:open_shell).returns("s1", "s2")
|
213
|
-
service.stubs(:close_shell)
|
214
|
-
service.stubs(:run_command).yields("command-xxx")
|
215
|
-
service.stubs(:get_command_output).returns(echo_output)
|
216
|
-
stub_powershell_script("s1",
|
217
|
-
"[environment]::OSVersion.Version.tostring()", version_output)
|
218
|
-
end
|
219
|
-
|
220
|
-
it "resets the shell when #max_commands threshold is tripped" do
|
221
|
-
iterations = 35
|
222
|
-
reset_times = iterations / (15 - 2)
|
223
|
-
|
224
|
-
service.expects(:close_shell).times(reset_times)
|
225
|
-
executor.open
|
226
|
-
iterations.times { executor.run_cmd("echo", ["Hello"]) }
|
227
|
-
|
228
|
-
logged_output.string.lines.select { |l|
|
229
|
-
l =~ debug_line_with("[CommandExecutor] Resetting WinRM shell")
|
230
|
-
}.size.must_equal reset_times
|
231
|
-
end
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
describe "#run_powershell_script" do
|
236
|
-
|
237
|
-
describe "when #open has not been previously called" do
|
238
|
-
|
239
|
-
it "raises a WinRMError error" do
|
240
|
-
err = proc {
|
241
|
-
executor.run_powershell_script("nope")
|
242
|
-
}.must_raise WinRM::WinRMError
|
243
|
-
err.message.must_equal "#{executor.class}#open must be called " \
|
244
|
-
"before any run methods are invoked"
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
describe "when #open has been previously called" do
|
249
|
-
|
250
|
-
let(:command_id) { "command-123" }
|
251
|
-
|
252
|
-
let(:echo_output) do
|
253
|
-
o = WinRM::Output.new
|
254
|
-
o[:exitcode] = 0
|
255
|
-
o[:data].concat([
|
256
|
-
{ :stdout => "Hello\r\n" },
|
257
|
-
{ :stderr => "Psst\r\n" }
|
258
|
-
])
|
259
|
-
o
|
260
|
-
end
|
261
|
-
|
262
|
-
before do
|
263
|
-
stub_powershell_script(shell_id, "echo Hello", echo_output, command_id)
|
264
|
-
|
265
|
-
executor.open
|
266
|
-
end
|
267
|
-
|
268
|
-
it "calls service#run_command" do
|
269
|
-
service.expects(:run_command).with(
|
270
|
-
shell_id,
|
271
|
-
"powershell",
|
272
|
-
["-encodedCommand", WinRM::PowershellScript.new("echo Hello").encoded]
|
273
|
-
)
|
274
|
-
|
275
|
-
executor.run_powershell_script("echo Hello")
|
276
|
-
end
|
277
|
-
|
278
|
-
it "calls service#get_command_output to get results" do
|
279
|
-
service.expects(:get_command_output).with(shell_id, command_id)
|
280
|
-
|
281
|
-
executor.run_powershell_script("echo Hello")
|
282
|
-
end
|
283
|
-
|
284
|
-
it "calls service#get_command_output with a block to get results" do
|
285
|
-
blk = proc { |_, _| "something" }
|
286
|
-
service.expects(:get_command_output).with(shell_id, command_id, &blk)
|
287
|
-
|
288
|
-
executor.run_powershell_script("echo Hello", &blk)
|
289
|
-
end
|
290
|
-
|
291
|
-
it "returns an Output object hash" do
|
292
|
-
executor.run_powershell_script("echo Hello").must_equal echo_output
|
293
|
-
end
|
294
|
-
|
295
|
-
it "runs the block in #get_command_output when given" do
|
296
|
-
io_out = StringIO.new
|
297
|
-
io_err = StringIO.new
|
298
|
-
|
299
|
-
output = executor.run_powershell_script("echo Hello") do |stdout, stderr|
|
300
|
-
io_out << stdout if stdout
|
301
|
-
io_err << stderr if stderr
|
302
|
-
end
|
303
|
-
|
304
|
-
io_out.string.must_equal "Hello\r\n"
|
305
|
-
io_err.string.must_equal "Psst\r\n"
|
306
|
-
output.must_equal echo_output
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
describe "when called many times over time" do
|
311
|
-
|
312
|
-
# use a "old" version of windows with lower max_commands threshold
|
313
|
-
# to trigger quicker shell recyles
|
314
|
-
let(:version_output) do
|
315
|
-
o = WinRM::Output.new
|
316
|
-
o[:exitcode] = 0
|
317
|
-
o[:data].concat([{ :stdout => "6.1.8500.0\r\n" }])
|
318
|
-
o
|
319
|
-
end
|
320
|
-
|
321
|
-
let(:echo_output) do
|
322
|
-
o = WinRM::Output.new
|
323
|
-
o[:exitcode] = 0
|
324
|
-
o[:data].concat([{ :stdout => "Hello\r\n" }])
|
325
|
-
o
|
326
|
-
end
|
327
|
-
|
328
|
-
before do
|
329
|
-
service.stubs(:open_shell).returns("s1", "s2")
|
330
|
-
service.stubs(:close_shell)
|
331
|
-
service.stubs(:run_command).yields("command-xxx")
|
332
|
-
service.stubs(:get_command_output).returns(echo_output)
|
333
|
-
stub_powershell_script("s1",
|
334
|
-
"[environment]::OSVersion.Version.tostring()", version_output)
|
335
|
-
end
|
336
|
-
|
337
|
-
it "resets the shell when #max_commands threshold is tripped" do
|
338
|
-
iterations = 35
|
339
|
-
reset_times = iterations / (15 - 2)
|
340
|
-
|
341
|
-
service.expects(:close_shell).times(reset_times)
|
342
|
-
executor.open
|
343
|
-
iterations.times { executor.run_powershell_script("echo Hello") }
|
344
|
-
|
345
|
-
logged_output.string.lines.select { |l|
|
346
|
-
l =~ debug_line_with("[CommandExecutor] Resetting WinRM shell")
|
347
|
-
}.size.must_equal reset_times
|
348
|
-
end
|
349
|
-
end
|
350
|
-
end
|
351
|
-
|
352
|
-
describe "#shell" do
|
353
|
-
|
354
|
-
it "is initially nil" do
|
355
|
-
executor.shell.must_equal nil
|
356
|
-
end
|
357
|
-
|
358
|
-
it "is set after #open is called" do
|
359
|
-
executor.open
|
360
|
-
|
361
|
-
executor.shell.must_equal shell_id
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
def decode(powershell)
|
366
|
-
Base64.strict_decode64(powershell).encode("UTF-8", "UTF-16LE")
|
367
|
-
end
|
368
|
-
|
369
|
-
def debug_line_with(msg)
|
370
|
-
%r{^D, .* : #{Regexp.escape(msg)}}
|
371
|
-
end
|
372
|
-
|
373
|
-
def regexify(string)
|
374
|
-
Regexp.new(Regexp.escape(string))
|
375
|
-
end
|
376
|
-
|
377
|
-
def regexify_line(string)
|
378
|
-
Regexp.new("^#{Regexp.escape(string)}$")
|
379
|
-
end
|
380
|
-
|
381
|
-
# rubocop:disable Metrics/ParameterLists
|
382
|
-
def stub_cmd(shell_id, cmd, args, output, command_id = nil, &block)
|
383
|
-
command_id ||= SecureRandom.uuid
|
384
|
-
|
385
|
-
service.stubs(:run_command).with(shell_id, cmd, args).yields(command_id)
|
386
|
-
service.stubs(:get_command_output).with(shell_id, command_id, &block).
|
387
|
-
yields(output.stdout, output.stderr).returns(output)
|
388
|
-
end
|
389
|
-
|
390
|
-
def stub_powershell_script(shell_id, script, output, command_id = nil)
|
391
|
-
stub_cmd(
|
392
|
-
shell_id,
|
393
|
-
"powershell",
|
394
|
-
["-encodedCommand", WinRM::PowershellScript.new(script).encoded],
|
395
|
-
output,
|
396
|
-
command_id
|
397
|
-
)
|
398
|
-
end
|
399
|
-
# rubocop:enable Metrics/ParameterLists
|
400
|
-
end
|