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, #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, #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, #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_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, #whyrun_assumption

Constructor Details

#initialize(rest_client) ⇒ ResourceReporter

Returns a new instance of ResourceReporter.



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

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.



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

def action_collection
  @action_collection
end

#error_descriptionsObject (readonly)

Returns the value of attribute error_descriptions.



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

def error_descriptions
  @error_descriptions
end

#exceptionObject (readonly)

Returns the value of attribute exception.



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

def exception
  @exception
end

#rest_clientObject (readonly)

Returns the value of attribute rest_client.



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

def rest_client
  @rest_client
end

#statusObject (readonly)

Returns the value of attribute status.



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

def status
  @status
end

Instance Method Details

#action_collection_registration(action_collection) ⇒ Object



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

def action_collection_registration(action_collection)
  @action_collection = action_collection
  action_collection.register(self) if reporting_enabled?
end

#cookbook_resolution_failed(expanded_run_list, exception) ⇒ Object



223
224
225
226
# File 'lib/chef/resource_reporter.rb', line 223

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



228
229
230
231
# File 'lib/chef/resource_reporter.rb', line 228

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

#end_timeObject



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

def end_time
  @run_status.end_time
end

#for_json(action_record) ⇒ Object



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

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



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

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



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

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

#node_nameObject



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

def node_name
  @run_status.node.name
end

#post_reporting_dataObject



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

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



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

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



119
120
121
122
# File 'lib/chef/resource_reporter.rb', line 119

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

#run_failed(exception) ⇒ Object



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

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



115
116
117
# File 'lib/chef/resource_reporter.rb', line 115

def run_id
  @run_status.run_id
end

#run_list_expand_failed(node, exception) ⇒ Object



218
219
220
221
# File 'lib/chef/resource_reporter.rb', line 218

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



134
135
136
# File 'lib/chef/resource_reporter.rb', line 134

def run_list_expanded(run_list_expansion)
  @expanded_run_list = run_list_expansion
end

#run_started(run_status) ⇒ Object



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

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



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

def start_time
  @run_status.start_time
end

#total_res_countObject



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

def total_res_count
  updated_resources.count
end

#updated_resourcesObject

get only the top level resources and strip out the subcollections



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

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