knife-windows 0.5.15 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -1,286 +1,296 @@
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
+
271
+ # Knife seems to ignore the return value of this method,
272
+ # so we exit to force the process exit code for this
273
+ # subcommand if returns is set
274
+ exit @exit_code if @exit_code && @exit_code != 0
275
+ @exit_code || 0
276
+ end
277
+ rescue WinRM::WinRMHTTPTransportError => e
278
+ case e.message
279
+ when /401/
280
+ if ! config[:suppress_auth_failure]
281
+ # Display errors if the caller hasn't opted to retry
282
+ ui.error "Failed to authenticate to #{@name_args[0].split(" ")} as #{config[:winrm_user]}"
283
+ ui.info "Response: #{e.message}"
284
+ raise e
285
+ end
286
+ @exit_code = 401
287
+ else
288
+ raise e
289
+ end
290
+ end
291
+ end
292
+
293
+ end
294
+ end
295
+ end
296
+