chef-handler-datadog-demo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|