winrm 2.0.3 → 2.1.0
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/.gitignore +10 -10
- data/.rubocop.yml +26 -26
- data/.travis.yml +11 -11
- data/Gemfile +3 -3
- data/README.md +260 -233
- data/Rakefile +34 -34
- data/Vagrantfile +6 -6
- data/WinrmAppveyor.psm1 +31 -31
- data/appveyor.yml +51 -51
- data/changelog.md +104 -101
- data/lib/winrm.rb +39 -39
- data/lib/winrm/connection.rb +83 -82
- data/lib/winrm/connection_opts.rb +91 -91
- data/lib/winrm/exceptions.rb +76 -76
- data/lib/winrm/http/response_handler.rb +96 -96
- data/lib/winrm/http/transport.rb +424 -424
- data/lib/winrm/http/transport_factory.rb +68 -68
- data/lib/winrm/output.rb +59 -59
- data/lib/winrm/psrp/create_pipeline.xml.erb +167 -167
- data/lib/winrm/psrp/fragment.rb +70 -70
- data/lib/winrm/psrp/init_runspace_pool.xml.erb +224 -224
- data/lib/winrm/psrp/message.rb +130 -130
- data/lib/winrm/psrp/message_data.rb +42 -42
- data/lib/winrm/psrp/message_data/base.rb +49 -49
- data/lib/winrm/psrp/message_data/error_record.rb +68 -68
- data/lib/winrm/psrp/message_data/pipeline_host_call.rb +32 -32
- data/lib/winrm/psrp/message_data/pipeline_output.rb +49 -49
- data/lib/winrm/psrp/message_data/pipeline_state.rb +40 -40
- data/lib/winrm/psrp/message_data/runspacepool_host_call.rb +32 -32
- data/lib/winrm/psrp/message_data/runspacepool_state.rb +39 -39
- data/lib/winrm/psrp/message_data/session_capability.rb +36 -36
- data/lib/winrm/psrp/message_defragmenter.rb +62 -62
- data/lib/winrm/psrp/message_factory.rb +75 -75
- data/lib/winrm/psrp/message_fragmenter.rb +60 -60
- data/lib/winrm/psrp/powershell_output_decoder.rb +139 -139
- data/lib/winrm/psrp/receive_response_reader.rb +97 -97
- data/lib/winrm/psrp/session_capability.xml.erb +7 -7
- data/lib/winrm/psrp/uuid.rb +40 -40
- data/lib/winrm/shells/base.rb +180 -175
- data/lib/winrm/shells/cmd.rb +65 -65
- data/lib/winrm/shells/power_shell.rb +202 -202
- data/lib/winrm/shells/retryable.rb +45 -45
- data/lib/winrm/shells/shell_factory.rb +58 -50
- data/lib/winrm/version.rb +7 -7
- data/lib/winrm/wsmv/base.rb +59 -59
- data/lib/winrm/wsmv/cleanup_command.rb +61 -61
- data/lib/winrm/wsmv/close_shell.rb +50 -50
- data/lib/winrm/wsmv/command.rb +101 -101
- data/lib/winrm/wsmv/command_output.rb +76 -76
- data/lib/winrm/wsmv/command_output_decoder.rb +55 -55
- data/lib/winrm/wsmv/configuration.rb +46 -46
- data/lib/winrm/wsmv/create_pipeline.rb +66 -66
- data/lib/winrm/wsmv/create_shell.rb +119 -119
- data/lib/winrm/wsmv/header.rb +203 -203
- data/lib/winrm/wsmv/init_runspace_pool.rb +95 -95
- data/lib/winrm/wsmv/iso8601_duration.rb +60 -60
- data/lib/winrm/wsmv/keep_alive.rb +68 -68
- data/lib/winrm/wsmv/receive_response_reader.rb +126 -126
- data/lib/winrm/wsmv/send_data.rb +68 -68
- data/lib/winrm/wsmv/soap.rb +51 -51
- data/lib/winrm/wsmv/wql_query.rb +79 -79
- data/lib/winrm/wsmv/write_stdin.rb +88 -88
- data/tests/integration/auth_timeout_spec.rb +18 -18
- data/tests/integration/cmd_spec.rb +131 -110
- data/tests/integration/config-example.yml +16 -16
- data/tests/integration/issue_59_spec.rb +26 -26
- data/tests/integration/powershell_spec.rb +165 -165
- data/tests/integration/spec_helper.rb +65 -65
- data/tests/integration/transport_spec.rb +99 -99
- data/tests/integration/wql_spec.rb +16 -16
- data/tests/matchers.rb +60 -60
- data/tests/spec/configuration_spec.rb +184 -184
- data/tests/spec/connection_spec.rb +39 -39
- data/tests/spec/exception_spec.rb +50 -50
- data/tests/spec/http/transport_factory_spec.rb +68 -68
- data/tests/spec/http/transport_spec.rb +44 -44
- data/tests/spec/output_spec.rb +127 -127
- data/tests/spec/psrp/fragment_spec.rb +62 -62
- data/tests/spec/psrp/message_data/base_spec.rb +13 -13
- data/tests/spec/psrp/message_data/error_record_spec.rb +41 -41
- data/tests/spec/psrp/message_data/pipeline_host_call_spec.rb +25 -25
- data/tests/spec/psrp/message_data/pipeline_output_spec.rb +32 -32
- data/tests/spec/psrp/message_data/pipeline_state_spec.rb +40 -40
- data/tests/spec/psrp/message_data/runspace_pool_host_call_spec.rb +25 -25
- data/tests/spec/psrp/message_data/runspacepool_state_spec.rb +16 -16
- data/tests/spec/psrp/message_data/session_capability_spec.rb +30 -30
- data/tests/spec/psrp/message_data_spec.rb +35 -35
- data/tests/spec/psrp/message_defragmenter_spec.rb +47 -47
- data/tests/spec/psrp/message_fragmenter_spec.rb +105 -105
- data/tests/spec/psrp/powershell_output_decoder_spec.rb +100 -100
- data/tests/spec/psrp/psrp_message_spec.rb +70 -70
- data/tests/spec/psrp/recieve_response_reader_spec.rb +172 -172
- data/tests/spec/psrp/uuid_spec.rb +28 -28
- data/tests/spec/response_handler_spec.rb +61 -61
- data/tests/spec/shells/base_spec.rb +202 -202
- data/tests/spec/shells/cmd_spec.rb +75 -75
- data/tests/spec/shells/powershell_spec.rb +175 -175
- data/tests/spec/spec_helper.rb +47 -47
- data/tests/spec/stubs/clixml/error_record.xml.erb +84 -84
- data/tests/spec/stubs/clixml/pipeline_state.xml.erb +88 -88
- data/tests/spec/stubs/responses/get_command_output_response.xml.erb +13 -13
- data/tests/spec/stubs/responses/get_command_output_response_not_done.xml.erb +10 -10
- data/tests/spec/stubs/responses/get_powershell_keepalive_response.xml.erb +10 -10
- data/tests/spec/stubs/responses/get_powershell_output_response.xml.erb +12 -12
- data/tests/spec/stubs/responses/get_powershell_output_response_not_done.xml.erb +9 -9
- data/tests/spec/stubs/responses/open_shell_v1.xml +19 -19
- data/tests/spec/stubs/responses/open_shell_v2.xml +20 -20
- data/tests/spec/stubs/responses/soap_fault_v1.xml +36 -36
- data/tests/spec/stubs/responses/soap_fault_v2.xml +42 -42
- data/tests/spec/stubs/responses/wmi_error_v2.xml +41 -41
- data/tests/spec/wsmv/cleanup_command_spec.rb +22 -22
- data/tests/spec/wsmv/close_shell_spec.rb +17 -17
- data/tests/spec/wsmv/command_output_decoder_spec.rb +37 -37
- data/tests/spec/wsmv/command_output_spec.rb +45 -45
- data/tests/spec/wsmv/command_spec.rb +19 -19
- data/tests/spec/wsmv/configuration_spec.rb +17 -17
- data/tests/spec/wsmv/create_pipeline_spec.rb +31 -31
- data/tests/spec/wsmv/create_shell_spec.rb +38 -38
- data/tests/spec/wsmv/init_runspace_pool_spec.rb +36 -36
- data/tests/spec/wsmv/keep_alive_spec.rb +21 -21
- data/tests/spec/wsmv/receive_response_reader_spec.rb +123 -123
- data/tests/spec/wsmv/send_data_spec.rb +30 -30
- data/tests/spec/wsmv/wql_query_spec.rb +13 -13
- data/tests/spec/wsmv/write_stdin_spec.rb +22 -22
- data/winrm.gemspec +42 -42
- metadata +2 -2
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
# encoding: UTF-8
|
|
2
|
-
describe WinRM::PSRP::UUID do
|
|
3
|
-
subject(:uuid_helper) do
|
|
4
|
-
Object.new.extend(WinRM::PSRP::UUID)
|
|
5
|
-
end
|
|
6
|
-
context 'uuid is nil' do
|
|
7
|
-
uuid = nil
|
|
8
|
-
it 'should return an empty byte array' do
|
|
9
|
-
bytes = uuid_helper.uuid_to_windows_guid_bytes(uuid)
|
|
10
|
-
expect(bytes).to eq([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
context 'uuid 08785e96-eb1b-4a74-a767-7b56e8f13ea9 with lower case letters' do
|
|
14
|
-
uuid = '08785e96-eb1b-4a74-a767-7b56e8f13ea9'
|
|
15
|
-
it 'should return a Windows GUID struct compatible little endian byte array' do
|
|
16
|
-
bytes = uuid_helper.uuid_to_windows_guid_bytes(uuid)
|
|
17
|
-
expect(bytes).to eq([150, 94, 120, 8, 27, 235, 116, 74, 167, 103, 123, 86, 232, 241, 62, 169])
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
context 'uuid 045F9E19D-8B77-4394-AB0C-197497661668 with upper case letters' do
|
|
21
|
-
uuid = '45F9E19D-8B77-4394-AB0C-197497661668'
|
|
22
|
-
it 'should return a Windows GUID struct compatible little endian byte array' do
|
|
23
|
-
bytes = uuid_helper.uuid_to_windows_guid_bytes(uuid)
|
|
24
|
-
expect(bytes).to eq(
|
|
25
|
-
[157, 225, 249, 69, 119, 139, 148, 67, 171, 12, 25, 116, 151, 102, 22, 104])
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
describe WinRM::PSRP::UUID do
|
|
3
|
+
subject(:uuid_helper) do
|
|
4
|
+
Object.new.extend(WinRM::PSRP::UUID)
|
|
5
|
+
end
|
|
6
|
+
context 'uuid is nil' do
|
|
7
|
+
uuid = nil
|
|
8
|
+
it 'should return an empty byte array' do
|
|
9
|
+
bytes = uuid_helper.uuid_to_windows_guid_bytes(uuid)
|
|
10
|
+
expect(bytes).to eq([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
context 'uuid 08785e96-eb1b-4a74-a767-7b56e8f13ea9 with lower case letters' do
|
|
14
|
+
uuid = '08785e96-eb1b-4a74-a767-7b56e8f13ea9'
|
|
15
|
+
it 'should return a Windows GUID struct compatible little endian byte array' do
|
|
16
|
+
bytes = uuid_helper.uuid_to_windows_guid_bytes(uuid)
|
|
17
|
+
expect(bytes).to eq([150, 94, 120, 8, 27, 235, 116, 74, 167, 103, 123, 86, 232, 241, 62, 169])
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
context 'uuid 045F9E19D-8B77-4394-AB0C-197497661668 with upper case letters' do
|
|
21
|
+
uuid = '45F9E19D-8B77-4394-AB0C-197497661668'
|
|
22
|
+
it 'should return a Windows GUID struct compatible little endian byte array' do
|
|
23
|
+
bytes = uuid_helper.uuid_to_windows_guid_bytes(uuid)
|
|
24
|
+
expect(bytes).to eq(
|
|
25
|
+
[157, 225, 249, 69, 119, 139, 148, 67, 171, 12, 25, 116, 151, 102, 22, 104])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
# encoding: UTF-8
|
|
2
|
-
require 'winrm/http/response_handler'
|
|
3
|
-
|
|
4
|
-
describe 'response handler', unit: true do
|
|
5
|
-
%w(v1 v2).each do |winrm_version|
|
|
6
|
-
context "winrm_version #{winrm_version}" do
|
|
7
|
-
let(:soap_fault) { stubbed_response("soap_fault_#{winrm_version}.xml") }
|
|
8
|
-
let(:open_shell) { stubbed_response("open_shell_#{winrm_version}.xml") }
|
|
9
|
-
|
|
10
|
-
describe "successful 200 #{winrm_version} response" do
|
|
11
|
-
it 'returns an xml doc' do
|
|
12
|
-
handler = WinRM::ResponseHandler.new(open_shell, 200)
|
|
13
|
-
xml_doc = handler.parse_to_xml
|
|
14
|
-
expect(xml_doc).to be_instance_of(REXML::Document)
|
|
15
|
-
expect(xml_doc.to_s).to eq(REXML::Document.new(open_shell).to_s)
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
describe "failed 500 #{winrm_version} response" do
|
|
20
|
-
it 'raises a WinRMHTTPTransportError' do
|
|
21
|
-
handler = WinRM::ResponseHandler.new('', 500)
|
|
22
|
-
expect { handler.parse_to_xml }.to raise_error(WinRM::WinRMHTTPTransportError)
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
describe "failed 401 #{winrm_version} response" do
|
|
27
|
-
it 'raises a WinRMAuthorizationError' do
|
|
28
|
-
handler = WinRM::ResponseHandler.new('', 401)
|
|
29
|
-
expect { handler.parse_to_xml }.to raise_error(WinRM::WinRMAuthorizationError)
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
describe "failed 400 #{winrm_version} response" do
|
|
34
|
-
it 'raises a WinRMWSManFault' do
|
|
35
|
-
handler = WinRM::ResponseHandler.new(soap_fault, 400)
|
|
36
|
-
begin
|
|
37
|
-
handler.parse_to_xml
|
|
38
|
-
rescue WinRM::WinRMWSManFault => e
|
|
39
|
-
expect(e.fault_code).to eq('2150858778')
|
|
40
|
-
expect(e.fault_description).to include(
|
|
41
|
-
'The specified class does not exist in the given namespace')
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
describe 'failed 500 WMI error response' do
|
|
49
|
-
let(:wmi_error) { stubbed_response('wmi_error_v2.xml') }
|
|
50
|
-
|
|
51
|
-
it 'raises a WinRMWMIError' do
|
|
52
|
-
handler = WinRM::ResponseHandler.new(wmi_error, 500)
|
|
53
|
-
begin
|
|
54
|
-
handler.parse_to_xml
|
|
55
|
-
rescue WinRM::WinRMWMIError => e
|
|
56
|
-
expect(e.error_code).to eq('2150859173')
|
|
57
|
-
expect(e.error).to include('The WS-Management service cannot process the request.')
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
require 'winrm/http/response_handler'
|
|
3
|
+
|
|
4
|
+
describe 'response handler', unit: true do
|
|
5
|
+
%w(v1 v2).each do |winrm_version|
|
|
6
|
+
context "winrm_version #{winrm_version}" do
|
|
7
|
+
let(:soap_fault) { stubbed_response("soap_fault_#{winrm_version}.xml") }
|
|
8
|
+
let(:open_shell) { stubbed_response("open_shell_#{winrm_version}.xml") }
|
|
9
|
+
|
|
10
|
+
describe "successful 200 #{winrm_version} response" do
|
|
11
|
+
it 'returns an xml doc' do
|
|
12
|
+
handler = WinRM::ResponseHandler.new(open_shell, 200)
|
|
13
|
+
xml_doc = handler.parse_to_xml
|
|
14
|
+
expect(xml_doc).to be_instance_of(REXML::Document)
|
|
15
|
+
expect(xml_doc.to_s).to eq(REXML::Document.new(open_shell).to_s)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe "failed 500 #{winrm_version} response" do
|
|
20
|
+
it 'raises a WinRMHTTPTransportError' do
|
|
21
|
+
handler = WinRM::ResponseHandler.new('', 500)
|
|
22
|
+
expect { handler.parse_to_xml }.to raise_error(WinRM::WinRMHTTPTransportError)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe "failed 401 #{winrm_version} response" do
|
|
27
|
+
it 'raises a WinRMAuthorizationError' do
|
|
28
|
+
handler = WinRM::ResponseHandler.new('', 401)
|
|
29
|
+
expect { handler.parse_to_xml }.to raise_error(WinRM::WinRMAuthorizationError)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe "failed 400 #{winrm_version} response" do
|
|
34
|
+
it 'raises a WinRMWSManFault' do
|
|
35
|
+
handler = WinRM::ResponseHandler.new(soap_fault, 400)
|
|
36
|
+
begin
|
|
37
|
+
handler.parse_to_xml
|
|
38
|
+
rescue WinRM::WinRMWSManFault => e
|
|
39
|
+
expect(e.fault_code).to eq('2150858778')
|
|
40
|
+
expect(e.fault_description).to include(
|
|
41
|
+
'The specified class does not exist in the given namespace')
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe 'failed 500 WMI error response' do
|
|
49
|
+
let(:wmi_error) { stubbed_response('wmi_error_v2.xml') }
|
|
50
|
+
|
|
51
|
+
it 'raises a WinRMWMIError' do
|
|
52
|
+
handler = WinRM::ResponseHandler.new(wmi_error, 500)
|
|
53
|
+
begin
|
|
54
|
+
handler.parse_to_xml
|
|
55
|
+
rescue WinRM::WinRMWMIError => e
|
|
56
|
+
expect(e.error_code).to eq('2150859173')
|
|
57
|
+
expect(e.error).to include('The WS-Management service cannot process the request.')
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -1,202 +1,202 @@
|
|
|
1
|
-
# encoding: UTF-8
|
|
2
|
-
|
|
3
|
-
require 'winrm/shells/base'
|
|
4
|
-
|
|
5
|
-
# Dummy shell class
|
|
6
|
-
class DummyShell < WinRM::Shells::Base
|
|
7
|
-
class << self
|
|
8
|
-
def finalize(connection_opts, transport, shell_id)
|
|
9
|
-
proc { DummyShell.close_shell(connection_opts, transport, shell_id) }
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def close_shell(_connection_opts, _transport, _shell_id)
|
|
13
|
-
@closed = true
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def closed?
|
|
17
|
-
@closed
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def open_shell
|
|
22
|
-
@closed = false
|
|
23
|
-
'shell_id'
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def send_command(_command, _arguments)
|
|
27
|
-
'command_id'
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def out_streams
|
|
31
|
-
%w(std)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
describe DummyShell do
|
|
36
|
-
let(:retry_limit) { 1 }
|
|
37
|
-
let(:shell_id) { 'shell_id' }
|
|
38
|
-
let(:output) { 'output' }
|
|
39
|
-
let(:command_id) { 'command_id' }
|
|
40
|
-
let(:payload) { 'message_payload' }
|
|
41
|
-
let(:command) { 'command' }
|
|
42
|
-
let(:arguments) { ['args'] }
|
|
43
|
-
let(:output_message) { double('output_message', build: 'output_message') }
|
|
44
|
-
let(:connection_options) { { max_commands: 100, retry_limit: retry_limit, retry_delay: 0 } }
|
|
45
|
-
let(:transport) { double('transport') }
|
|
46
|
-
let(:reader) { double('reader') }
|
|
47
|
-
|
|
48
|
-
before do
|
|
49
|
-
allow(subject).to receive(:response_reader).and_return(reader)
|
|
50
|
-
allow(subject).to receive(:command_output_message).with(shell_id, command_id)
|
|
51
|
-
.and_return(output_message)
|
|
52
|
-
allow(reader).to receive(:read_output).with(output_message).and_return(output)
|
|
53
|
-
allow(transport).to receive(:send_request)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
subject { described_class.new(connection_options, transport, Logging.logger['test']) }
|
|
57
|
-
|
|
58
|
-
shared_examples 'retry shell command' do
|
|
59
|
-
it 'only closes the shell if there are too many' do
|
|
60
|
-
if fault == WinRM::Shells::Base::TOO_MANY_COMMANDS
|
|
61
|
-
expect(DummyShell).to receive(:close_shell)
|
|
62
|
-
else
|
|
63
|
-
expect(DummyShell).not_to receive(:close_shell)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
subject.run(command, arguments)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
it 'opens a new shell' do
|
|
70
|
-
expect(subject).to receive(:open).and_call_original.twice
|
|
71
|
-
|
|
72
|
-
subject.run(command, arguments)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
it 'retries the command once' do
|
|
76
|
-
expect(subject).to receive(:send_command).twice
|
|
77
|
-
|
|
78
|
-
subject.run(command, arguments)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
describe '#run' do
|
|
83
|
-
it 'opens a shell' do
|
|
84
|
-
subject.run(command, arguments)
|
|
85
|
-
expect(subject.shell_id).not_to be nil
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
it 'returns output from generated command' do
|
|
89
|
-
expect(subject.run(command, arguments)).to eq output
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
it 'sends cleanup message through transport' do
|
|
93
|
-
allow(SecureRandom).to receive(:uuid).and_return('uuid')
|
|
94
|
-
expect(transport).to receive(:send_request)
|
|
95
|
-
.with(
|
|
96
|
-
WinRM::WSMV::CleanupCommand.new(
|
|
97
|
-
connection_options,
|
|
98
|
-
shell_uri: nil,
|
|
99
|
-
shell_id: shell_id,
|
|
100
|
-
command_id: command_id
|
|
101
|
-
).build
|
|
102
|
-
)
|
|
103
|
-
subject.run(command, arguments)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
it 'does not error if cleanup is aborted' do
|
|
107
|
-
allow(SecureRandom).to receive(:uuid).and_return('uuid')
|
|
108
|
-
expect(transport).to receive(:send_request)
|
|
109
|
-
.with(
|
|
110
|
-
WinRM::WSMV::CleanupCommand.new(
|
|
111
|
-
connection_options,
|
|
112
|
-
shell_uri: nil,
|
|
113
|
-
shell_id: shell_id,
|
|
114
|
-
command_id: command_id
|
|
115
|
-
).build
|
|
116
|
-
).and_raise(WinRM::WinRMWSManFault.new('oops', '995'))
|
|
117
|
-
subject.run(command, arguments)
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
it 'opens a shell only once when shell is already open' do
|
|
121
|
-
expect(subject).to receive(:open_shell).and_call_original.once
|
|
122
|
-
subject.run(command, arguments)
|
|
123
|
-
subject.run(command, arguments)
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
describe 'connection resets' do
|
|
127
|
-
before do
|
|
128
|
-
@times_called = 0
|
|
129
|
-
|
|
130
|
-
allow(subject).to receive(:send_command) do
|
|
131
|
-
@times_called += 1
|
|
132
|
-
raise WinRM::WinRMWSManFault.new('oops', fault) if @times_called == 1
|
|
133
|
-
command_id
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
context 'when shell is closed on server' do
|
|
138
|
-
let(:fault) { '2150858843' }
|
|
139
|
-
|
|
140
|
-
include_examples 'retry shell command'
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
context 'when shell accesses a deleted registry key' do
|
|
144
|
-
let(:fault) { '2147943418' }
|
|
145
|
-
|
|
146
|
-
include_examples 'retry shell command'
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
context 'when maximum number of concurrent shells is exceeded' do
|
|
150
|
-
let(:fault) { '2150859174' }
|
|
151
|
-
|
|
152
|
-
include_examples 'retry shell command'
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
context 'open_shell fails' do
|
|
157
|
-
let(:retry_limit) { 2 }
|
|
158
|
-
let(:output_message2) { double('message') }
|
|
159
|
-
|
|
160
|
-
it 'retries and raises failure if it never succeeds' do
|
|
161
|
-
expect(subject).to receive(:open_shell)
|
|
162
|
-
.and_raise(Errno::ECONNREFUSED).exactly(retry_limit).times
|
|
163
|
-
expect { subject.run(command) }.to raise_error(Errno::ECONNREFUSED)
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
it 'retries and returns shell on success' do
|
|
167
|
-
@times = 0
|
|
168
|
-
allow(subject).to receive(:command_output_message).with('shell_id 2', command_id)
|
|
169
|
-
.and_return(output_message2)
|
|
170
|
-
allow(reader).to receive(:read_output)
|
|
171
|
-
.with(output_message2).and_return(output)
|
|
172
|
-
allow(subject).to receive(:open_shell) do
|
|
173
|
-
@times += 1
|
|
174
|
-
raise(Errno::ECONNREFUSED) if @times == 1
|
|
175
|
-
"shell_id #{@times}"
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
subject.run(command, arguments)
|
|
179
|
-
expect(subject.shell_id).to eq 'shell_id 2'
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
describe '#close' do
|
|
185
|
-
it 'does not close if not opened' do
|
|
186
|
-
expect(DummyShell).not_to receive(:close_shell)
|
|
187
|
-
subject.close
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
it 'close shell if opened' do
|
|
191
|
-
subject.run(command, arguments)
|
|
192
|
-
subject.close
|
|
193
|
-
expect(DummyShell.closed?).to be(true)
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
it 'nils out the shell_id' do
|
|
197
|
-
subject.run(command, arguments)
|
|
198
|
-
subject.close
|
|
199
|
-
expect(subject.shell_id).to be(nil)
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
end
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
require 'winrm/shells/base'
|
|
4
|
+
|
|
5
|
+
# Dummy shell class
|
|
6
|
+
class DummyShell < WinRM::Shells::Base
|
|
7
|
+
class << self
|
|
8
|
+
def finalize(connection_opts, transport, shell_id)
|
|
9
|
+
proc { DummyShell.close_shell(connection_opts, transport, shell_id) }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def close_shell(_connection_opts, _transport, _shell_id)
|
|
13
|
+
@closed = true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def closed?
|
|
17
|
+
@closed
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def open_shell
|
|
22
|
+
@closed = false
|
|
23
|
+
'shell_id'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def send_command(_command, _arguments)
|
|
27
|
+
'command_id'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def out_streams
|
|
31
|
+
%w(std)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe DummyShell do
|
|
36
|
+
let(:retry_limit) { 1 }
|
|
37
|
+
let(:shell_id) { 'shell_id' }
|
|
38
|
+
let(:output) { 'output' }
|
|
39
|
+
let(:command_id) { 'command_id' }
|
|
40
|
+
let(:payload) { 'message_payload' }
|
|
41
|
+
let(:command) { 'command' }
|
|
42
|
+
let(:arguments) { ['args'] }
|
|
43
|
+
let(:output_message) { double('output_message', build: 'output_message') }
|
|
44
|
+
let(:connection_options) { { max_commands: 100, retry_limit: retry_limit, retry_delay: 0 } }
|
|
45
|
+
let(:transport) { double('transport') }
|
|
46
|
+
let(:reader) { double('reader') }
|
|
47
|
+
|
|
48
|
+
before do
|
|
49
|
+
allow(subject).to receive(:response_reader).and_return(reader)
|
|
50
|
+
allow(subject).to receive(:command_output_message).with(shell_id, command_id)
|
|
51
|
+
.and_return(output_message)
|
|
52
|
+
allow(reader).to receive(:read_output).with(output_message).and_return(output)
|
|
53
|
+
allow(transport).to receive(:send_request)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
subject { described_class.new(connection_options, transport, Logging.logger['test']) }
|
|
57
|
+
|
|
58
|
+
shared_examples 'retry shell command' do
|
|
59
|
+
it 'only closes the shell if there are too many' do
|
|
60
|
+
if fault == WinRM::Shells::Base::TOO_MANY_COMMANDS
|
|
61
|
+
expect(DummyShell).to receive(:close_shell)
|
|
62
|
+
else
|
|
63
|
+
expect(DummyShell).not_to receive(:close_shell)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
subject.run(command, arguments)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'opens a new shell' do
|
|
70
|
+
expect(subject).to receive(:open).and_call_original.twice
|
|
71
|
+
|
|
72
|
+
subject.run(command, arguments)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'retries the command once' do
|
|
76
|
+
expect(subject).to receive(:send_command).twice
|
|
77
|
+
|
|
78
|
+
subject.run(command, arguments)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe '#run' do
|
|
83
|
+
it 'opens a shell' do
|
|
84
|
+
subject.run(command, arguments)
|
|
85
|
+
expect(subject.shell_id).not_to be nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'returns output from generated command' do
|
|
89
|
+
expect(subject.run(command, arguments)).to eq output
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'sends cleanup message through transport' do
|
|
93
|
+
allow(SecureRandom).to receive(:uuid).and_return('uuid')
|
|
94
|
+
expect(transport).to receive(:send_request)
|
|
95
|
+
.with(
|
|
96
|
+
WinRM::WSMV::CleanupCommand.new(
|
|
97
|
+
connection_options,
|
|
98
|
+
shell_uri: nil,
|
|
99
|
+
shell_id: shell_id,
|
|
100
|
+
command_id: command_id
|
|
101
|
+
).build
|
|
102
|
+
)
|
|
103
|
+
subject.run(command, arguments)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'does not error if cleanup is aborted' do
|
|
107
|
+
allow(SecureRandom).to receive(:uuid).and_return('uuid')
|
|
108
|
+
expect(transport).to receive(:send_request)
|
|
109
|
+
.with(
|
|
110
|
+
WinRM::WSMV::CleanupCommand.new(
|
|
111
|
+
connection_options,
|
|
112
|
+
shell_uri: nil,
|
|
113
|
+
shell_id: shell_id,
|
|
114
|
+
command_id: command_id
|
|
115
|
+
).build
|
|
116
|
+
).and_raise(WinRM::WinRMWSManFault.new('oops', '995'))
|
|
117
|
+
subject.run(command, arguments)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'opens a shell only once when shell is already open' do
|
|
121
|
+
expect(subject).to receive(:open_shell).and_call_original.once
|
|
122
|
+
subject.run(command, arguments)
|
|
123
|
+
subject.run(command, arguments)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe 'connection resets' do
|
|
127
|
+
before do
|
|
128
|
+
@times_called = 0
|
|
129
|
+
|
|
130
|
+
allow(subject).to receive(:send_command) do
|
|
131
|
+
@times_called += 1
|
|
132
|
+
raise WinRM::WinRMWSManFault.new('oops', fault) if @times_called == 1
|
|
133
|
+
command_id
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
context 'when shell is closed on server' do
|
|
138
|
+
let(:fault) { '2150858843' }
|
|
139
|
+
|
|
140
|
+
include_examples 'retry shell command'
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
context 'when shell accesses a deleted registry key' do
|
|
144
|
+
let(:fault) { '2147943418' }
|
|
145
|
+
|
|
146
|
+
include_examples 'retry shell command'
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
context 'when maximum number of concurrent shells is exceeded' do
|
|
150
|
+
let(:fault) { '2150859174' }
|
|
151
|
+
|
|
152
|
+
include_examples 'retry shell command'
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
context 'open_shell fails' do
|
|
157
|
+
let(:retry_limit) { 2 }
|
|
158
|
+
let(:output_message2) { double('message') }
|
|
159
|
+
|
|
160
|
+
it 'retries and raises failure if it never succeeds' do
|
|
161
|
+
expect(subject).to receive(:open_shell)
|
|
162
|
+
.and_raise(Errno::ECONNREFUSED).exactly(retry_limit).times
|
|
163
|
+
expect { subject.run(command) }.to raise_error(Errno::ECONNREFUSED)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it 'retries and returns shell on success' do
|
|
167
|
+
@times = 0
|
|
168
|
+
allow(subject).to receive(:command_output_message).with('shell_id 2', command_id)
|
|
169
|
+
.and_return(output_message2)
|
|
170
|
+
allow(reader).to receive(:read_output)
|
|
171
|
+
.with(output_message2).and_return(output)
|
|
172
|
+
allow(subject).to receive(:open_shell) do
|
|
173
|
+
@times += 1
|
|
174
|
+
raise(Errno::ECONNREFUSED) if @times == 1
|
|
175
|
+
"shell_id #{@times}"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
subject.run(command, arguments)
|
|
179
|
+
expect(subject.shell_id).to eq 'shell_id 2'
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
describe '#close' do
|
|
185
|
+
it 'does not close if not opened' do
|
|
186
|
+
expect(DummyShell).not_to receive(:close_shell)
|
|
187
|
+
subject.close
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'close shell if opened' do
|
|
191
|
+
subject.run(command, arguments)
|
|
192
|
+
subject.close
|
|
193
|
+
expect(DummyShell.closed?).to be(true)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'nils out the shell_id' do
|
|
197
|
+
subject.run(command, arguments)
|
|
198
|
+
subject.close
|
|
199
|
+
expect(subject.shell_id).to be(nil)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|