chef-handler-datadog 0.12.1 → 0.15.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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +158 -0
- data/.gitignore +0 -1
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +33 -0
- data/CONTRIBUTING.md +14 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +296 -0
- data/README.md +1 -3
- data/chef-handler-datadog.gemspec +10 -10
- data/lib/chef/handler/datadog.rb +18 -13
- data/lib/chef/handler/datadog_chef_events.rb +3 -0
- data/lib/chef/handler/datadog_chef_tags.rb +2 -1
- data/lib/chef_handler_datadog.rb +1 -1
- data/spec/datadog_spec.rb +216 -166
- data/spec/support/cassettes/Chef_Handler_Datadog/failed_Chef_run/sets_alert_handles_when_specified.yml +239 -181
- data/spec/support/cassettes/Chef_Handler_Datadog/failed_Chef_run/sets_event_title_correctly.yml +119 -90
- data/spec/support/cassettes/Chef_Handler_Datadog/failed_Chef_run/sets_priority_correctly.yml +119 -90
- data/spec/support/cassettes/Chef_Handler_Datadog/hostname/uses_the_node_name_when_no_config_specified.yml +119 -89
- data/spec/support/cassettes/Chef_Handler_Datadog/hostname/uses_the_specified_hostname_when_provided.yml +119 -89
- 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 +119 -89
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_correct_hostname_on_an_ec2_node/uses_the_instance_id_when_config_is_specified.yml +119 -89
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_correct_hostname_on_an_ec2_node/uses_the_instance_id_when_no_config_specified.yml +119 -89
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/emits_events/posts_an_event.yml +119 -89
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/emits_events/sets_priority_correctly.yml +119 -89
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/emits_metrics/reports_metrics.yml +119 -89
- data/spec/support/cassettes/Chef_Handler_Datadog/reports_metrics_event_and_sets_tags/sets_tags/puts_the_tags_for_the_current_node.yml +119 -89
- data/spec/support/cassettes/Chef_Handler_Datadog/resources/failure_during_compile_phase/only_emits_the_run_status_metrics.yml +74 -56
- data/spec/support/cassettes/Chef_Handler_Datadog/resources/failure_during_compile_phase/posts_an_event.yml +74 -56
- data/spec/support/cassettes/Chef_Handler_Datadog/resources/failure_during_compile_phase_with_an_elapsed_time_and_incomplete_resource_collection/only_emits_the_run_status_metrics.yml +73 -55
- data/spec/support/cassettes/Chef_Handler_Datadog/resources/failure_during_compile_phase_with_an_elapsed_time_and_incomplete_resource_collection/posts_an_event.yml +73 -55
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_policy_tags_are_enabled/sets_the_policy_name_and_policy_group_tags.yml +120 -90
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_policy_tags_are_not_enabled/does_not_set_the_policy_name_and_policy_group_tags.yml +111 -81
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_empty_scope_prefix.yml +119 -90
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_empty_tag_prefix.yml +117 -127
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_user-specified_scope_prefix.yml +119 -90
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/allows_for_user-specified_tag_prefix.yml +121 -92
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_specified/sets_the_role_and_env_and_tags.yml +119 -90
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_tag_blacklist_is_specified/does_not_include_the_tag_s_specified.yml +118 -88
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_tag_blacklist_is_unspecified/should_include_all_of_the_tag_s_.yml +118 -88
- data/spec/support/cassettes/Chef_Handler_Datadog/tags/when_unspecified/sets_role_env_and_nothing_else.yml +119 -90
- data/spec/support/cassettes/Chef_Handler_Datadog/tags_submission_retries/when_not_specified/does_not_retry_after_a_failed_submission.yml +112 -124
- data/spec/support/cassettes/Chef_Handler_Datadog/tags_submission_retries/when_specified_as_2_retries/retries_no_more_than_twice.yml +108 -210
- data/spec/support/cassettes/Chef_Handler_Datadog/tags_submission_retries/when_specified_as_2_retries/stops_retrying_once_submission_is_successful.yml +110 -168
- data/spec/support/cassettes/Chef_Handler_Datadog/updated_resources/posts_an_event.yml +119 -89
- metadata +13 -24
- data/.travis.yml +0 -34
- data/gemfiles/chef_12.7.gemfile +0 -16
- data/gemfiles/chef_12.gemfile +0 -16
- data/gemfiles/chef_13.gemfile +0 -16
- data/gemfiles/chef_14.gemfile +0 -16
- data/spec/support/cassettes/Chef_Handler_Datadog/handles_no_application_key/fails_when_no_application_key_is_provided.yml +0 -143
- data/spec/support/cassettes/Chef_Handler_Datadog/when_reporting_to_multiple_endpoints/emits_events/posts_an_event.yml +0 -575
- data/spec/support/cassettes/Chef_Handler_Datadog/when_reporting_to_multiple_endpoints/emits_metrics/reports_metrics.yml +0 -575
- data/spec/support/cassettes/Chef_Handler_Datadog/when_reporting_to_multiple_endpoints/sets_tags/puts_the_tags_for_the_current_node.yml +0 -575
data/README.md
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
An Exception and Report Handler for Chef.
|
|
4
4
|
|
|
5
5
|
[](http://badge.fury.io/rb/chef-handler-datadog)
|
|
6
|
-
[](https://codeclimate.com/github/DataDog/chef-handler-datadog)
|
|
8
|
-
[](https://gemnasium.com/DataDog/chef-handler-datadog)
|
|
6
|
+
[](https://circleci.com/gh/DataDog/chef-handler-datadog)
|
|
9
7
|
|
|
10
8
|
## Using chef-handler-datadog
|
|
11
9
|
|
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
require File.expand_path('lib/chef_handler_datadog', __dir__)
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |gem|
|
|
6
|
-
gem.name
|
|
7
|
-
gem.summary
|
|
8
|
-
gem.description
|
|
9
|
-
gem.license
|
|
10
|
-
gem.version
|
|
6
|
+
gem.name = 'chef-handler-datadog'
|
|
7
|
+
gem.summary = 'Chef Handler reports events and metrics to Datadog'
|
|
8
|
+
gem.description = 'This Handler will report the events and metrics for a chef-client run to Datadog.'
|
|
9
|
+
gem.license = 'BSD'
|
|
10
|
+
gem.version = ChefHandlerDatadog::VERSION
|
|
11
11
|
|
|
12
|
-
gem.files
|
|
13
|
-
gem.executables
|
|
14
|
-
gem.test_files
|
|
15
|
-
gem.require_paths
|
|
12
|
+
gem.files = `git ls-files`.split($\) # rubocop:disable Style/SpecialGlobalVars
|
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
15
|
+
gem.require_paths = ['lib']
|
|
16
16
|
gem.extra_rdoc_files = ['README.md', 'LICENSE.txt']
|
|
17
17
|
|
|
18
|
-
gem.add_dependency 'dogapi', '
|
|
18
|
+
gem.add_dependency 'dogapi', '~> 1.44.0'
|
|
19
19
|
|
|
20
20
|
gem.add_development_dependency 'appraisal', '~> 2.0.1'
|
|
21
21
|
gem.add_development_dependency 'bundler'
|
data/lib/chef/handler/datadog.rb
CHANGED
|
@@ -78,9 +78,9 @@ class Chef
|
|
|
78
78
|
@metrics.emit_to_datadog dog
|
|
79
79
|
@event.emit_to_datadog dog
|
|
80
80
|
@tags.send_update_to_datadog dog
|
|
81
|
-
rescue
|
|
82
|
-
Chef::Log.error("Could not
|
|
83
|
-
Chef::Log.error('
|
|
81
|
+
rescue => e
|
|
82
|
+
Chef::Log.error("Could not send/emit to Datadog:\n" + e.to_s)
|
|
83
|
+
Chef::Log.error('Event data to be submitted was:')
|
|
84
84
|
Chef::Log.error(@event.event_title)
|
|
85
85
|
Chef::Log.error(@event.event_body)
|
|
86
86
|
Chef::Log.error('Tags to be set for this run:')
|
|
@@ -129,15 +129,20 @@ class Chef
|
|
|
129
129
|
def prepare_the_pack
|
|
130
130
|
dogs = []
|
|
131
131
|
endpoints.each do |url, api_key, app_key|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
begin
|
|
133
|
+
dogs.push(Dogapi::Client.new(
|
|
134
|
+
api_key,
|
|
135
|
+
app_key,
|
|
136
|
+
nil, # host
|
|
137
|
+
nil, # device
|
|
138
|
+
false, # silent
|
|
139
|
+
nil, # timeout
|
|
140
|
+
url,
|
|
141
|
+
config[:skip_ssl_validation]
|
|
142
|
+
))
|
|
143
|
+
rescue => e
|
|
144
|
+
Chef::Log.error("Could not create API Client '#{url}'\n #{e.to_s}")
|
|
145
|
+
end
|
|
141
146
|
end
|
|
142
147
|
dogs
|
|
143
148
|
end
|
|
@@ -159,7 +164,7 @@ class Chef
|
|
|
159
164
|
# then add extra endpoints
|
|
160
165
|
extra_endpoints = @config[:extra_endpoints] || []
|
|
161
166
|
extra_endpoints.each do |endpoint|
|
|
162
|
-
url = endpoint[:url] || config_url()
|
|
167
|
+
url = endpoint[:api_url] || endpoint[:url] || config_url()
|
|
163
168
|
api_key = endpoint[:api_key]
|
|
164
169
|
app_key = endpoint[:application_key]
|
|
165
170
|
endpoints << [url, api_key, app_key] if validate_keys(api_key, app_key, false)
|
|
@@ -92,6 +92,7 @@ class DatadogChefTags
|
|
|
92
92
|
def send_update_to_datadog(dog)
|
|
93
93
|
tags = combined_host_tags
|
|
94
94
|
retries = @retries
|
|
95
|
+
rc = []
|
|
95
96
|
begin
|
|
96
97
|
loop do
|
|
97
98
|
should_retry = false
|
|
@@ -114,7 +115,7 @@ class DatadogChefTags
|
|
|
114
115
|
break unless should_retry
|
|
115
116
|
end
|
|
116
117
|
rescue StandardError => e
|
|
117
|
-
Chef::Log.warn("Could not determine whether #{@hostname}'s tags were successfully submitted to Datadog: #{rc}. Error:\n#{e}")
|
|
118
|
+
Chef::Log.warn("Could not determine whether #{@hostname}'s tags were successfully submitted to Datadog: #{rc.inspect}. Error:\n#{e}")
|
|
118
119
|
end
|
|
119
120
|
end
|
|
120
121
|
|
data/lib/chef_handler_datadog.rb
CHANGED
data/spec/datadog_spec.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# encoding: utf-8
|
|
2
2
|
require 'spec_helper'
|
|
3
3
|
|
|
4
|
-
describe Chef::Handler::Datadog, :
|
|
4
|
+
describe Chef::Handler::Datadog, vcr: :new_episodes do
|
|
5
5
|
# The #report method currently long and clunky, and we need to simulate a
|
|
6
6
|
# Chef run to test all aspects of this, as well as push values into the test.
|
|
7
7
|
before(:all) do
|
|
@@ -18,22 +18,31 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
18
18
|
|
|
19
19
|
before(:each) do
|
|
20
20
|
@handler = Chef::Handler::Datadog.new(
|
|
21
|
-
:
|
|
22
|
-
:
|
|
21
|
+
api_key: API_KEY,
|
|
22
|
+
application_key: APPLICATION_KEY,
|
|
23
23
|
)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
describe 'initialize' do
|
|
27
27
|
it 'should allow config hash to have string keys' do
|
|
28
28
|
Chef::Handler::Datadog.new(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
api_key: API_KEY,
|
|
30
|
+
application_key: APPLICATION_KEY,
|
|
31
|
+
tag_prefix: 'tag',
|
|
32
|
+
scope_prefix: nil
|
|
33
33
|
)
|
|
34
34
|
end
|
|
35
|
+
|
|
36
|
+
it 'should create a Dogapi client for the endpoint' do
|
|
37
|
+
dogs = @handler.instance_variable_get(:@dogs)
|
|
38
|
+
|
|
39
|
+
# Check that we do have a Dogapi client
|
|
40
|
+
expect(dogs.length).to eq(1)
|
|
41
|
+
end
|
|
35
42
|
end
|
|
36
43
|
|
|
44
|
+
|
|
45
|
+
|
|
37
46
|
describe 'reports metrics event and sets tags' do
|
|
38
47
|
# Construct a good run_status
|
|
39
48
|
before(:each) do
|
|
@@ -57,7 +66,7 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
57
66
|
context 'emits metrics' do
|
|
58
67
|
it 'reports metrics' do
|
|
59
68
|
expect(a_request(:post, METRICS_ENDPOINT).with(
|
|
60
|
-
:
|
|
69
|
+
query: { api_key: @handler.config[:api_key] }
|
|
61
70
|
)).to have_been_made.times(5)
|
|
62
71
|
end
|
|
63
72
|
end
|
|
@@ -65,17 +74,18 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
65
74
|
context 'emits events' do
|
|
66
75
|
it 'posts an event' do
|
|
67
76
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
68
|
-
:
|
|
69
|
-
:
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
query: { api_key: @handler.config[:api_key] },
|
|
78
|
+
body: hash_including(msg_text: 'Chef updated 0 resources out of 0 resources total.',
|
|
79
|
+
msg_title: "Chef completed in 5 seconds on #{@node.name} ",
|
|
80
|
+
tags: ['env:testing']),
|
|
72
81
|
)).to have_been_made.times(1)
|
|
73
82
|
end
|
|
74
83
|
|
|
75
84
|
it 'sets priority correctly' do
|
|
76
85
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
77
|
-
:
|
|
78
|
-
:
|
|
86
|
+
query: { api_key: @handler.config[:api_key] },
|
|
87
|
+
body: hash_including(alert_type: 'success',
|
|
88
|
+
priority: 'low'),
|
|
79
89
|
)).to have_been_made.times(1)
|
|
80
90
|
end
|
|
81
91
|
end
|
|
@@ -85,15 +95,15 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
85
95
|
# We no longer need to query the tag api for current tags,
|
|
86
96
|
# rather udpate only the tags for the designated source type
|
|
87
97
|
expect(a_request(:get, HOST_TAG_ENDPOINT + @node.name).with(
|
|
88
|
-
:
|
|
89
|
-
|
|
98
|
+
headers: { 'Dd-Api-Key' => @handler.config[:api_key],
|
|
99
|
+
'Dd-Application-Key' => @handler.config[:application_key] },
|
|
90
100
|
)).to have_been_made.times(0)
|
|
91
101
|
|
|
92
102
|
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
|
93
|
-
:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
:
|
|
103
|
+
headers: { 'Dd-Api-Key' => @handler.config[:api_key],
|
|
104
|
+
'Dd-Application-Key' => @handler.config[:application_key] },
|
|
105
|
+
query: { source: 'chef' },
|
|
106
|
+
body: { tags: ['env:testing'] },
|
|
97
107
|
)).to have_been_made.times(1)
|
|
98
108
|
end
|
|
99
109
|
end
|
|
@@ -104,7 +114,7 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
104
114
|
@node = Chef::Node.build('chef.handler.datadog.test-ec2')
|
|
105
115
|
@node.send(:chef_environment, 'testing')
|
|
106
116
|
|
|
107
|
-
@node.automatic_attrs['ec2'] = { :
|
|
117
|
+
@node.automatic_attrs['ec2'] = { instance_id: 'i-123456' }
|
|
108
118
|
|
|
109
119
|
@run_context = Chef::RunContext.new(@node, {}, @events)
|
|
110
120
|
@run_status = Chef::RunStatus.new(@node, @events)
|
|
@@ -119,9 +129,9 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
119
129
|
@handler.run_report_unsafe(@run_status)
|
|
120
130
|
|
|
121
131
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
122
|
-
:
|
|
123
|
-
:
|
|
124
|
-
|
|
132
|
+
query: { api_key: @handler.config[:api_key] },
|
|
133
|
+
body: hash_including(msg_title: 'Chef completed in 5 seconds on i-123456 ',
|
|
134
|
+
host: 'i-123456'),
|
|
125
135
|
)).to have_been_made.times(1)
|
|
126
136
|
end
|
|
127
137
|
|
|
@@ -130,9 +140,9 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
130
140
|
@handler.run_report_unsafe(@run_status)
|
|
131
141
|
|
|
132
142
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
133
|
-
:
|
|
134
|
-
:
|
|
135
|
-
|
|
143
|
+
query: { api_key: @handler.config[:api_key] },
|
|
144
|
+
body: hash_including(msg_title: 'Chef completed in 5 seconds on i-123456 ',
|
|
145
|
+
host: 'i-123456'),
|
|
136
146
|
)).to have_been_made.times(1)
|
|
137
147
|
end
|
|
138
148
|
|
|
@@ -141,9 +151,9 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
141
151
|
@handler.run_report_unsafe(@run_status)
|
|
142
152
|
|
|
143
153
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
144
|
-
:
|
|
145
|
-
:
|
|
146
|
-
|
|
154
|
+
query: { api_key: @handler.config[:api_key] },
|
|
155
|
+
body: hash_including(msg_title: "Chef completed in 5 seconds on #{@node.name} ",
|
|
156
|
+
host: @node.name),
|
|
147
157
|
)).to have_been_made.times(1)
|
|
148
158
|
end
|
|
149
159
|
end
|
|
@@ -166,9 +176,9 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
166
176
|
@handler.run_report_unsafe(@run_status)
|
|
167
177
|
|
|
168
178
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
169
|
-
:
|
|
170
|
-
:
|
|
171
|
-
|
|
179
|
+
query: { api_key: @handler.config[:api_key] },
|
|
180
|
+
body: hash_including(msg_title: "Chef completed in 5 seconds on #{@node.name} ",
|
|
181
|
+
host: @node.name),
|
|
172
182
|
)).to have_been_made.times(1)
|
|
173
183
|
end
|
|
174
184
|
|
|
@@ -177,11 +187,29 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
177
187
|
@handler.run_report_unsafe(@run_status)
|
|
178
188
|
|
|
179
189
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
180
|
-
:
|
|
181
|
-
:
|
|
182
|
-
|
|
190
|
+
query: { api_key: @handler.config[:api_key] },
|
|
191
|
+
body: hash_including(msg_title: 'Chef completed in 5 seconds on my-imaginary-hostname.local ',
|
|
192
|
+
host: 'my-imaginary-hostname.local'),
|
|
183
193
|
)).to have_been_made.times(1)
|
|
184
194
|
end
|
|
195
|
+
|
|
196
|
+
describe 'when dogapi-rb fails to calculate a hostname' do
|
|
197
|
+
before(:each) do
|
|
198
|
+
allow(Dogapi::Client).to receive(:new).and_raise("getaddrinfo: Name or service not known")
|
|
199
|
+
|
|
200
|
+
@handler = Chef::Handler::Datadog.new(
|
|
201
|
+
api_key: API_KEY,
|
|
202
|
+
application_key: APPLICATION_KEY,
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it 'the reporter should not fail the chef run' do
|
|
207
|
+
@handler.config[:hostname] = 'my-imaginary-hostname.local'
|
|
208
|
+
@handler.run_report_unsafe(@run_status)
|
|
209
|
+
|
|
210
|
+
expect(a_request(:post, EVENTS_ENDPOINT)).to have_been_made.times(0)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
185
213
|
end
|
|
186
214
|
|
|
187
215
|
context 'tags' do
|
|
@@ -209,10 +237,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
209
237
|
@handler.run_report_unsafe(@run_status)
|
|
210
238
|
|
|
211
239
|
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
|
212
|
-
:
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
:
|
|
240
|
+
headers: { 'Dd-Api-Key' => @handler.config[:api_key],
|
|
241
|
+
'Dd-Application-Key' => @handler.config[:application_key] },
|
|
242
|
+
query: { source: 'chef' },
|
|
243
|
+
body: hash_including(tags: [
|
|
216
244
|
'env:hostile', 'role:highlander', 'tag:the_one_and_only', 'tag:datacenter:my-cloud'
|
|
217
245
|
]),
|
|
218
246
|
)).to have_been_made.times(1)
|
|
@@ -224,10 +252,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
224
252
|
@handler.run_report_unsafe(@run_status)
|
|
225
253
|
|
|
226
254
|
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
|
227
|
-
:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
:
|
|
255
|
+
headers: { 'Dd-Api-Key' => @handler.config[:api_key],
|
|
256
|
+
'Dd-Application-Key' => @handler.config[:application_key] },
|
|
257
|
+
query: { source: 'chef' },
|
|
258
|
+
body: hash_including(tags: [
|
|
231
259
|
'env:hostile', 'role:highlander', 'custom-prefix-the_one_and_only', 'custom-prefix-datacenter:my-cloud'
|
|
232
260
|
]),
|
|
233
261
|
)).to have_been_made.times(1)
|
|
@@ -239,10 +267,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
239
267
|
@handler.run_report_unsafe(@run_status)
|
|
240
268
|
|
|
241
269
|
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
|
242
|
-
:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
:
|
|
270
|
+
headers: { 'Dd-Api-Key' => @handler.config[:api_key],
|
|
271
|
+
'Dd-Application-Key' => @handler.config[:application_key] },
|
|
272
|
+
query: { source: 'chef' },
|
|
273
|
+
body: hash_including(tags: [
|
|
246
274
|
'env:hostile', 'role:highlander', 'the_one_and_only', 'datacenter:my-cloud'
|
|
247
275
|
]),
|
|
248
276
|
)).to have_been_made.times(1)
|
|
@@ -253,10 +281,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
253
281
|
@handler.run_report_unsafe(@run_status)
|
|
254
282
|
|
|
255
283
|
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
|
256
|
-
:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
:
|
|
284
|
+
headers: { 'Dd-Api-Key' => @handler.config[:api_key],
|
|
285
|
+
'Dd-Application-Key' => @handler.config[:application_key] },
|
|
286
|
+
query: { source: 'chef' },
|
|
287
|
+
body: hash_including(tags: [
|
|
260
288
|
'custom-prefix-env:hostile', 'custom-prefix-role:highlander'
|
|
261
289
|
]),
|
|
262
290
|
)).to have_been_made.times(1)
|
|
@@ -267,10 +295,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
267
295
|
@handler.run_report_unsafe(@run_status)
|
|
268
296
|
|
|
269
297
|
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
|
270
|
-
:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
:
|
|
298
|
+
headers: { 'Dd-Api-Key' => @handler.config[:api_key],
|
|
299
|
+
'Dd-Application-Key' => @handler.config[:application_key] },
|
|
300
|
+
query: { source: 'chef' },
|
|
301
|
+
body: hash_including(tags: [
|
|
274
302
|
'env:hostile', 'role:highlander'
|
|
275
303
|
]),
|
|
276
304
|
)).to have_been_made.times(1)
|
|
@@ -282,10 +310,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
282
310
|
@handler.run_report_unsafe(@run_status)
|
|
283
311
|
|
|
284
312
|
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
|
285
|
-
:
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
:
|
|
313
|
+
headers: { 'Dd-Api-Key' => @handler.config[:api_key],
|
|
314
|
+
'Dd-Application-Key' => @handler.config[:application_key] },
|
|
315
|
+
query: { source: 'chef' },
|
|
316
|
+
body: hash_including(tags: [
|
|
289
317
|
'env:hostile', 'role:highlander'
|
|
290
318
|
]),
|
|
291
319
|
)).to have_been_made.times(1)
|
|
@@ -299,10 +327,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
299
327
|
@handler.run_report_unsafe(@run_status)
|
|
300
328
|
|
|
301
329
|
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
|
302
|
-
:
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
:
|
|
330
|
+
headers: { 'Dd-Api-Key' => @handler.config[:api_key],
|
|
331
|
+
'Dd-Application-Key' => @handler.config[:application_key] },
|
|
332
|
+
query: { source: 'chef' },
|
|
333
|
+
body: hash_including(tags: [
|
|
306
334
|
'env:hostile', 'role:highlander', 'tag:allowed_tag'
|
|
307
335
|
]),
|
|
308
336
|
)).to have_been_made.times(1)
|
|
@@ -315,10 +343,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
315
343
|
@handler.run_report_unsafe(@run_status)
|
|
316
344
|
|
|
317
345
|
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
|
318
|
-
:
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
:
|
|
346
|
+
headers: { 'Dd-Api-Key' => @handler.config[:api_key],
|
|
347
|
+
'Dd-Application-Key' => @handler.config[:application_key] },
|
|
348
|
+
query: { source: 'chef' },
|
|
349
|
+
body: hash_including(tags: [
|
|
322
350
|
'env:hostile', 'role:highlander', 'tag:allowed_tag', 'tag:not_allowed_tag'
|
|
323
351
|
]),
|
|
324
352
|
)).to have_been_made.times(1)
|
|
@@ -336,10 +364,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
336
364
|
@handler.run_report_unsafe(@run_status)
|
|
337
365
|
|
|
338
366
|
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
|
339
|
-
:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
:
|
|
367
|
+
headers: { 'Dd-Api-Key' => @handler.config[:api_key],
|
|
368
|
+
'Dd-Application-Key' => @handler.config[:application_key] },
|
|
369
|
+
query: { source: 'chef' },
|
|
370
|
+
body: hash_including(tags: [
|
|
343
371
|
'env:hostile', 'role:highlander'
|
|
344
372
|
]),
|
|
345
373
|
)).to have_been_made.times(1)
|
|
@@ -358,10 +386,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
358
386
|
@handler.run_report_unsafe(@run_status)
|
|
359
387
|
|
|
360
388
|
expect(a_request(:put, HOST_TAG_ENDPOINT + @node.name).with(
|
|
361
|
-
:
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
:
|
|
389
|
+
headers: { 'Dd-Api-Key' => @handler.config[:api_key],
|
|
390
|
+
'Dd-Application-Key' => @handler.config[:application_key] },
|
|
391
|
+
query: { source: 'chef' },
|
|
392
|
+
body: hash_including(tags: [
|
|
365
393
|
'env:hostile', 'role:highlander', 'policy_group:the_policy_group', 'policy_name:the_policy_name'
|
|
366
394
|
]),
|
|
367
395
|
)).to have_been_made.times(1)
|
|
@@ -370,6 +398,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
370
398
|
end
|
|
371
399
|
|
|
372
400
|
context 'tags submission retries' do
|
|
401
|
+
let(:dog) do
|
|
402
|
+
@handler.instance_variable_get(:@dogs)[0]
|
|
403
|
+
end
|
|
404
|
+
|
|
373
405
|
before(:each) do
|
|
374
406
|
@node = Chef::Node.build('chef.handler.datadog.test-tags-retries')
|
|
375
407
|
|
|
@@ -380,7 +412,6 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
380
412
|
@events = Chef::EventDispatch::Dispatcher.new
|
|
381
413
|
@run_context = Chef::RunContext.new(@node, {}, @events)
|
|
382
414
|
@run_status = Chef::RunStatus.new(@node, @events)
|
|
383
|
-
|
|
384
415
|
@expected_time = Time.now
|
|
385
416
|
allow(Time).to receive(:now).and_return(@expected_time, @expected_time + 5)
|
|
386
417
|
@run_status.start_clock
|
|
@@ -397,44 +428,30 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
397
428
|
end
|
|
398
429
|
|
|
399
430
|
it 'retries no more than twice' do
|
|
400
|
-
|
|
431
|
+
# Define mock update_tags function which returns the result of an HTTP 404 error
|
|
432
|
+
allow(dog).to receive(:update_tags).and_return([404, 'Not Found'])
|
|
401
433
|
|
|
402
|
-
expect(
|
|
403
|
-
|
|
404
|
-
'application_key' => @handler.config[:application_key],
|
|
405
|
-
'source' => 'chef' },
|
|
406
|
-
:body => hash_including(:tags => [
|
|
407
|
-
'env:hostile', 'role:highlander', 'tag:the_one_and_only'
|
|
408
|
-
]),
|
|
409
|
-
)).to have_been_made.times(3)
|
|
434
|
+
expect(dog).to receive(:update_tags).exactly(3).times
|
|
435
|
+
@handler.run_report_unsafe(@run_status)
|
|
410
436
|
end
|
|
411
437
|
|
|
412
438
|
it 'stops retrying once submission is successful' do
|
|
413
|
-
|
|
439
|
+
# Define mock update_tags function which returns the result of an HTTP 404 error once
|
|
440
|
+
allow(dog).to receive(:update_tags).and_return([404, 'Not Found'], [201, 'Created'])
|
|
414
441
|
|
|
415
|
-
expect(
|
|
416
|
-
|
|
417
|
-
'application_key' => @handler.config[:application_key],
|
|
418
|
-
'source' => 'chef' },
|
|
419
|
-
:body => hash_including(:tags => [
|
|
420
|
-
'env:hostile', 'role:highlander', 'tag:the_one_and_only'
|
|
421
|
-
]),
|
|
422
|
-
)).to have_been_made.times(2)
|
|
442
|
+
expect(dog).to receive(:update_tags).exactly(2).times
|
|
443
|
+
@handler.run_report_unsafe(@run_status)
|
|
423
444
|
end
|
|
424
445
|
end
|
|
425
446
|
|
|
426
447
|
describe 'when not specified' do
|
|
427
|
-
it 'does not retry after a failed submission'
|
|
428
|
-
|
|
448
|
+
it 'does not retry after a failed submission' do
|
|
449
|
+
# Define mock update_tags function which returns the result of an HTTP 404 error
|
|
450
|
+
allow(dog).to receive(:update_tags).and_return([404, 'Not Found'])
|
|
429
451
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
'source' => 'chef' },
|
|
434
|
-
:body => hash_including(:tags => [
|
|
435
|
-
'env:hostile', 'role:highlander', 'tag:the_one_and_only'
|
|
436
|
-
]),
|
|
437
|
-
)).to have_been_made.times(1)
|
|
452
|
+
|
|
453
|
+
expect(dog).to receive(:update_tags).exactly(:once)
|
|
454
|
+
@handler.run_report_unsafe(@run_status)
|
|
438
455
|
end
|
|
439
456
|
end
|
|
440
457
|
end
|
|
@@ -507,16 +524,16 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
507
524
|
|
|
508
525
|
it 'sets event title correctly' do
|
|
509
526
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
510
|
-
:
|
|
511
|
-
:
|
|
527
|
+
query: { api_key: @handler.config[:api_key] },
|
|
528
|
+
body: hash_including(msg_title: "Chef failed in 2 seconds on #{@node.name} "),
|
|
512
529
|
)).to have_been_made.times(1)
|
|
513
530
|
end
|
|
514
531
|
|
|
515
532
|
it 'sets priority correctly' do
|
|
516
533
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
517
|
-
:
|
|
518
|
-
:
|
|
519
|
-
|
|
534
|
+
query: { api_key: @handler.config[:api_key] },
|
|
535
|
+
body: hash_including(alert_type: 'error',
|
|
536
|
+
priority: 'normal'),
|
|
520
537
|
)).to have_been_made.times(1)
|
|
521
538
|
end
|
|
522
539
|
|
|
@@ -525,8 +542,8 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
525
542
|
@handler.run_report_unsafe(@run_status)
|
|
526
543
|
|
|
527
544
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
528
|
-
:
|
|
529
|
-
:
|
|
545
|
+
query: { api_key: @handler.config[:api_key] },
|
|
546
|
+
body: /Alerting: @alice @bob/
|
|
530
547
|
)).to have_been_made.times(1)
|
|
531
548
|
end
|
|
532
549
|
end
|
|
@@ -556,9 +573,10 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
556
573
|
|
|
557
574
|
it 'posts an event' do
|
|
558
575
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
559
|
-
:
|
|
560
|
-
:
|
|
561
|
-
:
|
|
576
|
+
query: { api_key: @handler.config[:api_key] },
|
|
577
|
+
# FIXME: msg_text is "\n$$$\n- [whiskers] (dynamically defined)\n\n$$$\n" - is this a bug?
|
|
578
|
+
body: hash_including(#msg_text: 'Chef updated 1 resources out of 2 resources total.',
|
|
579
|
+
msg_title: "Chef completed in 8 seconds on #{@node.name} "),
|
|
562
580
|
)).to have_been_made.times(1)
|
|
563
581
|
end
|
|
564
582
|
end
|
|
@@ -578,15 +596,15 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
578
596
|
|
|
579
597
|
it 'only emits the run status metrics' do
|
|
580
598
|
expect(a_request(:post, METRICS_ENDPOINT).with(
|
|
581
|
-
:
|
|
599
|
+
query: { api_key: @handler.config[:api_key] }
|
|
582
600
|
)).to have_been_made.times(2)
|
|
583
601
|
end
|
|
584
602
|
|
|
585
603
|
it 'posts an event' do
|
|
586
604
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
587
|
-
:
|
|
588
|
-
:
|
|
589
|
-
|
|
605
|
+
query: { api_key: @handler.config[:api_key] },
|
|
606
|
+
body: hash_including(msg_text: 'Chef was unable to complete a run, an error during compilation may have occurred.',
|
|
607
|
+
msg_title: "Chef failed during compile phase on #{@node.name} "),
|
|
590
608
|
)).to have_been_made.times(1)
|
|
591
609
|
end
|
|
592
610
|
end
|
|
@@ -608,15 +626,15 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
608
626
|
|
|
609
627
|
it 'only emits the run status metrics' do
|
|
610
628
|
expect(a_request(:post, METRICS_ENDPOINT).with(
|
|
611
|
-
:
|
|
629
|
+
query: { api_key: @handler.config[:api_key] }
|
|
612
630
|
)).to have_been_made.times(2)
|
|
613
631
|
end
|
|
614
632
|
|
|
615
633
|
it 'posts an event' do
|
|
616
634
|
expect(a_request(:post, EVENTS_ENDPOINT).with(
|
|
617
|
-
:
|
|
618
|
-
:
|
|
619
|
-
|
|
635
|
+
query: { api_key: @handler.config[:api_key] },
|
|
636
|
+
body: hash_including(msg_text: 'Chef was unable to complete a run, an error during compilation may have occurred.',
|
|
637
|
+
msg_title: "Chef failed during compile phase on #{@node.name} "),
|
|
620
638
|
)).to have_been_made.times(1)
|
|
621
639
|
end
|
|
622
640
|
end
|
|
@@ -734,6 +752,31 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
734
752
|
expect(handler.send(:endpoints)).to eq(triplets[0..1])
|
|
735
753
|
end
|
|
736
754
|
end
|
|
755
|
+
|
|
756
|
+
context 'when using api url instead of url' do
|
|
757
|
+
it 'returns available triplets' do
|
|
758
|
+
triplets = [
|
|
759
|
+
['https://app.datadoghq.com', 'api_key_2' , 'app_key_2'],
|
|
760
|
+
['https://app.example.com', 'api_key_3', 'app_key_3'],
|
|
761
|
+
['https://app.example.com', 'api_key_4', 'app_key_4']
|
|
762
|
+
]
|
|
763
|
+
handler = Chef::Handler::Datadog.new api_key: triplets[0][1],
|
|
764
|
+
application_key: triplets[0][2],
|
|
765
|
+
url: triplets[0][0],
|
|
766
|
+
extra_endpoints: [{
|
|
767
|
+
api_url: triplets[1][0],
|
|
768
|
+
url: triplets[0][0],
|
|
769
|
+
api_key: triplets[1][1],
|
|
770
|
+
application_key: triplets[1][2]
|
|
771
|
+
}, {
|
|
772
|
+
api_url: triplets[2][0],
|
|
773
|
+
url:triplets[0][0],
|
|
774
|
+
api_key: triplets[2][1],
|
|
775
|
+
application_key: triplets[2][2]
|
|
776
|
+
}]
|
|
777
|
+
expect(handler.send(:endpoints)).to eq(triplets)
|
|
778
|
+
end
|
|
779
|
+
end
|
|
737
780
|
end
|
|
738
781
|
|
|
739
782
|
context 'when reporting to multiple endpoints' do
|
|
@@ -744,17 +787,29 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
744
787
|
let(:host_tag_endpoint2) { base_url2 + '/api/v1/tags/hosts/' }
|
|
745
788
|
let(:metrics_endpoint2) { base_url2 + '/api/v1/series' }
|
|
746
789
|
let(:handler) do
|
|
747
|
-
Chef::Handler::Datadog.new
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
790
|
+
Chef::Handler::Datadog.new(api_key: API_KEY,
|
|
791
|
+
application_key: APPLICATION_KEY,
|
|
792
|
+
url: BASE_URL,
|
|
793
|
+
extra_endpoints: [{
|
|
794
|
+
api_key: api_key2,
|
|
795
|
+
application_key: application_key2,
|
|
796
|
+
url: base_url2
|
|
797
|
+
}])
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
let(:dogs) do
|
|
801
|
+
handler.instance_variable_get(:@dogs)
|
|
755
802
|
end
|
|
803
|
+
|
|
756
804
|
# Construct a good run_status
|
|
757
805
|
before(:each) do
|
|
806
|
+
dogs.each do |dog|
|
|
807
|
+
# Define mock functions to avoid failures when connecting to the app.example.com endpoint
|
|
808
|
+
allow(dog).to receive(:emit_point).and_return(true)
|
|
809
|
+
allow(dog).to receive(:emit_event).and_return([200, "{'event': 'My event'}"])
|
|
810
|
+
allow(dog).to receive(:update_tags).and_return([201, "Created"])
|
|
811
|
+
end
|
|
812
|
+
|
|
758
813
|
@node = Chef::Node.build('chef.handler.datadog.test')
|
|
759
814
|
@node.send(:chef_environment, 'testing')
|
|
760
815
|
@events = Chef::EventDispatch::Dispatcher.new
|
|
@@ -767,56 +822,51 @@ describe Chef::Handler::Datadog, :vcr => :new_episodes do
|
|
|
767
822
|
@run_status.stop_clock
|
|
768
823
|
|
|
769
824
|
@run_status.run_context = @run_context
|
|
825
|
+
end
|
|
770
826
|
|
|
771
|
-
|
|
772
|
-
|
|
827
|
+
it 'should create multiple Dogapi clients' do
|
|
828
|
+
expect(dogs.length).to eq(2)
|
|
773
829
|
end
|
|
774
830
|
|
|
775
831
|
context 'emits metrics' do
|
|
776
|
-
it 'reports metrics' do
|
|
777
|
-
expect(
|
|
778
|
-
:query => { 'api_key' => API_KEY }
|
|
779
|
-
)).to have_been_made.times(5)
|
|
832
|
+
it 'reports metrics to the first endpoint' do
|
|
833
|
+
expect(dogs[0]).to receive(:emit_point).exactly(5).times
|
|
780
834
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
835
|
+
handler.run_report_unsafe(@run_status)
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
it 'reports metrics to the second endpoint' do
|
|
839
|
+
expect(dogs[1]).to receive(:emit_point).exactly(5).times
|
|
840
|
+
|
|
841
|
+
handler.run_report_unsafe(@run_status)
|
|
784
842
|
end
|
|
785
843
|
end
|
|
786
844
|
|
|
787
845
|
context 'emits events' do
|
|
788
|
-
it 'posts an event' do
|
|
789
|
-
expect(
|
|
790
|
-
:query => { 'api_key' => API_KEY },
|
|
791
|
-
:body => hash_including(:msg_text => 'Chef updated 0 resources out of 0 resources total.'),
|
|
792
|
-
:body => hash_including(:msg_title => "Chef completed in 5 seconds on #{@node.name} "),
|
|
793
|
-
:body => hash_including(:tags => ['env:testing']),
|
|
794
|
-
)).to have_been_made.times(1)
|
|
846
|
+
it 'posts an event to the first endpoint' do
|
|
847
|
+
expect(dogs[0]).to receive(:emit_event).exactly(:once)
|
|
795
848
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
849
|
+
handler.run_report_unsafe(@run_status)
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
it 'posts an event to the second endpoint' do
|
|
853
|
+
expect(dogs[1]).to receive(:emit_event).exactly(:once)
|
|
854
|
+
|
|
855
|
+
handler.run_report_unsafe(@run_status)
|
|
802
856
|
end
|
|
803
857
|
end
|
|
804
858
|
|
|
805
859
|
context 'sets tags' do
|
|
806
|
-
it 'puts the tags for the current node' do
|
|
807
|
-
expect(
|
|
808
|
-
:query => { 'api_key' => API_KEY,
|
|
809
|
-
'application_key' => APPLICATION_KEY,
|
|
810
|
-
'source' => 'chef' },
|
|
811
|
-
:body => { 'tags' => ['env:testing'] },
|
|
812
|
-
)).to have_been_made.times(1)
|
|
860
|
+
it 'puts the tags for the current node on the first endpoint' do
|
|
861
|
+
expect(dogs[0]).to receive(:update_tags).exactly(:once)
|
|
813
862
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
863
|
+
handler.run_report_unsafe(@run_status)
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
it 'puts the tags for the current node on the second endpoint' do
|
|
867
|
+
expect(dogs[1]).to receive(:update_tags).exactly(:once)
|
|
868
|
+
|
|
869
|
+
handler.run_report_unsafe(@run_status)
|
|
820
870
|
end
|
|
821
871
|
end
|
|
822
872
|
end
|