class Chef::ResourceReporter

Constants

PROTOCOL_VERSION

Attributes

action_collection[R]
error_descriptions[R]
exception[R]
rest_client[R]
status[R]

Public Class Methods

new(rest_client) click to toggle source
# 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

Public Instance Methods

action_collection_registration(action_collection) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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_time() click to toggle source
# File lib/chef/resource_reporter.rb, line 177
def end_time
  @run_status.end_time
end
for_json(action_record) click to toggle source
# 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) click to toggle source
# 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 = {}) click to toggle source
# 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_name() click to toggle source
# File lib/chef/resource_reporter.rb, line 169
def node_name
  @run_status.node.name
end
post_reporting_data() click to toggle source
# 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_data() click to toggle source
# 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) click to toggle source
# File lib/chef/resource_reporter.rb, line 117
def run_completed(node)
  @status = "success"
  post_reporting_data
end
run_failed(exception) click to toggle source
# 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_id() click to toggle source
# File lib/chef/resource_reporter.rb, line 113
def run_id
  @run_status.run_id
end
run_list_expand_failed(node, exception) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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_time() click to toggle source
# File lib/chef/resource_reporter.rb, line 173
def start_time
  @run_status.start_time
end
total_res_count() click to toggle source
# File lib/chef/resource_reporter.rb, line 186
def total_res_count
  updated_resources.count
end
updated_resources() click to toggle source

get only the top level resources and strip out the subcollections

# 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

Private Instance Methods

encode_gzip(data) click to toggle source
# File lib/chef/resource_reporter.rb, line 236
def encode_gzip(data)
  "".tap do |out|
    Zlib::GzipWriter.wrap(StringIO.new(out)) { |gz| gz << data }
  end
end
reporting_enabled?() click to toggle source
# File lib/chef/resource_reporter.rb, line 232
def reporting_enabled?
  Chef::Config[:enable_reporting] && !Chef::Config[:why_run] && !@runs_endpoint_failed
end