module Chef::DataCollector::RunEndMessage

Public Class Methods

construct_message(data_collector, status) click to toggle source

Construct the message payload that is sent to the DataCollector server at the end of a Chef run.

@param data_collector [Chef::DataCollector::Reporter] the calling data_collector instance @param status [String] the overall status of the run, either “success” or “failure”

@return [Hash] A hash containing the run end message data.

# File lib/chef/data_collector/run_end_message.rb, line 42
def construct_message(data_collector, status)
  action_collection = data_collector.action_collection
  run_status = data_collector.run_status
  node = data_collector.node

  message = {
    "chef_server_fqdn" => URI(Chef::Config[:chef_server_url]).host,
    "entity_uuid" => Chef::Config[:chef_guid],
    "expanded_run_list" => data_collector.expanded_run_list,
    "id" => run_status&.run_id,
    "message_version" => "1.1.0",
    "message_type" => "run_converge",
    "node" => node&.data_for_save || {},
    "node_name" => node&.name || data_collector.node_name,
    "organization_name" => organization,
    "resources" => all_action_records(action_collection),
    "run_id" => run_status&.run_id,
    "run_list" => node&.run_list&.for_json || [],
    "cookbooks" => ( node && node["cookbooks"] ) || {},
    "policy_name" => node&.policy_name,
    "policy_group" => node&.policy_group,
    "start_time" => run_status&.start_time&.utc&.iso8601,
    "end_time" => run_status&.end_time&.utc&.iso8601,
    "source" => solo_run? ? "chef_solo" : "chef_client",
    "status" => status,
    "total_resource_count" => all_action_records(action_collection).count,
    "updated_resource_count" => updated_resource_count(action_collection),
    "deprecations" => data_collector.deprecations.to_a,
  }

  if run_status&.exception
    message["error"] = {
      "class" => run_status.exception.class,
      "message" => run_status.exception.message,
      "backtrace" => run_status.exception.backtrace,
      "description" => data_collector.error_description,
    }
  end

  message
end

Private Class Methods

action_record_for_json(action_record) click to toggle source

@return [Hash] the Hash representation of the action_record for sending as JSON

# File lib/chef/data_collector/run_end_message.rb, line 106
def action_record_for_json(action_record)
  new_resource = action_record.new_resource
  current_resource = action_record.current_resource
  after_resource = action_record.after_resource

  hash = {
    "type" => new_resource.resource_name.to_sym,
    "name" => new_resource.name.to_s,
    "id" => safe_resource_identity(new_resource),
    "after" => safe_state_for_resource_reporter(after_resource || new_resource),
    "before" => safe_state_for_resource_reporter(current_resource),
    "duration" => action_record.elapsed_time.nil? ? "" : (action_record.elapsed_time * 1000).to_i.to_s,
    "delta" => new_resource.respond_to?(:diff) && updated_or_failed?(action_record) ? new_resource.diff : "",
    "ignore_failure" => new_resource.ignore_failure,
    "result" => action_record.action.to_s,
    "status" => action_record_status_for_json(action_record),
  }

  # don't use the new_resource for the after_resource if it is skipped or failed
  if action_record.status == :skipped || action_record.status == :failed || action_record.status == :unprocessed
    hash["after"] = {}
  end

  if new_resource.cookbook_name
    hash["cookbook_name"]    = new_resource.cookbook_name
    hash["cookbook_version"] = new_resource.cookbook_version&.version
    hash["recipe_name"]      = new_resource.recipe_name
  end

  hash["conditional"] = action_record.conditional.to_text if action_record.status == :skipped

  unless action_record.exception.nil?
    hash["error_message"] = action_record.exception.message

    hash["error"] = {
      "class" => action_record.exception.class,
      "message" => action_record.exception.message,
      "backtrace" => action_record.exception.backtrace,
      "description" => action_record.error_description,
    }
  end

  hash
end
action_record_status_for_json(action_record) click to toggle source

Helper to convert action record status (symbols) to strings for the Data Collector server. Does a bit of necessary underscores-to-dashes conversion to comply with the Data Collector API.

@return [String] resource status (

# File lib/chef/data_collector/run_end_message.rb, line 178
def action_record_status_for_json(action_record)
  action = action_record.status.to_s
  action = "up-to-date" if action == "up_to_date"
  action
end
action_records(action_collection) click to toggle source

@return [Array<Chef::ActionCollection::ActionRecord>] list of all action_records for all resources

# File lib/chef/data_collector/run_end_message.rb, line 94
def action_records(action_collection)
  return [] if action_collection.nil?

  action_collection.action_records
end
all_action_records(action_collection) click to toggle source

@return [Array<Hash>] list of all action_records rendered as a Hash for sending to JSON

# File lib/chef/data_collector/run_end_message.rb, line 101
def all_action_records(action_collection)
  action_records(action_collection).map { |rec| action_record_for_json(rec) }
end
safe_resource_identity(new_resource) click to toggle source

If the identity property of a resource has been lazied (via a lazy name resource) evaluating it for an unprocessed resource (where the preconditions have not been met) may cause the lazy evaluator to throw – and would otherwise crash the data collector.

@return [String] the resource's identity property

# File lib/chef/data_collector/run_end_message.rb, line 157
def safe_resource_identity(new_resource)
  new_resource.identity.to_s
rescue => e
  "unknown identity (due to #{e.class})"
end
safe_state_for_resource_reporter(resource) click to toggle source

FIXME: This is likely necessary due to the same lazy issue with properties and failing resources?

@return [Hash] the resource's reported state properties

# File lib/chef/data_collector/run_end_message.rb, line 167
def safe_state_for_resource_reporter(resource)
  resource ? resource.state_for_resource_reporter : {}
rescue
  {}
end
updated_or_failed?(action_record) click to toggle source

@return [Boolean] True if the resource was updated or failed

# File lib/chef/data_collector/run_end_message.rb, line 185
def updated_or_failed?(action_record)
  action_record.status == :updated || action_record.status == :failed
end
updated_resource_count(action_collection) click to toggle source

@return [Integer] the number of resources successfully updated in the chef-client run

# File lib/chef/data_collector/run_end_message.rb, line 87
def updated_resource_count(action_collection)
  return 0 if action_collection.nil?

  action_collection.filtered_collection(up_to_date: false, skipped: false, unprocessed: false, failed: false).count
end