chef-handler-datadog 0.9.0 → 0.10.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/Appraisals +9 -1
- data/CHANGELOG.md +15 -0
- data/chef-handler-datadog.gemspec +1 -1
- data/gemfiles/chef_11.gemfile +2 -0
- data/lib/chef/handler/datadog.rb +70 -12
- data/lib/chef/handler/datadog_chef_events.rb +12 -20
- data/lib/chef/handler/datadog_chef_metrics.rb +8 -15
- data/lib/chef/handler/datadog_chef_tags.rb +33 -34
- data/lib/chef_handler_datadog.rb +1 -1
- data/spec/datadog_spec.rb +266 -7
- data/spec/support/cassettes/Chef_Handler_Datadog/failed_Chef_run/sets_alert_handles_when_specified.yml +90 -12
- data/spec/support/cassettes/Chef_Handler_Datadog/failed_Chef_run/sets_event_title_correctly.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/failed_Chef_run/sets_priority_correctly.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/hostname/uses_the_node_name_when_no_config_specified.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/hostname/uses_the_specified_hostname_when_provided.yml +45 -6
- 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 +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_correct_hostname_on_an_ec2_node/uses_the_instance_id_when_config_is_specified.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_correct_hostname_on_an_ec2_node/uses_the_instance_id_when_no_config_specified.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/emits_events/posts_an_event.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/emits_events/sets_priority_correctly.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/emits_metrics/reports_metrics.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/sets_tags/puts_the_tags_for_the_current_node.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/resources/failure_during_compile_phase/{only_emits_a_failure_metric.yml → only_emits_the_run_status_metrics.yml} +43 -4
- data/spec/support/cassettes/Chef_Handler_Datadog/resources/failure_during_compile_phase/posts_an_event.yml +43 -4
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_empty_scope_prefix.yml +290 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_empty_tag_prefix.yml +84 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_user-specified_scope_prefix.yml +290 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_user-specified_tag_prefix.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/sets_the_role_and_env_and_tags.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_tag_blacklist_is_specified/does_not_include_the_tag_s_specified.yml +289 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_tag_blacklist_is_unspecified/should_include_all_of_the_tag_s_.yml +289 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_unspecified/sets_role_env_and_nothing_else.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/tags_submission_retries/when_not_specified/does_not_retry_after_a_failed_submission.yml +37 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags_submission_retries/when_specified_as_2_retries/retries_no_more_than_twice.yml +37 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/tags_submission_retries/when_specified_as_2_retries/stops_retrying_once_submission_is_successful.yml +37 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/updated_resources/posts_an_event.yml +45 -6
- data/spec/support/cassettes/Chef_Handler_Datadog/when_reporting_to_multiple_endpoints/emits_events/posts_an_event.yml +575 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/when_reporting_to_multiple_endpoints/emits_metrics/reports_metrics.yml +575 -0
- data/spec/support/cassettes/Chef_Handler_Datadog/when_reporting_to_multiple_endpoints/sets_tags/puts_the_tags_for_the_current_node.yml +575 -0
- metadata +24 -11
- data/spec/support/cassettes/Chef_Handler_Datadog/resources/failure_during_compile_phase/does_not_emit_metrics.yml +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f22fc0a3aee09aa96893c748ed07af486d6d20b1
|
4
|
+
data.tar.gz: a0cc1fb8210e79b63b4426cb3698403e7b4c4e72
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1f2f15ac30d0c56271703fe698f3a03b09b2654df96edc87bc744ec6b4702d59590a84455f28398663059cff19419d3372b5b09520dcea7d4ddb17c0dce2be7
|
7
|
+
data.tar.gz: bbb290f4667294fb9c9e03fbaa4c652877a5b44f6904083aee69d8aca7b8a7e3b9c9da29ab8336d833242ac7fda4bc1589fd8ec4312f83ae7c3beef0f8f16515
|
data/.travis.yml
CHANGED
data/Appraisals
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
# Latest release of mainline Chef versions here.
|
4
|
-
%w(10
|
4
|
+
%w(10 12).each do |tv|
|
5
5
|
appraise "chef-#{tv}" do
|
6
6
|
gem 'chef', "~> #{tv}.0"
|
7
7
|
end
|
@@ -14,3 +14,11 @@ appraise 'chef-10.14.4' do
|
|
14
14
|
# See: http://git.io/vecAn
|
15
15
|
gem 'ohai', '< 8.0'
|
16
16
|
end
|
17
|
+
|
18
|
+
appraise 'chef-11' do
|
19
|
+
gem 'chef', '~> 11.0'
|
20
|
+
# for some reason bundler installs json 2.x and rack 2.x even when they don't support the version of
|
21
|
+
# ruby installed, so let's force compatible versions here
|
22
|
+
gem 'json', '< 2.0'
|
23
|
+
gem 'rack', '< 2.0'
|
24
|
+
end
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
Changes
|
2
2
|
=======
|
3
3
|
|
4
|
+
# 0.10.0 / Unreleased
|
5
|
+
|
6
|
+
* [FEATURE] Allow passing `scope_prefix` param that prefixes env and role tags [#82][] [@DanielMuller][]
|
7
|
+
* [FEATURE] Support regex tag blacklist [#86][] [@ABrehm264][]
|
8
|
+
* [FEATURE] Support multiple endpoints [#87][] [@degemer][]
|
9
|
+
* [OPTIMIZE] Always emit success and failure metrics [#89][] [@devonbleak][]
|
10
|
+
|
4
11
|
# 0.9.0 / 2016-03-22
|
5
12
|
|
6
13
|
* [FEATURE] Enable users to select tag prefix [#81][] [@mstepniowski][]
|
@@ -89,9 +96,17 @@ And all other versions were prior to this. See git history for more.
|
|
89
96
|
[#78]: https://github.com/DataDog/chef-handler-datadog/issues/78
|
90
97
|
[#80]: https://github.com/DataDog/chef-handler-datadog/issues/80
|
91
98
|
[#81]: https://github.com/DataDog/chef-handler-datadog/issues/81
|
99
|
+
[#82]: https://github.com/DataDog/chef-handler-datadog/issues/82
|
100
|
+
[#86]: https://github.com/DataDog/chef-handler-datadog/issues/86
|
101
|
+
[#87]: https://github.com/DataDog/chef-handler-datadog/issues/87
|
102
|
+
[#89]: https://github.com/DataDog/chef-handler-datadog/issues/89
|
103
|
+
[@ABrehm264]: https://github.com/ABrehm264
|
104
|
+
[@DanielMuller]: https://github.com/DanielMuller
|
92
105
|
[@alq]: https://github.com/alq
|
93
106
|
[@bigbam505]: https://github.com/bigbam505
|
94
107
|
[@datwiz]: https://github.com/datwiz
|
108
|
+
[@degemer]: https://github.com/degemer
|
109
|
+
[@devonbleak]: https://github.com/devonbleak
|
95
110
|
[@dwradcliffe]: https://github.com/dwradcliffe
|
96
111
|
[@jhulten]: https://github.com/jhulten
|
97
112
|
[@miketheman]: https://github.com/miketheman
|
@@ -14,7 +14,7 @@ Gem::Specification.new do |gem|
|
|
14
14
|
gem.require_paths = ['lib']
|
15
15
|
gem.extra_rdoc_files = ['README.md', 'LICENSE.txt']
|
16
16
|
|
17
|
-
gem.add_dependency 'dogapi', '>= 1.
|
17
|
+
gem.add_dependency 'dogapi', '>= 1.23'
|
18
18
|
|
19
19
|
gem.add_development_dependency 'appraisal', '~> 2.0.1'
|
20
20
|
gem.add_development_dependency 'bundler'
|
data/gemfiles/chef_11.gemfile
CHANGED
data/lib/chef/handler/datadog.rb
CHANGED
@@ -18,8 +18,8 @@ class Chef
|
|
18
18
|
# It should be passed along from the node/role/environemnt attributes, as the default is nil.
|
19
19
|
def initialize(config = {})
|
20
20
|
@config = Mash.new(config)
|
21
|
-
|
22
|
-
@
|
21
|
+
|
22
|
+
@dogs = prepare_the_pack
|
23
23
|
end
|
24
24
|
|
25
25
|
def report
|
@@ -28,8 +28,11 @@ class Chef
|
|
28
28
|
|
29
29
|
# prepare the metrics, event, and tags information to be reported
|
30
30
|
prepare_report_for_datadog
|
31
|
-
|
32
|
-
|
31
|
+
|
32
|
+
@dogs.each do |dog|
|
33
|
+
# post the report information to the datadog service
|
34
|
+
send_report_to_datadog dog
|
35
|
+
end
|
33
36
|
ensure
|
34
37
|
# restore the env proxy settings before leaving to avoid downstream side-effects
|
35
38
|
restore_env_proxies unless ENV['DATADOG_PROXY'].nil?
|
@@ -44,24 +47,22 @@ class Chef
|
|
44
47
|
# prepare chef run metrics
|
45
48
|
@metrics =
|
46
49
|
DatadogChefMetrics.new
|
47
|
-
.with_dogapi_client(@dog)
|
48
50
|
.with_hostname(hostname)
|
49
51
|
.with_run_status(run_status)
|
50
52
|
|
51
53
|
# Collect and prepare tags
|
52
54
|
@tags =
|
53
55
|
DatadogChefTags.new
|
54
|
-
.with_dogapi_client(@dog)
|
55
56
|
.with_hostname(hostname)
|
56
57
|
.with_run_status(run_status)
|
57
|
-
.with_application_key(config[:application_key])
|
58
58
|
.with_tag_prefix(config[:tag_prefix])
|
59
59
|
.with_retries(config[:tags_submission_retries])
|
60
|
+
.with_tag_blacklist(config[:tags_blacklist_regex])
|
61
|
+
.with_scope_prefix(config[:scope_prefix])
|
60
62
|
|
61
63
|
# Build the chef event information
|
62
64
|
@event =
|
63
65
|
DatadogChefEvents.new
|
64
|
-
.with_dogapi_client(@dog)
|
65
66
|
.with_hostname(hostname)
|
66
67
|
.with_run_status(run_status)
|
67
68
|
.with_failure_notifications(@config['notify_on_failure'])
|
@@ -69,10 +70,12 @@ class Chef
|
|
69
70
|
end
|
70
71
|
|
71
72
|
# Submit metrics, event, and tags information to datadog
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
@
|
73
|
+
#
|
74
|
+
# @param dog [Dogapi::Client] Dogapi Client to be used
|
75
|
+
def send_report_to_datadog(dog)
|
76
|
+
@metrics.emit_to_datadog dog
|
77
|
+
@event.emit_to_datadog dog
|
78
|
+
@tags.send_update_to_datadog dog
|
76
79
|
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
|
77
80
|
Chef::Log.error("Could not connect to Datadog. Connection error:\n" + e)
|
78
81
|
Chef::Log.error('Data to be submitted was:')
|
@@ -118,6 +121,61 @@ class Chef
|
|
118
121
|
ENV['http_proxy'] = @env_http_proxy
|
119
122
|
ENV['https_proxy'] = @env_https_proxy
|
120
123
|
end
|
124
|
+
|
125
|
+
# create and configure all the Dogapi Clients to be used
|
126
|
+
#
|
127
|
+
# @return [Array] all Dogapi::Client to be used
|
128
|
+
def prepare_the_pack
|
129
|
+
dogs = []
|
130
|
+
endpoints.each do |url, api_key, app_key|
|
131
|
+
dogs.push(Dogapi::Client.new(
|
132
|
+
api_key,
|
133
|
+
app_key,
|
134
|
+
nil, # host
|
135
|
+
nil, # device
|
136
|
+
true, # silent
|
137
|
+
nil, # timeout
|
138
|
+
url
|
139
|
+
))
|
140
|
+
end
|
141
|
+
dogs
|
142
|
+
end
|
143
|
+
|
144
|
+
# return all endpoints as a list of triplets [url, api_key, application_key]
|
145
|
+
def endpoints
|
146
|
+
validate_keys(@config[:api_key], @config[:application_key], true)
|
147
|
+
|
148
|
+
endpoints = [[@config[:url], @config[:api_key], @config[:application_key]]]
|
149
|
+
|
150
|
+
extra_endpoints = @config[:extra_endpoints] || []
|
151
|
+
|
152
|
+
extra_endpoints.each do |endpoint|
|
153
|
+
url = endpoint[:url] || @config[:url]
|
154
|
+
api_key = endpoint[:api_key]
|
155
|
+
app_key = endpoint[:application_key]
|
156
|
+
endpoints << [url, api_key, app_key] if validate_keys(api_key, app_key, false)
|
157
|
+
end
|
158
|
+
endpoints
|
159
|
+
end
|
160
|
+
|
161
|
+
# Validate endpoints config (api_key and application key)
|
162
|
+
# fails if incorrect and should_fail is true (needed for the default)
|
163
|
+
# Doesn't fail for the other endpoints but logs a warning
|
164
|
+
def validate_keys(api_key, app_key, should_fail)
|
165
|
+
if api_key.nil?
|
166
|
+
Chef::Log.warn('You need an API key to communicate with Datadog')
|
167
|
+
fail ArgumentError, 'Missing Datadog Api Key' if should_fail
|
168
|
+
return false
|
169
|
+
end
|
170
|
+
if app_key.nil?
|
171
|
+
Chef::Log.warn('You need an application key to let Chef tag your nodes ' \
|
172
|
+
'in Datadog. Visit https://app.datadoghq.com/account/settings#api to ' \
|
173
|
+
'create one and update your datadog attributes in the datadog cookbook.')
|
174
|
+
fail ArgumentError, 'Missing Datadog Application Key' if should_fail
|
175
|
+
return false
|
176
|
+
end
|
177
|
+
true
|
178
|
+
end
|
121
179
|
end # end class Datadog
|
122
180
|
end # end class Handler
|
123
181
|
end # end class Chef
|
@@ -7,7 +7,6 @@ require 'dogapi'
|
|
7
7
|
# helper class for sending events about chef runs
|
8
8
|
class DatadogChefEvents
|
9
9
|
def initialize
|
10
|
-
@dog = nil
|
11
10
|
@hostname = nil
|
12
11
|
@run_status = nil
|
13
12
|
@failure_notfications = nil
|
@@ -21,15 +20,6 @@ class DatadogChefEvents
|
|
21
20
|
@event_body = ''
|
22
21
|
end
|
23
22
|
|
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
23
|
# set the target hostname (chef node name)
|
34
24
|
#
|
35
25
|
# @param hostname [String] hostname to use for the handler report
|
@@ -67,18 +57,20 @@ class DatadogChefEvents
|
|
67
57
|
end
|
68
58
|
|
69
59
|
# Emit Chef event to Datadog
|
70
|
-
|
60
|
+
#
|
61
|
+
# @param dog [Dogapi::Client] Dogapi Client to be used
|
62
|
+
def emit_to_datadog(dog)
|
71
63
|
@event_body = ''
|
72
64
|
build_event_data
|
73
|
-
evt =
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
65
|
+
evt = dog.emit_event(Dogapi::Event.new(@event_body,
|
66
|
+
msg_title: @event_title,
|
67
|
+
event_type: 'config_management.run',
|
68
|
+
event_object: @hostname,
|
69
|
+
alert_type: @alert_type,
|
70
|
+
priority: @event_priority,
|
71
|
+
source_type_name: 'chef',
|
72
|
+
tags: @tags
|
73
|
+
), host: @hostname)
|
82
74
|
|
83
75
|
begin
|
84
76
|
# FIXME: nice-to-have: abstract format of return value away a bit
|
@@ -4,20 +4,10 @@ require 'dogapi'
|
|
4
4
|
# helper class for sending datadog metrics from a chef run
|
5
5
|
class DatadogChefMetrics
|
6
6
|
def initialize
|
7
|
-
@dog = nil
|
8
7
|
@hostname = ''
|
9
8
|
@run_status = nil
|
10
9
|
end
|
11
10
|
|
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
11
|
# set the target hostname (chef node name)
|
22
12
|
#
|
23
13
|
# @param hostname [String] hostname used for reporting metrics
|
@@ -37,18 +27,21 @@ class DatadogChefMetrics
|
|
37
27
|
end
|
38
28
|
|
39
29
|
# Emit Chef metrics to Datadog
|
40
|
-
|
30
|
+
#
|
31
|
+
# @param dog [Dogapi::Client] Dogapi Client to be used
|
32
|
+
def emit_to_datadog(dog)
|
41
33
|
# Send base success/failure metric
|
42
|
-
|
34
|
+
dog.emit_point('chef.run.success', @run_status.success? ? 1 : 0, host: @hostname, type: 'counter')
|
35
|
+
dog.emit_point('chef.run.failure', @run_status.success? ? 0 : 1, host: @hostname, type: 'counter')
|
43
36
|
|
44
37
|
# If there is a failure during compile phase, a large portion of
|
45
38
|
# run_status may be unavailable. Bail out here
|
46
39
|
warn_msg = 'Error during compile phase, no Datadog metrics available.'
|
47
40
|
return Chef::Log.warn(warn_msg) if compile_error?
|
48
41
|
|
49
|
-
|
50
|
-
|
51
|
-
|
42
|
+
dog.emit_point('chef.resources.total', @run_status.all_resources.length, host: @hostname)
|
43
|
+
dog.emit_point('chef.resources.updated', @run_status.updated_resources.length, host: @hostname)
|
44
|
+
dog.emit_point('chef.resources.elapsed_time', @run_status.elapsed_time, host: @hostname)
|
52
45
|
Chef::Log.debug('Submitted Chef metrics back to Datadog')
|
53
46
|
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
|
54
47
|
Chef::Log.error("Could not send metrics to Datadog. Connection error:\n" + e)
|
@@ -11,17 +11,10 @@ class DatadogChefTags
|
|
11
11
|
@run_status = nil
|
12
12
|
@application_key = nil
|
13
13
|
@tag_prefix = 'tag:'
|
14
|
+
@scope_prefix = nil
|
14
15
|
@retries = 0
|
15
16
|
@combined_host_tags = nil
|
16
|
-
|
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
|
17
|
+
@regex_black_list = nil
|
25
18
|
end
|
26
19
|
|
27
20
|
# set the chef run status used for the report
|
@@ -46,26 +39,6 @@ class DatadogChefTags
|
|
46
39
|
self
|
47
40
|
end
|
48
41
|
|
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
42
|
# set the prefix to be added to all Chef tags
|
70
43
|
#
|
71
44
|
# @param tag_prefix [String] prefix to be added to all Chef tags
|
@@ -85,14 +58,34 @@ class DatadogChefTags
|
|
85
58
|
self
|
86
59
|
end
|
87
60
|
|
61
|
+
# set the blacklist regexp, node tags matching this regex won't be sent
|
62
|
+
#
|
63
|
+
# @param tags_blacklist_regex [String] regexp-formatted string
|
64
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
65
|
+
def with_tag_blacklist(tags_blacklist_regex)
|
66
|
+
@regex_black_list = Regexp.new(tags_blacklist_regex, Regexp::IGNORECASE) unless tags_blacklist_regex.nil? || tags_blacklist_regex.empty?
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# set the prefix to be added to Datadog tags (Role, Env)
|
71
|
+
#
|
72
|
+
# @param scope_prefix [String] prefix to be added to Datadog tags
|
73
|
+
# @return [DatadogChefTags] instance reference to self enabling method chaining
|
74
|
+
def with_scope_prefix(scope_prefix)
|
75
|
+
@scope_prefix = scope_prefix unless scope_prefix.nil?
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
88
79
|
# send updated chef run generated tags to Datadog
|
89
|
-
|
80
|
+
#
|
81
|
+
# @param dog [Dogapi::Client] Dogapi Client to be used
|
82
|
+
def send_update_to_datadog(dog)
|
90
83
|
tags = combined_host_tags
|
91
84
|
retries = @retries
|
92
85
|
begin
|
93
86
|
loop do
|
94
87
|
should_retry = false
|
95
|
-
rc =
|
88
|
+
rc = dog.update_tags(@hostname, tags, 'chef')
|
96
89
|
# See FIXME in DatadogChefEvents::emit_to_datadog about why I feel dirty repeating this code here
|
97
90
|
if rc.length < 2
|
98
91
|
Chef::Log.warn("Unexpected response from Datadog Tags API: #{rc}")
|
@@ -126,15 +119,21 @@ class DatadogChefTags
|
|
126
119
|
private
|
127
120
|
|
128
121
|
def node_roles
|
129
|
-
@node.run_list.roles.map! { |role|
|
122
|
+
@node.run_list.roles.map! { |role| "#{@scope_prefix}role:#{role}" }
|
130
123
|
end
|
131
124
|
|
132
125
|
def node_env
|
133
|
-
|
126
|
+
"#{@scope_prefix}env:#{@node.chef_environment}" if @node.respond_to?('chef_environment')
|
134
127
|
end
|
135
128
|
|
136
129
|
def node_tags
|
137
130
|
return [] unless @node.tags
|
138
|
-
@node.tags.map { |tag| "#{@tag_prefix}#{tag}" }
|
131
|
+
output = @node.tags.map { |tag| "#{@tag_prefix}#{tag}" }
|
132
|
+
|
133
|
+
# No blacklist, return all results
|
134
|
+
return output if @regex_black_list.nil?
|
135
|
+
|
136
|
+
# The blacklist is set, so return the items which are not filtered by it.
|
137
|
+
output.select { |t| !@regex_black_list.match(t) }
|
139
138
|
end
|
140
139
|
end # end class DatadogChefTags
|
data/lib/chef_handler_datadog.rb
CHANGED
data/spec/datadog_spec.rb
CHANGED
@@ -29,6 +29,7 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
29
29
|
'api_key' => API_KEY,
|
30
30
|
'application_key' => APPLICATION_KEY,
|
31
31
|
'tag_prefix' => 'tag',
|
32
|
+
'scope_prefix' => nil
|
32
33
|
)
|
33
34
|
end
|
34
35
|
end
|
@@ -57,7 +58,7 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
57
58
|
it 'reports metrics' do
|
58
59
|
expect(a_request(:post, METRICS_ENDPOINT).with(
|
59
60
|
:query => { 'api_key' => @handler.config[:api_key] }
|
60
|
-
)).to have_been_made.times(
|
61
|
+
)).to have_been_made.times(5)
|
61
62
|
end
|
62
63
|
end
|
63
64
|
|
@@ -246,6 +247,34 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
246
247
|
]),
|
247
248
|
)).to have_been_made.times(1)
|
248
249
|
end
|
250
|
+
|
251
|
+
it 'allows for user-specified scope prefix' do
|
252
|
+
@handler.config[:scope_prefix] = 'custom-prefix-'
|
253
|
+
@handler.run_report_unsafe(@run_status)
|
254
|
+
|
255
|
+
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
256
|
+
:query => { 'api_key' => @handler.config[:api_key],
|
257
|
+
'application_key' => @handler.config[:application_key],
|
258
|
+
'source' => 'chef' },
|
259
|
+
:body => hash_including(:tags => [
|
260
|
+
'custom-prefix-env:hostile', 'custom-prefix-role:highlander'
|
261
|
+
]),
|
262
|
+
)).to have_been_made.times(1)
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'allows for empty scope prefix' do
|
266
|
+
@handler.config[:scope_prefix] = ''
|
267
|
+
@handler.run_report_unsafe(@run_status)
|
268
|
+
|
269
|
+
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
270
|
+
:query => { 'api_key' => @handler.config[:api_key],
|
271
|
+
'application_key' => @handler.config[:application_key],
|
272
|
+
'source' => 'chef' },
|
273
|
+
:body => hash_including(:tags => [
|
274
|
+
'env:hostile', 'role:highlander'
|
275
|
+
]),
|
276
|
+
)).to have_been_made.times(1)
|
277
|
+
end
|
249
278
|
end
|
250
279
|
|
251
280
|
describe 'when unspecified' do
|
@@ -262,6 +291,39 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
262
291
|
)).to have_been_made.times(1)
|
263
292
|
end
|
264
293
|
end
|
294
|
+
|
295
|
+
describe 'when tag blacklist is specified' do
|
296
|
+
it 'does not include the tag(s) specified' do
|
297
|
+
@node.normal.tags = ['allowed_tag', 'not_allowed_tag']
|
298
|
+
@handler.config[:tags_blacklist_regex] = 'not_allowed.*'
|
299
|
+
@handler.run_report_unsafe(@run_status)
|
300
|
+
|
301
|
+
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
302
|
+
:query => { 'api_key' => @handler.config[:api_key],
|
303
|
+
'application_key' => @handler.config[:application_key],
|
304
|
+
'source' => 'chef' },
|
305
|
+
:body => hash_including(:tags => [
|
306
|
+
'env:hostile', 'role:highlander', 'tag:allowed_tag'
|
307
|
+
]),
|
308
|
+
)).to have_been_made.times(1)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
describe 'when tag blacklist is unspecified' do
|
313
|
+
it 'should include all of the tag(s)' do
|
314
|
+
@node.normal.tags = ['allowed_tag', 'not_allowed_tag']
|
315
|
+
@handler.run_report_unsafe(@run_status)
|
316
|
+
|
317
|
+
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
318
|
+
:query => { 'api_key' => @handler.config[:api_key],
|
319
|
+
'application_key' => @handler.config[:application_key],
|
320
|
+
'source' => 'chef' },
|
321
|
+
:body => hash_including(:tags => [
|
322
|
+
'env:hostile', 'role:highlander', 'tag:allowed_tag', 'tag:not_allowed_tag'
|
323
|
+
]),
|
324
|
+
)).to have_been_made.times(1)
|
325
|
+
end
|
326
|
+
end
|
265
327
|
end
|
266
328
|
|
267
329
|
context 'tags submission retries' do
|
@@ -355,11 +417,9 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
355
417
|
end
|
356
418
|
|
357
419
|
it 'fails when no application key is provided' do
|
358
|
-
@handler.config[:application_key] = nil
|
359
|
-
|
360
420
|
# TODO: figure out how to capture output of Chef::Log
|
361
421
|
# Run the report, catch the error
|
362
|
-
expect {
|
422
|
+
expect { Chef::Handler::Datadog.new(api_key: API_KEY, application_key: nil) }.to raise_error
|
363
423
|
end
|
364
424
|
end
|
365
425
|
|
@@ -382,7 +442,7 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
382
442
|
Chef::Resource.new('nose'),
|
383
443
|
Chef::Resource.new('tail'),
|
384
444
|
Chef::Resource.new('fur')
|
385
|
-
|
445
|
+
]
|
386
446
|
all_resources.map { |r| r.updated_by_last_action(true) }
|
387
447
|
@run_context.resource_collection.all_resources.replace(all_resources)
|
388
448
|
|
@@ -474,10 +534,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
474
534
|
@handler.run_report_unsafe(@run_status)
|
475
535
|
end
|
476
536
|
|
477
|
-
it 'only emits
|
537
|
+
it 'only emits the run status metrics' do
|
478
538
|
expect(a_request(:post, METRICS_ENDPOINT).with(
|
479
539
|
:query => { 'api_key' => @handler.config[:api_key] }
|
480
|
-
)).to have_been_made.times(
|
540
|
+
)).to have_been_made.times(2)
|
481
541
|
end
|
482
542
|
|
483
543
|
it 'posts an event' do
|
@@ -490,6 +550,205 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
490
550
|
end
|
491
551
|
end
|
492
552
|
|
553
|
+
describe '#endpoints' do
|
554
|
+
context 'with a basic config' do
|
555
|
+
it 'returns the correct triplet' do
|
556
|
+
handler = Chef::Handler::Datadog.new api_key: API_KEY, application_key: APPLICATION_KEY
|
557
|
+
expect(handler.send(:endpoints)).to eq([[nil, API_KEY, APPLICATION_KEY]])
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
context 'with no url and two pairs of keys' do
|
562
|
+
it 'returns the correct triplets' do
|
563
|
+
triplets = [
|
564
|
+
[nil, API_KEY, APPLICATION_KEY],
|
565
|
+
[nil, 'api_key_2', 'app_key_2'],
|
566
|
+
[nil, 'api_key_3', 'app_key_3']
|
567
|
+
]
|
568
|
+
handler = Chef::Handler::Datadog.new api_key: triplets[0][1],
|
569
|
+
application_key: triplets[0][2],
|
570
|
+
extra_endpoints: [{
|
571
|
+
api_key: triplets[1][1],
|
572
|
+
application_key: triplets[1][2]
|
573
|
+
}, {
|
574
|
+
api_key: triplets[2][1],
|
575
|
+
application_key: triplets[2][2]
|
576
|
+
}]
|
577
|
+
expect(handler.send(:endpoints)).to eq(triplets)
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
context 'with one url and two pairs of keys' do
|
582
|
+
it 'returns the correct triplets' do
|
583
|
+
triplets = [
|
584
|
+
['https://app.example.com', API_KEY, APPLICATION_KEY],
|
585
|
+
['https://app.example.com', 'api_key_2', 'app_key_2'],
|
586
|
+
['https://app.example.com', 'api_key_3', 'app_key_3']
|
587
|
+
]
|
588
|
+
handler = Chef::Handler::Datadog.new api_key: triplets[0][1],
|
589
|
+
application_key: triplets[0][2],
|
590
|
+
url: triplets[0][0],
|
591
|
+
extra_endpoints: [{
|
592
|
+
api_key: triplets[1][1],
|
593
|
+
application_key: triplets[1][2]
|
594
|
+
}, {
|
595
|
+
api_key: triplets[2][1],
|
596
|
+
application_key: triplets[2][2]
|
597
|
+
}]
|
598
|
+
expect(handler.send(:endpoints)).to eq(triplets)
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
602
|
+
context 'with multiple urls' do
|
603
|
+
it 'returns the correct triplets' do
|
604
|
+
triplets = [
|
605
|
+
['https://app.datadoghq.com', 'api_key_2', 'app_key_2'],
|
606
|
+
['https://app.example.com', 'api_key_3', 'app_key_3'],
|
607
|
+
['https://app.example.com', 'api_key_4', 'app_key_4']
|
608
|
+
]
|
609
|
+
handler = Chef::Handler::Datadog.new api_key: triplets[0][1],
|
610
|
+
application_key: triplets[0][2],
|
611
|
+
url: triplets[0][0],
|
612
|
+
extra_endpoints: [{
|
613
|
+
url: triplets[1][0],
|
614
|
+
api_key: triplets[1][1],
|
615
|
+
application_key: triplets[1][2]
|
616
|
+
}, {
|
617
|
+
url: triplets[2][0],
|
618
|
+
api_key: triplets[2][1],
|
619
|
+
application_key: triplets[2][2]
|
620
|
+
}]
|
621
|
+
expect(handler.send(:endpoints)).to eq(triplets)
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
context 'when missing application keys' do
|
626
|
+
it 'returns available triplets' do
|
627
|
+
triplets = [
|
628
|
+
[nil, API_KEY, APPLICATION_KEY],
|
629
|
+
[nil, 'api_key_2', 'app_key_2'],
|
630
|
+
[nil, 'api_key_3', 'app_key_3']
|
631
|
+
]
|
632
|
+
handler = Chef::Handler::Datadog.new api_key: triplets[0][1],
|
633
|
+
application_key: triplets[0][2],
|
634
|
+
extra_endpoints: [{
|
635
|
+
api_key: triplets[1][1],
|
636
|
+
application_key: triplets[1][2]
|
637
|
+
}, {
|
638
|
+
api_key: triplets[2][1]
|
639
|
+
}]
|
640
|
+
expect(handler.send(:endpoints)).to eq(triplets[0..1])
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
context 'when missing api keys' do
|
645
|
+
it 'returns available triplets' do
|
646
|
+
triplets = [
|
647
|
+
['https://app.datadoghq.com', 'api_key_2', 'app_key_2'],
|
648
|
+
['https://app.example.com', 'api_key_3', 'app_key_3'],
|
649
|
+
['https://app.example.com', 'api_key_4', 'app_key_4']
|
650
|
+
]
|
651
|
+
handler = Chef::Handler::Datadog.new api_key: triplets[0][1],
|
652
|
+
application_key: triplets[0][2],
|
653
|
+
url: triplets[0][0],
|
654
|
+
extra_endpoints: [{
|
655
|
+
url: triplets[1][0],
|
656
|
+
api_key: triplets[1][1],
|
657
|
+
application_key: triplets[1][2]
|
658
|
+
}, {
|
659
|
+
url: triplets[2][0],
|
660
|
+
application_key: triplets[2][2]
|
661
|
+
}]
|
662
|
+
expect(handler.send(:endpoints)).to eq(triplets[0..1])
|
663
|
+
end
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
context 'when reporting to multiple endpoints' do
|
668
|
+
let(:api_key2) { 'api_key_example' }
|
669
|
+
let(:application_key2) { 'application_key_example' }
|
670
|
+
let(:base_url2) { 'https://app.example.com' }
|
671
|
+
let(:events_endpoint2) { base_url2 + '/api/v1/events' }
|
672
|
+
let(:host_tag_endpoint2) { base_url2 + '/api/v1/tags/hosts/' }
|
673
|
+
let(:metrics_endpoint2) { base_url2 + '/api/v1/series' }
|
674
|
+
let(:handler) do
|
675
|
+
Chef::Handler::Datadog.new api_key: API_KEY,
|
676
|
+
application_key: APPLICATION_KEY,
|
677
|
+
url: BASE_URL,
|
678
|
+
extra_endpoints: [{
|
679
|
+
api_key: api_key2,
|
680
|
+
application_key: application_key2,
|
681
|
+
url: base_url2
|
682
|
+
}]
|
683
|
+
end
|
684
|
+
# Construct a good run_status
|
685
|
+
before(:each) do
|
686
|
+
@node = Chef::Node.build('chef.handler.datadog.test')
|
687
|
+
@node.send(:chef_environment, 'testing')
|
688
|
+
@events = Chef::EventDispatch::Dispatcher.new
|
689
|
+
@run_context = Chef::RunContext.new(@node, {}, @events)
|
690
|
+
@run_status = Chef::RunStatus.new(@node, @events)
|
691
|
+
|
692
|
+
@expected_time = Time.now
|
693
|
+
allow(Time).to receive(:now).and_return(@expected_time, @expected_time + 5)
|
694
|
+
@run_status.start_clock
|
695
|
+
@run_status.stop_clock
|
696
|
+
|
697
|
+
@run_status.run_context = @run_context
|
698
|
+
|
699
|
+
# Run the report
|
700
|
+
handler.run_report_unsafe(@run_status)
|
701
|
+
end
|
702
|
+
|
703
|
+
context 'emits metrics' do
|
704
|
+
it 'reports metrics' do
|
705
|
+
expect(a_request(:post, METRICS_ENDPOINT).with(
|
706
|
+
:query => { 'api_key' => API_KEY }
|
707
|
+
)).to have_been_made.times(5)
|
708
|
+
|
709
|
+
expect(a_request(:post, metrics_endpoint2).with(
|
710
|
+
:query => { 'api_key' => api_key2 }
|
711
|
+
)).to have_been_made.times(5)
|
712
|
+
end
|
713
|
+
end
|
714
|
+
|
715
|
+
context 'emits events' do
|
716
|
+
it 'posts an event' do
|
717
|
+
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
718
|
+
:query => { 'api_key' => API_KEY },
|
719
|
+
:body => hash_including(:msg_text => 'Chef updated 0 resources out of 0 resources total.'),
|
720
|
+
:body => hash_including(:msg_title => "Chef completed in 5 seconds on #{@node.name} "),
|
721
|
+
:body => hash_including(:tags => ['env:testing']),
|
722
|
+
)).to have_been_made.times(1)
|
723
|
+
|
724
|
+
expect(a_request(:post, events_endpoint2).with(
|
725
|
+
:query => { 'api_key' => api_key2 },
|
726
|
+
:body => hash_including(:msg_text => 'Chef updated 0 resources out of 0 resources total.'),
|
727
|
+
:body => hash_including(:msg_title => "Chef completed in 5 seconds on #{@node.name} "),
|
728
|
+
:body => hash_including(:tags => ['env:testing']),
|
729
|
+
)).to have_been_made.times(1)
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
context 'sets tags' do
|
734
|
+
it 'puts the tags for the current node' do
|
735
|
+
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
736
|
+
:query => { 'api_key' => API_KEY,
|
737
|
+
'application_key' => APPLICATION_KEY,
|
738
|
+
'source' => 'chef' },
|
739
|
+
:body => { 'tags' => ['env:testing'] },
|
740
|
+
)).to have_been_made.times(1)
|
741
|
+
|
742
|
+
expect(a_request(:put, host_tag_endpoint2 + @node.name).with(
|
743
|
+
:query => { 'api_key' => api_key2,
|
744
|
+
'application_key' => application_key2,
|
745
|
+
'source' => 'chef' },
|
746
|
+
:body => { 'tags' => ['env:testing'] },
|
747
|
+
)).to have_been_made.times(1)
|
748
|
+
end
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
493
752
|
# TODO: test failures:
|
494
753
|
# @run_status.exception = Exception.new('Boy howdy!')
|
495
754
|
end
|