chef-handler-datadog-demo 0.1.0

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