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.
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