lib/chef/knife/winrm.rb in knife-windows-0.5.15 vs lib/chef/knife/winrm.rb in knife-windows-0.6.0
- old
+ new
@@ -1,286 +1,296 @@
-#
-# Author:: Seth Chisamore (<schisamo@opscode.com>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/knife'
-require 'chef/knife/winrm_base'
-
-class Chef
- class Knife
- class Winrm < Knife
-
- include Chef::Knife::WinrmBase
-
- deps do
- require 'readline'
- require 'chef/search/query'
- require 'em-winrm'
- end
-
- attr_writer :password
-
- banner "knife winrm QUERY COMMAND (options)"
-
- option :attribute,
- :short => "-a ATTR",
- :long => "--attribute ATTR",
- :description => "The attribute to use for opening the connection - default is fqdn",
- :default => "fqdn"
-
- option :returns,
- :long => "--returns CODES",
- :description => "A comma delimited list of return codes which indicate success",
- :default => nil,
- :proc => Proc.new { |codes|
- Chef::Config[:knife][:returns] = codes.split(',').collect {|item| item.to_i} }
-
- option :manual,
- :short => "-m",
- :long => "--manual-list",
- :boolean => true,
- :description => "QUERY is a space separated list of servers",
- :default => false
-
- def session
- session_opts = {}
- session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
- @session ||= begin
- s = EventMachine::WinRM::Session.new(session_opts)
- s.on_output do |host, data|
- print_data(host, data)
- end
- s.on_error do |host, err|
- print_data(host, err, :red)
- end
- s.on_command_complete do |host|
- host = host == :all ? 'All Servers' : host
- Chef::Log.debug("command complete on #{host}")
- end
- s
- end
-
- end
-
- # TODO: Copied from Knife::Core:GenericPresenter. Should be extracted
- def extract_nested_value(data, nested_value_spec)
- nested_value_spec.split(".").each do |attr|
- if data.nil?
- nil # don't get no method error on nil
- elsif data.respond_to?(attr.to_sym)
- data = data.send(attr.to_sym)
- elsif data.respond_to?(:[])
- data = data[attr]
- else
- data = begin
- data.send(attr.to_sym)
- rescue NoMethodError
- nil
- end
- end
- end
- ( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data
- end
-
- def configure_session
- list = case config[:manual]
- when true
- @name_args[0].split(" ")
- when false
- r = Array.new
- q = Chef::Search::Query.new
- @action_nodes = q.search(:node, @name_args[0])[0]
- @action_nodes.each do |item|
- i = extract_nested_value(item, config[:attribute])
- r.push(i) unless i.nil?
- end
- r
- end
- if list.length == 0
- if @action_nodes.length == 0
- ui.fatal("No nodes returned from search!")
- else
- ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes":"node"} found, " +
- "but does not have the required attribute (#{config[:attribute]}) to establish the connection. " +
- "Try setting another attribute to open the connection using --attribute.")
- end
- exit 10
- end
- session_from_list(list)
- end
-
- def session_from_list(list)
- list.each do |item|
- Chef::Log.debug("Adding #{item}")
- session_opts = {}
- session_opts[:user] = config[:winrm_user] = Chef::Config[:knife][:winrm_user] || config[:winrm_user]
- session_opts[:password] = config[:winrm_password] = Chef::Config[:knife][:winrm_password] || config[:winrm_password]
- session_opts[:port] = Chef::Config[:knife][:winrm_port] || config[:winrm_port]
- session_opts[:keytab] = Chef::Config[:knife][:kerberos_keytab_file] if Chef::Config[:knife][:kerberos_keytab_file]
- session_opts[:realm] = Chef::Config[:knife][:kerberos_realm] if Chef::Config[:knife][:kerberos_realm]
- session_opts[:service] = Chef::Config[:knife][:kerberos_service] if Chef::Config[:knife][:kerberos_service]
- session_opts[:ca_trust_path] = Chef::Config[:knife][:ca_trust_file] if Chef::Config[:knife][:ca_trust_file]
- session_opts[:operation_timeout] = 1800 # 30 min OperationTimeout for long bootstraps fix for KNIFE_WINDOWS-8
-
- ## If you have a \\ in your name you need to use NTLM domain authentication
- if session_opts[:user].split("\\").length.eql?(2)
- session_opts[:basic_auth_only] = false
- else
- session_opts[:basic_auth_only] = true
- end
-
- if config.keys.any? {|k| k.to_s =~ /kerberos/ }
- session_opts[:transport] = :kerberos
- session_opts[:basic_auth_only] = false
- else
- session_opts[:transport] = (Chef::Config[:knife][:winrm_transport] || config[:winrm_transport]).to_sym
- session_opts[:disable_sspi] = true
- if session_opts[:user] and
- (not session_opts[:password])
- session_opts[:password] = Chef::Config[:knife][:winrm_password] = config[:winrm_password] = get_password
-
- end
- end
-
- session.use(item, session_opts)
-
- @longest = item.length if item.length > @longest
- end
- session
- end
-
- def print_data(host, data, color = :cyan)
- if data =~ /\n/
- data.split(/\n/).each { |d| print_data(host, d, color) }
- else
- padding = @longest - host.length
- print ui.color(host, color)
- padding.downto(0) { print " " }
- puts data.chomp
- end
- end
-
- def winrm_command(command, subsession=nil)
- subsession ||= session
- subsession.relay_command(command)
- end
-
- def get_password
- @password ||= ui.ask("Enter your password: ") { |q| q.echo = false }
- end
-
- # Present the prompt and read a single line from the console. It also
- # detects ^D and returns "exit" in that case. Adds the input to the
- # history, unless the input is empty. Loops repeatedly until a non-empty
- # line is input.
- def read_line
- loop do
- command = reader.readline("#{ui.color('knife-winrm>', :bold)} ", true)
-
- if command.nil?
- command = "exit"
- puts(command)
- else
- command.strip!
- end
-
- unless command.empty?
- return command
- end
- end
- end
-
- def reader
- Readline
- end
-
- def interactive
- puts "Connected to #{ui.list(session.servers.collect { |s| ui.color(s.host, :cyan) }, :inline, " and ")}"
- puts
- puts "To run a command on a list of servers, do:"
- puts " on SERVER1 SERVER2 SERVER3; COMMAND"
- puts " Example: on latte foamy; echo foobar"
- puts
- puts "To exit interactive mode, use 'quit!'"
- puts
- while 1
- command = read_line
- case command
- when 'quit!'
- puts 'Bye!'
- session.close
- break
- when /^on (.+?); (.+)$/
- raw_list = $1.split(" ")
- server_list = Array.new
- session.servers.each do |session_server|
- server_list << session_server if raw_list.include?(session_server.host)
- end
- command = $2
- winrm_command(command, session.on(*server_list))
- else
- winrm_command(command)
- end
- end
- end
-
- def check_for_errors!(exit_codes)
-
- exit_codes.each do |host, value|
- unless Chef::Config[:knife][:returns].include? value.to_i
- @exit_code = 1
- ui.error "Failed to execute command on #{host} return code #{value}"
- end
- end
-
- end
-
- def run
- STDOUT.sync = STDERR.sync = true
-
- begin
- @longest = 0
-
- configure_session
-
- case @name_args[1]
- when "interactive"
- interactive
- else
- winrm_command(@name_args[1..-1].join(" "))
-
- if config[:returns]
- check_for_errors! session.exit_codes
- end
-
- session.close
- @exit_code || 0
- end
- rescue WinRM::WinRMHTTPTransportError => e
- case e.message
- when /401/
- ui.error "Failed to authenticate to #{@name_args[0].split(" ")} as #{config[:winrm_user]}"
- ui.info "Response: #{e.message}"
- else
- raise e
- end
- end
- end
-
- end
- end
-end
-
+#
+# Author:: Seth Chisamore (<schisamo@opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+require 'chef/knife/winrm_base'
+
+class Chef
+ class Knife
+ class Winrm < Knife
+
+ include Chef::Knife::WinrmBase
+
+ deps do
+ require 'readline'
+ require 'chef/search/query'
+ require 'em-winrm'
+ end
+
+ attr_writer :password
+
+ banner "knife winrm QUERY COMMAND (options)"
+
+ option :attribute,
+ :short => "-a ATTR",
+ :long => "--attribute ATTR",
+ :description => "The attribute to use for opening the connection - default is fqdn",
+ :default => "fqdn"
+
+ option :returns,
+ :long => "--returns CODES",
+ :description => "A comma delimited list of return codes which indicate success",
+ :default => nil,
+ :proc => Proc.new { |codes|
+ Chef::Config[:knife][:returns] = codes.split(',').collect {|item| item.to_i} }
+
+ option :manual,
+ :short => "-m",
+ :long => "--manual-list",
+ :boolean => true,
+ :description => "QUERY is a space separated list of servers",
+ :default => false
+
+ def session
+ session_opts = {}
+ session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
+ @session ||= begin
+ s = EventMachine::WinRM::Session.new(session_opts)
+ s.on_output do |host, data|
+ print_data(host, data)
+ end
+ s.on_error do |host, err|
+ print_data(host, err, :red)
+ end
+ s.on_command_complete do |host|
+ host = host == :all ? 'All Servers' : host
+ Chef::Log.debug("command complete on #{host}")
+ end
+ s
+ end
+
+ end
+
+ # TODO: Copied from Knife::Core:GenericPresenter. Should be extracted
+ def extract_nested_value(data, nested_value_spec)
+ nested_value_spec.split(".").each do |attr|
+ if data.nil?
+ nil # don't get no method error on nil
+ elsif data.respond_to?(attr.to_sym)
+ data = data.send(attr.to_sym)
+ elsif data.respond_to?(:[])
+ data = data[attr]
+ else
+ data = begin
+ data.send(attr.to_sym)
+ rescue NoMethodError
+ nil
+ end
+ end
+ end
+ ( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data
+ end
+
+ def configure_session
+ list = case config[:manual]
+ when true
+ @name_args[0].split(" ")
+ when false
+ r = Array.new
+ q = Chef::Search::Query.new
+ @action_nodes = q.search(:node, @name_args[0])[0]
+ @action_nodes.each do |item|
+ i = extract_nested_value(item, config[:attribute])
+ r.push(i) unless i.nil?
+ end
+ r
+ end
+ if list.length == 0
+ if @action_nodes.length == 0
+ ui.fatal("No nodes returned from search!")
+ else
+ ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes":"node"} found, " +
+ "but does not have the required attribute (#{config[:attribute]}) to establish the connection. " +
+ "Try setting another attribute to open the connection using --attribute.")
+ end
+ exit 10
+ end
+ session_from_list(list)
+ end
+
+ def session_from_list(list)
+ list.each do |item|
+ Chef::Log.debug("Adding #{item}")
+ session_opts = {}
+ session_opts[:user] = config[:winrm_user] = Chef::Config[:knife][:winrm_user] || config[:winrm_user]
+ session_opts[:password] = config[:winrm_password] = Chef::Config[:knife][:winrm_password] || config[:winrm_password]
+ session_opts[:port] = Chef::Config[:knife][:winrm_port] || config[:winrm_port]
+ session_opts[:keytab] = Chef::Config[:knife][:kerberos_keytab_file] if Chef::Config[:knife][:kerberos_keytab_file]
+ session_opts[:realm] = Chef::Config[:knife][:kerberos_realm] if Chef::Config[:knife][:kerberos_realm]
+ session_opts[:service] = Chef::Config[:knife][:kerberos_service] if Chef::Config[:knife][:kerberos_service]
+ session_opts[:ca_trust_path] = Chef::Config[:knife][:ca_trust_file] if Chef::Config[:knife][:ca_trust_file]
+ session_opts[:operation_timeout] = 1800 # 30 min OperationTimeout for long bootstraps fix for KNIFE_WINDOWS-8
+
+ ## If you have a \\ in your name you need to use NTLM domain authentication
+ if session_opts[:user].split("\\").length.eql?(2)
+ session_opts[:basic_auth_only] = false
+ else
+ session_opts[:basic_auth_only] = true
+ end
+
+ if config.keys.any? {|k| k.to_s =~ /kerberos/ }
+ session_opts[:transport] = :kerberos
+ session_opts[:basic_auth_only] = false
+ else
+ session_opts[:transport] = (Chef::Config[:knife][:winrm_transport] || config[:winrm_transport]).to_sym
+ session_opts[:disable_sspi] = true
+ if session_opts[:user] and
+ (not session_opts[:password])
+ session_opts[:password] = Chef::Config[:knife][:winrm_password] = config[:winrm_password] = get_password
+
+ end
+ end
+
+ session.use(item, session_opts)
+
+ @longest = item.length if item.length > @longest
+ end
+ session
+ end
+
+ def print_data(host, data, color = :cyan)
+ if data =~ /\n/
+ data.split(/\n/).each { |d| print_data(host, d, color) }
+ else
+ padding = @longest - host.length
+ print ui.color(host, color)
+ padding.downto(0) { print " " }
+ puts data.chomp
+ end
+ end
+
+ def winrm_command(command, subsession=nil)
+ subsession ||= session
+ subsession.relay_command(command)
+ end
+
+ def get_password
+ @password ||= ui.ask("Enter your password: ") { |q| q.echo = false }
+ end
+
+ # Present the prompt and read a single line from the console. It also
+ # detects ^D and returns "exit" in that case. Adds the input to the
+ # history, unless the input is empty. Loops repeatedly until a non-empty
+ # line is input.
+ def read_line
+ loop do
+ command = reader.readline("#{ui.color('knife-winrm>', :bold)} ", true)
+
+ if command.nil?
+ command = "exit"
+ puts(command)
+ else
+ command.strip!
+ end
+
+ unless command.empty?
+ return command
+ end
+ end
+ end
+
+ def reader
+ Readline
+ end
+
+ def interactive
+ puts "Connected to #{ui.list(session.servers.collect { |s| ui.color(s.host, :cyan) }, :inline, " and ")}"
+ puts
+ puts "To run a command on a list of servers, do:"
+ puts " on SERVER1 SERVER2 SERVER3; COMMAND"
+ puts " Example: on latte foamy; echo foobar"
+ puts
+ puts "To exit interactive mode, use 'quit!'"
+ puts
+ while 1
+ command = read_line
+ case command
+ when 'quit!'
+ puts 'Bye!'
+ session.close
+ break
+ when /^on (.+?); (.+)$/
+ raw_list = $1.split(" ")
+ server_list = Array.new
+ session.servers.each do |session_server|
+ server_list << session_server if raw_list.include?(session_server.host)
+ end
+ command = $2
+ winrm_command(command, session.on(*server_list))
+ else
+ winrm_command(command)
+ end
+ end
+ end
+
+ def check_for_errors!(exit_codes)
+
+ exit_codes.each do |host, value|
+ unless Chef::Config[:knife][:returns].include? value.to_i
+ @exit_code = 1
+ ui.error "Failed to execute command on #{host} return code #{value}"
+ end
+ end
+
+ end
+
+ def run
+ STDOUT.sync = STDERR.sync = true
+
+ begin
+ @longest = 0
+
+ configure_session
+
+ case @name_args[1]
+ when "interactive"
+ interactive
+ else
+ winrm_command(@name_args[1..-1].join(" "))
+
+ if config[:returns]
+ check_for_errors! session.exit_codes
+ end
+
+ session.close
+
+ # Knife seems to ignore the return value of this method,
+ # so we exit to force the process exit code for this
+ # subcommand if returns is set
+ exit @exit_code if @exit_code && @exit_code != 0
+ @exit_code || 0
+ end
+ rescue WinRM::WinRMHTTPTransportError => e
+ case e.message
+ when /401/
+ if ! config[:suppress_auth_failure]
+ # Display errors if the caller hasn't opted to retry
+ ui.error "Failed to authenticate to #{@name_args[0].split(" ")} as #{config[:winrm_user]}"
+ ui.info "Response: #{e.message}"
+ raise e
+ end
+ @exit_code = 401
+ else
+ raise e
+ end
+ end
+ end
+
+ end
+ end
+end
+