Skip to content

Instantly share code, notes, and snippets.

@blalor
Created July 2, 2015 14:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save blalor/49bfc73473ebb84e790a to your computer and use it in GitHub Desktop.
Save blalor/49bfc73473ebb84e790a to your computer and use it in GitHub Desktop.
Chef report handler for Consul
## chef report handler for Consul
## depends on a chef-client check being registered with the agent
## • pings TTL-style check with ok/warning status on chef run pass/fail
## • stores run report and node attributes in chef/reports/<datacenter>/<consul_node_name>
require "chef/handler"
require "rest_client"
class ConsulHandler < Chef::Handler
attr_reader :config
attr_reader :base_url
def initialize(config = {})
@config = config
@config[:check_id] ||= "chef-client"
@config[:agent_port] ||= 8500
## run_status is not set, yet, so node property will be nil. defer
## setting @config[:report_key_path] until it's needed.
@base_url = "http://localhost:#{config[:agent_port]}/v1"
end
def report
if node[:consul][:node_name].nil? or node[:consul][:node_name].empty? then
Chef::Log.error("empty Consul node name")
else
submit_health_check()
update_report()
end
end
private
def submit_health_check
## status is pass, warn, fail
## "fail" causes services on this host to be unreachable via Consul
## An immediate "fail" feels too strict, but never failing as long as
## chef continues to run (and fail) feels too loose. May need to keep
## track of how many chef failures there are and fail after > 1 (?)
status = run_status.success? ? "pass" : "warn"
params = {}
if run_status.success? then
params[:note] = "chef run completed successfully in #{run_status.elapsed_time} seconds"
else
params[:note] = "chef run failed: #{run_status.formatted_exception}"
end
Chef::Log.info("submitting Consul health check with status #{status}")
begin
RestClient.get(
"#{base_url}/agent/check/#{status}/#{config[:check_id]}",
:params => params
)
rescue RestClient::Exception, Errno::ECONNREFUSED => e
Chef::Log.error("Could not submit health check to Consul: #{e.message}")
end
end
def update_report
@config[:report_key_path] ||= "chef/reports/#{node[:consul][:datacenter]}"
full_key_path = "#{config[:report_key_path]}/#{node[:consul][:node_name]}"
## *all* recipes! Chef -- at least this version -- doesn't consider foo
## and foo::default equivalent, so they would appear twice.
recipes = node.run_state[:seen_recipes].map{ |k, v| k.include?("::") ? k : k + "::default" }.uniq
report_data = {
:node_name => node.name,
:fqdn => node["fqdn"],
:environment => node.chef_environment,
:run_list => node.run_list,
:roles => node["roles"],
:recipes => recipes,
:attrs => {
:normal => node.normal_attrs,
:default => node.default_attrs,
:override => node.override_attrs,
:automatic => node.automatic_attrs,
},
:end_time => run_status.end_time.utc.iso8601,
:elapsed_time => run_status.elapsed_time,
}
Chef::Log.info("updating Consul report at #{full_key_path}")
begin
RestClient.put(
"#{base_url}/kv/#{full_key_path}",
Chef::JSONCompat.to_json(report_data)
)
rescue RestClient::Exception, Errno::ECONNREFUSED => e
Chef::Log.error("Could not submit report data to Consul: #{e.message}")
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment