diff options
Diffstat (limited to 'lib/chef/formatters/error_inspectors')
8 files changed, 931 insertions, 0 deletions
diff --git a/lib/chef/formatters/error_inspectors/api_error_formatting.rb b/lib/chef/formatters/error_inspectors/api_error_formatting.rb new file mode 100644 index 0000000000..bb5379ed3f --- /dev/null +++ b/lib/chef/formatters/error_inspectors/api_error_formatting.rb @@ -0,0 +1,111 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2012 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. +# + +class Chef + module Formatters + + module APIErrorFormatting + + NETWORK_ERROR_CLASSES = [Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError] + + def describe_network_errors(error_description) + error_description.section("Networking Error:",<<-E) +#{exception.message} + +Your chef_server_url may be misconfigured, or the network could be down. +E + error_description.section("Relevant Config Settings:",<<-E) +chef_server_url "#{server_url}" +E + end + + def describe_401_error(error_description) + if clock_skew? + error_description.section("Authentication Error:",<<-E) +Failed to authenticate to the chef server (http 401). +The request failed because your clock has drifted by more than 15 minutes. +Syncing your clock to an NTP Time source should resolve the issue. +E + else + error_description.section("Authentication Error:",<<-E) +Failed to authenticate to the chef server (http 401). +E + + error_description.section("Server Response:", format_rest_error) + error_description.section("Relevant Config Settings:",<<-E) +chef_server_url "#{server_url}" +node_name "#{username}" +client_key "#{api_key}" + +If these settings are correct, your client_key may be invalid. +E + end + end + + def describe_400_error(error_description) + error_description.section("Invalid Request Data:",<<-E) +The data in your request was invalid (HTTP 400). +E + error_description.section("Server Response:",format_rest_error) + end + + def describe_500_error(error_description) + error_description.section("Unknown Server Error:",<<-E) +The server had a fatal error attempting to load the node data. +E + error_description.section("Server Response:", format_rest_error) + end + + def describe_503_error(error_description) + error_description.section("Server Unavailable","The Chef Server is temporarily unavailable") + error_description.section("Server Response:", format_rest_error) + end + + + # Fallback for unexpected/uncommon http errors + def describe_http_error(error_description) + error_description.section("Unexpected API Request Failure:", format_rest_error) + end + + # Parses JSON from the error response sent by Chef Server and returns the + # error message + def format_rest_error + Array(Chef::JSONCompat.from_json(exception.response.body)["error"]).join('; ') + rescue Exception + exception.response.body + end + + def username + config[:node_name] + end + + def api_key + config[:client_key] + end + + def server_url + config[:chef_server_url] + end + + def clock_skew? + exception.response.body =~ /synchronize the clock/i + end + + end + end +end diff --git a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb new file mode 100644 index 0000000000..1fa8a70b52 --- /dev/null +++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb @@ -0,0 +1,106 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2012 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. +# + +class Chef + module Formatters + module ErrorInspectors + + # == CompileErrorInspector + # Wraps exceptions that occur during the compile phase of a Chef run and + # tries to find the code responsible for the error. + class CompileErrorInspector + + attr_reader :path + attr_reader :exception + + def initialize(path, exception) + @path, @exception = path, exception + end + + def add_explanation(error_description) + case exception + when Chef::Exceptions::RecipeNotFound + error_description.section(exception.class.name, exception.message) + else + error_description.section(exception.class.name, exception.message) + + traceback = filtered_bt.map {|line| " #{line}"}.join("\n") + error_description.section("Cookbook Trace:", traceback) + error_description.section("Relevant File Content:", context) + end + end + + def context + context_lines = [] + context_lines << "#{culprit_file}:\n\n" + Range.new(display_lower_bound, display_upper_bound).each do |i| + line_nr = (i + 1).to_s.rjust(3) + indicator = (i + 1) == culprit_line ? ">> " : ": " + context_lines << "#{line_nr}#{indicator}#{file_lines[i]}" + end + context_lines.join("") + end + + def display_lower_bound + lower = (culprit_line - 8) + lower = 0 if lower < 0 + lower + end + + def display_upper_bound + upper = (culprit_line + 8) + upper = file_lines.size if upper > file_lines.size + upper + end + + def file_lines + @file_lines ||= IO.readlines(culprit_file) + end + + def culprit_backtrace_entry + @culprit_backtrace_entry ||= begin + bt_entry = filtered_bt.first + Chef::Log.debug("backtrace entry for compile error: '#{bt_entry}'") + bt_entry + end + end + + def culprit_line + @culprit_line ||= begin + line_number = culprit_backtrace_entry[/^(?:.\:)?[^:]+:([\d]+)/,1].to_i + Chef::Log.debug("Line number of compile error: '#{line_number}'") + line_number + end + end + + def culprit_file + @culprit_file ||= culprit_backtrace_entry[/^((?:.\:)?[^:]+):([\d]+)/,1] + end + + def filtered_bt + filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/ } + r = exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }} + Chef::Log.debug("filtered backtrace of compile error: #{r.join(",")}") + return r.count > 0 ? r : exception.backtrace + end + + end + + end + end +end diff --git a/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb new file mode 100644 index 0000000000..5642070336 --- /dev/null +++ b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb @@ -0,0 +1,146 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2012 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/formatters/error_inspectors/api_error_formatting' + +class Chef + module Formatters + module ErrorInspectors + class CookbookResolveErrorInspector + + attr_reader :exception + attr_reader :expanded_run_list + + include APIErrorFormatting + + def initialize(expanded_run_list, exception) + @expanded_run_list = expanded_run_list + @exception = exception + end + + def add_explanation(error_description) + case exception + when Net::HTTPServerException, Net::HTTPFatalError + humanize_http_exception(error_description) + when *NETWORK_ERROR_CLASSES + describe_network_errors(error_description) + else + error_description.section("Unexpected Error:","#{exception.class.name}: #{exception.message}") + end + end + + def humanize_http_exception(error_description) + response = exception.response + case response + when Net::HTTPUnauthorized + # TODO: this is where you'd see conflicts b/c of username/clientname stuff + describe_401_error(error_description) + when Net::HTTPForbidden + # TODO: we're rescuing errors from Node.find_or_create + # * could be no write on nodes container + # * could be no read on the node + error_description.section("Authorization Error",<<-E) +This client is not authorized to read some of the information required to +access its coobooks (HTTP 403). + +To access its cookbooks, a client needs to be able to read its environment and +all of the cookbooks in its expanded run list. +E + error_description.section("Expanded Run List:", expanded_run_list_ul) + error_description.section("Server Response:", format_rest_error) + when Net::HTTPPreconditionFailed + describe_412_error(error_description) + when Net::HTTPBadRequest + describe_400_error(error_description) + when Net::HTTPNotFound + when Net::HTTPInternalServerError + describe_500_error(error_description) + when Net::HTTPBadGateway, Net::HTTPServiceUnavailable + describe_503_error(error_description) + else + describe_http_error(error_description) + end + end + + def describe_412_error(error_description) + explanation = "" + error_reasons = extract_412_error_message + if !error_reasons.respond_to?(:key?) + explanation << error_reasons.to_s + else + if error_reasons.key?("non_existent_cookbooks") && !Array(error_reasons["non_existent_cookbooks"]).empty? + explanation << "The following cookbooks are required by the client but don't exist on the server:\n" + Array(error_reasons["non_existent_cookbooks"]).each do |cookbook| + explanation << "* #{cookbook}\n" + end + explanation << "\n" + end + if error_reasons.key?("cookbooks_with_no_versions") && !Array(error_reasons["cookbooks_with_no_versions"]).empty? + explanation << "The following cookbooks exist on the server, but there is no version that meets\nthe version constraints in this environment:\n" + Array(error_reasons["cookbooks_with_no_versions"]).each do |cookbook| + explanation << "* #{cookbook}\n" + end + explanation << "\n" + end + end + + error_description.section("Missing Cookbooks:", explanation) + error_description.section("Expanded Run List:", expanded_run_list_ul) + end + + def expanded_run_list_ul + @expanded_run_list.map {|i| "* #{i}"}.join("\n") + end + + # In my tests, the error from the server is double JSON encoded, but we + # should not rely on this not getting fixed. + # + # Return *should* be a Hash like this: + # { "non_existent_cookbooks" => ["nope"], + # "cookbooks_with_no_versions" => [], + # "message" => "Run list contains invalid items: no such cookbook nope."} + def extract_412_error_message + # Example: + # "{\"error\":[\"{\\\"non_existent_cookbooks\\\":[\\\"nope\\\"],\\\"cookbooks_with_no_versions\\\":[],\\\"message\\\":\\\"Run list contains invalid items: no such cookbook nope.\\\"}\"]}" + + wrapped_error_message = attempt_json_parse(exception.response.body) + unless wrapped_error_message.kind_of?(Hash) && wrapped_error_message.key?("error") + return wrapped_error_message.to_s + end + + error_description = Array(wrapped_error_message["error"]).first + if error_description.kind_of?(Hash) + return error_description + end + attempt_json_parse(error_description) + end + + private + + def attempt_json_parse(maybe_json_string) + Chef::JSONCompat.from_json(maybe_json_string) + rescue Exception + maybe_json_string + end + + + end + end + end +end + diff --git a/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb new file mode 100644 index 0000000000..054984a50e --- /dev/null +++ b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb @@ -0,0 +1,80 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2012 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/formatters/error_inspectors/api_error_formatting' + +class Chef + module Formatters + module ErrorInspectors + + # == CookbookSyncErrorInspector + # Generates human-friendly explanations for errors encountered during + # cookbook sync. + #-- + # TODO: Not sure what errors are commonly seen during cookbook sync, so + # the messaging is kinda generic. + class CookbookSyncErrorInspector + + include APIErrorFormatting + + attr_reader :exception + attr_reader :cookbooks + + def initialize(cookbooks, exception) + @cookbooks, @exception = cookbooks, exception + end + + def add_explanation(error_description) + case exception + when *NETWORK_ERROR_CLASSES + describe_network_errors(error_description) + when Net::HTTPServerException, Net::HTTPFatalError + humanize_http_exception(error_description) + else + error_description.section("Unexpected Error:","#{exception.class.name}: #{exception.message}") + end + end + + def config + Chef::Config + end + + def humanize_http_exception(error_description) + response = exception.response + case response + when Net::HTTPUnauthorized + # TODO: this is where you'd see conflicts b/c of username/clientname stuff + describe_401_error(error_description) + when Net::HTTPBadRequest + describe_400_error(error_description) + when Net::HTTPNotFound + when Net::HTTPInternalServerError + describe_500_error(error_description) + when Net::HTTPBadGateway, Net::HTTPServiceUnavailable + describe_503_error(error_description) + else + describe_http_error(error_description) + end + end + + end + end + end +end + + diff --git a/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb new file mode 100644 index 0000000000..7168ac0edb --- /dev/null +++ b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb @@ -0,0 +1,125 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2012 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/formatters/error_inspectors/api_error_formatting' + +class Chef + module Formatters + module ErrorInspectors + + + # == APIErrorInspector + # Wraps exceptions caused by API calls to the server. + class NodeLoadErrorInspector + + include APIErrorFormatting + + attr_reader :exception + attr_reader :node_name + attr_reader :config + + def initialize(node_name, exception, config) + @node_name = node_name + @exception = exception + @config = config + end + + def add_explanation(error_description) + case exception + when Net::HTTPServerException, Net::HTTPFatalError + humanize_http_exception(error_description) + when *NETWORK_ERROR_CLASSES + describe_network_errors(error_description) + when Chef::Exceptions::PrivateKeyMissing + error_description.section("Private Key Not Found:",<<-E) +Your private key could not be loaded. If the key file exists, ensure that it is +readable by chef-client. +E + error_description.section("Relevant Config Settings:",<<-E) +client_key "#{api_key}" +E + else + error_description.section("Unexpected Error:","#{exception.class.name}: #{exception.message}") + end + end + + def humanize_http_exception(error_description) + response = exception.response + case response + when Net::HTTPUnauthorized + # TODO: this is where you'd see conflicts b/c of username/clientname stuff + describe_401_error(error_description) + when Net::HTTPForbidden + # TODO: we're rescuing errors from Node.find_or_create + # * could be no write on nodes container + # * could be no read on the node + error_description.section("Authorization Error",<<-E) +Your client is not authorized to load the node data (HTTP 403). +E + error_description.section("Server Response:", format_rest_error) + + error_description.section("Possible Causes:",<<-E) +* Your client (#{username}) may have misconfigured authorization permissions. +E + when Net::HTTPBadRequest + describe_400_error(error_description) + when Net::HTTPNotFound + describe_404_error(error_description) + when Net::HTTPInternalServerError + describe_500_error(error_description) + when Net::HTTPBadGateway, Net::HTTPServiceUnavailable + describe_503_error(error_description) + else + describe_http_error(error_description) + end + end + + # Custom 404 error messaging. Users sometimes see 404s when they have + # misconfigured server URLs, and the wrong one redirects to the new + # one, e.g., PUT http://wrong.url/nodes/node-name becomes a GET after a + # redirect. + def describe_404_error(error_description) + error_description.section("Resource Not Found:",<<-E) +The server returned a HTTP 404. This usually indicates that your chef_server_url is incorrect. +E + error_description.section("Relevant Config Settings:",<<-E) +chef_server_url "#{server_url}" +E + end + + def username + config[:node_name] + end + + def api_key + config[:client_key] + end + + def server_url + config[:chef_server_url] + end + + def clock_skew? + exception.response.body =~ /synchronize the clock/i + end + + end + + end + end +end diff --git a/lib/chef/formatters/error_inspectors/registration_error_inspector.rb b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb new file mode 100644 index 0000000000..5389f9f7d0 --- /dev/null +++ b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb @@ -0,0 +1,137 @@ +class Chef + module Formatters + module ErrorInspectors + + # == RegistrationErrorInspector + # Wraps exceptions that occur during the client registration process and + # suggests possible causes. + #-- + # TODO: Lots of duplication with the node_load_error_inspector, just + # slightly tweaked to talk about validation keys instead of other keys. + class RegistrationErrorInspector + attr_reader :exception + attr_reader :node_name + attr_reader :config + + def initialize(node_name, exception, config) + @node_name = node_name + @exception = exception + @config = config + end + + def add_explanation(error_description) + case exception + when Net::HTTPServerException, Net::HTTPFatalError + humanize_http_exception(error_description) + when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError + error_description.section("Network Error:",<<-E) +There was a network error connecting to the Chef Server: +#{exception.message} +E + error_description.section("Relevant Config Settings:",<<-E) +chef_server_url "#{server_url}" + +If your chef_server_url is correct, your network could be down. +E + when Chef::Exceptions::PrivateKeyMissing + error_description.section("Private Key Not Found:",<<-E) +Your private key could not be loaded. If the key file exists, ensure that it is +readable by chef-client. +E + error_description.section("Relevant Config Settings:",<<-E) +validation_key "#{api_key}" +E + else + "#{exception.class.name}: #{exception.message}" + end + end + + def humanize_http_exception(error_description) + response = exception.response + case response + when Net::HTTPUnauthorized + if clock_skew? + error_description.section("Authentication Error:",<<-E) +Failed to authenticate to the chef server (http 401). +The request failed because your clock has drifted by more than 15 minutes. +Syncing your clock to an NTP Time source should resolve the issue. +E + else + error_description.section("Authentication Error:",<<-E) +Failed to authenticate to the chef server (http 401). +E + + error_description.section("Server Response:", format_rest_error) + error_description.section("Relevant Config Settings:",<<-E) +chef_server_url "#{server_url}" +validation_client_name "#{username}" +validation_key "#{api_key}" + +If these settings are correct, your validation_key may be invalid. +E + end + when Net::HTTPForbidden + error_description.section("Authorization Error:",<<-E) +Your validation client is not authorized to create the client for this node (HTTP 403). +E + error_description.section("Possible Causes:",<<-E) +* There may already be a client named "#{config[:node_name]}" +* Your validation client (#{username}) may have misconfigured authorization permissions. +E + when Net::HTTPBadRequest + error_description.section("Invalid Request Data:",<<-E) +The data in your request was invalid (HTTP 400). +E + error_description.section("Server Response:",format_rest_error) + when Net::HTTPNotFound + error_description.section("Resource Not Found:",<<-E) +The server returned a HTTP 404. This usually indicates that your chef_server_url is incorrect. +E + error_description.section("Relevant Config Settings:",<<-E) +chef_server_url "#{server_url}" +E + when Net::HTTPInternalServerError + error_description.section("Unknown Server Error:",<<-E) +The server had a fatal error attempting to load the node data. +E + error_description.section("Server Response:", format_rest_error) + when Net::HTTPBadGateway, Net::HTTPServiceUnavailable + error_description.section("Server Unavailable","The Chef Server is temporarily unavailable") + error_description.section("Server Response:", format_rest_error) + else + error_description.section("Unexpected API Request Failure:", format_rest_error) + end + end + + def username + #config[:node_name] + config[:validation_client_name] + end + + def api_key + config[:validation_key] + #config[:client_key] + end + + def server_url + config[:chef_server_url] + end + + def clock_skew? + exception.response.body =~ /synchronize the clock/i + end + + # Parses JSON from the error response sent by Chef Server and returns the + # error message + #-- + # TODO: this code belongs in Chef::REST + def format_rest_error + Array(Chef::JSONCompat.from_json(exception.response.body)["error"]).join('; ') + rescue Exception + exception.response.body + end + + end + end + end +end diff --git a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb new file mode 100644 index 0000000000..57d8de0ef9 --- /dev/null +++ b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb @@ -0,0 +1,108 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Author:: Tyler Cloke (<tyler@opscode.com>) +# Copyright:: Copyright (c) 2012 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. +# + +class Chef + module Formatters + module ErrorInspectors + class ResourceFailureInspector + + attr_reader :resource + attr_reader :action + attr_reader :exception + + def initialize(resource, action, exception) + @resource = resource + @action = action + @exception = exception + end + + def add_explanation(error_description) + error_description.section(exception.class.name, exception.message) + + unless filtered_bt.empty? + error_description.section("Cookbook Trace:", filtered_bt.join("\n")) + end + + unless dynamic_resource? + error_description.section("Resource Declaration:", recipe_snippet) + end + + error_description.section("Compiled Resource:", "#{resource.to_text}") + + # Template errors get wrapped in an exception class that can show the relevant template code, + # so add them to the error output. + if exception.respond_to?(:source_listing) + error_description.section("Template Context:", "#{exception.source_location}\n#{exception.source_listing}") + end + end + + def recipe_snippet + return nil if dynamic_resource? + @snippet ||= begin + if file = resource.source_line[/^(([\w]:)?[^:]+):([\d]+)/,1] and line = resource.source_line[/^#{file}:([\d]+)/,1].to_i + lines = IO.readlines(file) + + relevant_lines = ["# In #{file}\n\n"] + + + current_line = line - 1 + current_line = 0 if current_line < 0 + nesting = 0 + + loop do + + # low rent parser. try to gracefully handle nested blocks in resources + nesting += 1 if lines[current_line] =~ /[\s]+do[\s]*/ + nesting -= 1 if lines[current_line] =~ /end[\s]*$/ + + relevant_lines << format_line(current_line, lines[current_line]) + + break if lines[current_line + 1].nil? + break if current_line >= (line + 50) + break if nesting <= 0 + + current_line += 1 + end + relevant_lines << format_line(current_line + 1, lines[current_line + 1]) if lines[current_line + 1] + relevant_lines.join("") + end + end + end + + def dynamic_resource? + !resource.source_line + end + + def filtered_bt + filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/ } + exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }} + end + + private + + def format_line(line_nr, line) + # Print line number as 1-indexed not zero + line_nr_string = (line_nr + 1).to_s.rjust(3) + ": " + line_nr_string + line + end + + end + end + end +end diff --git a/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb b/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb new file mode 100644 index 0000000000..ac19a983af --- /dev/null +++ b/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb @@ -0,0 +1,118 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Author:: Tyler Cloke (<tyler@opscode.com>) +# Copyright:: Copyright (c) 2012 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/formatters/error_inspectors/api_error_formatting' + +class Chef + module Formatters + module ErrorInspectors + class RunListExpansionErrorInspector + + include APIErrorFormatting + + attr_reader :exception + attr_reader :node + + def initialize(node, exception) + @node, @exception = node, exception + end + + def add_explanation(error_description) + case exception + when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError + error_description.section("Networking Error:",<<-E) +#{exception.message} + +Your chef_server_url may be misconfigured, or the network could be down. +E + when Net::HTTPServerException, Net::HTTPFatalError + humanize_http_exception(error_description) + when Chef::Exceptions::MissingRole + describe_missing_role(error_description) + else + error_description.section("Unexpected Error:","#{exception.class.name}: #{exception.message}") + end + end + + def describe_missing_role(error_description) + error_description.section("Missing Role(s) in Run List:", missing_roles_explained) + original_run_list = node.run_list.map {|item| "* #{item}"}.join("\n") + error_description.section("Original Run List", original_run_list) + end + + def missing_roles_explained + run_list_expansion.missing_roles_with_including_role.map do |role, includer| + "* #{role} included by '#{includer}'" + end.join("\n") + end + + def run_list_expansion + exception.expansion + end + + def config + Chef::Config + end + + def humanize_http_exception(error_description) + response = exception.response + case response + when Net::HTTPUnauthorized + error_description.section("Authentication Error:",<<-E) +Failed to authenticate to the chef server (http 401). +E + + error_description.section("Server Response:", format_rest_error) + error_description.section("Relevant Config Settings:",<<-E) +chef_server_url "#{server_url}" +node_name "#{username}" +client_key "#{api_key}" + +If these settings are correct, your client_key may be invalid. +E + when Net::HTTPForbidden + # TODO: we're rescuing errors from Node.find_or_create + # * could be no write on nodes container + # * could be no read on the node + error_description.section("Authorization Error",<<-E) +Your client is not authorized to load one or more of your roles (HTTP 403). +E + error_description.section("Server Response:", format_rest_error) + + error_description.section("Possible Causes:",<<-E) +* Your client (#{username}) may have misconfigured authorization permissions. +E + when Net::HTTPInternalServerError + error_description.section("Unknown Server Error:",<<-E) +The server had a fatal error attempting to load a role. +E + error_description.section("Server Response:", format_rest_error) + when Net::HTTPBadGateway, Net::HTTPServiceUnavailable + error_description.section("Server Unavailable","The Chef Server is temporarily unavailable") + error_description.section("Server Response:", format_rest_error) + else + error_description.section("Unexpected API Request Failure:", format_rest_error) + end + end + + end + end + end +end + |