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.



58
59
60
61
62
63
64
65
# File 'lib/chef/resource_reporter.rb', line 58

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.



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

def action_collection
  @action_collection
end

#error_descriptionsObject (readonly)

Returns the value of attribute error_descriptions.



52
53
54
# File 'lib/chef/resource_reporter.rb', line 52

def error_descriptions
  @error_descriptions
end

#exceptionObject (readonly)

Returns the value of attribute exception.



51
52
53
# File 'lib/chef/resource_reporter.rb', line 51

def exception
  @exception
end

#rest_clientObject (readonly)

Returns the value of attribute rest_client.



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

def rest_client
  @rest_client
end

#statusObject (readonly)

Returns the value of attribute status.



50
51
52
# File 'lib/chef/resource_reporter.rb', line 50

def status
  @status
end

Instance Method Details

#action_collection_registration(action_collection) ⇒ Object



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

def action_collection_registration(action_collection)
  @action_collection = action_collection
end

#cookbook_resolution_failed(expanded_run_list, exception) ⇒ Object



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

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



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

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

#end_timeObject



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

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
# 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 : {}
  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



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

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



164
165
166
167
# File 'lib/chef/resource_reporter.rb', line 164

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

#node_nameObject



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

def node_name
  @run_status.node.name
end

#post_reporting_dataObject



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/chef/resource_reporter.rb', line 140

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



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/chef/resource_reporter.rb', line 190

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



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

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

#run_failed(exception) ⇒ Object



122
123
124
125
126
127
128
129
130
# File 'lib/chef/resource_reporter.rb', line 122

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



113
114
115
# File 'lib/chef/resource_reporter.rb', line 113

def run_id
  @run_status.run_id
end

#run_list_expand_failed(node, exception) ⇒ Object



215
216
217
218
# File 'lib/chef/resource_reporter.rb', line 215

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



132
133
134
# File 'lib/chef/resource_reporter.rb', line 132

def run_list_expanded(run_list_expansion)
  @expanded_run_list = run_list_expansion
end

#run_started(run_status) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/chef/resource_reporter.rb', line 67

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



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

def start_time
  @run_status.start_time
end

#total_res_countObject



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

def total_res_count
  updated_resources.count
end

#updated_resourcesObject

get only the top level resources and strip out the subcollections



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

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