kennel 1.80.0 → 1.83.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 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