chef-handler-datadog-demo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env.example +2 -0
- data/.gitignore +63 -0
- data/.rspec +1 -0
- data/.rubocop.yml +44 -0
- data/.travis.yml +25 -0
- data/Appraisals +16 -0
- data/CHANGELOG.md +102 -0
- data/CONTRIBUTING.md +23 -0
- data/Gemfile +14 -0
- data/Guardfile +37 -0
- data/LICENSE.txt +20 -0
- data/README.md +40 -0
- data/Rakefile +23 -0
- data/chef-handler-datadog-demo.gemspec +33 -0
- data/gemfiles/chef_10.14.4.gemfile +17 -0
- data/gemfiles/chef_10.gemfile +16 -0
- data/gemfiles/chef_11.gemfile +16 -0
- data/gemfiles/chef_12.gemfile +16 -0
- data/lib/chef/handler/datadog_demo.rb +123 -0
- data/lib/chef/handler/datadog_demo_chef_events.rb +174 -0
- data/lib/chef/handler/datadog_demo_chef_metrics.rb +62 -0
- data/lib/chef/handler/datadog_demo_chef_tags.rb +140 -0
- data/lib/chef_handler_datadog_demo.rb +6 -0
- data/spec/datadog_spec.rb +495 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/failed_Chef_run/sets_alert_handles_when_specified.yml +513 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/failed_Chef_run/sets_event_title_correctly.yml +258 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/failed_Chef_run/sets_priority_correctly.yml +258 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/handles_no_application_key/fails_when_no_application_key_is_provided.yml +143 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/hostname/uses_the_node_name_when_no_config_specified.yml +250 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/hostname/uses_the_specified_hostname_when_provided.yml +250 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_correct_hostname_on_an_ec2_node/does_not_use_the_instance_id_when_config_specified_to_false.yml +250 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_correct_hostname_on_an_ec2_node/uses_the_instance_id_when_config_is_specified.yml +250 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_correct_hostname_on_an_ec2_node/uses_the_instance_id_when_no_config_specified.yml +250 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/emits_events/posts_an_event.yml +250 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/emits_events/sets_priority_correctly.yml +250 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/emits_metrics/reports_metrics.yml +250 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/sets_tags/puts_the_tags_for_the_current_node.yml +250 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/resources/failure_during_compile_phase/does_not_emit_metrics.yml +82 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/resources/failure_during_compile_phase/only_emits_a_failure_metric.yml +134 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/resources/failure_during_compile_phase/posts_an_event.yml +134 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_empty_tag_prefix.yml +251 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_user-specified_tag_prefix.yml +251 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/sets_the_role_and_env_and_tags.yml +251 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_unspecified/sets_role_env_and_nothing_else.yml +251 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags_submission_retries/when_not_specified/does_not_retry_after_a_failed_submission.yml +241 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags_submission_retries/when_specified_as_2_retries/retries_no_more_than_twice.yml +331 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags_submission_retries/when_specified_as_2_retries/stops_retrying_once_submission_is_successful.yml +287 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/updated_resources/posts_an_event.yml +250 -0
- metadata +285 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "http://rubygems.org"
|
4
|
+
|
5
|
+
gem "chef", "10.14.4"
|
6
|
+
gem "ohai", "< 8.0"
|
7
|
+
|
8
|
+
group :localdev do
|
9
|
+
gem "guard"
|
10
|
+
gem "guard-rspec"
|
11
|
+
gem "guard-rubocop"
|
12
|
+
gem "pry"
|
13
|
+
gem "terminal-notifier-guard"
|
14
|
+
gem "travis-lint"
|
15
|
+
end
|
16
|
+
|
17
|
+
gemspec :path => "../"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "http://rubygems.org"
|
4
|
+
|
5
|
+
gem "chef", "~> 10.0"
|
6
|
+
|
7
|
+
group :localdev do
|
8
|
+
gem "guard"
|
9
|
+
gem "guard-rspec"
|
10
|
+
gem "guard-rubocop"
|
11
|
+
gem "pry"
|
12
|
+
gem "terminal-notifier-guard"
|
13
|
+
gem "travis-lint"
|
14
|
+
end
|
15
|
+
|
16
|
+
gemspec :path => "../"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "http://rubygems.org"
|
4
|
+
|
5
|
+
gem "chef", "~> 11.0"
|
6
|
+
|
7
|
+
group :localdev do
|
8
|
+
gem "guard"
|
9
|
+
gem "guard-rspec"
|
10
|
+
gem "guard-rubocop"
|
11
|
+
gem "pry"
|
12
|
+
gem "terminal-notifier-guard"
|
13
|
+
gem "travis-lint"
|
14
|
+
end
|
15
|
+
|
16
|
+
gemspec :path => "../"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "http://rubygems.org"
|
4
|
+
|
5
|
+
gem "chef", "~> 12.0"
|
6
|
+
|
7
|
+
group :localdev do
|
8
|
+
gem "guard"
|
9
|
+
gem "guard-rspec"
|
10
|
+
gem "guard-rubocop"
|
11
|
+
gem "pry"
|
12
|
+
gem "terminal-notifier-guard"
|
13
|
+
gem "travis-lint"
|
14
|
+
end
|
15
|
+
|
16
|
+
gemspec :path => "../"
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'chef/handler'
|
4
|
+
require 'chef/mash'
|
5
|
+
require 'dogapi'
|
6
|
+
require_relative 'datadog_demo_chef_metrics'
|
7
|
+
require_relative 'datadog_demo_chef_tags'
|
8
|
+
require_relative 'datadog_demo_chef_events'
|
9
|
+
|
10
|
+
class Chef
|
11
|
+
class Handler
|
12
|
+
# Datadog handler to send Chef run details to Datadog
|
13
|
+
class DatadogDemo < Chef::Handler
|
14
|
+
attr_reader :config
|
15
|
+
|
16
|
+
# For the tags to work, the client must have created an Application Key on the
|
17
|
+
# "Account Settings" page here: https://app.datadoghq.com/account/settings
|
18
|
+
# It should be passed along from the node/role/environemnt attributes, as the default is nil.
|
19
|
+
def initialize(config = {})
|
20
|
+
@config = Mash.new(config)
|
21
|
+
# If *any* api_key is not provided, this will fail immediately.
|
22
|
+
@dog = Dogapi::Client.new(@config[:api_key], @config[:application_key])
|
23
|
+
end
|
24
|
+
|
25
|
+
def report
|
26
|
+
# use datadog agent proxy settings, if available
|
27
|
+
use_agent_proxy unless ENV['DATADOG_PROXY'].nil?
|
28
|
+
|
29
|
+
# prepare the metrics, event, and tags information to be reported
|
30
|
+
prepare_report_for_datadog
|
31
|
+
# post the report information to the datadog service
|
32
|
+
send_report_to_datadog
|
33
|
+
ensure
|
34
|
+
# restore the env proxy settings before leaving to avoid downstream side-effects
|
35
|
+
restore_env_proxies unless ENV['DATADOG_PROXY'].nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# prepare metrics, event, and tags data for posting to datadog
|
41
|
+
def prepare_report_for_datadog
|
42
|
+
# uses class method accessors for run_status and config
|
43
|
+
hostname = resolve_correct_hostname
|
44
|
+
# prepare chef run metrics
|
45
|
+
@metrics =
|
46
|
+
DatadogChefMetrics.new
|
47
|
+
.with_dogapi_client(@dog)
|
48
|
+
.with_hostname(hostname)
|
49
|
+
.with_run_status(run_status)
|
50
|
+
|
51
|
+
# Collect and prepare tags
|
52
|
+
@tags =
|
53
|
+
DatadogChefTags.new
|
54
|
+
.with_dogapi_client(@dog)
|
55
|
+
.with_hostname(hostname)
|
56
|
+
.with_run_status(run_status)
|
57
|
+
.with_application_key(config[:application_key])
|
58
|
+
.with_tag_prefix(config[:tag_prefix])
|
59
|
+
.with_retries(config[:tags_submission_retries])
|
60
|
+
|
61
|
+
# Build the chef event information
|
62
|
+
@event =
|
63
|
+
DatadogChefEvents.new
|
64
|
+
.with_dogapi_client(@dog)
|
65
|
+
.with_hostname(hostname)
|
66
|
+
.with_run_status(run_status)
|
67
|
+
.with_failure_notifications(@config['notify_on_failure'])
|
68
|
+
.with_tags(@tags.combined_host_tags)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Submit metrics, event, and tags information to datadog
|
72
|
+
def send_report_to_datadog
|
73
|
+
@metrics.emit_to_datadog
|
74
|
+
@event.emit_to_datadog
|
75
|
+
@tags.send_update_to_datadog
|
76
|
+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
|
77
|
+
Chef::Log.error("Could not connect to Datadog. Connection error:\n" + e)
|
78
|
+
Chef::Log.error('Data to be submitted was:')
|
79
|
+
Chef::Log.error(@event.event_title)
|
80
|
+
Chef::Log.error(@event.event_body)
|
81
|
+
Chef::Log.error('Tags to be set for this run:')
|
82
|
+
Chef::Log.error(@tags.combined_host_tags)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Select which hostname to report back to Datadog.
|
86
|
+
# Makes decision based on inputs from `config` and when absent, use the
|
87
|
+
# node's `ec2` attribute existence to make the decision.
|
88
|
+
#
|
89
|
+
# @return [String] the hostname decided upon
|
90
|
+
def resolve_correct_hostname
|
91
|
+
node = run_status.node
|
92
|
+
use_ec2_instance_id = !config.key?(:use_ec2_instance_id) ||
|
93
|
+
(config.key?(:use_ec2_instance_id) && config[:use_ec2_instance_id])
|
94
|
+
|
95
|
+
if config[:hostname]
|
96
|
+
puts "found hostname #{config[:hostname]} in config object"
|
97
|
+
config[:hostname]
|
98
|
+
elsif use_ec2_instance_id && node.attribute?('ec2') && node.ec2.attribute?('instance_id')
|
99
|
+
node.ec2.instance_id
|
100
|
+
else
|
101
|
+
node.name
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Using the agent proxy settings requires setting http(s)_proxy
|
106
|
+
# env vars. However, original env var settings need to be
|
107
|
+
# preserved for restoration at the end of the handler.
|
108
|
+
def use_agent_proxy
|
109
|
+
Chef::Log.info('Using agent proxy settings')
|
110
|
+
@env_http_proxy = ENV['http_proxy']
|
111
|
+
@env_https_proxy = ENV['https_proxy']
|
112
|
+
ENV['http_proxy'] = ENV['DATADOG_PROXY']
|
113
|
+
ENV['https_proxy'] = ENV['DATADOG_PROXY']
|
114
|
+
end
|
115
|
+
|
116
|
+
# Restore environment proxy settings to pre-report values
|
117
|
+
def restore_env_proxies
|
118
|
+
ENV['http_proxy'] = @env_http_proxy
|
119
|
+
ENV['https_proxy'] = @env_https_proxy
|
120
|
+
end
|
121
|
+
end # end class Datadog
|
122
|
+
end # end class Handler
|
123
|
+
end # end class Chef
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'chef/handler'
|
4
|
+
require 'chef/mash'
|
5
|
+
require 'dogapi'
|
6
|
+
|
7
|
+
# helper class for sending events about chef runs
|
8
|
+
class DatadogChefEvents
|
9
|
+
def initialize
|
10
|
+
@dog = nil
|
11
|
+
@hostname = nil
|
12
|
+
@run_status = nil
|
13
|
+
@failure_notfications = nil
|
14
|
+
|
15
|
+
@alert_type = ''
|
16
|
+
@event_priority = ''
|
17
|
+
@event_title = ''
|
18
|
+
# TODO: refactor how event_body is constructed in the class methods
|
19
|
+
# handling of the event_body is a bit clunky and depends on the order of
|
20
|
+
# method calls
|
21
|
+
@event_body = ''
|
22
|
+
end
|
23
|
+
|
24
|
+
# set the dogapi client handle
|
25
|
+
#
|
26
|
+
# @param dogapi_client [Dogapi::Client] datadog api client handle
|
27
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
28
|
+
def with_dogapi_client(dogapi_client)
|
29
|
+
@dog = dogapi_client
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# set the target hostname (chef node name)
|
34
|
+
#
|
35
|
+
# @param hostname [String] hostname to use for the handler report
|
36
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
37
|
+
def with_hostname(hostname)
|
38
|
+
@hostname = hostname
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# set the chef run status used for the report
|
43
|
+
#
|
44
|
+
# @param run_status [Chef::RunStatus] current chef run status
|
45
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
46
|
+
def with_run_status(run_status)
|
47
|
+
@run_status = run_status
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# set the failure notification list
|
52
|
+
#
|
53
|
+
# @param failure_notifications [Array] set of datadog notification handles
|
54
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
55
|
+
def with_failure_notifications(failure_notifications)
|
56
|
+
@failure_notifications = failure_notifications
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
# set the datadog host tags associated with the event
|
61
|
+
#
|
62
|
+
# @param [Array] the set of host tags
|
63
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
64
|
+
def with_tags(tags)
|
65
|
+
@tags = tags
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Emit Chef event to Datadog
|
70
|
+
def emit_to_datadog
|
71
|
+
@event_body = ''
|
72
|
+
build_event_data
|
73
|
+
evt = @dog.emit_event(Dogapi::Event.new(@event_body,
|
74
|
+
msg_title: @event_title,
|
75
|
+
event_type: 'config_management.run',
|
76
|
+
event_object: @hostname,
|
77
|
+
alert_type: @alert_type,
|
78
|
+
priority: @event_priority,
|
79
|
+
source_type_name: 'chef',
|
80
|
+
tags: @tags
|
81
|
+
), host: @hostname)
|
82
|
+
|
83
|
+
begin
|
84
|
+
# FIXME: nice-to-have: abstract format of return value away a bit
|
85
|
+
# in dogapi directly. See https://github.com/DataDog/dogapi-rb/issues/18
|
86
|
+
if evt.length < 2
|
87
|
+
Chef::Log.warn("Unexpected response from Datadog Event API: #{evt}")
|
88
|
+
else
|
89
|
+
# [http_response_code, {"event" => {"url" => "...", ...}}]
|
90
|
+
# 2xx means ok
|
91
|
+
if evt[0].to_i / 100 != 2
|
92
|
+
Chef::Log.warn("Could not submit event to Datadog (HTTP call failed): #{evt[0]}")
|
93
|
+
else
|
94
|
+
Chef::Log.debug("Successfully submitted Chef event to Datadog for #{@hostname} at #{evt[1]['event']['url']}")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
rescue
|
98
|
+
Chef::Log.warn("Could not determine whether chef run was successfully submitted to Datadog: #{evt}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def pluralize(number, noun)
|
105
|
+
case number
|
106
|
+
when 0..1
|
107
|
+
"less than 1 #{noun}"
|
108
|
+
else
|
109
|
+
"#{number.round} #{noun}s"
|
110
|
+
end
|
111
|
+
rescue
|
112
|
+
Chef::Log.warn("Cannot make #{number} more legible")
|
113
|
+
"#{number} #{noun}s"
|
114
|
+
end
|
115
|
+
|
116
|
+
# Compose a list of resources updated during a run.
|
117
|
+
def update_resource_list
|
118
|
+
# No resources updated?
|
119
|
+
return unless @run_status.updated_resources.length.to_i > 0
|
120
|
+
|
121
|
+
if @run_status.failed?
|
122
|
+
# Shorten the list when there is a failure for stacktrace debugging
|
123
|
+
report_resources = @run_status.updated_resources.last(5)
|
124
|
+
else
|
125
|
+
report_resources = @run_status.updated_resources
|
126
|
+
end
|
127
|
+
|
128
|
+
@event_body = "\n$$$\n"
|
129
|
+
report_resources.each do |r|
|
130
|
+
@event_body << "- #{r} (#{r.defined_at})\n"
|
131
|
+
end
|
132
|
+
@event_body << "\n$$$\n"
|
133
|
+
end
|
134
|
+
|
135
|
+
# Marshal the Event data for submission
|
136
|
+
def build_event_data
|
137
|
+
# bail early in case of a compiletime failure
|
138
|
+
# OPTIMIZE: Use better inspectors to handle failure scenarios, refactor needed.
|
139
|
+
if @run_status.elapsed_time.nil?
|
140
|
+
@alert_type = 'error'
|
141
|
+
@event_title = "Chef failed during compile phase on #{@hostname} "
|
142
|
+
@event_priority = 'normal'
|
143
|
+
@event_body = 'Chef was unable to complete a run, an error during compilation may have occurred.'
|
144
|
+
else
|
145
|
+
run_time = pluralize(@run_status.elapsed_time, 'second')
|
146
|
+
|
147
|
+
# This is the first line of the Event body, the rest is appended here.
|
148
|
+
@event_body = "Chef updated #{@run_status.updated_resources.length} resources out of #{@run_status.all_resources.length} resources total."
|
149
|
+
|
150
|
+
# Update resource list, truncated when failed to 5
|
151
|
+
# update will add to the event_body
|
152
|
+
update_resource_list
|
153
|
+
|
154
|
+
if @run_status.success?
|
155
|
+
@alert_type = 'success'
|
156
|
+
@event_priority = 'low'
|
157
|
+
@event_title = "Chef completed in #{run_time} on #{@hostname} "
|
158
|
+
else
|
159
|
+
@alert_type = 'error'
|
160
|
+
@event_priority = 'normal'
|
161
|
+
@event_title = "Chef failed in #{run_time} on #{@hostname} "
|
162
|
+
|
163
|
+
if @failure_notifications
|
164
|
+
handles = @failure_notifications
|
165
|
+
# convert the notification handle array to a string
|
166
|
+
@event_body << "\nAlerting: #{handles.join(' ')}\n"
|
167
|
+
end
|
168
|
+
|
169
|
+
@event_body << "\n$$$\n#{@run_status.formatted_exception}\n$$$\n"
|
170
|
+
@event_body << "\n$$$\n#{@run_status.backtrace.join("\n")}\n$$$\n"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end # end module DatadogChefEvent
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'dogapi'
|
3
|
+
|
4
|
+
# helper class for sending datadog metrics from a chef run
|
5
|
+
class DatadogChefMetrics
|
6
|
+
def initialize
|
7
|
+
@dog = nil
|
8
|
+
@hostname = ''
|
9
|
+
@run_status = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
# set the dogapi client handle
|
13
|
+
#
|
14
|
+
# @param dogapi_client [Dogapi::Client] datadog api client
|
15
|
+
# @return [DatadogChefMetrics] instance reference to self enabling method chaining
|
16
|
+
def with_dogapi_client(dogapi_client)
|
17
|
+
@dog = dogapi_client
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# set the target hostname (chef node name)
|
22
|
+
#
|
23
|
+
# @param hostname [String] hostname used for reporting metrics
|
24
|
+
# @return [DatadogChefMetrics] instance reference to self enabling method chaining
|
25
|
+
def with_hostname(hostname)
|
26
|
+
@hostname = hostname
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# set the chef run status used for the report
|
31
|
+
#
|
32
|
+
# @param run_status [Chef::RunStatus] current run status
|
33
|
+
# @return [DatadogChefMetrics] instance reference to self enabling method chaining
|
34
|
+
def with_run_status(run_status)
|
35
|
+
@run_status = run_status
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
# Emit Chef metrics to Datadog
|
40
|
+
def emit_to_datadog
|
41
|
+
# Send base success/failure metric
|
42
|
+
@dog.emit_point(@run_status.success? ? 'chef.run.success' : 'chef.run.failure', 1, host: @hostname, type: 'counter')
|
43
|
+
|
44
|
+
# If there is a failure during compile phase, a large portion of
|
45
|
+
# run_status may be unavailable. Bail out here
|
46
|
+
warn_msg = 'Error during compile phase, no Datadog metrics available.'
|
47
|
+
return Chef::Log.warn(warn_msg) if compile_error?
|
48
|
+
|
49
|
+
@dog.emit_point('chef.resources.total', @run_status.all_resources.length, host: @hostname)
|
50
|
+
@dog.emit_point('chef.resources.updated', @run_status.updated_resources.length, host: @hostname)
|
51
|
+
@dog.emit_point('chef.resources.elapsed_time', @run_status.elapsed_time, host: @hostname)
|
52
|
+
Chef::Log.debug('Submitted Chef metrics back to Datadog')
|
53
|
+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
|
54
|
+
Chef::Log.error("Could not send metrics to Datadog. Connection error:\n" + e)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def compile_error?
|
60
|
+
@run_status.all_resources.nil? || @run_status.elapsed_time.nil? || @run_status.updated_resources.nil?
|
61
|
+
end
|
62
|
+
end # end class DatadogChefMetrics
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'chef/handler'
|
4
|
+
require 'chef/mash'
|
5
|
+
require 'dogapi'
|
6
|
+
|
7
|
+
# helper class for sending datadog tags from chef runs
|
8
|
+
class DatadogChefTags
|
9
|
+
def initialize
|
10
|
+
@node = nil
|
11
|
+
@run_status = nil
|
12
|
+
@application_key = nil
|
13
|
+
@tag_prefix = 'tag:'
|
14
|
+
@retries = 0
|
15
|
+
@combined_host_tags = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# set the dogapi client handle
|
19
|
+
#
|
20
|
+
# @param dogapi_client [Dogapi::Client] datadog api client handle
|
21
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
22
|
+
def with_dogapi_client(dogapi_client)
|
23
|
+
@dog = dogapi_client
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# set the chef run status used for the report
|
28
|
+
#
|
29
|
+
# @param run_status [Chef::RunStatus] current chef run status
|
30
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
31
|
+
def with_run_status(run_status)
|
32
|
+
@run_status = run_status
|
33
|
+
# Build up an array of Chef tags that will be sent back
|
34
|
+
# Selects all [env, roles, tags] from the Node's object and reformats
|
35
|
+
# them to `key:value` e.g. `role:database-master`.
|
36
|
+
@node = run_status.node
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# set the target hostname (chef node name)
|
41
|
+
#
|
42
|
+
# @param hostname [String] hostname to use for the handler report
|
43
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
44
|
+
def with_hostname(hostname)
|
45
|
+
@hostname = hostname
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
# set the datadog application key
|
50
|
+
#
|
51
|
+
# TODO: the application key is only needed for error checking, e.g. an app key exists
|
52
|
+
# would be cleaner to push this check up to the data prep method in the
|
53
|
+
# calling handler class
|
54
|
+
#
|
55
|
+
# @param application_key [String] datadog application key used for chef reports
|
56
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
57
|
+
def with_application_key(application_key)
|
58
|
+
@application_key = application_key
|
59
|
+
if @application_key.nil?
|
60
|
+
Chef::Log.warn('You need an application key to let Chef tag your nodes ' \
|
61
|
+
'in Datadog. Visit https://app.datadoghq.com/account/settings#api to ' \
|
62
|
+
'create one and update your datadog attributes in the datadog cookbook.'
|
63
|
+
)
|
64
|
+
fail ArgumentError, 'Missing Datadog Application Key'
|
65
|
+
end
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# set the prefix to be added to all Chef tags
|
70
|
+
#
|
71
|
+
# @param tag_prefix [String] prefix to be added to all Chef tags
|
72
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
73
|
+
def with_tag_prefix(tag_prefix)
|
74
|
+
@tag_prefix = tag_prefix unless tag_prefix.nil?
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
# set the number of retries when sending tags, when the host is not yet present
|
79
|
+
# on Datadog
|
80
|
+
#
|
81
|
+
# @param retries [Integer] number of retries
|
82
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
83
|
+
def with_retries(retries)
|
84
|
+
@retries = retries unless retries.nil?
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
# send updated chef run generated tags to Datadog
|
89
|
+
def send_update_to_datadog
|
90
|
+
tags = combined_host_tags
|
91
|
+
retries = @retries
|
92
|
+
begin
|
93
|
+
loop do
|
94
|
+
should_retry = false
|
95
|
+
rc = @dog.update_tags(@hostname, tags, 'chef')
|
96
|
+
# See FIXME in DatadogChefEvents::emit_to_datadog about why I feel dirty repeating this code here
|
97
|
+
if rc.length < 2
|
98
|
+
Chef::Log.warn("Unexpected response from Datadog Tags API: #{rc}")
|
99
|
+
else
|
100
|
+
if retries > 0 && rc[0].to_i == 404
|
101
|
+
Chef::Log.debug("Host #{@hostname} not yet present on Datadog, re-submitting tags in 2 seconds")
|
102
|
+
sleep 2
|
103
|
+
retries -= 1
|
104
|
+
should_retry = true
|
105
|
+
elsif rc[0].to_i / 100 != 2
|
106
|
+
Chef::Log.warn("Could not submit #{tags} tags for #{@hostname} to Datadog: #{rc}")
|
107
|
+
else
|
108
|
+
Chef::Log.debug("Successfully updated #{@hostname}'s tags to #{tags.join(', ')}")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
break unless should_retry
|
112
|
+
end
|
113
|
+
rescue
|
114
|
+
Chef::Log.warn("Could not determine whether #{@hostname}'s tags were successfully submitted to Datadog: #{rc}")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# return a combined array of tags that should be sent to Datadog
|
119
|
+
#
|
120
|
+
# @return [Array] the set of host tags based off the chef run
|
121
|
+
def combined_host_tags
|
122
|
+
# Combine (union) all arrays. Removes duplicates if found.
|
123
|
+
node_env.split | node_roles | node_tags
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def node_roles
|
129
|
+
@node.run_list.roles.map! { |role| 'role:' + role }
|
130
|
+
end
|
131
|
+
|
132
|
+
def node_env
|
133
|
+
'env:' + @node.chef_environment if @node.respond_to?('chef_environment')
|
134
|
+
end
|
135
|
+
|
136
|
+
def node_tags
|
137
|
+
return [] unless @node.tags
|
138
|
+
@node.tags.map { |tag| "#{@tag_prefix}#{tag}" }
|
139
|
+
end
|
140
|
+
end # end class DatadogChefTags
|