kennel 1.80.0 → 1.83.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cc9d71dff0d4c4bf8a29d9c58c1b62efbb739f3ebdc0d63fb7bd1a3e1cfec7c
4
- data.tar.gz: 312fbda2e1577d071ba7cad7a6c5571b6b3913517cc3e5e9b92f7858637f96dc
3
+ metadata.gz: 5c3c766383bf7f533e74b935a6faf2272e552bb7c974fa15261c70aa9d31e800
4
+ data.tar.gz: 339c1dcad92fc215d2a26b7091bbe57dc98d3f6ed5a01666e1ee683162bf20e0
5
5
  SHA512:
6
- metadata.gz: 8d07c6d405178e7114c1ebaf67ce3f92a09a411208d20353bf7d07f453093e322b98f2e8e2f501cf902a601fafa1b352b5fa19bff9d54f1d042e87fc7ef81277
7
- data.tar.gz: 9d681c94f1d61bbc60a098ea0b6fe12e3efc5b6e3c771ec49743db8e6edaae1c83a64ae322a9f5759fc677465b199842f0401c8a38973d1bbaa14157f53f94da
6
+ metadata.gz: 97abfc77652ab1afb6192c97f038dc6caa33a2f8aa505c62c6782fef1359b431d93cf293c737d9f2ed03c9bb4d22d0474ac916586aa7ed15863900dc8eb1791d
7
+ data.tar.gz: 13e247e4ab000410155b9f4835686168a2017c1ee2fd8b4e0f6619c54f0b055581b1acc855d12ac06b0ed3920782722fbffa1d303ea7aa04ed92104ab1ec6908
data/Readme.md CHANGED
@@ -212,15 +212,20 @@ removing the `id` will cause kennel to create a new resource in datadog.
212
212
  Some validations might be too strict for your usecase or just wrong, please [open an issue](https://github.com/grosser/kennel/issues) and
213
213
  to unblock use the `validate: -> { false }` option.
214
214
 
215
- ### Linking with kennel_ids
215
+ ### Linking resources with kennel_id
216
216
 
217
- To link to existing monitors via their kennel_id `projects kennel_id` + `:` + `monitors kennel id`
217
+ Link resources with their kennel_id in the format `project kennel_id` + `:` + `resource kennel_id`,
218
+ this should be used to create dependent resources like monitor + slos,
219
+ so they can be created in a single update and can be re-created if any of them is deleted.
218
220
 
219
- - Screens `uptime` widgets can use `monitor: {id: "foo:bar"}`
220
- - Screens `alert_graph` widgets can use `alert_id: "foo:bar"`
221
- - Monitors `composite` can use `query: -> { "%{foo:bar} || %{foo:baz}" }`
222
- - Monitors `slo alert` can use `query: -> { "error_budget(\"%{foo:bar}\").over(\"7d\") > 123.0" }`
223
- - Slos can use `monitor_ids: -> ["foo:bar"]`
221
+ |Resource|Type|Syntax|
222
+ |---|---|---|
223
+ |Dashboard|uptime|`monitor: {id: "foo:bar"}`|
224
+ |Dashboard|alert_graph|`alert_id: "foo:bar"`|
225
+ |Dashboard|slo|`slo_id: "foo:bar"`|
226
+ |Monitor|composite|`query: -> { "%{foo:bar} && %{foo:baz}" }`|
227
+ |Monitor|slo alert|`query: -> { "error_budget(\"%{foo:bar}\").over(\"7d\") > 123.0" }`|
228
+ |Slo|monitor|`monitor_ids: -> ["foo:bar"]`|
224
229
 
225
230
  ### Debugging changes locally
226
231
 
@@ -268,9 +273,19 @@ Run `rake kennel:alerts TAG=service:my-service` to see all un-muted alerts for a
268
273
 
269
274
  ### Grepping through all of datadog
270
275
 
271
- `rake kennel:dump`
276
+ ```Bash
277
+ rake kennel:dump > tmp/dump
278
+ cat tmp/dump | grep foo
279
+ ```
272
280
  focus on a single type: `TYPE=monitors`
273
281
 
282
+ Show full resources or just their urls by pattern:
283
+ ```Bash
284
+ rake kennel:dump_grep DUMP=tmp/dump PATTERN=foo URLS=true
285
+ https://foo.datadog.com/dasboard/123
286
+ https://foo.datadog.com/monitor/123
287
+ ```
288
+
274
289
  ### Find all monitors with No-Data
275
290
 
276
291
  `rake kennel:nodata TAG=team:foo`
data/lib/kennel/api.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
+ # encapsulates knowledge around how the api works
3
4
  class Api
5
+ CACHE_FILE = "tmp/cache/details"
6
+
4
7
  def initialize(app_key, api_key)
5
8
  @app_key = app_key
6
9
  @api_key = api_key
@@ -49,8 +52,27 @@ module Kennel
49
52
  request :delete, "/api/v1/#{api_resource}/#{id}", params: { force: "true" }, ignore_404: true
50
53
  end
51
54
 
55
+ def fill_details!(api_resource, list)
56
+ return unless api_resource == "dashboard"
57
+ details_cache do |cache|
58
+ Utils.parallel(list) { |a| fill_detail!(api_resource, a, cache) }
59
+ end
60
+ end
61
+
52
62
  private
53
63
 
64
+ # Make diff work even though we cannot mass-fetch definitions
65
+ def fill_detail!(api_resource, a, cache)
66
+ args = [api_resource, a.fetch(:id)]
67
+ full = cache.fetch(args, a.fetch(:modified_at)) { show(*args) }
68
+ a.merge!(full)
69
+ end
70
+
71
+ def details_cache(&block)
72
+ cache = FileCache.new CACHE_FILE, Kennel::VERSION
73
+ cache.open(&block)
74
+ end
75
+
54
76
  def request(method, path, body: nil, params: {}, ignore_404: false)
55
77
  params = params.merge(application_key: @app_key, api_key: @api_key)
56
78
  query = Faraday::FlatParamsEncoder.encode(params)
@@ -5,7 +5,6 @@ module Kennel
5
5
  include TemplateVariables
6
6
  include OptionalValidations
7
7
 
8
- API_LIST_INCOMPLETE = true
9
8
  DASHBOARD_DEFAULTS = { template_variables: [] }.freeze
10
9
  READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [
11
10
  :author_handle, :author_name, :modified_at, :url, :is_read_only, :notify_list
@@ -124,7 +123,7 @@ module Kennel
124
123
  @json
125
124
  end
126
125
 
127
- def url(id)
126
+ def self.url(id)
128
127
  Utils.path_to_url "/dashboard/#{id}"
129
128
  end
130
129
 
@@ -140,16 +139,16 @@ module Kennel
140
139
  when "uptime"
141
140
  if ids = definition[:monitor_ids]
142
141
  definition[:monitor_ids] = ids.map do |id|
143
- tracking_id?(id) ? resolve_link(id, :monitor, id_map, **args) : id
142
+ tracking_id?(id) ? (resolve_link(id, :monitor, id_map, **args) || id) : id
144
143
  end
145
144
  end
146
145
  when "alert_graph"
147
146
  if (id = definition[:alert_id]) && tracking_id?(id)
148
- definition[:alert_id] = resolve_link(id, :monitor, id_map, **args).to_s
147
+ definition[:alert_id] = (resolve_link(id, :monitor, id_map, **args) || id).to_s
149
148
  end
150
149
  when "slo"
151
150
  if (id = definition[:slo_id]) && tracking_id?(id)
152
- definition[:slo_id] = resolve_link(id, :slo, id_map, **args).to_s
151
+ definition[:slo_id] = (resolve_link(id, :slo, id_map, **args) || id).to_s
153
152
  end
154
153
  end
155
154
  end
@@ -186,22 +185,26 @@ module Kennel
186
185
  end
187
186
 
188
187
  def render_definitions(definitions)
189
- definitions.map do |title, type, display_type, queries, options = {}, ignored = nil|
190
- # validate inputs
191
- if ignored || (!title || !type || !queries || !options.is_a?(Hash))
192
- raise ArgumentError, "Expected exactly 5 arguments for each definition (title, type, display_type, queries, options)"
193
- end
194
- if (SUPPORTED_DEFINITION_OPTIONS | options.keys) != SUPPORTED_DEFINITION_OPTIONS
195
- raise ArgumentError, "Supported options are: #{SUPPORTED_DEFINITION_OPTIONS.map(&:inspect).join(", ")}"
196
- end
188
+ definitions.map do |title, type, display_type, queries, options = {}, too_many_args = nil|
189
+ if title.is_a?(Hash) && !type
190
+ title # user gave a full widget, just use it
191
+ else
192
+ # validate inputs
193
+ if too_many_args || (!title || !type || !queries || !options.is_a?(Hash))
194
+ raise ArgumentError, "Expected exactly 5 arguments for each definition (title, type, display_type, queries, options)"
195
+ end
196
+ if (SUPPORTED_DEFINITION_OPTIONS | options.keys) != SUPPORTED_DEFINITION_OPTIONS
197
+ raise ArgumentError, "Supported options are: #{SUPPORTED_DEFINITION_OPTIONS.map(&:inspect).join(", ")}"
198
+ end
197
199
 
198
- # build definition
199
- requests = Array(queries).map do |q|
200
- request = { q: q }
201
- request[:display_type] = display_type if display_type
202
- request
200
+ # build definition
201
+ requests = Array(queries).map do |q|
202
+ request = { q: q }
203
+ request[:display_type] = display_type if display_type
204
+ request
205
+ end
206
+ { definition: { title: title, type: type, requests: requests, **options } }
203
207
  end
204
- { definition: { title: title, type: type, requests: requests, **options } }
205
208
  end
206
209
  end
207
210
  end
@@ -116,7 +116,7 @@ module Kennel
116
116
  when "composite", "slo alert"
117
117
  type = (as_json[:type] == "composite" ? :monitor : :slo)
118
118
  as_json[:query] = as_json[:query].gsub(/%{(.*?)}/) do
119
- resolve_link($1, type, id_map, **args)
119
+ resolve_link($1, type, id_map, **args) || $&
120
120
  end
121
121
  end
122
122
  end
@@ -125,7 +125,7 @@ module Kennel
125
125
  "monitor"
126
126
  end
127
127
 
128
- def url(id)
128
+ def self.url(id)
129
129
  Utils.path_to_url "/monitors##{id}/edit"
130
130
  end
131
131
 
@@ -6,7 +6,6 @@ module Kennel
6
6
  READONLY_ATTRIBUTES = [
7
7
  :deleted, :id, :created, :created_at, :creator, :org_id, :modified, :modified_at, :api_resource
8
8
  ].freeze
9
- API_LIST_INCOMPLETE = false
10
9
 
11
10
  settings :id, :kennel_id
12
11
 
@@ -19,6 +18,10 @@ module Kennel
19
18
  end
20
19
  end
21
20
 
21
+ def api_resource_map
22
+ subclasses.map { |s| [s.api_resource, s] }.to_h
23
+ end
24
+
22
25
  private
23
26
 
24
27
  def normalize(_expected, actual)
@@ -65,19 +68,18 @@ module Kennel
65
68
 
66
69
  private
67
70
 
68
- def resolve_link(id, type, id_map, force:)
69
- value = id_map[id]
70
- if value == :new
71
+ def resolve_link(tracking_id, type, id_map, force:)
72
+ id = id_map[tracking_id]
73
+ if id == :new
71
74
  if force
72
- # TODO: remove the need for this by sorting monitors by missing resolutions
73
- invalid! "#{id} needs to already exist, try again"
75
+ invalid! "#{type} #{tracking_id} was referenced but is also created by the current run.\nIt could not be created because of a circular dependency, try creating only some of the resources"
74
76
  else
75
- id # will be re-resolved by syncer after the linked object was created
77
+ nil # will be re-resolved after the linked object was created
76
78
  end
77
- elsif value
78
- value
79
+ elsif id
80
+ id
79
81
  else
80
- invalid! "Unable to find #{type} #{id} (does not exist and is not being created by the current run)"
82
+ invalid! "Unable to find #{type} #{tracking_id} (does not exist and is not being created by the current run)"
81
83
  end
82
84
  end
83
85
 
@@ -58,7 +58,7 @@ module Kennel
58
58
  "slo"
59
59
  end
60
60
 
61
- def url(id)
61
+ def self.url(id)
62
62
  Utils.path_to_url "/slo?slo_id=#{id}"
63
63
  end
64
64
 
@@ -69,7 +69,7 @@ module Kennel
69
69
  def resolve_linked_tracking_ids!(id_map, **args)
70
70
  return unless as_json[:monitor_ids] # ignore_default can remove it
71
71
  as_json[:monitor_ids] = as_json[:monitor_ids].map do |id|
72
- id.is_a?(String) ? resolve_link(id, :monitor, id_map, **args) : id
72
+ id.is_a?(String) ? (resolve_link(id, :monitor, id_map, **args) || id) : id
73
73
  end
74
74
  end
75
75
 
data/lib/kennel/syncer.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
3
  class Syncer
4
- CACHE_FILE = "tmp/cache/details" # keep in sync with .travis.yml caching
5
4
  TRACKING_FIELDS = [:message, :description].freeze
6
5
  DELETE_ORDER = ["dashboard", "slo", "monitor"].freeze # dashboards references monitors + slos, slos reference monitors
7
6
 
@@ -38,25 +37,16 @@ module Kennel
38
37
  end
39
38
 
40
39
  def update
41
- changed = (@create + @update).map { |_, e| e }
42
-
43
- @create.each do |_, e|
44
- e.resolve_linked_tracking_ids!({}, force: true)
45
-
40
+ each_resolved @create do |_, e|
46
41
  reply = @api.create e.class.api_resource, e.as_json
47
42
  id = reply.fetch(:id)
48
-
49
- # resolve ids we could previously no resolve
50
- changed.delete e
51
- resolve_linked_tracking_ids! from: [reply], to: changed
52
-
53
- Kennel.out.puts "Created #{e.class.api_resource} #{tracking_id(e.as_json)} #{e.url(id)}"
43
+ populate_id_map [reply] # allow resolving ids we could previously no resolve
44
+ Kennel.out.puts "Created #{e.class.api_resource} #{tracking_id(e.as_json)} #{e.class.url(id)}"
54
45
  end
55
46
 
56
- @update.each do |id, e|
57
- e.resolve_linked_tracking_ids!({}, force: true)
47
+ each_resolved @update do |id, e|
58
48
  @api.update e.class.api_resource, id, e.as_json
59
- Kennel.out.puts "Updated #{e.class.api_resource} #{tracking_id(e.as_json)} #{e.url(id)}"
49
+ Kennel.out.puts "Updated #{e.class.api_resource} #{tracking_id(e.as_json)} #{e.class.url(id)}"
60
50
  end
61
51
 
62
52
  @delete.each do |id, _, a|
@@ -67,6 +57,37 @@ module Kennel
67
57
 
68
58
  private
69
59
 
60
+ # loop over items until everything is resolved or crash when we get stuck
61
+ # this solves cases like composite monitors depending on each other or monitor->monitor slo->slo monitor chains
62
+ def each_resolved(list)
63
+ list = list.dup
64
+ loop do
65
+ return if list.empty?
66
+ list.reject! do |id, e|
67
+ if resolved?(e)
68
+ yield id, e
69
+ true
70
+ else
71
+ false
72
+ end
73
+ end ||
74
+ assert_resolved(list[0][1]) # resolve something or show a circular dependency error
75
+ end
76
+ end
77
+
78
+ # TODO: optimize by storing an instance variable if already resolved
79
+ def resolved?(e)
80
+ assert_resolved e
81
+ true
82
+ rescue ValidationError
83
+ false
84
+ end
85
+
86
+ # raises ValidationError when not resolved
87
+ def assert_resolved(e)
88
+ resolve_linked_tracking_ids! [e], force: true
89
+ end
90
+
70
91
  def noop?
71
92
  @create.empty? && @update.empty? && @delete.empty?
72
93
  end
@@ -74,9 +95,15 @@ module Kennel
74
95
  def calculate_diff
75
96
  @update = []
76
97
  @delete = []
98
+ @id_map = {}
77
99
 
78
100
  actual = Progress.progress("Downloading definitions") { download_definitions }
79
- resolve_linked_tracking_ids! from: actual, to: @expected
101
+
102
+ # resolve dependencies to avoid diff
103
+ populate_id_map actual
104
+ @expected.each { |e| @id_map[e.tracking_id] ||= :new }
105
+ resolve_linked_tracking_ids! @expected
106
+
80
107
  filter_by_project! actual
81
108
 
82
109
  Progress.progress "Diffing" do
@@ -89,10 +116,10 @@ module Kennel
89
116
  end
90
117
  end
91
118
 
92
- details_cache do |cache|
93
- # fill details of things we need to compare (only do this part in parallel for safety & balancing)
94
- Utils.parallel(items.select { |e, _| e && e.class::API_LIST_INCOMPLETE }) { |_, a| fill_details(a, cache) }
95
- end
119
+ # fill details of things we need to compare
120
+ detailed = Hash.new { |h, k| h[k] = [] }
121
+ items.each { |e, a| detailed[a[:api_resource]] << a if e }
122
+ detailed.each { |api_resource, actuals| @api.fill_details! api_resource, actuals }
96
123
 
97
124
  # pick out things to update or delete
98
125
  items.each do |e, a|
@@ -107,27 +134,11 @@ module Kennel
107
134
 
108
135
  ensure_all_ids_found
109
136
  @create = @expected.map { |e| [nil, e] }
110
- @create.sort_by! { |_, e| -DELETE_ORDER.index(e.class.api_resource) }
111
137
  end
112
138
 
113
139
  @delete.sort_by! { |_, _, a| DELETE_ORDER.index a.fetch(:api_resource) }
114
140
  end
115
141
 
116
- # Make diff work even though we cannot mass-fetch definitions
117
- def fill_details(a, cache)
118
- resource = a.fetch(:api_resource)
119
- args = [resource, a.fetch(:id)]
120
- full = cache.fetch(args, a[:modified] || a.fetch(:modified_at)) do
121
- @api.show(*args)
122
- end
123
- a.merge!(full)
124
- end
125
-
126
- def details_cache(&block)
127
- cache = FileCache.new CACHE_FILE, Kennel::VERSION
128
- cache.open(&block)
129
- end
130
-
131
142
  def download_definitions
132
143
  Utils.parallel(Models::Record.subclasses.map(&:api_resource)) do |api_resource|
133
144
  results = @api.list(api_resource, with_downtimes: false) # lookup monitors without adding unnecessary downtime information
@@ -214,10 +225,12 @@ module Kennel
214
225
  end
215
226
  end
216
227
 
217
- def resolve_linked_tracking_ids!(from:, to:)
218
- map = from.each_with_object({}) { |a, lookup| lookup[tracking_id(a)] = a.fetch(:id) }
219
- to.each { |e| map[e.tracking_id] ||= :new }
220
- to.each { |e| e.resolve_linked_tracking_ids!(map, force: false) }
228
+ def populate_id_map(actual)
229
+ actual.each { |a| @id_map[tracking_id(a)] = a.fetch(:id) }
230
+ end
231
+
232
+ def resolve_linked_tracking_ids!(list, force: false)
233
+ list.each { |e| e.resolve_linked_tracking_ids!(@id_map, force: force) }
221
234
  end
222
235
 
223
236
  def filter_by_project!(definitions)
data/lib/kennel/tasks.rb CHANGED
@@ -130,15 +130,41 @@ namespace :kennel do
130
130
  if type = ENV["TYPE"]
131
131
  [type]
132
132
  else
133
- Kennel::Models::Record.subclasses.map(&:api_resource)
133
+ Kennel::Models::Record.api_resource_map.keys
134
134
  end
135
+ api = Kennel.send(:api)
136
+ list = nil
137
+
135
138
  resources.each do |resource|
136
- Kennel.send(:api).list(resource).each do |r|
139
+ Kennel::Progress.progress("Downloading #{resource}") do
140
+ list = api.list(resource)
141
+ api.fill_details!(resource, list)
142
+ end
143
+ list.each do |r|
144
+ r[:api_resource] = resource
137
145
  Kennel.out.puts JSON.pretty_generate(r)
138
146
  end
139
147
  end
140
148
  end
141
149
 
150
+ desc "Find items from dump by pattern DUMP= PATTERN= [URLS=true]"
151
+ task dump_grep: :environment do
152
+ file = ENV.fetch("DUMP")
153
+ pattern = Regexp.new ENV.fetch("PATTERN")
154
+ items = File.read(file).gsub("}\n{", "}--SPLIT--{").split("--SPLIT--")
155
+ models = Kennel::Models::Record.api_resource_map
156
+ found = items.grep(pattern)
157
+ exit 1 if found.empty?
158
+ found.each do |resource|
159
+ if ENV["URLS"]
160
+ parsed = JSON.parse(resource)
161
+ Kennel.out.puts models[parsed.fetch("api_resource")].url(parsed.fetch("id"))
162
+ else
163
+ Kennel.out.puts resource
164
+ end
165
+ end
166
+ end
167
+
142
168
  task :environment do
143
169
  require "kennel"
144
170
  gem "dotenv"
data/lib/kennel/utils.rb CHANGED
@@ -23,6 +23,11 @@ module Kennel
23
23
  .downcase
24
24
  end
25
25
 
26
+ # for child projects, not used internally
27
+ def title_case(string)
28
+ string.split(/[\s_]/).map(&:capitalize) * " "
29
+ end
30
+
26
31
  # simplified version of https://apidock.com/rails/ActiveSupport/Inflector/parameterize
27
32
  def parameterize(string)
28
33
  string
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.80.0"
3
+ VERSION = "1.83.0"
4
4
  end
data/template/Readme.md CHANGED
@@ -194,15 +194,20 @@ removing the `id` will cause kennel to create a new resource in datadog.
194
194
  Some validations might be too strict for your usecase or just wrong, please [open an issue](https://github.com/grosser/kennel/issues) and
195
195
  to unblock use the `validate: -> { false }` option.
196
196
 
197
- ### Linking with kennel_ids
197
+ ### Linking resources with kennel_id
198
198
 
199
- To link to existing monitors via their kennel_id `projects kennel_id` + `:` + `monitors kennel id`
199
+ Link resources with their kennel_id in the format `project kennel_id` + `:` + `resource kennel_id`,
200
+ this should be used to create dependent resources like monitor + slos,
201
+ so they can be created in a single update and can be re-created if any of them is deleted.
200
202
 
201
- - Screens `uptime` widgets can use `monitor: {id: "foo:bar"}`
202
- - Screens `alert_graph` widgets can use `alert_id: "foo:bar"`
203
- - Monitors `composite` can use `query: -> { "%{foo:bar} || %{foo:baz}" }`
204
- - Monitors `slo alert` can use `query: -> { "error_budget(\"%{foo:bar}\").over(\"7d\") > 123.0" }`
205
- - Slos can use `monitor_ids: -> ["foo:bar"]`
203
+ |Resource|Type|Syntax|
204
+ |---|---|---|
205
+ |Dashboard|uptime|`monitor: {id: "foo:bar"}`|
206
+ |Dashboard|alert_graph|`alert_id: "foo:bar"`|
207
+ |Dashboard|slo|`slo_id: "foo:bar"`|
208
+ |Monitor|composite|`query: -> { "%{foo:bar} && %{foo:baz}" }`|
209
+ |Monitor|slo alert|`query: -> { "error_budget(\"%{foo:bar}\").over(\"7d\") > 123.0" }`|
210
+ |Slo|monitor|`monitor_ids: -> ["foo:bar"]`|
206
211
 
207
212
  ### Debugging changes locally
208
213
 
@@ -250,9 +255,19 @@ Run `rake kennel:alerts TAG=service:my-service` to see all un-muted alerts for a
250
255
 
251
256
  ### Grepping through all of datadog
252
257
 
253
- `rake kennel:dump`
258
+ ```Bash
259
+ rake kennel:dump > tmp/dump
260
+ cat tmp/dump | grep foo
261
+ ```
254
262
  focus on a single type: `TYPE=monitors`
255
263
 
264
+ Show full resources or just their urls by pattern:
265
+ ```Bash
266
+ rake kennel:dump_grep DUMP=tmp/dump PATTERN=foo URLS=true
267
+ https://foo.datadog.com/dasboard/123
268
+ https://foo.datadog.com/monitor/123
269
+ ```
270
+
256
271
  ### Find all monitors with No-Data
257
272
 
258
273
  `rake kennel:nodata TAG=team:foo`
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kennel
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.80.0
4
+ version: 1.83.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-07 00:00:00.000000000 Z
11
+ date: 2021-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday