lib/chef/knife/winrm.rb in knife-windows-1.0.0.rc.1 vs lib/chef/knife/winrm.rb in knife-windows-1.0.0.rc.2

- old
+ new

@@ -1,191 +1,212 @@ -# -# 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_knife_base' -require 'chef/knife/windows_cert_generate' -require 'chef/knife/windows_cert_install' -require 'chef/knife/windows_listener_create' -require 'chef/knife/winrm_session' - -class Chef - class Knife - class Winrm < Knife - - include Chef::Knife::WinrmCommandSharedFunctions - - deps do - require 'readline' - require 'chef/search/query' - end - - attr_writer :password - - banner "knife winrm QUERY COMMAND (options)" - - option :returns, - :long => "--returns CODES", - :description => "A comma delimited list of return codes which indicate success", - :default => "0" - - def run - STDOUT.sync = STDERR.sync = true - - configure_session - validate_password - execute_remote_command - end - - def execute_remote_command - begin - case @name_args[1] - when "interactive" - interactive - else - relay_winrm_command(@name_args[1..-1].join(" ")) - - if config[:returns] - check_for_errors! - end - - # 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 #{locate_config_value(:winrm_user)}" - ui.info "Response: #{e.message}" - ui.info "Hint: Please check winrm configuration 'winrm get winrm/config/service' AllowUnencrypted flag on remote server." - raise e - end - @exit_code = 401 - else - raise e - end - end - end - - def relay_winrm_command(command) - Chef::Log.debug(command) - @winrm_sessions.each do |s| - s.relay_command(command) - 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 - - private - - def interactive - puts "WARN: Deprecated functionality. This will not be supported in future knife-windows releases." - 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!' - break - when /^on (.+?); (.+)$/ - raw_list = $1.split(" ") - server_list = Array.new - @winrm_sessions.each do |session_server| - server_list << session_server if raw_list.include?(session_server.host) - end - command = $2 - relay_winrm_command(command, server_list) - else - relay_winrm_command(command) - end - end - end - - def check_for_errors! - @winrm_sessions.each do |session| - session_exit_code = session.exit_code - unless success_return_codes.include? session_exit_code.to_i - @exit_code = session_exit_code.to_i - ui.error "Failed to execute command on #{session.host} return code #{session_exit_code}" - end - end - 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 success_return_codes - #Redundant if the CLI options parsing occurs - return [0] unless config[:returns] - return @success_return_codes ||= config[:returns].split(',').collect {|item| item.to_i} - 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_knife_base' +require 'chef/knife/windows_cert_generate' +require 'chef/knife/windows_cert_install' +require 'chef/knife/windows_listener_create' +require 'chef/knife/winrm_session' +require 'chef/knife/knife_windows_base' + +class Chef + class Knife + class Winrm < Knife + + include Chef::Knife::WinrmCommandSharedFunctions + include Chef::Knife::KnifeWindowsBase + + FAILED_BASIC_HINT ||= "Hint: Please check winrm configuration 'winrm get winrm/config/service' AllowUnencrypted flag on remote server." + FAILED_NOT_BASIC_HINT ||= <<-eos.gsub /^\s+/, "" + Hint: Make sure to prefix domain usernames with the correct domain name. + Hint: Local user names should be prefixed with computer name or IP address. + EXAMPLE: my_domain\\user_namer + eos + + deps do + require 'readline' + require 'chef/search/query' + end + + attr_writer :password + + banner "knife winrm QUERY COMMAND (options)" + + option :returns, + :long => "--returns CODES", + :description => "A comma delimited list of return codes which indicate success", + :default => "0" + + def run + STDOUT.sync = STDERR.sync = true + + configure_session + validate_password + execute_remote_command + end + + def execute_remote_command + begin + case @name_args[1] + when "interactive" + interactive + else + relay_winrm_command(@name_args[1..-1].join(" ")) + + if config[:returns] + check_for_errors! + end + + # 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, WinRM::WinRMAuthorizationError => e + if authorization_error?(e) + 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 #{locate_config_value(:winrm_user)}" + ui.info "Response: #{e.message}" + ui.info get_failed_authentication_hint + raise e + end + @exit_code = 401 + else + raise e + end + end + end + + def relay_winrm_command(command) + Chef::Log.debug(command) + @winrm_sessions.each do |s| + s.relay_command(command) + 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 + + private + + def interactive + puts "WARN: Deprecated functionality. This will not be supported in future knife-windows releases." + 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!' + break + when /^on (.+?); (.+)$/ + raw_list = $1.split(" ") + server_list = Array.new + @winrm_sessions.each do |session_server| + server_list << session_server if raw_list.include?(session_server.host) + end + command = $2 + relay_winrm_command(command, server_list) + else + relay_winrm_command(command) + end + end + end + + def check_for_errors! + @winrm_sessions.each do |session| + session_exit_code = session.exit_code + unless success_return_codes.include? session_exit_code.to_i + @exit_code = session_exit_code.to_i + ui.error "Failed to execute command on #{session.host} return code #{session_exit_code}" + end + end + 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 authorization_error?(exception) + exception.is_a?(WinRM::WinRMAuthorizationError) || + exception.message =~ /401/ + end + + def success_return_codes + #Redundant if the CLI options parsing occurs + return [0] unless config[:returns] + return @success_return_codes ||= config[:returns].split(',').collect {|item| item.to_i} + end + + def get_failed_authentication_hint + if @session_opts[:basic_auth_only] + FAILED_BASIC_HINT + else + FAILED_NOT_BASIC_HINT + end + end + end + end +end +