kennel 1.79.0 → 1.82.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Readme.md +13 -7
- data/lib/kennel.rb +36 -8
- data/lib/kennel/api.rb +22 -0
- data/lib/kennel/models/dashboard.rb +21 -18
- data/lib/kennel/models/monitor.rb +5 -3
- data/lib/kennel/models/record.rb +8 -10
- data/lib/kennel/models/slo.rb +1 -1
- data/lib/kennel/syncer.rb +51 -38
- data/lib/kennel/tasks.rb +8 -1
- data/lib/kennel/utils.rb +5 -0
- data/lib/kennel/version.rb +1 -1
- data/template/Readme.md +13 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97ae92e1ab137731096bbedde401059fc2b8d411ff667df489056f05a3cf42aa
|
4
|
+
data.tar.gz: c48683c4888e16da817885a44a876638086c32e8e370aaf16553c31b7fde0136
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89f7a44c794130ecd4a4238ace5846d050412fd923cc5ae358cb469370c3dd380569bffe991b2251bc80baf4e183bd765591295b043ffc51fd88c5593b723f21
|
7
|
+
data.tar.gz: 4a9eece67d3f0e0cf8dd56a21440ce585397d531c4aa82d1d4c3d43db08169b57dd1e982ba74e68326ae61ed8278ac4d5fed7eefe8f6979dcf5a3c3a3e98ceb0
|
data/Readme.md
CHANGED
@@ -84,7 +84,7 @@ end
|
|
84
84
|
- `cp .env.example .env`
|
85
85
|
- open [Datadog API Settings](https://app.datadoghq.com/account/settings#api)
|
86
86
|
- create a `API Key` or get an existing one from an admin, then add it to `.env` as `DATADOG_API_KEY`
|
87
|
-
-
|
87
|
+
- open [Datadog API Settings](https://app.datadoghq.com/access/application-keys) and create a new key, then add it to `.env` as `DATADOG_APP_KEY=`
|
88
88
|
- change the `DATADOG_SUBDOMAIN=app` in `.env` to your companies subdomain if you have one
|
89
89
|
- verify it works by running `rake plan`, it might show some diff, but should not crash
|
90
90
|
-->
|
@@ -212,14 +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
|
215
|
+
### Linking resources with kennel_id
|
216
216
|
|
217
|
-
|
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
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
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"]`|
|
223
229
|
|
224
230
|
### Debugging changes locally
|
225
231
|
|
data/lib/kennel.rb
CHANGED
@@ -39,12 +39,7 @@ module Kennel
|
|
39
39
|
attr_accessor :out, :err
|
40
40
|
|
41
41
|
def generate
|
42
|
-
|
43
|
-
generated.each do |part|
|
44
|
-
path = "generated/#{part.tracking_id.sub(":", "/")}.json"
|
45
|
-
FileUtils.mkdir_p(File.dirname(path))
|
46
|
-
File.write(path, JSON.pretty_generate(part.as_json) << "\n")
|
47
|
-
end
|
42
|
+
store generated
|
48
43
|
end
|
49
44
|
|
50
45
|
def plan
|
@@ -58,6 +53,35 @@ module Kennel
|
|
58
53
|
|
59
54
|
private
|
60
55
|
|
56
|
+
def store(parts)
|
57
|
+
Progress.progress "Storing" do
|
58
|
+
old = Dir["generated/**/*"]
|
59
|
+
used = []
|
60
|
+
|
61
|
+
Utils.parallel(parts, max: 2) do |part|
|
62
|
+
path = "generated/#{part.tracking_id.tr("/", ":").sub(":", "/")}.json"
|
63
|
+
used << File.dirname(path) # only 1 level of sub folders, so this is safe
|
64
|
+
used << path
|
65
|
+
write_file_if_necessary(path, JSON.pretty_generate(part.as_json) << "\n")
|
66
|
+
end
|
67
|
+
|
68
|
+
# deleting all is slow, so only delete the extras
|
69
|
+
(old - used).each { |p| FileUtils.rm_rf(p) }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_file_if_necessary(path, content)
|
74
|
+
# 99% case
|
75
|
+
begin
|
76
|
+
return if File.read(path) == content
|
77
|
+
rescue Errno::ENOENT
|
78
|
+
FileUtils.mkdir_p(File.dirname(path))
|
79
|
+
end
|
80
|
+
|
81
|
+
# slow 1% case
|
82
|
+
File.write(path, content)
|
83
|
+
end
|
84
|
+
|
61
85
|
def syncer
|
62
86
|
@syncer ||= Syncer.new(api, generated, project: ENV["PROJECT"])
|
63
87
|
end
|
@@ -73,8 +97,12 @@ module Kennel
|
|
73
97
|
parts = Models::Project.recursive_subclasses.flat_map do |project_class|
|
74
98
|
project_class.new.validated_parts
|
75
99
|
end
|
76
|
-
parts.
|
77
|
-
|
100
|
+
parts.group_by(&:tracking_id).each do |tracking_id, same|
|
101
|
+
next if same.size == 1
|
102
|
+
raise <<~ERROR
|
103
|
+
#{tracking_id} is defined #{same.size} times
|
104
|
+
use a different `kennel_id` when defining multiple projects/monitors/dashboards to avoid this conflict
|
105
|
+
ERROR
|
78
106
|
end
|
79
107
|
parts
|
80
108
|
end
|
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
|
@@ -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 = {},
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
@@ -112,9 +112,11 @@ module Kennel
|
|
112
112
|
end
|
113
113
|
|
114
114
|
def resolve_linked_tracking_ids!(id_map, **args)
|
115
|
-
|
116
|
-
|
117
|
-
|
115
|
+
case as_json[:type]
|
116
|
+
when "composite", "slo alert"
|
117
|
+
type = (as_json[:type] == "composite" ? :monitor : :slo)
|
118
|
+
as_json[:query] = as_json[:query].gsub(/%{(.*?)}/) do
|
119
|
+
resolve_link($1, type, id_map, **args) || $&
|
118
120
|
end
|
119
121
|
end
|
120
122
|
end
|
data/lib/kennel/models/record.rb
CHANGED
@@ -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
|
|
@@ -65,19 +64,18 @@ module Kennel
|
|
65
64
|
|
66
65
|
private
|
67
66
|
|
68
|
-
def resolve_link(
|
69
|
-
|
70
|
-
if
|
67
|
+
def resolve_link(tracking_id, type, id_map, force:)
|
68
|
+
id = id_map[tracking_id]
|
69
|
+
if id == :new
|
71
70
|
if force
|
72
|
-
#
|
73
|
-
invalid! "#{id} needs to already exist, try again"
|
71
|
+
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
72
|
else
|
75
|
-
|
73
|
+
nil # will be re-resolved after the linked object was created
|
76
74
|
end
|
77
|
-
elsif
|
78
|
-
|
75
|
+
elsif id
|
76
|
+
id
|
79
77
|
else
|
80
|
-
invalid! "Unable to find #{type} #{
|
78
|
+
invalid! "Unable to find #{type} #{tracking_id} (does not exist and is not being created by the current run)"
|
81
79
|
end
|
82
80
|
end
|
83
81
|
|
data/lib/kennel/models/slo.rb
CHANGED
@@ -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,23 +37,14 @@ module Kennel
|
|
38
37
|
end
|
39
38
|
|
40
39
|
def update
|
41
|
-
|
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
|
-
|
43
|
+
populate_id_map [reply] # allow resolving ids we could previously no resolve
|
53
44
|
Kennel.out.puts "Created #{e.class.api_resource} #{tracking_id(e.as_json)} #{e.url(id)}"
|
54
45
|
end
|
55
46
|
|
56
|
-
@update
|
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
49
|
Kennel.out.puts "Updated #{e.class.api_resource} #{tracking_id(e.as_json)} #{e.url(id)}"
|
60
50
|
end
|
@@ -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
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
218
|
-
|
219
|
-
|
220
|
-
|
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
@@ -132,8 +132,15 @@ namespace :kennel do
|
|
132
132
|
else
|
133
133
|
Kennel::Models::Record.subclasses.map(&:api_resource)
|
134
134
|
end
|
135
|
+
api = Kennel.send(:api)
|
136
|
+
list = nil
|
137
|
+
|
135
138
|
resources.each do |resource|
|
136
|
-
Kennel.
|
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|
|
137
144
|
Kennel.out.puts JSON.pretty_generate(r)
|
138
145
|
end
|
139
146
|
end
|
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
|
data/lib/kennel/version.rb
CHANGED
data/template/Readme.md
CHANGED
@@ -67,7 +67,7 @@ end
|
|
67
67
|
- `cp .env.example .env`
|
68
68
|
- open [Datadog API Settings](https://app.datadoghq.com/account/settings#api)
|
69
69
|
- create a `API Key` or get an existing one from an admin, then add it to `.env` as `DATADOG_API_KEY`
|
70
|
-
-
|
70
|
+
- open [Datadog API Settings](https://app.datadoghq.com/access/application-keys) and create a new key, then add it to `.env` as `DATADOG_APP_KEY=`
|
71
71
|
- change the `DATADOG_SUBDOMAIN=app` in `.env` to your companies subdomain if you have one
|
72
72
|
- verify it works by running `rake plan`, it might show some diff, but should not crash
|
73
73
|
|
@@ -194,14 +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
|
197
|
+
### Linking resources with kennel_id
|
198
198
|
|
199
|
-
|
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
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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"]`|
|
205
211
|
|
206
212
|
### Debugging changes locally
|
207
213
|
|
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.
|
4
|
+
version: 1.82.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:
|
11
|
+
date: 2021-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|