kennel 1.78.3 → 1.81.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Readme.md +14 -8
- data/lib/kennel.rb +36 -8
- data/lib/kennel/models/dashboard.rb +5 -5
- data/lib/kennel/models/monitor.rb +16 -5
- data/lib/kennel/models/record.rb +8 -9
- data/lib/kennel/models/slo.rb +1 -1
- data/lib/kennel/syncer.rb +47 -18
- data/lib/kennel/utils.rb +5 -0
- data/lib/kennel/version.rb +1 -1
- data/template/Readme.md +12 -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: 4ecb9c89748e6eeee268d0d744622b536242af1c1d7f615bf77fc5963e1630d8
|
4
|
+
data.tar.gz: e6cae4da66e0947163d1f441bce2afb19f1818dac0ed9418143b3f2a2480b220
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a3342551a926a55c580571524cadbb32bf7b02b77a914f32e1edf2a07b6772091373286c36871d9651a57899134364d8c9c5894a8ef1e46f72ad091ca241990
|
7
|
+
data.tar.gz: 6eb25f647a3a7d47c0858ca03f0649e151f6437d711701ea24f1dd3b742b5adbfbd3f0318a531076b2236063dda194bbdec72237eaf3f1159f447ac86a95ee68
|
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
|
|
@@ -296,5 +302,5 @@ Author
|
|
296
302
|
[Michael Grosser](http://grosser.it)<br/>
|
297
303
|
michael@grosser.it<br/>
|
298
304
|
License: MIT<br/>
|
299
|
-
|
305
|
+
![CI](https://github.com/grosser/kennel/workflows/CI/badge.svg)
|
300
306
|
<!-- NOT IN -->
|
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
|
@@ -105,7 +105,7 @@ module Kennel
|
|
105
105
|
|
106
106
|
def as_json
|
107
107
|
return @json if @json
|
108
|
-
all_widgets = render_definitions + widgets
|
108
|
+
all_widgets = render_definitions(definitions) + widgets
|
109
109
|
expand_q all_widgets
|
110
110
|
|
111
111
|
@json = {
|
@@ -140,16 +140,16 @@ module Kennel
|
|
140
140
|
when "uptime"
|
141
141
|
if ids = definition[:monitor_ids]
|
142
142
|
definition[:monitor_ids] = ids.map do |id|
|
143
|
-
tracking_id?(id) ? resolve_link(id, :monitor, id_map, **args) : id
|
143
|
+
tracking_id?(id) ? (resolve_link(id, :monitor, id_map, **args) || id) : id
|
144
144
|
end
|
145
145
|
end
|
146
146
|
when "alert_graph"
|
147
147
|
if (id = definition[:alert_id]) && tracking_id?(id)
|
148
|
-
definition[:alert_id] = resolve_link(id, :monitor, id_map, **args).to_s
|
148
|
+
definition[:alert_id] = (resolve_link(id, :monitor, id_map, **args) || id).to_s
|
149
149
|
end
|
150
150
|
when "slo"
|
151
151
|
if (id = definition[:slo_id]) && tracking_id?(id)
|
152
|
-
definition[:slo_id] = resolve_link(id, :slo, id_map, **args).to_s
|
152
|
+
definition[:slo_id] = (resolve_link(id, :slo, id_map, **args) || id).to_s
|
153
153
|
end
|
154
154
|
end
|
155
155
|
end
|
@@ -185,7 +185,7 @@ module Kennel
|
|
185
185
|
invalid! "template_variable_presets must be sorted by name" if presets && presets != presets.sort_by { |p| p[:name] }
|
186
186
|
end
|
187
187
|
|
188
|
-
def render_definitions
|
188
|
+
def render_definitions(definitions)
|
189
189
|
definitions.map do |title, type, display_type, queries, options = {}, ignored = nil|
|
190
190
|
# validate inputs
|
191
191
|
if ignored || (!title || !type || !queries || !options.is_a?(Hash))
|
@@ -12,6 +12,10 @@ module Kennel
|
|
12
12
|
:multi, :matching_downtimes, :overall_state_modified, :overall_state, :restricted_roles
|
13
13
|
]
|
14
14
|
|
15
|
+
MONITOR_DEFAULTS = {
|
16
|
+
priority: nil
|
17
|
+
}.freeze
|
18
|
+
|
15
19
|
# defaults that datadog uses when options are not sent, so safe to leave out if our values match their defaults
|
16
20
|
MONITOR_OPTION_DEFAULTS = {
|
17
21
|
evaluation_delay: nil,
|
@@ -27,7 +31,7 @@ module Kennel
|
|
27
31
|
settings(
|
28
32
|
:query, :name, :message, :escalation_message, :critical, :type, :renotify_interval, :warning, :timeout_h, :evaluation_delay,
|
29
33
|
:ok, :no_data_timeframe, :notify_no_data, :notify_audit, :tags, :critical_recovery, :warning_recovery, :require_full_window,
|
30
|
-
:threshold_windows, :new_host_delay
|
34
|
+
:threshold_windows, :new_host_delay, :priority
|
31
35
|
)
|
32
36
|
|
33
37
|
defaults(
|
@@ -46,7 +50,8 @@ module Kennel
|
|
46
50
|
evaluation_delay: -> { MONITOR_OPTION_DEFAULTS.fetch(:evaluation_delay) },
|
47
51
|
critical_recovery: -> { nil },
|
48
52
|
warning_recovery: -> { nil },
|
49
|
-
threshold_windows: -> { nil }
|
53
|
+
threshold_windows: -> { nil },
|
54
|
+
priority: -> { MONITOR_DEFAULTS.fetch(:priority) }
|
50
55
|
)
|
51
56
|
|
52
57
|
def as_json
|
@@ -57,6 +62,7 @@ module Kennel
|
|
57
62
|
query: query.strip,
|
58
63
|
message: message.strip,
|
59
64
|
tags: tags.uniq,
|
65
|
+
priority: priority,
|
60
66
|
options: {
|
61
67
|
timeout_h: timeout_h,
|
62
68
|
notify_no_data: notify_no_data,
|
@@ -106,9 +112,11 @@ module Kennel
|
|
106
112
|
end
|
107
113
|
|
108
114
|
def resolve_linked_tracking_ids!(id_map, **args)
|
109
|
-
|
110
|
-
|
111
|
-
|
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) || $&
|
112
120
|
end
|
113
121
|
end
|
114
122
|
end
|
@@ -129,6 +137,9 @@ module Kennel
|
|
129
137
|
|
130
138
|
def self.normalize(expected, actual)
|
131
139
|
super
|
140
|
+
|
141
|
+
ignore_default(expected, actual, MONITOR_DEFAULTS)
|
142
|
+
|
132
143
|
options = actual.fetch(:options)
|
133
144
|
options.delete(:silenced) # we do not manage silenced, so ignore it when diffing
|
134
145
|
|
data/lib/kennel/models/record.rb
CHANGED
@@ -65,19 +65,18 @@ module Kennel
|
|
65
65
|
|
66
66
|
private
|
67
67
|
|
68
|
-
def resolve_link(
|
69
|
-
|
70
|
-
if
|
68
|
+
def resolve_link(tracking_id, type, id_map, force:)
|
69
|
+
id = id_map[tracking_id]
|
70
|
+
if id == :new
|
71
71
|
if force
|
72
|
-
#
|
73
|
-
invalid! "#{id} needs to already exist, try again"
|
72
|
+
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
73
|
else
|
75
|
-
|
74
|
+
nil # will be re-resolved after the linked object was created
|
76
75
|
end
|
77
|
-
elsif
|
78
|
-
|
76
|
+
elsif id
|
77
|
+
id
|
79
78
|
else
|
80
|
-
invalid! "Unable to find #{type} #{
|
79
|
+
invalid! "Unable to find #{type} #{tracking_id} (does not exist and is not being created by the current run)"
|
81
80
|
end
|
82
81
|
end
|
83
82
|
|
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
@@ -38,23 +38,14 @@ module Kennel
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def update
|
41
|
-
|
42
|
-
|
43
|
-
@create.each do |_, e|
|
44
|
-
e.resolve_linked_tracking_ids!({}, force: true)
|
45
|
-
|
41
|
+
each_resolved @create do |_, e|
|
46
42
|
reply = @api.create e.class.api_resource, e.as_json
|
47
43
|
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
|
-
|
44
|
+
populate_id_map [reply] # allow resolving ids we could previously no resolve
|
53
45
|
Kennel.out.puts "Created #{e.class.api_resource} #{tracking_id(e.as_json)} #{e.url(id)}"
|
54
46
|
end
|
55
47
|
|
56
|
-
@update
|
57
|
-
e.resolve_linked_tracking_ids!({}, force: true)
|
48
|
+
each_resolved @update do |id, e|
|
58
49
|
@api.update e.class.api_resource, id, e.as_json
|
59
50
|
Kennel.out.puts "Updated #{e.class.api_resource} #{tracking_id(e.as_json)} #{e.url(id)}"
|
60
51
|
end
|
@@ -67,6 +58,37 @@ module Kennel
|
|
67
58
|
|
68
59
|
private
|
69
60
|
|
61
|
+
# loop over items until everything is resolved or crash when we get stuck
|
62
|
+
# this solves cases like composite monitors depending on each other or monitor->monitor slo->slo monitor chains
|
63
|
+
def each_resolved(list)
|
64
|
+
list = list.dup
|
65
|
+
loop do
|
66
|
+
return if list.empty?
|
67
|
+
list.reject! do |id, e|
|
68
|
+
if resolved?(e)
|
69
|
+
yield id, e
|
70
|
+
true
|
71
|
+
else
|
72
|
+
false
|
73
|
+
end
|
74
|
+
end ||
|
75
|
+
assert_resolved(list[0][1]) # resolve something or show a circular dependency error
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# TODO: optimize by storing an instance variable if already resolved
|
80
|
+
def resolved?(e)
|
81
|
+
assert_resolved e
|
82
|
+
true
|
83
|
+
rescue ValidationError
|
84
|
+
false
|
85
|
+
end
|
86
|
+
|
87
|
+
# raises ValidationError when not resolved
|
88
|
+
def assert_resolved(e)
|
89
|
+
resolve_linked_tracking_ids! [e], force: true
|
90
|
+
end
|
91
|
+
|
70
92
|
def noop?
|
71
93
|
@create.empty? && @update.empty? && @delete.empty?
|
72
94
|
end
|
@@ -74,9 +96,15 @@ module Kennel
|
|
74
96
|
def calculate_diff
|
75
97
|
@update = []
|
76
98
|
@delete = []
|
99
|
+
@id_map = {}
|
77
100
|
|
78
101
|
actual = Progress.progress("Downloading definitions") { download_definitions }
|
79
|
-
|
102
|
+
|
103
|
+
# resolve dependencies to avoid diff
|
104
|
+
populate_id_map actual
|
105
|
+
@expected.each { |e| @id_map[e.tracking_id] ||= :new }
|
106
|
+
resolve_linked_tracking_ids! @expected
|
107
|
+
|
80
108
|
filter_by_project! actual
|
81
109
|
|
82
110
|
Progress.progress "Diffing" do
|
@@ -107,7 +135,6 @@ module Kennel
|
|
107
135
|
|
108
136
|
ensure_all_ids_found
|
109
137
|
@create = @expected.map { |e| [nil, e] }
|
110
|
-
@create.sort_by! { |_, e| -DELETE_ORDER.index(e.class.api_resource) }
|
111
138
|
end
|
112
139
|
|
113
140
|
@delete.sort_by! { |_, _, a| DELETE_ORDER.index a.fetch(:api_resource) }
|
@@ -214,10 +241,12 @@ module Kennel
|
|
214
241
|
end
|
215
242
|
end
|
216
243
|
|
217
|
-
def
|
218
|
-
|
219
|
-
|
220
|
-
|
244
|
+
def populate_id_map(actual)
|
245
|
+
actual.each { |a| @id_map[tracking_id(a)] = a.fetch(:id) }
|
246
|
+
end
|
247
|
+
|
248
|
+
def resolve_linked_tracking_ids!(list, force: false)
|
249
|
+
list.each { |e| e.resolve_linked_tracking_ids!(@id_map, force: force) }
|
221
250
|
end
|
222
251
|
|
223
252
|
def filter_by_project!(definitions)
|
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
|
|
@@ -196,12 +196,17 @@ to unblock use the `validate: -> { false }` option.
|
|
196
196
|
|
197
197
|
### Linking with kennel_ids
|
198
198
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
199
|
+
Link resources via their kennel_id `projects kennel_id` + `:` + `monitors kennel id`,
|
200
|
+
this should be used to create dependent resources like monitor + slos.
|
201
|
+
|
202
|
+
|Resource|Type|Syntax|
|
203
|
+
|---|---|---|
|
204
|
+
|Dashboard|uptime|`monitor: {id: "foo:bar"}`|
|
205
|
+
|Dashboard|alert_graph|`alert_id: "foo:bar"`|
|
206
|
+
|Dashboard|slo|`slo_id: "foo:bar"`|
|
207
|
+
|Monitor|composite|`query: -> { "%{foo:bar} && %{foo:baz}" }`|
|
208
|
+
|Monitor|slo alert|`query: -> { "error_budget(\"%{foo:bar}\").over(\"7d\") > 123.0" }`|
|
209
|
+
|Slo|monitor|`monitor_ids: -> ["foo:bar"]`|
|
205
210
|
|
206
211
|
### Debugging changes locally
|
207
212
|
|
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.81.1
|
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-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|