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.

[View source]

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

[View source]

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

[View source]

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

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

[View source]

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

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

#end_timeObject

[View source]

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

def end_time
  @run_status.end_time
end

#for_json(action_record) ⇒ Object

[View source]

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

[View source]

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

[View source]

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

[View source]

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

def node_name
  @run_status.node.name
end

#post_reporting_dataObject

[View source]

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

[View source]

190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# 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

[View source]

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

[View source]

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

[View source]

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

[View source]

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

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

[View source]

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

[View source]

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

[View source]

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

def start_time
  @run_status.start_time
end

#total_res_countObject

[View source]

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

[View source]

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