class Chef::DataCollector::Reporter

The DataCollector is mode-agnostic reporting tool which can be used with server-based and solo-based clients. It can report to a file, to an authenticated Chef Automate reporting endpoint, or to a user-supplied webhook. It sends two messages: one at the start of the run and one at the end of the run. Most early failures in the actual Chef::Client itself are reported, but parsing of the client.rb must have succeeded and some code in Chef::Application could throw so early as to prevent reporting. If exceptions are thrown both run-start and run-end messages are still sent in pairs.

Attributes

action_collection[R]

@return [Chef::ActionCollection] the action collection object

deprecations[R]

@return [Set<Hash>] the accumulated list of deprecation warnings

events[R]

@return [Chef::EventDispatch::Dispatcher] the event dispatcher

expanded_run_list[R]

@return [Chef::RunList::RunListExpansion] the expanded run list

node[R]

@return [Chef::Node] the chef node

run_status[R]

@return [Chef::RunStatus] the run status

Public Class Methods

new(events) click to toggle source

@param events [Chef::EventDispatch::Dispatcher] the event dispatcher

# File lib/chef/data_collector.rb, line 65
def initialize(events)
  @events = events
  @expanded_run_list = {}
  @deprecations = Set.new
end

Public Instance Methods

action_collection_registration(action_collection) click to toggle source

Hook event to register with the action_collection if we are still enabled.

This is also how we wire up to the action_collection since it passes itself as the argument.

(see EventDispatch::Base#action_collection_registration)

# File lib/chef/data_collector.rb, line 105
def action_collection_registration(action_collection)
  @action_collection = action_collection
end
deprecation(message, location = caller(2..2)[0]) click to toggle source

Hook event to accumulating deprecation messages

(see EventDispatch::Base#deprecation)

# File lib/chef/data_collector.rb, line 127
def deprecation(message, location = caller(2..2)[0])
  @deprecations << { message: message.message, url: message.url, location: message.location }
end
node_load_success(node) click to toggle source

Hook to grab the node object after it has been successfully loaded

(see EventDispatch::Base#node_load_success)

# File lib/chef/data_collector.rb, line 86
def node_load_success(node)
  @node = node
end
run_completed(node) click to toggle source

Hook to send the run completion message with a status of success

(see EventDispatch::Base#run_completed)

# File lib/chef/data_collector.rb, line 135
def run_completed(node)
  send_run_completion("success")
end
run_failed(exception) click to toggle source

Hook to send the run completion message with a status of failed

(see EventDispatch::Base#run_failed)

# File lib/chef/data_collector.rb, line 143
def run_failed(exception)
  send_run_completion("failure")
end
run_list_expanded(run_list_expansion) click to toggle source

The expanded run list is stored for later use by the run_completed event and message.

(see EventDispatch::Base#run_list_expanded)

# File lib/chef/data_collector.rb, line 95
def run_list_expanded(run_list_expansion)
  @expanded_run_list = run_list_expansion
end
run_start(chef_version, run_status) click to toggle source

Hook to grab the run_status. We also make the decision to run or not run here (our config has been parsed so we should know if we need to run, we unregister if we do not want to run).

(see EventDispatch::Base#run_start)

# File lib/chef/data_collector.rb, line 77
def run_start(chef_version, run_status)
  events.unregister(self) unless Chef::DataCollector::ConfigValidation.should_be_enabled?
  @run_status = run_status
end
run_started(run_status) click to toggle source
  • Creates and writes our NodeUUID back to the node object

  • Sanity checks the data collector

  • Sends the run start message

  • If the run_start message fails, this may disable the rest of data collection or fail hard

(see EventDispatch::Base#run_started)

# File lib/chef/data_collector.rb, line 116
def run_started(run_status)
  Chef::DataCollector::ConfigValidation.validate_server_url!
  Chef::DataCollector::ConfigValidation.validate_output_locations!

  send_run_start
end

Private Instance Methods

headers() click to toggle source

@return [Hash] HTTP headers for the data collector endpoint

# File lib/chef/data_collector.rb, line 278
def headers
  headers = { "Content-Type" => "application/json" }

  unless Chef::Config[:data_collector][:token].nil?
    headers["x-data-collector-token"] = Chef::Config[:data_collector][:token]
    headers["x-data-collector-auth"]  = "version=1.0"
  end

  headers
end
send_run_completion(status) click to toggle source

Send the run completion message to the configured server or output locations

@param status [String] Either “success” or “failed”

# File lib/chef/data_collector.rb, line 267
def send_run_completion(status)
  # this is necessary to send a run_start message when we fail before the run_started chef event.
  # we adhere to a contract that run_start + run_completion events happen in pairs.
  send_run_start unless sent_run_start?

  message = Chef::DataCollector::RunEndMessage.construct_message(self, status)
  send_to_data_collector(message)
  send_to_output_locations(message)
end
send_run_start() click to toggle source

Send the run start message to the configured server or output locations

# File lib/chef/data_collector.rb, line 256
def send_run_start
  message = Chef::DataCollector::RunStartMessage.construct_message(self)
  send_to_data_collector(message)
  send_to_output_locations(message)
  @sent_run_start = true
end
send_to_data_collector(message) click to toggle source

Handle POST’ing data to the data collector. Note that this is a totally separate concern from the array of URI’s in the extra configured output_locations.

On failure this will unregister the data collector (if there are no other configured output_locations) and optionally will either silently continue or fail hard depending on configuration.

@param message [Hash] message to send

# File lib/chef/data_collector.rb, line 174
def send_to_data_collector(message)
  return unless Chef::Config[:data_collector][:server_url]

  @http ||= setup_http_client(Chef::Config[:data_collector][:server_url])
  @http.post(nil, message, headers)
rescue => e
  # Do not disable data collector reporter if additional output_locations have been specified
  events.unregister(self) unless Chef::Config[:data_collector][:output_locations]

  begin
    code = e&.response&.code.to_s
  rescue
    # i really don't care
  end

  code ||= "No HTTP Code"

  msg = "Error while reporting run start to Data Collector. URL: #{Chef::Config[:data_collector][:server_url]} Exception: #{code} -- #{e.message} "

  if Chef::Config[:data_collector][:raise_on_failure]
    Chef::Log.error(msg)
    raise
  else
    if code == "404"
      # Make the message non-scary for folks who don't have automate:
      msg << " (This is normal if you do not have #{ChefUtils::Dist::Automate::PRODUCT})"
      Chef::Log.debug(msg)
    else
      Chef::Log.warn(msg)
    end
  end
end
send_to_file_location(file_name, message) click to toggle source

Sends a single message to a file, rendered as JSON.

@param file_name [String] the file to write to @param message [Hash] the message to render as JSON

# File lib/chef/data_collector.rb, line 228
def send_to_file_location(file_name, message)
  File.open(File.expand_path(file_name), "a") do |fh|
    fh.puts Chef::JSONCompat.to_json(message, validate_utf8: false)
  end
end
send_to_http_location(http_url, message) click to toggle source

Sends a single message to a http uri, rendered as JSON. Maintains a cache of Chef::HTTP objects to use on subsequent requests.

@param http_url [String] the configured http uri string endpoint to send to @param message [Hash] the message to render as JSON

# File lib/chef/data_collector.rb, line 240
def send_to_http_location(http_url, message)
  @http_output_locations_clients[http_url] ||= setup_http_client(http_url)
  @http_output_locations_clients[http_url].post(nil, message, headers)
rescue
  # FIXME: we do all kinds of complexity to deal with errors in send_to_data_collector and we just don't care here, which feels like
  # like poor behavior on several different levels, at least its a warn now... (I don't quite understand why it was written this way)
  Chef::Log.warn("Data collector failed to send to URL location #{http_url}. Please check your configured data_collector.output_locations")
end
send_to_output_locations(message) click to toggle source

Process sending the configured message to all the extra output locations.

@param message [Hash] message to send

# File lib/chef/data_collector.rb, line 211
def send_to_output_locations(message)
  return unless Chef::Config[:data_collector][:output_locations]

  Chef::DataCollector::ConfigValidation.validate_output_locations!
  Chef::Config[:data_collector][:output_locations].each do |type, locations|
    Array(locations).each do |location|
      send_to_file_location(location, message) if type == :files
      send_to_http_location(location, message) if type == :urls
    end
  end
end
sent_run_start?() click to toggle source

@return [Boolean] if we’ve sent a run_start message yet

# File lib/chef/data_collector.rb, line 250
def sent_run_start?
  !!@sent_run_start
end
setup_http_client(url) click to toggle source

Construct a http client for either the main data collector or for the http output_locations.

Note that based on the token setting either the main data collector and all the http output_locations are going to all require chef-server authentication or not. There is no facility to mix-and-match on a per-url basis.

@param url [String] the string url to connect to @returns [Chef::HTTP] the appropriate Chef::HTTP subclass instance to use

# File lib/chef/data_collector.rb, line 158
def setup_http_client(url)
  if Chef::Config[:data_collector][:token].nil?
    Chef::ServerAPI.new(url, validate_utf8: false)
  else
    Chef::HTTP::SimpleJSON.new(url, validate_utf8: false)
  end
end