kennel 2.2.1 → 2.22.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/Readme.md +75 -4
- data/lib/kennel/api.rb +103 -42
- data/lib/kennel/filter.rb +7 -11
- data/lib/kennel/importer.rb +14 -1
- data/lib/kennel/models/dashboard.rb +4 -3
- data/lib/kennel/models/monitor.rb +122 -55
- data/lib/kennel/models/project.rb +20 -4
- data/lib/kennel/models/record.rb +2 -6
- data/lib/kennel/models/slo.rb +41 -7
- data/lib/kennel/models/synthetic_test.rb +3 -0
- data/lib/kennel/models/team.rb +5 -3
- data/lib/kennel/parts_serializer.rb +30 -11
- data/lib/kennel/projects_provider.rb +3 -2
- data/lib/kennel/subclass_tracking.rb +8 -0
- data/lib/kennel/syncer/matched_expected.rb +7 -1
- data/lib/kennel/syncer/plan_printer.rb +1 -1
- data/lib/kennel/syncer/resolver.rb +1 -1
- data/lib/kennel/syncer.rb +25 -27
- data/lib/kennel/tasks/dump.rb +54 -0
- data/lib/kennel/tasks/import.rb +17 -0
- data/lib/kennel/tasks/nodata.rb +53 -0
- data/lib/kennel/tasks/tracking_id.rb +14 -0
- data/lib/kennel/tasks/validate_mentions.rb +41 -0
- data/lib/kennel/tasks/validate_plan.rb +114 -0
- data/lib/kennel/tasks.rb +9 -159
- data/lib/kennel/utils.rb +3 -2
- data/lib/kennel/version.rb +1 -1
- data/lib/kennel.rb +13 -7
- data/template/Readme.md +66 -4
- metadata +22 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b931b9c2d75d6e0d69967e3088795e72d8a5f3f558ff1dd1b6de03efde7aabf8
|
|
4
|
+
data.tar.gz: '081dde24122f351f92c7dc8f16e7877ec3dcd1f7226bd1e4fc126abd3044798d'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ffd01589e499c34d72dd477f5e3e00369582c42cacbf7eae19493e791b84ecbac42c48c5bf34e3d8c8a6245f555d29f62e3cb879cc2e24892b555766f9b5ffcc
|
|
7
|
+
data.tar.gz: 7a95114123013fd90a64993f86f217b5f4379e0d3dc6b19d876d2ac42f404b4907ef35b2396c811c4291838ca8183073523090bb2d89882dbbffa6972b40bb38
|
data/Readme.md
CHANGED
|
@@ -206,6 +206,7 @@ end
|
|
|
206
206
|
class MyProject < Kennel::Models::Project
|
|
207
207
|
defaults(
|
|
208
208
|
team: -> { Teams::MyTeam.new }, # use existing team or create new one in teams/
|
|
209
|
+
# kennel_id: -> { "my_project" } # Custom kennel_id (default is snake_cased class name)
|
|
209
210
|
parts: -> {
|
|
210
211
|
[
|
|
211
212
|
Kennel::Models::Monitor.new(
|
|
@@ -249,6 +250,7 @@ Remove the code that created the resource. The next update will delete it (see a
|
|
|
249
250
|
- go to [datadog dashboard UI](https://app.datadoghq.com/dashboard/lists) and click on _New Dashboard_ to find a dashboard
|
|
250
251
|
- run `URL='https://app.datadoghq.com/dashboard/bet-foo-bar' bundle exec rake kennel:import` and copy the output
|
|
251
252
|
- find or create a project in `projects/`
|
|
253
|
+
- tags: only `team:` tags are submitted to datadog since nothing else is supported
|
|
252
254
|
- add a dashboard to `parts: [` list, for example:
|
|
253
255
|
```Ruby
|
|
254
256
|
class MyProject < Kennel::Models::Project
|
|
@@ -264,8 +266,9 @@ Remove the code that created the resource. The next update will delete it (see a
|
|
|
264
266
|
template_variables: -> { ["environment"] }, # see https://docs.datadoghq.com/api/?lang=ruby#timeboards
|
|
265
267
|
kennel_id: -> { "overview-dashboard" }, # make up a unique name
|
|
266
268
|
layout_type: -> { "ordered" },
|
|
269
|
+
widgets: -> { "... raw widget definitions, most flexible ..." },
|
|
267
270
|
definitions: -> {
|
|
268
|
-
[ #
|
|
271
|
+
[ # each element is a graph in the dashboard, alternatively a hash for complete control just like in `widgets`
|
|
269
272
|
[
|
|
270
273
|
# title, viz, type, query, edit an existing graph and see the json definition
|
|
271
274
|
"Graph name", "timeseries", "area", "sum:mystats.foobar{$environment}"
|
|
@@ -274,7 +277,7 @@ Remove the code that created the resource. The next update will delete it (see a
|
|
|
274
277
|
# queries can be an Array as well, this will generate multiple requests
|
|
275
278
|
# for a single graph
|
|
276
279
|
"Graph name", "timeseries", "area", ["sum:mystats.foobar{$environment}", "sum:mystats.success{$environment}"],
|
|
277
|
-
# add events too ...
|
|
280
|
+
# add events too ... (also supports `:markers` and `:precision`)
|
|
278
281
|
events: [{q: "tags:foobar,deploy", tags_execution: "and"}]
|
|
279
282
|
]
|
|
280
283
|
]
|
|
@@ -286,6 +289,55 @@ Remove the code that created the resource. The next update will delete it (see a
|
|
|
286
289
|
end
|
|
287
290
|
```
|
|
288
291
|
|
|
292
|
+
### Adding a new synthetic test
|
|
293
|
+
- go to [datadog synthetic tests UI](https://app.datadoghq.com/synthetics/tests) and click on _New_ to create a test
|
|
294
|
+
- see below
|
|
295
|
+
|
|
296
|
+
### Updating an existing synthetic test
|
|
297
|
+
- go to [datadog synthetic tests UI](https://app.datadoghq.com/synthetics/tests) to find a test
|
|
298
|
+
- run `URL='https://app.datadoghq.com/synthetics/details/abc-def-ghi' bundle exec rake kennel:import` and copy the output
|
|
299
|
+
- find or create a project in `projects/`
|
|
300
|
+
- add a synthetic test to `parts: [` list, for example:
|
|
301
|
+
```Ruby
|
|
302
|
+
class MyProject < Kennel::Models::Project
|
|
303
|
+
defaults(
|
|
304
|
+
team: -> { Teams::MyTeam.new },
|
|
305
|
+
parts: -> {
|
|
306
|
+
[
|
|
307
|
+
Kennel::Models::SyntheticTest.new(
|
|
308
|
+
self,
|
|
309
|
+
id: -> { "abc-def-ghi" }, # id from datadog url, not needed when creating a new test
|
|
310
|
+
kennel_id: -> { "my-api-test" },
|
|
311
|
+
name: -> { "My API Test" },
|
|
312
|
+
type: -> { "api" },
|
|
313
|
+
subtype: -> { "http" },
|
|
314
|
+
locations: -> { :all }, # use all locations, or specify: ["aws:us-east-1", "aws:eu-west-1"]
|
|
315
|
+
message: -> {
|
|
316
|
+
<<~TEXT
|
|
317
|
+
API check failed!
|
|
318
|
+
#{super()}
|
|
319
|
+
TEXT
|
|
320
|
+
},
|
|
321
|
+
options: -> {
|
|
322
|
+
{
|
|
323
|
+
tick_every: 60,
|
|
324
|
+
min_failure_duration: 0,
|
|
325
|
+
min_location_failed: 1
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
config: -> {
|
|
329
|
+
{
|
|
330
|
+
assertions: [{ type: "statusCode", operator: "is", target: 200 }],
|
|
331
|
+
request: { method: "GET", url: "https://example.com/health" }
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
)
|
|
335
|
+
]
|
|
336
|
+
}
|
|
337
|
+
)
|
|
338
|
+
end
|
|
339
|
+
```
|
|
340
|
+
|
|
289
341
|
### Updating existing resources with id
|
|
290
342
|
Setting `id` makes kennel take over a manually created datadog resource.
|
|
291
343
|
When manually creating to import, it is best to remove the `id` and delete the manually created resource.
|
|
@@ -327,10 +379,11 @@ module ProjectA
|
|
|
327
379
|
- Use `TRACKING_ID=<project-kennel_id>:<resource-kennel_id>` for single resource:
|
|
328
380
|
|
|
329
381
|
Use the project kennel_id and the resources kennel_id, for example `class ProjectA` and `FooAlert` would give `project_a:foo_alert`.
|
|
382
|
+
Alternatively use the path of the generated file `TRACKING_ID=generated/project_a/foo_alert.json`
|
|
330
383
|
|
|
331
384
|
### Skipping validations
|
|
332
385
|
Some validations might be too strict for your usecase or just wrong, please [open an issue](https://github.com/grosser/kennel/issues) and
|
|
333
|
-
to unblock use
|
|
386
|
+
to unblock use `ignored_errors: [:name_of_the_error]`.
|
|
334
387
|
|
|
335
388
|
### Linking resources with kennel_id
|
|
336
389
|
Link resources with their kennel_id in the format `project kennel_id` + `:` + `resource kennel_id`,
|
|
@@ -339,7 +392,7 @@ so they can be created in a single update and can be re-created if any of them i
|
|
|
339
392
|
|
|
340
393
|
|Resource|Type|Syntax|
|
|
341
394
|
|---|---|---|
|
|
342
|
-
|Dashboard|uptime|`
|
|
395
|
+
|Dashboard|uptime|`monitor_ids: ["foo:bar", "foo:baz"]`|
|
|
343
396
|
|Dashboard|alert_graph|`alert_id: "foo:bar"`|
|
|
344
397
|
|Dashboard|slo|`slo_id: "foo:bar"`|
|
|
345
398
|
|Dashboard|timeseries|`queries: [{ data_source: "slo", slo_id: "foo:bar" }]`|
|
|
@@ -388,6 +441,12 @@ Run `rake kennel:alerts TAG=service:my-service` to see all un-muted alerts for a
|
|
|
388
441
|
### Validating mentions work
|
|
389
442
|
`rake kennel:validate_mentions` should run as part of CI
|
|
390
443
|
|
|
444
|
+
Use `KNOWN=foo@bar.com,baz@bar.com` to exempt mentions that are not returned by the API.
|
|
445
|
+
Use `KNOWN_RANDOM=@sns-foo,@sns-bar` to ignore for example SNS handles that are randomly invalid in the API.
|
|
446
|
+
|
|
447
|
+
### Validating planned changes
|
|
448
|
+
`rake kennel:validate_plan` validates planned monitor and dashboard changes against the Datadog API.
|
|
449
|
+
|
|
391
450
|
### Grepping through all of datadog
|
|
392
451
|
```Bash
|
|
393
452
|
rake kennel:dump > tmp/dump
|
|
@@ -405,6 +464,9 @@ https://foo.datadog.com/monitor/123
|
|
|
405
464
|
### Find all monitors with No-Data
|
|
406
465
|
`rake kennel:nodata TAG=team:foo`
|
|
407
466
|
|
|
467
|
+
- `FORMAT=json` to output as JSON with tracking IDs
|
|
468
|
+
- `THRESHOLD_DAYS=N` to filter to monitors with N+ days in no-data
|
|
469
|
+
|
|
408
470
|
### Finding the tracking id of a resource
|
|
409
471
|
|
|
410
472
|
When trying to link resources together, this avoids having to go through datadog UI.
|
|
@@ -421,6 +483,15 @@ rake kennel:tracking_id ID=123 RESOURCE=monitor
|
|
|
421
483
|
- Setting `FORCE_GET_CACHE=true` will cache all get requests, which makes benchmarking improvements more reliable.
|
|
422
484
|
- Setting `STORE=false` will make `rake plan` not update the files on disk and save a bit of time
|
|
423
485
|
|
|
486
|
+
### Other Environment Variables
|
|
487
|
+
| Variable | Description | Default |
|
|
488
|
+
|----------|-------------|---------|
|
|
489
|
+
| `KENNEL_MARKER_TEXT` | Custom marker text to namespace multiple Kennel instances in the same Datadog account. Each instance will only manage resources with its marker. | `Managed by kennel` |
|
|
490
|
+
| `KENNEL_API_CACHE_FILE` | Path to the API cache file for dashboard details. | `tmp/cache/details` |
|
|
491
|
+
| `KENNEL_NO_GENERATE` | When set, skip generating files during `plan` or `update_datadog`. Useful when generated files are already up to date. | - |
|
|
492
|
+
| `NO_IGNORED_ERRORS` | When set, show all validation errors including ones suppressed via `ignored_errors`. | - |
|
|
493
|
+
| `SHOW_UNCACHED_FILL_DETAILS` | When set, print the number of uncached dashboard `show` requests made to fill details during diffing. | - |
|
|
494
|
+
|
|
424
495
|
### Integration testing
|
|
425
496
|
```Bash
|
|
426
497
|
rake play
|
data/lib/kennel/api.rb
CHANGED
|
@@ -5,8 +5,6 @@ module Kennel
|
|
|
5
5
|
class Api
|
|
6
6
|
CACHE_FILE = ENV.fetch("KENNEL_API_CACHE_FILE", "tmp/cache/details")
|
|
7
7
|
|
|
8
|
-
RateLimitParams = Data.define(:limit, :period, :remaining, :reset, :name)
|
|
9
|
-
|
|
10
8
|
def self.with_tracking(api_resource, reply)
|
|
11
9
|
klass = Models::Record.api_resource_map[api_resource]
|
|
12
10
|
return reply unless klass # do not blow up on unknown models
|
|
@@ -21,11 +19,12 @@ module Kennel
|
|
|
21
19
|
@app_key = app_key || ENV.fetch("DATADOG_APP_KEY")
|
|
22
20
|
@api_key = api_key || ENV.fetch("DATADOG_API_KEY")
|
|
23
21
|
url = Utils.path_to_url("")
|
|
24
|
-
@client = Faraday.new(url: url)
|
|
22
|
+
@client = Faraday.new(url: url)
|
|
25
23
|
end
|
|
26
24
|
|
|
27
|
-
def show(api_resource, id, params = {})
|
|
28
|
-
response = request :get, "/api/v1/#{api_resource}/#{id}", params: params
|
|
25
|
+
def show(api_resource, id, params = {}, ignore_404: false)
|
|
26
|
+
response = request :get, "/api/v1/#{api_resource}/#{id}", params: params, ignore_404: ignore_404
|
|
27
|
+
return if response.nil? # 404 that was ignored
|
|
29
28
|
response = response.fetch(:data) if api_resource == "slo"
|
|
30
29
|
response[:id] = response.delete(:public_id) if api_resource == "synthetics/tests"
|
|
31
30
|
self.class.with_tracking(api_resource, response)
|
|
@@ -56,6 +55,8 @@ module Kennel
|
|
|
56
55
|
end
|
|
57
56
|
|
|
58
57
|
def update(api_resource, id, attributes)
|
|
58
|
+
restore_widget_ids(id, attributes) if api_resource == "dashboard"
|
|
59
|
+
|
|
59
60
|
response = request :put, "/api/v1/#{api_resource}/#{id}", body: attributes
|
|
60
61
|
response[:id] = response.delete(:public_id) if api_resource == "synthetics/tests"
|
|
61
62
|
self.class.with_tracking(api_resource, response)
|
|
@@ -73,14 +74,47 @@ module Kennel
|
|
|
73
74
|
end
|
|
74
75
|
end
|
|
75
76
|
|
|
77
|
+
# fill the resource with the full response from the `show` if `list` does not return it
|
|
76
78
|
def fill_details!(api_resource, list)
|
|
77
79
|
details_cache do |cache|
|
|
78
|
-
Utils.parallel(list) { |a| fill_detail!(api_resource, a, cache) }
|
|
80
|
+
results = Utils.parallel(list) { |a| fill_detail!(api_resource, a, cache) }
|
|
81
|
+
|
|
82
|
+
# drop resources that were not found (race condition between listing and showing)
|
|
83
|
+
list.select!.with_index { |_, i| results[i][1] }
|
|
84
|
+
|
|
85
|
+
results.count { |uncached, _| uncached }
|
|
79
86
|
end
|
|
80
87
|
end
|
|
81
88
|
|
|
82
89
|
private
|
|
83
90
|
|
|
91
|
+
# keep widget ids stable so stored dashboard urls that point at a widget do not break on every update
|
|
92
|
+
# widget ids are dropped when diffing since they change on every update, but that also changes them in datadog
|
|
93
|
+
# and breaks stored urls that point at a specific widget, so restore the previous ids by matching widget titles
|
|
94
|
+
def restore_widget_ids(id, attributes)
|
|
95
|
+
return unless (widgets = attributes[:widgets])
|
|
96
|
+
return unless (current = show("dashboard", id, {}, ignore_404: true))
|
|
97
|
+
|
|
98
|
+
ids = {}
|
|
99
|
+
each_widget_with_key(current[:widgets]) { |key, widget| ids[key] = widget[:id] }
|
|
100
|
+
each_widget_with_key(widgets) do |key, widget|
|
|
101
|
+
next unless (id = ids[key])
|
|
102
|
+
widget[:id] ||= id # do not set nil, that breaks updates
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# yield each widget with a key built from its (optional) group title and its own title
|
|
107
|
+
def each_widget_with_key(widgets, prefix: nil, &block)
|
|
108
|
+
widgets.each do |widget|
|
|
109
|
+
title = widget.dig(:definition, :title)
|
|
110
|
+
key = [prefix, title].compact.join("-")
|
|
111
|
+
yield key, widget
|
|
112
|
+
if (nested = widget.dig(:definition, :widgets))
|
|
113
|
+
each_widget_with_key(nested, prefix: title, &block)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
84
118
|
def with_pagination(enabled, params)
|
|
85
119
|
return yield params unless enabled
|
|
86
120
|
raise ArgumentError if params[:limit] || params[:offset]
|
|
@@ -97,10 +131,16 @@ module Kennel
|
|
|
97
131
|
end
|
|
98
132
|
|
|
99
133
|
# Make diff work even though we cannot mass-fetch definitions
|
|
134
|
+
# @return [uncached, found]
|
|
100
135
|
def fill_detail!(api_resource, a, cache)
|
|
136
|
+
uncached = false
|
|
101
137
|
args = [api_resource, a.fetch(:id)]
|
|
102
|
-
full = cache.fetch(args, a.fetch(:modified_at))
|
|
103
|
-
|
|
138
|
+
full = cache.fetch(args, a.fetch(:modified_at)) do
|
|
139
|
+
uncached = true
|
|
140
|
+
show(*args, ignore_404: true)
|
|
141
|
+
end
|
|
142
|
+
a.merge!(full) if full
|
|
143
|
+
[uncached, !!full]
|
|
104
144
|
end
|
|
105
145
|
|
|
106
146
|
def details_cache(&block)
|
|
@@ -108,44 +148,23 @@ module Kennel
|
|
|
108
148
|
cache.open(&block)
|
|
109
149
|
end
|
|
110
150
|
|
|
111
|
-
def request(method, path, body: nil, params: {}, ignore_404: false)
|
|
151
|
+
def request(method, path, body: nil, params: {}, ignore_404: false, tries: 5)
|
|
112
152
|
path = "#{path}?#{Faraday::FlatParamsEncoder.encode(params)}" if params.any?
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
request.headers["DD-API-KEY"] = @api_key
|
|
123
|
-
request.headers["DD-APPLICATION-KEY"] = @app_key
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
rate_limit = RateLimitParams.new(
|
|
128
|
-
limit: response.headers["x-ratelimit-limit"],
|
|
129
|
-
period: response.headers["x-ratelimit-period"],
|
|
130
|
-
remaining: response.headers["x-ratelimit-remaining"],
|
|
131
|
-
reset: response.headers["x-ratelimit-reset"],
|
|
132
|
-
name: response.headers["x-ratelimit-name"]
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
if response.status == 429
|
|
136
|
-
message = "Datadog rate limit #{rate_limit.name.inspect} hit"
|
|
137
|
-
message += " (#{rate_limit.limit} requests per #{rate_limit.period} seconds)"
|
|
138
|
-
message += "; sleeping #{rate_limit.reset} seconds before trying again"
|
|
139
|
-
Kennel.err.puts message
|
|
140
|
-
sleep rate_limit.reset.to_f
|
|
141
|
-
redo
|
|
153
|
+
cached = (ENV["FORCE_GET_CACHE"] && method == :get)
|
|
154
|
+
|
|
155
|
+
with_cache cached, path do
|
|
156
|
+
response = request_with_retries(path, tries) do
|
|
157
|
+
@client.send(method, path) do |request|
|
|
158
|
+
request.body = JSON.generate(body) if body
|
|
159
|
+
request.headers["Content-type"] = "application/json"
|
|
160
|
+
request.headers["DD-API-KEY"] = @api_key
|
|
161
|
+
request.headers["DD-APPLICATION-KEY"] = @app_key
|
|
142
162
|
end
|
|
143
|
-
|
|
144
|
-
break if i == tries - 1 || method != :get || response.status < 500
|
|
145
|
-
Kennel.err.puts "Retrying on server error #{response.status} for #{path}"
|
|
146
163
|
end
|
|
147
164
|
|
|
148
|
-
if
|
|
165
|
+
next if response.status == 404 && ignore_404
|
|
166
|
+
|
|
167
|
+
unless response.success?
|
|
149
168
|
message = "Error #{response.status} during #{method.upcase} #{path}\n"
|
|
150
169
|
message << "request:\n#{JSON.pretty_generate(body)}\nresponse:\n" if body
|
|
151
170
|
message << response.body.encode(message.encoding, invalid: :replace, undef: :replace)
|
|
@@ -160,6 +179,48 @@ module Kennel
|
|
|
160
179
|
end
|
|
161
180
|
end
|
|
162
181
|
|
|
182
|
+
# retry on rate-limits and server errors, giving up after `tries` attempts
|
|
183
|
+
def request_with_retries(path, tries, &block)
|
|
184
|
+
raise ArgumentError, "tries must be > 0" if tries < 1
|
|
185
|
+
response = nil
|
|
186
|
+
tries.times do |i|
|
|
187
|
+
response = Utils.retry Faraday::ConnectionFailed, Faraday::TimeoutError, times: 2, &block
|
|
188
|
+
|
|
189
|
+
# we do not count 429s into the "tries", tries are only for real errors
|
|
190
|
+
# so this could loop forever if datadog consistently 429s
|
|
191
|
+
if response.status == 429
|
|
192
|
+
sleep_until_rate_limit_resets(response)
|
|
193
|
+
redo
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
last_try = (i == tries - 1)
|
|
197
|
+
break if last_try || response.status < 500
|
|
198
|
+
Kennel.err.puts "Retrying on server error #{response.status} for #{path}"
|
|
199
|
+
sleep retry_backoff_time(i)
|
|
200
|
+
end
|
|
201
|
+
response
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def sleep_until_rate_limit_resets(response)
|
|
205
|
+
limit = response.headers["x-ratelimit-limit"]
|
|
206
|
+
period = response.headers["x-ratelimit-period"]
|
|
207
|
+
reset = response.headers["x-ratelimit-reset"]
|
|
208
|
+
name = response.headers["x-ratelimit-name"]
|
|
209
|
+
|
|
210
|
+
message = "Datadog rate limit #{name.inspect} hit"
|
|
211
|
+
message += " (#{limit} requests per #{period} seconds)"
|
|
212
|
+
message += "; sleeping #{reset} seconds before trying again"
|
|
213
|
+
|
|
214
|
+
Kennel.err.puts message
|
|
215
|
+
sleep reset.to_f
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# 0:0.1s - 4:2s
|
|
219
|
+
def retry_backoff_time(attempt)
|
|
220
|
+
base = 2**attempt
|
|
221
|
+
0.1 * base * (0.5 + (rand / 2.0))
|
|
222
|
+
end
|
|
223
|
+
|
|
163
224
|
# allow caching all requests to speedup/benchmark logic that includes repeated requests
|
|
164
225
|
def with_cache(enabled, key)
|
|
165
226
|
return yield unless enabled
|
data/lib/kennel/filter.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Kennel
|
|
4
4
|
class Filter
|
|
5
|
+
ID_SEPARATOR = ":"
|
|
5
6
|
attr_reader :project_filter
|
|
6
7
|
|
|
7
8
|
def initialize
|
|
@@ -22,23 +23,18 @@ module Kennel
|
|
|
22
23
|
!project_filter.nil?
|
|
23
24
|
end
|
|
24
25
|
|
|
25
|
-
def
|
|
26
|
+
def filters_project_id?(project_id)
|
|
26
27
|
!filtering? || project_filter.include?(project_id)
|
|
27
28
|
end
|
|
28
29
|
|
|
29
|
-
def
|
|
30
|
+
def filters_tracking_id?(tracking_id)
|
|
30
31
|
return true unless filtering?
|
|
31
32
|
return tracking_id_filter.include?(tracking_id) if tracking_id_filter
|
|
32
33
|
|
|
33
|
-
project_id = tracking_id.split(
|
|
34
|
+
project_id = tracking_id.split(ID_SEPARATOR, 2).first
|
|
34
35
|
project_filter.include?(project_id)
|
|
35
36
|
end
|
|
36
37
|
|
|
37
|
-
def tracking_id_for_path(tracking_id)
|
|
38
|
-
return tracking_id unless tracking_id.end_with?(".json")
|
|
39
|
-
tracking_id.sub("generated/", "").sub(".json", "").sub("/", ":")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
38
|
private
|
|
43
39
|
|
|
44
40
|
attr_reader :tracking_id_filter
|
|
@@ -46,7 +42,7 @@ module Kennel
|
|
|
46
42
|
# needs to be called after read_tracking_id_filter_from_env
|
|
47
43
|
def read_project_filter_from_env
|
|
48
44
|
project_names = ENV["PROJECT"]&.split(",")&.sort&.uniq
|
|
49
|
-
tracking_project_names = tracking_id_filter&.map { |id| id.split(
|
|
45
|
+
tracking_project_names = tracking_id_filter&.map { |id| id.split(ID_SEPARATOR, 2).first }&.sort&.uniq
|
|
50
46
|
if project_names && tracking_project_names && project_names != tracking_project_names
|
|
51
47
|
# avoid everything being filtered out
|
|
52
48
|
raise "do not set a different PROJECT= when using TRACKING_ID="
|
|
@@ -57,8 +53,8 @@ module Kennel
|
|
|
57
53
|
def read_tracking_id_filter_from_env
|
|
58
54
|
return unless (tracking_id = ENV["TRACKING_ID"])
|
|
59
55
|
tracking_id.split(",").map do |id|
|
|
60
|
-
# allow
|
|
61
|
-
tracking_id_for_path(id)
|
|
56
|
+
# allow using the generated/ path from `git diff` to update objects without manually converting
|
|
57
|
+
id.include?(ID_SEPARATOR) ? id : PartsSerializer.tracking_id_for_path(id)
|
|
62
58
|
end.sort.uniq
|
|
63
59
|
end
|
|
64
60
|
|
data/lib/kennel/importer.rb
CHANGED
|
@@ -21,6 +21,12 @@ module Kennel
|
|
|
21
21
|
data = @api.show(model.api_resource, id)
|
|
22
22
|
|
|
23
23
|
id = data.fetch(:id) # keep native value
|
|
24
|
+
if resource == "slo"
|
|
25
|
+
# only set primary if needed to reduce clutter
|
|
26
|
+
if data[:thresholds] && data[:thresholds].min_by { |t| t[:timeframe].to_i }[:timeframe] != data[:timeframe]
|
|
27
|
+
data[:primary] = data[:timeframe]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
24
30
|
model.normalize({}, data) # removes id
|
|
25
31
|
data[:id] = id
|
|
26
32
|
|
|
@@ -80,7 +86,14 @@ module Kennel
|
|
|
80
86
|
end
|
|
81
87
|
when "synthetics/tests"
|
|
82
88
|
data[:locations] = :all if data[:locations].sort == Kennel::Models::SyntheticTest::LOCATIONS.sort
|
|
83
|
-
|
|
89
|
+
when "slo"
|
|
90
|
+
# sli_specification it is only used by datadog if the user switched to "Bad Events"
|
|
91
|
+
# otherwise user would be trying to set sli_specification but the slo does not change since query is used
|
|
92
|
+
if data.key?(:query) && data.key?(:sli_specification)
|
|
93
|
+
delete = (data.dig(:sli_specification, :count, :bad_events_formula) ? :query : :sli_specification)
|
|
94
|
+
data.delete delete
|
|
95
|
+
end
|
|
96
|
+
else # uncovered
|
|
84
97
|
# noop
|
|
85
98
|
end
|
|
86
99
|
|
|
@@ -208,9 +208,10 @@ module Kennel
|
|
|
208
208
|
end
|
|
209
209
|
end
|
|
210
210
|
|
|
211
|
-
def
|
|
212
|
-
|
|
213
|
-
|
|
211
|
+
def allowed_update_error(actual)
|
|
212
|
+
actual_type = actual[:layout_type]
|
|
213
|
+
return if actual_type == layout_type
|
|
214
|
+
"cannot update layout_type from #{actual_type} to #{layout_type}"
|
|
214
215
|
end
|
|
215
216
|
|
|
216
217
|
private
|