class Chef::Formatters::ErrorInspectors::CookbookResolveErrorInspector

Attributes

exception[R]
expanded_run_list[R]

Public Class Methods

new(expanded_run_list, exception) click to toggle source
# File lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb, line 31
def initialize(expanded_run_list, exception)
  @expanded_run_list = expanded_run_list
  @exception = exception
end

Public Instance Methods

add_explanation(error_description) click to toggle source
# File lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb, line 36
def add_explanation(error_description)
  case exception
  when Net::HTTPClientException, Net::HTTPFatalError
    humanize_http_exception(error_description)
  when EOFError
    describe_eof_error(error_description)
  when *NETWORK_ERROR_CLASSES
    describe_network_errors(error_description)
  else
    error_description.section("Unexpected Error:", "#{exception.class.name}: #{exception.message}")
  end
end
describe_412_error(error_description) click to toggle source
# File lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb, line 84
        def describe_412_error(error_description)
          explanation = ""
          error_reasons = extract_412_error_message

          # Prepare the error message if there is detailed information
          # about individual cookbooks.
          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

          if !explanation.empty?
            error_description.section("Missing Cookbooks:", explanation)
          else
            # If we don't have any cookbook details print a more
            # generic error message.
            if error_reasons.respond_to?(:key?) && error_reasons["message"]
              explanation << "Error message: #{error_reasons["message"]}\n"
            end

            explanation << <<~EOM
              You might be able to resolve this issue with:
                1-) Removing cookbook versions that depend on deleted cookbooks.
                2-) Removing unused cookbook versions.
                3-) Pinning exact cookbook versions using environments.
            EOM
            error_description.section("Cookbook dependency resolution error:", explanation)
          end

          error_description.section("Expanded Run List:", expanded_run_list_ul)
        end
expanded_run_list_ul() click to toggle source
# File lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb, line 130
def expanded_run_list_ul
  @expanded_run_list.map { |i| "* #{i}" }.join("\n")
end
extract_412_error_message() click to toggle source

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."}
# File lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb, line 141
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
humanize_http_exception(error_description) click to toggle source
# File lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb, line 49
        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 cookbooks (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)
          when Net::HTTPNotAcceptable
            describe_406_error(error_description, response)
          else
            describe_http_error(error_description)
          end
        end

Private Instance Methods

attempt_json_parse(maybe_json_string) click to toggle source
# File lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb, line 159
def attempt_json_parse(maybe_json_string)
  Chef::JSONCompat.from_json(maybe_json_string)
rescue Exception
  maybe_json_string
end