Class: Chef::ResourceReporter

Inherits:
EventDispatch::Base show all
Defined in:
lib/chef/resource_reporter.rb

Constant Summary collapse

PROTOCOL_VERSION =
"0.1.0".freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from EventDispatch::Base

#attribute_changed, #attribute_file_load_failed, #attribute_file_loaded, #attribute_load_complete, #attribute_load_start, #compliance_input_enabled, #compliance_input_loaded, #compliance_load_complete, #compliance_load_start, #compliance_profile_enabled, #compliance_profile_loaded, #compliance_waiver_enabled, #compliance_waiver_loaded, #converge_complete, #converge_failed, #converge_start, #cookbook_clean_complete, #cookbook_clean_start, #cookbook_compilation_complete, #cookbook_compilation_start, #cookbook_gem_failed, #cookbook_gem_finished, #cookbook_gem_installing, #cookbook_gem_start, #cookbook_gem_using, #cookbook_resolution_complete, #cookbook_resolution_start, #cookbook_sync_complete, #cookbook_sync_start, #definition_file_load_failed, #definition_file_loaded, #definition_load_complete, #definition_load_start, #deprecation, #handler_executed, #handlers_completed, #handlers_start, #inputs_load_complete, #inputs_load_start, #key_migration_status, #library_file_load_failed, #library_file_loaded, #library_load_complete, #library_load_start, #lwrp_file_load_failed, #lwrp_file_loaded, #lwrp_load_complete, #lwrp_load_start, #msg, #node_load_completed, #node_load_failed, #node_load_start, #node_load_success, #ohai_completed, #ohai_plugin_file_load_failed, #ohai_plugin_file_loaded, #ohai_plugin_load_complete, #ohai_plugin_load_start, #policyfile_loaded, #profiles_load_complete, #profiles_load_start, #provider_requirement_failed, #recipe_file_load_failed, #recipe_file_loaded, #recipe_load_complete, #recipe_load_start, #recipe_not_found, #registration_completed, #registration_failed, #registration_start, #removed_cookbook_file, #resource_action_start, #resource_after_state_loaded, #resource_bypassed, #resource_completed, #resource_current_state_load_bypassed, #resource_current_state_loaded, #resource_failed, #resource_failed_retriable, #resource_skipped, #resource_up_to_date, #resource_update_applied, #resource_update_progress, #resource_updated, #run_start, #skipping_registration, #stream_closed, #stream_opened, #stream_output, #synchronized_cookbook, #updated_cookbook_file, #waivers_load_complete, #waivers_load_start, #whyrun_assumption

Constructor Details

#initialize(rest_client) ⇒ ResourceReporter

Returns a new instance of ResourceReporter.



62
63
64
65
66
67
68
69
# File 'lib/chef/resource_reporter.rb', line 62

def initialize(rest_client)
  @pending_update = nil
  @status = "success"
  @exception = nil
  @rest_client = rest_client
  @error_descriptions = {}
  @expanded_run_list = {}
end

Instance Attribute Details

#action_collectionObject (readonly)

Returns the value of attribute action_collection.



57
58
59
# File 'lib/chef/resource_reporter.rb', line 57

def action_collection
  @action_collection
end

#error_descriptionsObject (readonly)

Returns the value of attribute error_descriptions.



56
57
58
# File 'lib/chef/resource_reporter.rb', line 56

def error_descriptions
  @error_descriptions
end

#exceptionObject (readonly)

Returns the value of attribute exception.



55
56
57
# File 'lib/chef/resource_reporter.rb', line 55

def exception
  @exception
end

#rest_clientObject (readonly)

Returns the value of attribute rest_client.



58
59
60
# File 'lib/chef/resource_reporter.rb', line 58

def rest_client
  @rest_client
end

#statusObject (readonly)

Returns the value of attribute status.



54
55
56
# File 'lib/chef/resource_reporter.rb', line 54

def status
  @status
end

Instance Method Details

#action_collection_registration(action_collection) ⇒ Object



140
141
142
# File 'lib/chef/resource_reporter.rb', line 140

def action_collection_registration(action_collection)
  @action_collection = action_collection
end

#cookbook_resolution_failed(expanded_run_list, exception) ⇒ Object



225
226
227
228
# File 'lib/chef/resource_reporter.rb', line 225

def cookbook_resolution_failed(expanded_run_list, exception)
  description = Formatters::ErrorMapper.cookbook_resolution_failed(expanded_run_list, exception)
  @error_descriptions = description.for_json
end

#cookbook_sync_failed(cookbooks, exception) ⇒ Object



230
231
232
233
# File 'lib/chef/resource_reporter.rb', line 230

def cookbook_sync_failed(cookbooks, exception)
  description = Formatters::ErrorMapper.cookbook_sync_failed(cookbooks, exception)
  @error_descriptions = description.for_json
end

#end_timeObject



181
182
183
# File 'lib/chef/resource_reporter.rb', line 181

def end_time
  @run_status.end_time
end

#for_json(action_record) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/chef/resource_reporter.rb', line 26

def for_json(action_record)
  new_resource = action_record.new_resource
  current_resource = action_record.current_resource

  as_hash = {}
  as_hash["type"]     = new_resource.resource_name.to_sym
  as_hash["name"]     = new_resource.name.to_s
  as_hash["id"]       = new_resource.identity.to_s
  as_hash["after"]    = new_resource.state_for_resource_reporter
  as_hash["before"]   = current_resource ? current_resource.state_for_resource_reporter : {}

  # if the "before" hash is too large, we need to truncate it to avoid 413 errors
  # 640K ought be enough for every Windows registry entry
  as_hash["before"]   = {} if as_hash["before"].to_json.length > 657920
  as_hash["duration"] = ( action_record.elapsed_time * 1000 ).to_i.to_s
  as_hash["delta"]    = new_resource.diff if new_resource.respond_to?(:diff)
  as_hash["delta"]    = "" if as_hash["delta"].nil?

  # TODO: rename as "action"
  as_hash["result"] = action_record.action.to_s
  if new_resource.cookbook_name
    as_hash["cookbook_name"] = new_resource.cookbook_name
    as_hash["cookbook_version"] = new_resource.cookbook_version&.version
  end

  as_hash
end

#handle_error_starting_run(e, url) ⇒ Object



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
# File 'lib/chef/resource_reporter.rb', line 85

def handle_error_starting_run(e, url)
  message = "Reporting error starting run. URL: #{url} "
  code = if e.response.code
           e.response.code.to_s
         else
           "Exception Code Empty"
         end

  if !e.response || (code != "404" && code != "406")
    exception = "Exception: #{code} "
    if Chef::Config[:enable_reporting_url_fatals]
      reporting_status = "Reporting fatals enabled. Aborting run. "
      Chef::Log.error(message + exception + reporting_status)
      raise
    else
      reporting_status = "Disabling reporting for run."
      Chef::Log.info(message + exception + reporting_status)
    end
  else
    reason = "Received #{code}. "
    if code == "406"
      reporting_status = "Client version not supported. Please update the client. Disabling reporting for run."
      Chef::Log.info(message + reason + reporting_status)
    else
      reporting_status = "Disabling reporting for run."
      Chef::Log.trace(message + reason + reporting_status)
    end
  end

  @runs_endpoint_failed = true
end

#headers(additional_headers = {}) ⇒ Object



168
169
170
171
# File 'lib/chef/resource_reporter.rb', line 168

def headers(additional_headers = {})
  options = { "X-Ops-Reporting-Protocol-Version" => PROTOCOL_VERSION }
  options.merge(additional_headers)
end

#node_nameObject



173
174
175
# File 'lib/chef/resource_reporter.rb', line 173

def node_name
  @run_status.node.name
end

#post_reporting_dataObject



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/chef/resource_reporter.rb', line 144

def post_reporting_data
  if reporting_enabled?
    run_data = prepare_run_data
    resource_history_url = "reports/nodes/#{node_name}/runs/#{run_id}"
    Chef::Log.info("Sending resource update report (run-id: #{run_id})")
    Chef::Log.trace run_data.inspect
    compressed_data = encode_gzip(Chef::JSONCompat.to_json(run_data))
    Chef::Log.trace("Sending compressed run data...")
    # Since we're posting compressed data we can not directly call post which expects JSON
    begin
      rest_client.raw_request(:POST, resource_history_url, headers({ "Content-Encoding" => "gzip" }), compressed_data)
    rescue StandardError => e
      if e.respond_to? :response
        Chef::FileCache.store("failed-reporting-data.json", Chef::JSONCompat.to_json_pretty(run_data), 0640)
        Chef::Log.error("Failed to post reporting data to server (HTTP #{e.response.code}), saving to #{Chef::FileCache.load("failed-reporting-data.json", false)}")
      else
        Chef::Log.error("Failed to post reporting data to server (#{e})")
      end
    end
  else
    Chef::Log.trace("Server doesn't support resource history, skipping resource report.")
  end
end

#prepare_run_dataObject



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/chef/resource_reporter.rb', line 194

def prepare_run_data
  run_data = {}
  run_data["action"] = "end"

  run_data["resources"] = updated_resources.map do |action_record|
    for_json(action_record)
  end
  run_data["status"] = @status
  run_data["run_list"] = Chef::JSONCompat.to_json(@run_status.node.run_list)
  run_data["total_res_count"] = total_res_count.to_s
  run_data["data"] = {}
  run_data["start_time"] = start_time.to_s
  run_data["end_time"] = end_time.to_s
  run_data["expanded_run_list"] = Chef::JSONCompat.to_json(@expanded_run_list)

  if exception
    exception_data = {}
    exception_data["class"] = exception.inspect
    exception_data["message"] = exception.message
    exception_data["backtrace"] = Chef::JSONCompat.to_json(exception.backtrace)
    exception_data["description"] = @error_descriptions
    run_data["data"]["exception"] = exception_data
  end
  run_data
end

#run_completed(node) ⇒ Object



121
122
123
124
# File 'lib/chef/resource_reporter.rb', line 121

def run_completed(node)
  @status = "success"
  post_reporting_data
end

#run_failed(exception) ⇒ Object



126
127
128
129
130
131
132
133
134
# File 'lib/chef/resource_reporter.rb', line 126

def run_failed(exception)
  @exception = exception
  @status = "failure"
  # If we failed before we received the run_started callback, there's not much we can do
  # in terms of reporting
  if @run_status
    post_reporting_data
  end
end

#run_idObject



117
118
119
# File 'lib/chef/resource_reporter.rb', line 117

def run_id
  @run_status.run_id
end

#run_list_expand_failed(node, exception) ⇒ Object



220
221
222
223
# File 'lib/chef/resource_reporter.rb', line 220

def run_list_expand_failed(node, exception)
  description = Formatters::ErrorMapper.run_list_expand_failed(node, exception)
  @error_descriptions = description.for_json
end

#run_list_expanded(run_list_expansion) ⇒ Object



136
137
138
# File 'lib/chef/resource_reporter.rb', line 136

def run_list_expanded(run_list_expansion)
  @expanded_run_list = run_list_expansion
end

#run_started(run_status) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/chef/resource_reporter.rb', line 71

def run_started(run_status)
  @run_status = run_status

  if reporting_enabled?
    begin
      resource_history_url = "reports/nodes/#{node_name}/runs"
      server_response = rest_client.post(resource_history_url, { action: :start, run_id: run_id,
                                                                 start_time: start_time.to_s }, headers)
    rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
      handle_error_starting_run(e, resource_history_url)
    end
  end
end

#start_timeObject



177
178
179
# File 'lib/chef/resource_reporter.rb', line 177

def start_time
  @run_status.start_time
end

#total_res_countObject



190
191
192
# File 'lib/chef/resource_reporter.rb', line 190

def total_res_count
  updated_resources.count
end

#updated_resourcesObject

get only the top level resources and strip out the subcollections



186
187
188
# File 'lib/chef/resource_reporter.rb', line 186

def updated_resources
  @updated_resources ||= action_collection&.filtered_collection(max_nesting: 0, up_to_date: false, skipped: false, unprocessed: false) || {}
end