ridley 1.7.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,218 +0,0 @@
1
- # Silencing warnings because not all versions of GSSAPI support all of the GSSAPI methods
2
- # the gssapi gem attempts to attach to and these warnings are dumped to STDERR.
3
- silence_warnings do
4
- require 'winrm'
5
- end
6
-
7
- module Ridley
8
- module HostConnector
9
- class WinRM < HostConnector::Base
10
- require_relative 'winrm/command_uploader'
11
-
12
- DEFAULT_PORT = 5985
13
- EMBEDDED_RUBY_PATH = 'C:\opscode\chef\embedded\bin\ruby'.freeze
14
- SESSION_TYPE_COMMAND_METHODS = {
15
- powershell: :run_powershell_script,
16
- cmd: :run_cmd
17
- }.freeze
18
-
19
- # Execute a shell command on a node
20
- #
21
- # @param [String] host
22
- # the host to perform the action on
23
- # @param [String] command
24
- #
25
- # @option options [Symbol] :session_type (:cmd)
26
- # * :powershell - run the given command in a powershell session
27
- # * :cmd - run the given command in a cmd session
28
- # @option options [Hash] :winrm
29
- # * :user (String) a user that will login to each node and perform the bootstrap command on
30
- # * :password (String) the password for the user that will perform the bootstrap (required)
31
- # * :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)
32
- #
33
- # @return [HostConnector::Response]
34
- def run(host, command, options = {})
35
- options = options.reverse_merge(winrm: Hash.new, session_type: :cmd)
36
- options[:winrm].reverse_merge!(port: DEFAULT_PORT)
37
-
38
- command_uploaders = Array.new
39
- user = options[:winrm][:user]
40
- password = options[:winrm][:password]
41
- port = options[:winrm][:port]
42
- connection = winrm(host, port, options[:winrm].slice(:user, :password))
43
-
44
- unless command_method = SESSION_TYPE_COMMAND_METHODS[options[:session_type]]
45
- raise RuntimeError, "unknown session type: #{options[:session_type]}. Known session types " +
46
- "are: #{SESSION_TYPE_COMMAND_METHODS.keys}"
47
- end
48
-
49
- HostConnector::Response.new(host).tap do |response|
50
- begin
51
- command_uploaders << command_uploader = CommandUploader.new(connection)
52
- command = get_command(command, command_uploader)
53
-
54
- log.info "Running WinRM Command: '#{command}' on: '#{host}' as: '#{user}'"
55
-
56
- defer {
57
- output = connection.send(command_method, command) do |stdout, stderr|
58
- if stdout && stdout.present?
59
- response.stdout += stdout
60
- log.info "[#{host}](WinRM) #{stdout}"
61
- end
62
-
63
- if stderr && stderr.present?
64
- response.stderr += stderr
65
- log.info "[#{host}](WinRM) #{stderr}"
66
- end
67
- end
68
- response.exit_code = output[:exitcode]
69
- }
70
- rescue ::WinRM::WinRMHTTPTransportError => ex
71
- response.exit_code = :transport_error
72
- response.stderr = ex.message
73
- end
74
-
75
- case response.exit_code
76
- when 0
77
- log.info "Successfully ran WinRM command on: '#{host}' as: '#{user}'"
78
- when :transport_error
79
- log.info "A transport error occured while attempting to run a WinRM command on: '#{host}' as: '#{user}'"
80
- else
81
- log.info "Successfully ran WinRM command on: '#{host}' as: '#{user}', but it failed"
82
- end
83
- end
84
- ensure
85
- begin
86
- command_uploaders.map(&:cleanup)
87
- rescue ::WinRM::WinRMHTTPTransportError => ex
88
- log.info "Error cleaning up leftover Powershell scripts on some hosts"
89
- end
90
- end
91
-
92
- # Returns the command if it does not break the WinRM command length
93
- # limit. Otherwise, we return an execution of the command as a batch file.
94
- #
95
- # @param command [String]
96
- #
97
- # @return [String]
98
- def get_command(command, command_uploader)
99
- if command.length < CommandUploader::CHUNK_LIMIT
100
- command
101
- else
102
- log.debug "Detected a command that was longer than #{CommandUploader::CHUNK_LIMIT} characters. " +
103
- "Uploading command as a file to the host."
104
- command_uploader.upload(command)
105
- command_uploader.command
106
- end
107
- end
108
-
109
- # Bootstrap a node
110
- #
111
- # @param [String] host
112
- # the host to perform the action on
113
- #
114
- # @option options [Hash] :winrm
115
- # * :user (String) a user that will login to each node and perform the bootstrap command on
116
- # * :password (String) the password for the user that will perform the bootstrap (required)
117
- # * :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)
118
- #
119
- # @return [HostConnector::Response]
120
- def bootstrap(host, options = {})
121
- context = BootstrapContext::Windows.new(options)
122
-
123
- log.info "Bootstrapping host: #{host}"
124
- run(host, context.boot_command, options)
125
- end
126
-
127
- # Perform a chef client run on a node
128
- #
129
- # @param [String] host
130
- # the host to perform the action on
131
- #
132
- # @option options [Hash] :winrm
133
- # * :user (String) a user that will login to each node and perform the bootstrap command on
134
- # * :password (String) the password for the user that will perform the bootstrap (required)
135
- # * :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)
136
- #
137
- # @return [HostConnector::Response]
138
- def chef_client(host, options = {})
139
- run(host, "chef-client", options)
140
- end
141
-
142
- # Write your encrypted data bag secret on a node
143
- #
144
- # @param [String] host
145
- # the host to perform the action on
146
- # @param [String] secret
147
- # your organization's encrypted data bag secret
148
- #
149
- # @option options [Hash] :winrm
150
- # * :user (String) a user that will login to each node and perform the bootstrap command on
151
- # * :password (String) the password for the user that will perform the bootstrap (required)
152
- # * :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)
153
- #
154
- # @return [HostConnector::Response]
155
- def put_secret(host, secret, options = {})
156
- command = "echo #{secret} > C:\\chef\\encrypted_data_bag_secret"
157
- run(host, command, options)
158
- end
159
-
160
- # Execute line(s) of Ruby code on a node using Chef's embedded Ruby
161
- #
162
- # @param [String] host
163
- # the host to perform the action on
164
- # @param [Array<String>] command_lines
165
- # An Array of lines of the command to be executed
166
- #
167
- # @option options [Hash] :winrm
168
- # * :user (String) a user that will login to each node and perform the bootstrap command on
169
- # * :password (String) the password for the user that will perform the bootstrap (required)
170
- # * :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)
171
- #
172
- # @return [HostConnector::Response]
173
- def ruby_script(host, command_lines, options = {})
174
- command = "#{EMBEDDED_RUBY_PATH} -e \"#{command_lines.join(';')}\""
175
- run(host, command, options)
176
- end
177
-
178
- # Uninstall Chef from a node
179
- #
180
- # @param [String] host
181
- # the host to perform the action on
182
- #
183
- # @option options [Boolena] :skip_chef (false)
184
- # skip removal of the Chef package and the contents of the installation
185
- # directory. Setting this to true will only remove any data and configurations
186
- # generated by running Chef client.
187
- # @option options [Hash] :winrm
188
- # * :user (String) a user that will login to each node and perform the bootstrap command on
189
- # * :password (String) the password for the user that will perform the bootstrap (required)
190
- # * :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)
191
- #
192
- # @return [HostConnector::Response]
193
- def uninstall_chef(host, options = {})
194
- options[:session_type] = :powershell
195
- log.info "Uninstalling Chef from host: #{host}"
196
- run(host, CommandContext::WindowsUninstall.command(options), options)
197
- end
198
-
199
- private
200
-
201
- # @param [String] host
202
- # @param [Integer] port
203
- #
204
- # @option options [String] :user
205
- # @option options [String] :password
206
- #
207
- # @return [WinRM::WinRMWebService]
208
- def winrm(host, port, options = {})
209
- winrm_opts = { disable_sspi: true, basic_auth_only: true }
210
- winrm_opts[:user] = options[:user]
211
- winrm_opts[:pass] = options[:password]
212
- client = ::WinRM::WinRMWebService.new("http://#{host}:#{port}/wsman", :plaintext, winrm_opts)
213
- client.set_timeout(6000)
214
- client
215
- end
216
- end
217
- end
218
- end
@@ -1,87 +0,0 @@
1
- module Ridley
2
- module HostConnector
3
- class WinRM
4
- # @example
5
- # command_uploader = CommandUploader.new(long_command, winrm)
6
- # command_uploader.upload
7
- # command_uploader.command
8
- #
9
- # This class is used by WinRM Workers when the worker is told to execute a command that
10
- # might be too long for WinRM to handle.
11
- #
12
- # After an instance of this class is created, the upload method will upload the long command
13
- # to the node being worked on in chunks. Once on the node, some Powershell code will decode
14
- # the long_command into a batch file. The command method will return a String representing the
15
- # command the worker will use to execute the uploaded batch script.
16
- class CommandUploader
17
- CHUNK_LIMIT = 1024
18
-
19
- # @return [WinRM::WinRMWebService]
20
- attr_reader :winrm
21
- # @return [String]
22
- attr_reader :base64_file_name
23
- # @return [String]
24
- attr_reader :command_file_name
25
-
26
- # @param [WinRM::WinRMWebService] winrm
27
- def initialize(winrm)
28
- @winrm = winrm
29
- @base64_file_name = get_file_path("winrm-upload-base64-#{unique_string}")
30
- @command_file_name = get_file_path("winrm-upload-#{unique_string}.bat")
31
- end
32
-
33
- # Uploads the command encoded as base64 to a file on the host
34
- # and then uses Powershell to transform the base64 file into the
35
- # command that was originally passed through.
36
- #
37
- # @param [String] command_string
38
- def upload(command_string)
39
- upload_command(command_string)
40
- convert_command
41
- end
42
-
43
- # @return [String] the command to execute the uploaded file
44
- def command
45
- "cmd.exe /C #{command_file_name}"
46
- end
47
-
48
- # Runs a delete command on the files generated by #base64_file_name
49
- # and #command_file_name
50
- def cleanup
51
- winrm.run_cmd( "del #{base64_file_name} /F /Q" )
52
- winrm.run_cmd( "del #{command_file_name} /F /Q" )
53
- end
54
-
55
- private
56
-
57
- def upload_command(command_string)
58
- command_string_chars(command_string).each_slice(CHUNK_LIMIT) do |chunk|
59
- winrm.run_cmd( "echo #{chunk.join} >> \"#{base64_file_name}\"" )
60
- end
61
- end
62
-
63
- def command_string_chars(command_string)
64
- Base64.encode64(command_string).gsub("\n", '').chars.to_a
65
- end
66
-
67
- def convert_command
68
- winrm.powershell <<-POWERSHELL
69
- $base64_string = Get-Content \"#{base64_file_name}\"
70
- $bytes = [System.Convert]::FromBase64String($base64_string)
71
- $new_file = [System.IO.Path]::GetFullPath(\"#{command_file_name}\")
72
- [System.IO.File]::WriteAllBytes($new_file,$bytes)
73
- POWERSHELL
74
- end
75
-
76
- def unique_string
77
- @unique_string ||= "#{Process.pid}-#{Time.now.to_i}"
78
- end
79
-
80
- # @return [String]
81
- def get_file_path(file)
82
- (winrm.run_cmd("echo %TEMP%\\#{file}"))[:data][0][:stdout].chomp
83
- end
84
- end
85
- end
86
- end
87
- end
@@ -1,173 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Ridley::HostCommander do
4
- subject { described_class.new }
5
-
6
- describe "#run" do
7
- let(:host) { "reset.riotgames.com" }
8
- let(:command) { "ls" }
9
- let(:options) do
10
- { ssh: { port: 22 }, winrm: { port: 5985 } }
11
- end
12
-
13
- context "when communicating to a unix node" do
14
- before do
15
- subject.stub(:connector_port_open?).with(host, options[:winrm][:port]).and_return(false)
16
- subject.stub(:connector_port_open?).with(host, options[:ssh][:port], anything).and_return(true)
17
- end
18
-
19
- it "sends a #run message to the ssh host connector" do
20
- subject.send(:ssh).should_receive(:run).with(host, command, options)
21
-
22
- subject.run(host, command, options)
23
- end
24
- end
25
-
26
- context "when communicating to a windows node" do
27
- before do
28
- subject.stub(:connector_port_open?).with(host, options[:winrm][:port]).and_return(true)
29
- subject.stub(:connector_port_open?).with(host, options[:ssh][:port], anything).and_return(false)
30
- end
31
-
32
- it "sends a #run message to the ssh host connector" do
33
- subject.send(:winrm).should_receive(:run).with(host, command, options)
34
-
35
- subject.run(host, command, options)
36
- end
37
- end
38
- end
39
-
40
- describe "#bootstrap" do
41
- let(:host) { "reset.riotgames.com" }
42
- let(:options) do
43
- { ssh: { port: 22 }, winrm: { port: 5985 } }
44
- end
45
-
46
- context "when communicating to a unix node" do
47
- before do
48
- subject.stub(:connector_port_open?).with(host, options[:winrm][:port]).and_return(false)
49
- subject.stub(:connector_port_open?).with(host, options[:ssh][:port], anything).and_return(true)
50
- end
51
-
52
- it "sends a #bootstrap message to the ssh host connector" do
53
- subject.send(:ssh).should_receive(:bootstrap).with(host, options)
54
-
55
- subject.bootstrap(host, options)
56
- end
57
- end
58
-
59
- context "when communicating to a windows node" do
60
- before do
61
- subject.stub(:connector_port_open?).with(host, options[:winrm][:port]).and_return(true)
62
- subject.stub(:connector_port_open?).with(host, options[:ssh][:port], anything).and_return(false)
63
- end
64
-
65
- it "sends a #bootstrap message to the ssh host connector" do
66
- subject.send(:winrm).should_receive(:bootstrap).with(host, options)
67
-
68
- subject.bootstrap(host, options)
69
- end
70
- end
71
- end
72
-
73
- describe "#chef_client" do
74
- let(:host) { "reset.riotgames.com" }
75
- let(:options) do
76
- { ssh: { port: 22 }, winrm: { port: 5985 } }
77
- end
78
-
79
- context "when communicating to a unix node" do
80
- before do
81
- subject.stub(:connector_port_open?).with(host, options[:winrm][:port]).and_return(false)
82
- subject.stub(:connector_port_open?).with(host, options[:ssh][:port], anything).and_return(true)
83
- end
84
-
85
- it "sends a #chef_client message to the ssh host connector" do
86
- subject.send(:ssh).should_receive(:chef_client).with(host, options)
87
-
88
- subject.chef_client(host, options)
89
- end
90
- end
91
-
92
- context "when communicating to a windows node" do
93
- before do
94
- subject.stub(:connector_port_open?).with(host, options[:winrm][:port]).and_return(true)
95
- subject.stub(:connector_port_open?).with(host, options[:ssh][:port], anything).and_return(false)
96
- end
97
-
98
- it "sends a #chef_client message to the ssh host connector" do
99
- subject.send(:winrm).should_receive(:chef_client).with(host, options)
100
-
101
- subject.chef_client(host, options)
102
- end
103
- end
104
- end
105
-
106
- describe "#put_secret" do
107
- let(:host) { "reset.riotgames.com" }
108
- let(:secret) { "something_secret" }
109
- let(:options) do
110
- { ssh: { port: 22 }, winrm: { port: 5985 } }
111
- end
112
-
113
- context "when communicating to a unix node" do
114
- before do
115
- subject.stub(:connector_port_open?).with(host, options[:winrm][:port]).and_return(false)
116
- subject.stub(:connector_port_open?).with(host, options[:ssh][:port], anything).and_return(true)
117
- end
118
-
119
- it "sends a #put_secret message to the ssh host connector" do
120
- subject.send(:ssh).should_receive(:put_secret).with(host, secret, options)
121
-
122
- subject.put_secret(host, secret, options)
123
- end
124
- end
125
-
126
- context "when communicating to a windows node" do
127
- before do
128
- subject.stub(:connector_port_open?).with(host, options[:winrm][:port]).and_return(true)
129
- subject.stub(:connector_port_open?).with(host, options[:ssh][:port], anything).and_return(false)
130
- end
131
-
132
- it "sends a #put_secret message to the ssh host connector" do
133
- subject.send(:winrm).should_receive(:put_secret).with(host, secret, options)
134
-
135
- subject.put_secret(host, secret, options)
136
- end
137
- end
138
- end
139
-
140
- describe "#ruby_script" do
141
- let(:host) { "reset.riotgames.com" }
142
- let(:command_lines) { ["line one"] }
143
- let(:options) do
144
- { ssh: { port: 22 }, winrm: { port: 5985 } }
145
- end
146
-
147
- context "when communicating to a unix node" do
148
- before do
149
- subject.stub(:connector_port_open?).with(host, options[:winrm][:port]).and_return(false)
150
- subject.stub(:connector_port_open?).with(host, options[:ssh][:port], anything).and_return(true)
151
- end
152
-
153
- it "sends a #ruby_script message to the ssh host connector" do
154
- subject.send(:ssh).should_receive(:ruby_script).with(host, command_lines, options)
155
-
156
- subject.ruby_script(host, command_lines, options)
157
- end
158
- end
159
-
160
- context "when communicating to a windows node" do
161
- before do
162
- subject.stub(:connector_port_open?).with(host, options[:winrm][:port]).and_return(true)
163
- subject.stub(:connector_port_open?).with(host, options[:ssh][:port], anything).and_return(false)
164
- end
165
-
166
- it "sends a #ruby_script message to the ssh host connector" do
167
- subject.send(:winrm).should_receive(:ruby_script).with(host, command_lines, options)
168
-
169
- subject.ruby_script(host, command_lines, options)
170
- end
171
- end
172
- end
173
- end