chef-handler-datadog 0.9.0 → 0.10.0.rc1

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -0
  3. data/Appraisals +9 -1
  4. data/CHANGELOG.md +15 -0
  5. data/chef-handler-datadog.gemspec +1 -1
  6. data/gemfiles/chef_11.gemfile +2 -0
  7. data/lib/chef/handler/datadog.rb +70 -12
  8. data/lib/chef/handler/datadog_chef_events.rb +12 -20
  9. data/lib/chef/handler/datadog_chef_metrics.rb +8 -15
  10. data/lib/chef/handler/datadog_chef_tags.rb +33 -34
  11. data/lib/chef_handler_datadog.rb +1 -1
  12. data/spec/datadog_spec.rb +266 -7
  13. data/spec/support/cassettes/Chef_Handler_Datadog/failed_Chef_run/sets_alert_handles_when_specified.yml +90 -12
  14. data/spec/support/cassettes/Chef_Handler_Datadog/failed_Chef_run/sets_event_title_correctly.yml +45 -6
  15. data/spec/support/cassettes/Chef_Handler_Datadog/failed_Chef_run/sets_priority_correctly.yml +45 -6
  16. data/spec/support/cassettes/Chef_Handler_Datadog/hostname/uses_the_node_name_when_no_config_specified.yml +45 -6
  17. data/spec/support/cassettes/Chef_Handler_Datadog/hostname/uses_the_specified_hostname_when_provided.yml +45 -6
  18. 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
  19. 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
  20. 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
  21. data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/emits_events/posts_an_event.yml +45 -6
  22. data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/emits_events/sets_priority_correctly.yml +45 -6
  23. data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/emits_metrics/reports_metrics.yml +45 -6
  24. 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
  25. 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
  26. data/spec/support/cassettes/Chef_Handler_Datadog/resources/failure_during_compile_phase/posts_an_event.yml +43 -4
  27. data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_empty_scope_prefix.yml +290 -0
  28. data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_empty_tag_prefix.yml +84 -6
  29. data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_user-specified_scope_prefix.yml +290 -0
  30. data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_user-specified_tag_prefix.yml +45 -6
  31. data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/sets_the_role_and_env_and_tags.yml +45 -6
  32. data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_tag_blacklist_is_specified/does_not_include_the_tag_s_specified.yml +289 -0
  33. data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_tag_blacklist_is_unspecified/should_include_all_of_the_tag_s_.yml +289 -0
  34. data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_unspecified/sets_role_env_and_nothing_else.yml +45 -6
  35. data/spec/support/cassettes/Chef_Handler_Datadog/tags_submission_retries/when_not_specified/does_not_retry_after_a_failed_submission.yml +37 -0
  36. data/spec/support/cassettes/Chef_Handler_Datadog/tags_submission_retries/when_specified_as_2_retries/retries_no_more_than_twice.yml +37 -0
  37. data/spec/support/cassettes/Chef_Handler_Datadog/tags_submission_retries/when_specified_as_2_retries/stops_retrying_once_submission_is_successful.yml +37 -0
  38. data/spec/support/cassettes/Chef_Handler_Datadog/updated_resources/posts_an_event.yml +45 -6
  39. data/spec/support/cassettes/Chef_Handler_Datadog/when_reporting_to_multiple_endpoints/emits_events/posts_an_event.yml +575 -0
  40. data/spec/support/cassettes/Chef_Handler_Datadog/when_reporting_to_multiple_endpoints/emits_metrics/reports_metrics.yml +575 -0
  41. data/spec/support/cassettes/Chef_Handler_Datadog/when_reporting_to_multiple_endpoints/sets_tags/puts_the_tags_for_the_current_node.yml +575 -0
  42. metadata +24 -11
  43. 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: 79a5cd52c38bd2ea660a477778dc45fc24672bb6
4
- data.tar.gz: e2fbe94dc27216f97ceaebab7021bb17f5c013dc
3
+ metadata.gz: f22fc0a3aee09aa96893c748ed07af486d6d20b1
4
+ data.tar.gz: a0cc1fb8210e79b63b4426cb3698403e7b4c4e72
5
5
  SHA512:
6
- metadata.gz: 467c6a353533afc854f8e23bed60847d050a0f34f5d2ead72e692ca9e946868b5c2ef83d35fafe05e56993952cabd1b2da25b458015d62e0e615d22c287d2ff4
7
- data.tar.gz: c42c73edaa05e589240b59c1971901fdc7d48297be5d06a3901e82a51ac23002692ead16bfca3f3be94125ccd6455968d9dd11f941fc180d6ecdc913341f21f6
6
+ metadata.gz: c1f2f15ac30d0c56271703fe698f3a03b09b2654df96edc87bc744ec6b4702d59590a84455f28398663059cff19419d3372b5b09520dcea7d4ddb17c0dce2be7
7
+ data.tar.gz: bbb290f4667294fb9c9e03fbaa4c652877a5b44f6904083aee69d8aca7b8a7e3b9c9da29ab8336d833242ac7fda4bc1589fd8ec4312f83ae7c3beef0f8f16515
@@ -6,6 +6,12 @@ rvm:
6
6
  - 1.9.3
7
7
  - 2.1.5
8
8
 
9
+ before_install:
10
+ - gem update --system
11
+ - gem --version
12
+ # Use a stable version once 1.13.0 is released
13
+ - gem install bundler --version 1.13.0.rc.1
14
+
9
15
  bundler_args: --without=localdev
10
16
 
11
17
  gemfile:
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 11 12).each do |tv|
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
@@ -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.6'
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'
@@ -3,6 +3,8 @@
3
3
  source "http://rubygems.org"
4
4
 
5
5
  gem "chef", "~> 11.0"
6
+ gem "json", "< 2.0"
7
+ gem "rack", "< 2.0"
6
8
 
7
9
  group :localdev do
8
10
  gem "guard"
@@ -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
- # If *any* api_key is not provided, this will fail immediately.
22
- @dog = Dogapi::Client.new(@config[:api_key], @config[:application_key])
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
- # post the report information to the datadog service
32
- send_report_to_datadog
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
- def send_report_to_datadog
73
- @metrics.emit_to_datadog
74
- @event.emit_to_datadog
75
- @tags.send_update_to_datadog
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
- def emit_to_datadog
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 = @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)
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
- def emit_to_datadog
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
- @dog.emit_point(@run_status.success? ? 'chef.run.success' : 'chef.run.failure', 1, host: @hostname, type: 'counter')
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
- @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)
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
- 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
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
- def send_update_to_datadog
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 = @dog.update_tags(@hostname, tags, 'chef')
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| 'role:' + role }
122
+ @node.run_list.roles.map! { |role| "#{@scope_prefix}role:#{role}" }
130
123
  end
131
124
 
132
125
  def node_env
133
- 'env:' + @node.chef_environment if @node.respond_to?('chef_environment')
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
@@ -2,5 +2,5 @@
2
2
  # Helper module for version number only.
3
3
  # Real deal in 'chef/handler/datadog.rb'
4
4
  module ChefHandlerDatadog
5
- VERSION = '0.9.0'
5
+ VERSION = '0.10.0.rc1'
6
6
  end
@@ -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(4)
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 { @handler.run_report_unsafe(@run_status) }.to raise_error
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 a failure metric' do
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(1)
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