chef-handler-datadog-demo 0.1.0

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