knife-windows 0.5.14 → 0.5.15
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.
- data/.gitignore +4 -4
- data/.rspec +3 -3
- data/.travis.yml +6 -6
- data/CHANGELOG +14 -14
- data/Gemfile +11 -11
- data/LICENSE +201 -201
- data/README.rdoc +140 -140
- data/Rakefile +16 -16
- data/features/knife_help.feature +20 -20
- data/features/support/env.rb +5 -5
- data/knife-windows.gemspec +24 -24
- data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +222 -211
- data/lib/chef/knife/bootstrap/windows-shell.erb +68 -68
- data/lib/chef/knife/bootstrap_windows_base.rb +196 -196
- data/lib/chef/knife/bootstrap_windows_ssh.rb +93 -93
- data/lib/chef/knife/bootstrap_windows_winrm.rb +62 -62
- data/lib/chef/knife/core/windows_bootstrap_context.rb +177 -177
- data/lib/chef/knife/windows_helper.rb +34 -34
- data/lib/chef/knife/winrm.rb +286 -286
- data/lib/chef/knife/winrm_base.rb +99 -98
- data/lib/knife-windows/version.rb +6 -6
- data/spec/functional/bootstrap_download_spec.rb +120 -120
- data/spec/spec_helper.rb +63 -63
- data/spec/unit/knife/bootstrap_template_spec.rb +91 -91
- data/spec/unit/knife/winrm_spec.rb +82 -82
- metadata +12 -12
@@ -1,34 +1,34 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Chirag Jog (<chirag@clogeny.com>)
|
3
|
-
# Copyright:: Copyright (c) 2013 Opscode, Inc.
|
4
|
-
# License:: Apache License, Version 2.0
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#
|
18
|
-
|
19
|
-
require 'chef/knife'
|
20
|
-
require 'chef/knife/winrm'
|
21
|
-
require 'chef/knife/bootstrap_windows_ssh'
|
22
|
-
require 'chef/knife/bootstrap_windows_winrm'
|
23
|
-
|
24
|
-
class Chef
|
25
|
-
class Knife
|
26
|
-
class WindowsHelper < Knife
|
27
|
-
|
28
|
-
banner "#{BootstrapWindowsWinrm.banner}\n" +
|
29
|
-
"#{BootstrapWindowsSsh.banner}\n" +
|
30
|
-
"#{Winrm.banner}"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
1
|
+
#
|
2
|
+
# Author:: Chirag Jog (<chirag@clogeny.com>)
|
3
|
+
# Copyright:: Copyright (c) 2013 Opscode, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/knife'
|
20
|
+
require 'chef/knife/winrm'
|
21
|
+
require 'chef/knife/bootstrap_windows_ssh'
|
22
|
+
require 'chef/knife/bootstrap_windows_winrm'
|
23
|
+
|
24
|
+
class Chef
|
25
|
+
class Knife
|
26
|
+
class WindowsHelper < Knife
|
27
|
+
|
28
|
+
banner "#{BootstrapWindowsWinrm.banner}\n" +
|
29
|
+
"#{BootstrapWindowsSsh.banner}\n" +
|
30
|
+
"#{Winrm.banner}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
data/lib/chef/knife/winrm.rb
CHANGED
@@ -1,286 +1,286 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Seth Chisamore (<schisamo@opscode.com>)
|
3
|
-
# Copyright:: Copyright (c) 2011 Opscode, Inc.
|
4
|
-
# License:: Apache License, Version 2.0
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#
|
18
|
-
|
19
|
-
require 'chef/knife'
|
20
|
-
require 'chef/knife/winrm_base'
|
21
|
-
|
22
|
-
class Chef
|
23
|
-
class Knife
|
24
|
-
class Winrm < Knife
|
25
|
-
|
26
|
-
include Chef::Knife::WinrmBase
|
27
|
-
|
28
|
-
deps do
|
29
|
-
require 'readline'
|
30
|
-
require 'chef/search/query'
|
31
|
-
require 'em-winrm'
|
32
|
-
end
|
33
|
-
|
34
|
-
attr_writer :password
|
35
|
-
|
36
|
-
banner "knife winrm QUERY COMMAND (options)"
|
37
|
-
|
38
|
-
option :attribute,
|
39
|
-
:short => "-a ATTR",
|
40
|
-
:long => "--attribute ATTR",
|
41
|
-
:description => "The attribute to use for opening the connection - default is fqdn",
|
42
|
-
:default => "fqdn"
|
43
|
-
|
44
|
-
option :returns,
|
45
|
-
:long => "--returns CODES",
|
46
|
-
:description => "A comma delimited list of return codes which indicate success",
|
47
|
-
:default => nil,
|
48
|
-
:proc => Proc.new { |codes|
|
49
|
-
Chef::Config[:knife][:returns] = codes.split(',').collect {|item| item.to_i} }
|
50
|
-
|
51
|
-
option :manual,
|
52
|
-
:short => "-m",
|
53
|
-
:long => "--manual-list",
|
54
|
-
:boolean => true,
|
55
|
-
:description => "QUERY is a space separated list of servers",
|
56
|
-
:default => false
|
57
|
-
|
58
|
-
def session
|
59
|
-
session_opts = {}
|
60
|
-
session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
|
61
|
-
@session ||= begin
|
62
|
-
s = EventMachine::WinRM::Session.new(session_opts)
|
63
|
-
s.on_output do |host, data|
|
64
|
-
print_data(host, data)
|
65
|
-
end
|
66
|
-
s.on_error do |host, err|
|
67
|
-
print_data(host, err, :red)
|
68
|
-
end
|
69
|
-
s.on_command_complete do |host|
|
70
|
-
host = host == :all ? 'All Servers' : host
|
71
|
-
Chef::Log.debug("command complete on #{host}")
|
72
|
-
end
|
73
|
-
s
|
74
|
-
end
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
# TODO: Copied from Knife::Core:GenericPresenter. Should be extracted
|
79
|
-
def extract_nested_value(data, nested_value_spec)
|
80
|
-
nested_value_spec.split(".").each do |attr|
|
81
|
-
if data.nil?
|
82
|
-
nil # don't get no method error on nil
|
83
|
-
elsif data.respond_to?(attr.to_sym)
|
84
|
-
data = data.send(attr.to_sym)
|
85
|
-
elsif data.respond_to?(:[])
|
86
|
-
data = data[attr]
|
87
|
-
else
|
88
|
-
data = begin
|
89
|
-
data.send(attr.to_sym)
|
90
|
-
rescue NoMethodError
|
91
|
-
nil
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data
|
96
|
-
end
|
97
|
-
|
98
|
-
def configure_session
|
99
|
-
list = case config[:manual]
|
100
|
-
when true
|
101
|
-
@name_args[0].split(" ")
|
102
|
-
when false
|
103
|
-
r = Array.new
|
104
|
-
q = Chef::Search::Query.new
|
105
|
-
@action_nodes = q.search(:node, @name_args[0])[0]
|
106
|
-
@action_nodes.each do |item|
|
107
|
-
i = extract_nested_value(item, config[:attribute])
|
108
|
-
r.push(i) unless i.nil?
|
109
|
-
end
|
110
|
-
r
|
111
|
-
end
|
112
|
-
if list.length == 0
|
113
|
-
if @action_nodes.length == 0
|
114
|
-
ui.fatal("No nodes returned from search!")
|
115
|
-
else
|
116
|
-
ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes":"node"} found, " +
|
117
|
-
"but does not have the required attribute (#{config[:attribute]}) to establish the connection. " +
|
118
|
-
"Try setting another attribute to open the connection using --attribute.")
|
119
|
-
end
|
120
|
-
exit 10
|
121
|
-
end
|
122
|
-
session_from_list(list)
|
123
|
-
end
|
124
|
-
|
125
|
-
def session_from_list(list)
|
126
|
-
list.each do |item|
|
127
|
-
Chef::Log.debug("Adding #{item}")
|
128
|
-
session_opts = {}
|
129
|
-
session_opts[:user] = config[:winrm_user] = Chef::Config[:knife][:winrm_user] || config[:winrm_user]
|
130
|
-
session_opts[:password] = config[:winrm_password] = Chef::Config[:knife][:winrm_password] || config[:winrm_password]
|
131
|
-
session_opts[:port] = Chef::Config[:knife][:winrm_port] || config[:winrm_port]
|
132
|
-
session_opts[:keytab] = Chef::Config[:knife][:kerberos_keytab_file] if Chef::Config[:knife][:kerberos_keytab_file]
|
133
|
-
session_opts[:realm] = Chef::Config[:knife][:kerberos_realm] if Chef::Config[:knife][:kerberos_realm]
|
134
|
-
session_opts[:service] = Chef::Config[:knife][:kerberos_service] if Chef::Config[:knife][:kerberos_service]
|
135
|
-
session_opts[:ca_trust_path] = Chef::Config[:knife][:ca_trust_file] if Chef::Config[:knife][:ca_trust_file]
|
136
|
-
session_opts[:operation_timeout] = 1800 # 30 min OperationTimeout for long bootstraps fix for KNIFE_WINDOWS-8
|
137
|
-
|
138
|
-
## If you have a \\ in your name you need to use NTLM domain authentication
|
139
|
-
if session_opts[:user].split("\\").length.eql?(2)
|
140
|
-
session_opts[:basic_auth_only] = false
|
141
|
-
else
|
142
|
-
session_opts[:basic_auth_only] = true
|
143
|
-
end
|
144
|
-
|
145
|
-
if config.keys.any? {|k| k.to_s =~ /kerberos/ }
|
146
|
-
session_opts[:transport] = :kerberos
|
147
|
-
session_opts[:basic_auth_only] = false
|
148
|
-
else
|
149
|
-
session_opts[:transport] = (Chef::Config[:knife][:winrm_transport] || config[:winrm_transport]).to_sym
|
150
|
-
session_opts[:disable_sspi] = true
|
151
|
-
if session_opts[:user] and
|
152
|
-
(not session_opts[:password])
|
153
|
-
session_opts[:password] = Chef::Config[:knife][:winrm_password] = config[:winrm_password] = get_password
|
154
|
-
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
session.use(item, session_opts)
|
159
|
-
|
160
|
-
@longest = item.length if item.length > @longest
|
161
|
-
end
|
162
|
-
session
|
163
|
-
end
|
164
|
-
|
165
|
-
def print_data(host, data, color = :cyan)
|
166
|
-
if data =~ /\n/
|
167
|
-
data.split(/\n/).each { |d| print_data(host, d, color) }
|
168
|
-
else
|
169
|
-
padding = @longest - host.length
|
170
|
-
print ui.color(host, color)
|
171
|
-
padding.downto(0) { print " " }
|
172
|
-
puts data.chomp
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
def winrm_command(command, subsession=nil)
|
177
|
-
subsession ||= session
|
178
|
-
subsession.relay_command(command)
|
179
|
-
end
|
180
|
-
|
181
|
-
def get_password
|
182
|
-
@password ||= ui.ask("Enter your password: ") { |q| q.echo = false }
|
183
|
-
end
|
184
|
-
|
185
|
-
# Present the prompt and read a single line from the console. It also
|
186
|
-
# detects ^D and returns "exit" in that case. Adds the input to the
|
187
|
-
# history, unless the input is empty. Loops repeatedly until a non-empty
|
188
|
-
# line is input.
|
189
|
-
def read_line
|
190
|
-
loop do
|
191
|
-
command = reader.readline("#{ui.color('knife-winrm>', :bold)} ", true)
|
192
|
-
|
193
|
-
if command.nil?
|
194
|
-
command = "exit"
|
195
|
-
puts(command)
|
196
|
-
else
|
197
|
-
command.strip!
|
198
|
-
end
|
199
|
-
|
200
|
-
unless command.empty?
|
201
|
-
return command
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
def reader
|
207
|
-
Readline
|
208
|
-
end
|
209
|
-
|
210
|
-
def interactive
|
211
|
-
puts "Connected to #{ui.list(session.servers.collect { |s| ui.color(s.host, :cyan) }, :inline, " and ")}"
|
212
|
-
puts
|
213
|
-
puts "To run a command on a list of servers, do:"
|
214
|
-
puts " on SERVER1 SERVER2 SERVER3; COMMAND"
|
215
|
-
puts " Example: on latte foamy; echo foobar"
|
216
|
-
puts
|
217
|
-
puts "To exit interactive mode, use 'quit!'"
|
218
|
-
puts
|
219
|
-
while 1
|
220
|
-
command = read_line
|
221
|
-
case command
|
222
|
-
when 'quit!'
|
223
|
-
puts 'Bye!'
|
224
|
-
session.close
|
225
|
-
break
|
226
|
-
when /^on (.+?); (.+)$/
|
227
|
-
raw_list = $1.split(" ")
|
228
|
-
server_list = Array.new
|
229
|
-
session.servers.each do |session_server|
|
230
|
-
server_list << session_server if raw_list.include?(session_server.host)
|
231
|
-
end
|
232
|
-
command = $2
|
233
|
-
winrm_command(command, session.on(*server_list))
|
234
|
-
else
|
235
|
-
winrm_command(command)
|
236
|
-
end
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
def check_for_errors!(exit_codes)
|
241
|
-
|
242
|
-
exit_codes.each do |host, value|
|
243
|
-
unless Chef::Config[:knife][:returns].include? value.to_i
|
244
|
-
@exit_code = 1
|
245
|
-
ui.error "Failed to execute command on #{host} return code #{value}"
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
end
|
250
|
-
|
251
|
-
def run
|
252
|
-
STDOUT.sync = STDERR.sync = true
|
253
|
-
|
254
|
-
begin
|
255
|
-
@longest = 0
|
256
|
-
|
257
|
-
configure_session
|
258
|
-
|
259
|
-
case @name_args[1]
|
260
|
-
when "interactive"
|
261
|
-
interactive
|
262
|
-
else
|
263
|
-
winrm_command(@name_args[1..-1].join(" "))
|
264
|
-
|
265
|
-
if config[:returns]
|
266
|
-
check_for_errors! session.exit_codes
|
267
|
-
end
|
268
|
-
|
269
|
-
session.close
|
270
|
-
@exit_code || 0
|
271
|
-
end
|
272
|
-
rescue WinRM::WinRMHTTPTransportError => e
|
273
|
-
case e.message
|
274
|
-
when /401/
|
275
|
-
ui.error "Failed to authenticate to #{@name_args[0].split(" ")} as #{config[:winrm_user]}"
|
276
|
-
ui.info "Response: #{e.message}"
|
277
|
-
else
|
278
|
-
raise e
|
279
|
-
end
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
end
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
1
|
+
#
|
2
|
+
# Author:: Seth Chisamore (<schisamo@opscode.com>)
|
3
|
+
# Copyright:: Copyright (c) 2011 Opscode, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/knife'
|
20
|
+
require 'chef/knife/winrm_base'
|
21
|
+
|
22
|
+
class Chef
|
23
|
+
class Knife
|
24
|
+
class Winrm < Knife
|
25
|
+
|
26
|
+
include Chef::Knife::WinrmBase
|
27
|
+
|
28
|
+
deps do
|
29
|
+
require 'readline'
|
30
|
+
require 'chef/search/query'
|
31
|
+
require 'em-winrm'
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_writer :password
|
35
|
+
|
36
|
+
banner "knife winrm QUERY COMMAND (options)"
|
37
|
+
|
38
|
+
option :attribute,
|
39
|
+
:short => "-a ATTR",
|
40
|
+
:long => "--attribute ATTR",
|
41
|
+
:description => "The attribute to use for opening the connection - default is fqdn",
|
42
|
+
:default => "fqdn"
|
43
|
+
|
44
|
+
option :returns,
|
45
|
+
:long => "--returns CODES",
|
46
|
+
:description => "A comma delimited list of return codes which indicate success",
|
47
|
+
:default => nil,
|
48
|
+
:proc => Proc.new { |codes|
|
49
|
+
Chef::Config[:knife][:returns] = codes.split(',').collect {|item| item.to_i} }
|
50
|
+
|
51
|
+
option :manual,
|
52
|
+
:short => "-m",
|
53
|
+
:long => "--manual-list",
|
54
|
+
:boolean => true,
|
55
|
+
:description => "QUERY is a space separated list of servers",
|
56
|
+
:default => false
|
57
|
+
|
58
|
+
def session
|
59
|
+
session_opts = {}
|
60
|
+
session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
|
61
|
+
@session ||= begin
|
62
|
+
s = EventMachine::WinRM::Session.new(session_opts)
|
63
|
+
s.on_output do |host, data|
|
64
|
+
print_data(host, data)
|
65
|
+
end
|
66
|
+
s.on_error do |host, err|
|
67
|
+
print_data(host, err, :red)
|
68
|
+
end
|
69
|
+
s.on_command_complete do |host|
|
70
|
+
host = host == :all ? 'All Servers' : host
|
71
|
+
Chef::Log.debug("command complete on #{host}")
|
72
|
+
end
|
73
|
+
s
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
# TODO: Copied from Knife::Core:GenericPresenter. Should be extracted
|
79
|
+
def extract_nested_value(data, nested_value_spec)
|
80
|
+
nested_value_spec.split(".").each do |attr|
|
81
|
+
if data.nil?
|
82
|
+
nil # don't get no method error on nil
|
83
|
+
elsif data.respond_to?(attr.to_sym)
|
84
|
+
data = data.send(attr.to_sym)
|
85
|
+
elsif data.respond_to?(:[])
|
86
|
+
data = data[attr]
|
87
|
+
else
|
88
|
+
data = begin
|
89
|
+
data.send(attr.to_sym)
|
90
|
+
rescue NoMethodError
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data
|
96
|
+
end
|
97
|
+
|
98
|
+
def configure_session
|
99
|
+
list = case config[:manual]
|
100
|
+
when true
|
101
|
+
@name_args[0].split(" ")
|
102
|
+
when false
|
103
|
+
r = Array.new
|
104
|
+
q = Chef::Search::Query.new
|
105
|
+
@action_nodes = q.search(:node, @name_args[0])[0]
|
106
|
+
@action_nodes.each do |item|
|
107
|
+
i = extract_nested_value(item, config[:attribute])
|
108
|
+
r.push(i) unless i.nil?
|
109
|
+
end
|
110
|
+
r
|
111
|
+
end
|
112
|
+
if list.length == 0
|
113
|
+
if @action_nodes.length == 0
|
114
|
+
ui.fatal("No nodes returned from search!")
|
115
|
+
else
|
116
|
+
ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes":"node"} found, " +
|
117
|
+
"but does not have the required attribute (#{config[:attribute]}) to establish the connection. " +
|
118
|
+
"Try setting another attribute to open the connection using --attribute.")
|
119
|
+
end
|
120
|
+
exit 10
|
121
|
+
end
|
122
|
+
session_from_list(list)
|
123
|
+
end
|
124
|
+
|
125
|
+
def session_from_list(list)
|
126
|
+
list.each do |item|
|
127
|
+
Chef::Log.debug("Adding #{item}")
|
128
|
+
session_opts = {}
|
129
|
+
session_opts[:user] = config[:winrm_user] = Chef::Config[:knife][:winrm_user] || config[:winrm_user]
|
130
|
+
session_opts[:password] = config[:winrm_password] = Chef::Config[:knife][:winrm_password] || config[:winrm_password]
|
131
|
+
session_opts[:port] = Chef::Config[:knife][:winrm_port] || config[:winrm_port]
|
132
|
+
session_opts[:keytab] = Chef::Config[:knife][:kerberos_keytab_file] if Chef::Config[:knife][:kerberos_keytab_file]
|
133
|
+
session_opts[:realm] = Chef::Config[:knife][:kerberos_realm] if Chef::Config[:knife][:kerberos_realm]
|
134
|
+
session_opts[:service] = Chef::Config[:knife][:kerberos_service] if Chef::Config[:knife][:kerberos_service]
|
135
|
+
session_opts[:ca_trust_path] = Chef::Config[:knife][:ca_trust_file] if Chef::Config[:knife][:ca_trust_file]
|
136
|
+
session_opts[:operation_timeout] = 1800 # 30 min OperationTimeout for long bootstraps fix for KNIFE_WINDOWS-8
|
137
|
+
|
138
|
+
## If you have a \\ in your name you need to use NTLM domain authentication
|
139
|
+
if session_opts[:user].split("\\").length.eql?(2)
|
140
|
+
session_opts[:basic_auth_only] = false
|
141
|
+
else
|
142
|
+
session_opts[:basic_auth_only] = true
|
143
|
+
end
|
144
|
+
|
145
|
+
if config.keys.any? {|k| k.to_s =~ /kerberos/ }
|
146
|
+
session_opts[:transport] = :kerberos
|
147
|
+
session_opts[:basic_auth_only] = false
|
148
|
+
else
|
149
|
+
session_opts[:transport] = (Chef::Config[:knife][:winrm_transport] || config[:winrm_transport]).to_sym
|
150
|
+
session_opts[:disable_sspi] = true
|
151
|
+
if session_opts[:user] and
|
152
|
+
(not session_opts[:password])
|
153
|
+
session_opts[:password] = Chef::Config[:knife][:winrm_password] = config[:winrm_password] = get_password
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
session.use(item, session_opts)
|
159
|
+
|
160
|
+
@longest = item.length if item.length > @longest
|
161
|
+
end
|
162
|
+
session
|
163
|
+
end
|
164
|
+
|
165
|
+
def print_data(host, data, color = :cyan)
|
166
|
+
if data =~ /\n/
|
167
|
+
data.split(/\n/).each { |d| print_data(host, d, color) }
|
168
|
+
else
|
169
|
+
padding = @longest - host.length
|
170
|
+
print ui.color(host, color)
|
171
|
+
padding.downto(0) { print " " }
|
172
|
+
puts data.chomp
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def winrm_command(command, subsession=nil)
|
177
|
+
subsession ||= session
|
178
|
+
subsession.relay_command(command)
|
179
|
+
end
|
180
|
+
|
181
|
+
def get_password
|
182
|
+
@password ||= ui.ask("Enter your password: ") { |q| q.echo = false }
|
183
|
+
end
|
184
|
+
|
185
|
+
# Present the prompt and read a single line from the console. It also
|
186
|
+
# detects ^D and returns "exit" in that case. Adds the input to the
|
187
|
+
# history, unless the input is empty. Loops repeatedly until a non-empty
|
188
|
+
# line is input.
|
189
|
+
def read_line
|
190
|
+
loop do
|
191
|
+
command = reader.readline("#{ui.color('knife-winrm>', :bold)} ", true)
|
192
|
+
|
193
|
+
if command.nil?
|
194
|
+
command = "exit"
|
195
|
+
puts(command)
|
196
|
+
else
|
197
|
+
command.strip!
|
198
|
+
end
|
199
|
+
|
200
|
+
unless command.empty?
|
201
|
+
return command
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def reader
|
207
|
+
Readline
|
208
|
+
end
|
209
|
+
|
210
|
+
def interactive
|
211
|
+
puts "Connected to #{ui.list(session.servers.collect { |s| ui.color(s.host, :cyan) }, :inline, " and ")}"
|
212
|
+
puts
|
213
|
+
puts "To run a command on a list of servers, do:"
|
214
|
+
puts " on SERVER1 SERVER2 SERVER3; COMMAND"
|
215
|
+
puts " Example: on latte foamy; echo foobar"
|
216
|
+
puts
|
217
|
+
puts "To exit interactive mode, use 'quit!'"
|
218
|
+
puts
|
219
|
+
while 1
|
220
|
+
command = read_line
|
221
|
+
case command
|
222
|
+
when 'quit!'
|
223
|
+
puts 'Bye!'
|
224
|
+
session.close
|
225
|
+
break
|
226
|
+
when /^on (.+?); (.+)$/
|
227
|
+
raw_list = $1.split(" ")
|
228
|
+
server_list = Array.new
|
229
|
+
session.servers.each do |session_server|
|
230
|
+
server_list << session_server if raw_list.include?(session_server.host)
|
231
|
+
end
|
232
|
+
command = $2
|
233
|
+
winrm_command(command, session.on(*server_list))
|
234
|
+
else
|
235
|
+
winrm_command(command)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def check_for_errors!(exit_codes)
|
241
|
+
|
242
|
+
exit_codes.each do |host, value|
|
243
|
+
unless Chef::Config[:knife][:returns].include? value.to_i
|
244
|
+
@exit_code = 1
|
245
|
+
ui.error "Failed to execute command on #{host} return code #{value}"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
def run
|
252
|
+
STDOUT.sync = STDERR.sync = true
|
253
|
+
|
254
|
+
begin
|
255
|
+
@longest = 0
|
256
|
+
|
257
|
+
configure_session
|
258
|
+
|
259
|
+
case @name_args[1]
|
260
|
+
when "interactive"
|
261
|
+
interactive
|
262
|
+
else
|
263
|
+
winrm_command(@name_args[1..-1].join(" "))
|
264
|
+
|
265
|
+
if config[:returns]
|
266
|
+
check_for_errors! session.exit_codes
|
267
|
+
end
|
268
|
+
|
269
|
+
session.close
|
270
|
+
@exit_code || 0
|
271
|
+
end
|
272
|
+
rescue WinRM::WinRMHTTPTransportError => e
|
273
|
+
case e.message
|
274
|
+
when /401/
|
275
|
+
ui.error "Failed to authenticate to #{@name_args[0].split(" ")} as #{config[:winrm_user]}"
|
276
|
+
ui.info "Response: #{e.message}"
|
277
|
+
else
|
278
|
+
raise e
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|