Class: Chef::Formatters::ErrorInspectors::CookbookResolveErrorInspector

Inherits:
Object
  • Object
show all
Includes:
APIErrorFormatting
Defined in:
lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb

Constant Summary

Constants included from APIErrorFormatting

APIErrorFormatting::NETWORK_ERROR_CLASSES

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from APIErrorFormatting

#api_key, #clock_skew?, #describe_400_error, #describe_401_error, #describe_406_error, #describe_500_error, #describe_503_error, #describe_eof_error, #describe_http_error, #describe_network_errors, #format_rest_error, #safe_format_rest_error, #server_url, #username

Constructor Details

#initialize(expanded_run_list, exception) ⇒ CookbookResolveErrorInspector

Returns a new instance of CookbookResolveErrorInspector.



31
32
33
34
# 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

Instance Attribute Details

#exceptionObject (readonly)

Returns the value of attribute exception.



26
27
28
# File 'lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb', line 26

def exception
  @exception
end

#expanded_run_listObject (readonly)

Returns the value of attribute expanded_run_list.



27
28
29
# File 'lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb', line 27

def expanded_run_list
  @expanded_run_list
end

Instance Method Details

#add_explanation(error_description) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
# 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) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# 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_ulObject



130
131
132
# 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_messageObject

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."}


141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# 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.is_a?(Hash) && wrapped_error_message.key?("error")
    return wrapped_error_message.to_s
  end

  error_description = Array(wrapped_error_message["error"]).first
  if error_description.is_a?(Hash)
    return error_description
  end

  attempt_json_parse(error_description)
end

#humanize_http_exception(error_description) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# 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