kennel 1.139.0 → 1.141.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/kennel/models/monitor.rb +34 -5
- data/lib/kennel/models/record.rb +17 -38
- data/lib/kennel/optional_validations.rb +52 -43
- data/lib/kennel/version.rb +1 -1
- data/lib/kennel.rb +1 -1
- 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: e82dacccbb0c14e8b8bedd9545b782b7405ca057a02724e89aa03f0a624fa3e4
|
4
|
+
data.tar.gz: f70302b436c823c6cbcb765edbd56da1103b83fadf398c2c904372a8f73b36a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c34733bc887129a6bda1ebc96898ca46cec0346bb008af8020d4159d23db515de54d8db8813749ba7883c0417b0cecb243a530789a4a99d6aa146679996007aa
|
7
|
+
data.tar.gz: a7821e5e67899aa71a068bfabbd895f7c69a369a187b7245510fc969a69d4354bad352cfb953f64293140c6282fa21d38f0e8695ab60d9186a19b30faeddd019
|
@@ -30,12 +30,11 @@ module Kennel
|
|
30
30
|
}.freeze
|
31
31
|
DEFAULT_ESCALATION_MESSAGE = ["", nil].freeze
|
32
32
|
ALLOWED_PRIORITY_CLASSES = [NilClass, Integer].freeze
|
33
|
-
ALLOWED_UNLINKED = [] # rubocop:disable Style/MutableConstant placeholder for custom overrides
|
34
33
|
|
35
34
|
settings(
|
36
35
|
:query, :name, :message, :escalation_message, :critical, :type, :renotify_interval, :warning, :timeout_h, :evaluation_delay,
|
37
36
|
:ok, :no_data_timeframe, :notify_no_data, :notify_audit, :tags, :critical_recovery, :warning_recovery, :require_full_window,
|
38
|
-
:threshold_windows, :scheduling_options, :new_host_delay, :new_group_delay, :priority, :
|
37
|
+
:threshold_windows, :scheduling_options, :new_host_delay, :new_group_delay, :priority, :variables, :on_missing_data,
|
39
38
|
:notification_preset_name
|
40
39
|
)
|
41
40
|
|
@@ -265,6 +264,7 @@ module Kennel
|
|
265
264
|
end
|
266
265
|
|
267
266
|
validate_using_links(data)
|
267
|
+
validate_thresholds(data)
|
268
268
|
|
269
269
|
if type == "service check" && !data[:query].to_s.include?(".by(")
|
270
270
|
invalid! :query_must_include_by, "query must include a .by() at least .by(\"*\")"
|
@@ -320,16 +320,45 @@ module Kennel
|
|
320
320
|
case data[:type]
|
321
321
|
when "composite" # TODO: add slo to mirror resolve_linked_tracking_ids! logic
|
322
322
|
ids = data[:query].tr("-", "_").scan(/\b\d+\b/)
|
323
|
-
ids.reject! { |id| ALLOWED_UNLINKED.include?([tracking_id, id]) }
|
324
323
|
if ids.any?
|
325
324
|
invalid! :links_must_be_via_tracking_id, <<~MSG.rstrip
|
326
|
-
|
327
|
-
If
|
325
|
+
Use kennel ids in the query for linking monitors instead of #{ids}, for example `%{#{project.kennel_id}:<monitor id>}`
|
326
|
+
If the target monitors are not managed via kennel, add `ignored_errors: [:links_must_be_via_tracking_id] # linked monitors are not in kennel`
|
327
|
+
MSG
|
328
|
+
end
|
329
|
+
when "slo alert"
|
330
|
+
if (id = data[:query][/error_budget\("([a-f\d]+)"\)/, 1])
|
331
|
+
invalid! :links_must_be_via_tracking_id, <<~MSG
|
332
|
+
Use kennel ids in the query for linking alerts to slos instead of "#{id}", for example `error_budget("%{#{project.kennel_id}:slo_id_goes_here}")
|
333
|
+
If the target slo is not managed by kennel, then add `ignored_errors: [:links_must_be_via_tracking_id] # linked slo is not in kennel`
|
328
334
|
MSG
|
329
335
|
end
|
330
336
|
else # do nothing
|
331
337
|
end
|
332
338
|
end
|
339
|
+
|
340
|
+
# Prevent "Warning threshold (50.0) must be less than the alert threshold (20.0) with > comparison."
|
341
|
+
def validate_thresholds(data)
|
342
|
+
return unless (warning = data.dig(:options, :thresholds, :warning))
|
343
|
+
critical = data.dig(:options, :thresholds, :critical)
|
344
|
+
|
345
|
+
case data[:query]
|
346
|
+
when /<=?\s*\S+\s*$/
|
347
|
+
if warning <= critical
|
348
|
+
invalid!(
|
349
|
+
:alert_less_than_warning,
|
350
|
+
"Warning threshold (#{warning}) must be greater than the alert threshold (#{critical}) with < comparison"
|
351
|
+
)
|
352
|
+
end
|
353
|
+
when />=?\s*\S+\s*$/
|
354
|
+
if warning >= critical
|
355
|
+
invalid!(
|
356
|
+
:alert_less_than_warning,
|
357
|
+
"Warning threshold (#{warning}) must be less than the alert threshold (#{critical}) with > comparison"
|
358
|
+
)
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
333
362
|
end
|
334
363
|
end
|
335
364
|
end
|
data/lib/kennel/models/record.rb
CHANGED
@@ -2,14 +2,6 @@
|
|
2
2
|
module Kennel
|
3
3
|
module Models
|
4
4
|
class Record < Base
|
5
|
-
class PrepareError < StandardError
|
6
|
-
def initialize(tracking_id)
|
7
|
-
super("Error while preparing #{tracking_id}")
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
UnvalidatedRecordError = Class.new(StandardError)
|
12
|
-
|
13
5
|
include OptionalValidations
|
14
6
|
|
15
7
|
# Apart from if you just don't like the default for some reason,
|
@@ -85,12 +77,12 @@ module Kennel
|
|
85
77
|
end
|
86
78
|
end
|
87
79
|
|
88
|
-
attr_reader :project, :
|
80
|
+
attr_reader :project, :as_json
|
89
81
|
|
90
|
-
def initialize(project,
|
82
|
+
def initialize(project, ...)
|
91
83
|
raise ArgumentError, "First argument must be a project, not #{project.class}" unless project.is_a?(Project)
|
92
84
|
@project = project
|
93
|
-
super(
|
85
|
+
super(...)
|
94
86
|
end
|
95
87
|
|
96
88
|
def diff(actual)
|
@@ -141,29 +133,9 @@ module Kennel
|
|
141
133
|
end
|
142
134
|
|
143
135
|
def build
|
144
|
-
@
|
145
|
-
|
146
|
-
|
147
|
-
begin
|
148
|
-
json = build_json
|
149
|
-
(id = json.delete(:id)) && json[:id] = id
|
150
|
-
validate_json(json)
|
151
|
-
rescue StandardError
|
152
|
-
if unfiltered_validation_errors.empty?
|
153
|
-
@unfiltered_validation_errors = nil
|
154
|
-
raise PrepareError, safe_tracking_id # FIXME: this makes errors hard to debug when running tests
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
@filtered_validation_errors = filter_validation_errors
|
159
|
-
@as_json = json # Only valid if filtered_validation_errors.empty?
|
160
|
-
end
|
161
|
-
|
162
|
-
def as_json
|
163
|
-
# A courtesy to those tests that still expect as_json to perform validation and raise on error
|
164
|
-
build if @unfiltered_validation_errors.nil?
|
165
|
-
raise UnvalidatedRecordError, "#{safe_tracking_id} as_json called on invalid part" if filtered_validation_errors.any?
|
166
|
-
|
136
|
+
@as_json = build_json
|
137
|
+
(id = @as_json.delete(:id)) && @as_json[:id] = id
|
138
|
+
validate_json(@as_json)
|
167
139
|
@as_json
|
168
140
|
end
|
169
141
|
|
@@ -184,6 +156,17 @@ module Kennel
|
|
184
156
|
|
185
157
|
private
|
186
158
|
|
159
|
+
def validate_json(data)
|
160
|
+
bad = Kennel::Utils.all_keys(data).grep_v(Symbol).sort.uniq
|
161
|
+
return if bad.empty?
|
162
|
+
invalid!(
|
163
|
+
:hash_keys_must_be_symbols,
|
164
|
+
"Only use Symbols as hash keys to avoid permanent diffs when updating.\n" \
|
165
|
+
"Change these keys to be symbols (usually 'foo' => 1 --> 'foo': 1)\n" \
|
166
|
+
"#{bad.map(&:inspect).join("\n")}"
|
167
|
+
)
|
168
|
+
end
|
169
|
+
|
187
170
|
def resolve(value, type, id_map, force:)
|
188
171
|
return value unless tracking_id?(value)
|
189
172
|
resolve_link(value, type, id_map, force: force)
|
@@ -214,10 +197,6 @@ module Kennel
|
|
214
197
|
end
|
215
198
|
end
|
216
199
|
|
217
|
-
def invalid!(tag, message)
|
218
|
-
unfiltered_validation_errors << ValidationMessage.new(tag || OptionalValidations::UNIGNORABLE, message)
|
219
|
-
end
|
220
|
-
|
221
200
|
def raise_with_location(error, message)
|
222
201
|
super error, "#{message} for project #{project.kennel_id}"
|
223
202
|
end
|
@@ -4,80 +4,89 @@ module Kennel
|
|
4
4
|
ValidationMessage = Struct.new(:tag, :text)
|
5
5
|
|
6
6
|
UNIGNORABLE = :unignorable
|
7
|
+
UNUSED_IGNORES = :unused_ignores
|
7
8
|
|
8
9
|
def self.included(base)
|
9
10
|
base.settings :ignored_errors
|
10
11
|
base.defaults(ignored_errors: -> { [] })
|
12
|
+
base.attr_reader :validation_errors
|
11
13
|
end
|
12
14
|
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
def initialize(...)
|
16
|
+
super
|
17
|
+
@validation_errors = []
|
18
|
+
end
|
17
19
|
|
18
|
-
|
20
|
+
def invalid!(tag, message)
|
21
|
+
validation_errors << ValidationMessage.new(tag || OptionalValidations::UNIGNORABLE, message)
|
22
|
+
end
|
19
23
|
|
20
|
-
|
24
|
+
def self.valid?(parts)
|
25
|
+
parts_with_errors = parts.map { |p| [p, filter_validation_errors(p)] }
|
26
|
+
return true if parts_with_errors.all? { |_, errors| errors.empty? }
|
21
27
|
|
28
|
+
# print errors in order
|
29
|
+
example_tag = nil
|
22
30
|
Kennel.err.puts
|
23
|
-
parts_with_errors.sort_by
|
24
|
-
|
31
|
+
parts_with_errors.sort_by! { |p, _| p.safe_tracking_id }
|
32
|
+
parts_with_errors.each do |part, errors|
|
33
|
+
errors.each do |err|
|
25
34
|
Kennel.err.puts "#{part.safe_tracking_id} [#{err.tag.inspect}] #{err.text.gsub("\n", " ")}"
|
26
35
|
example_tag = err.tag unless err.tag == :unignorable
|
27
36
|
end
|
28
37
|
end
|
29
38
|
Kennel.err.puts
|
30
39
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
40
|
+
if example_tag
|
41
|
+
Kennel.err.puts <<~MESSAGE
|
42
|
+
If a particular error cannot be fixed, it can be marked as ignored via `ignored_errors`, e.g.:
|
43
|
+
Kennel::Models::Monitor.new(
|
44
|
+
...,
|
45
|
+
ignored_errors: [#{example_tag.inspect}]
|
46
|
+
)
|
37
47
|
|
38
|
-
|
48
|
+
MESSAGE
|
49
|
+
end
|
39
50
|
|
40
51
|
false
|
41
52
|
end
|
42
53
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
bad = Kennel::Utils.all_keys(data).grep_v(Symbol).sort.uniq
|
47
|
-
return if bad.empty?
|
48
|
-
invalid!(
|
49
|
-
:hash_keys_must_be_symbols,
|
50
|
-
"Only use Symbols as hash keys to avoid permanent diffs when updating.\n" \
|
51
|
-
"Change these keys to be symbols (usually 'foo' => 1 --> 'foo': 1)\n" \
|
52
|
-
"#{bad.map(&:inspect).join("\n")}"
|
53
|
-
)
|
54
|
-
end
|
54
|
+
def self.filter_validation_errors(part)
|
55
|
+
errors = part.validation_errors
|
56
|
+
ignored_tags = part.ignored_errors
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
if ignored_errors.empty?
|
58
|
+
if errors.empty? # 95% case, so keep it fast
|
59
|
+
if ignored_tags.empty? || ignored_tags.include?(UNUSED_IGNORES)
|
59
60
|
[]
|
60
61
|
else
|
61
|
-
|
62
|
+
# tell users to remove the whole line and not just an element
|
63
|
+
[
|
64
|
+
ValidationMessage.new(
|
65
|
+
UNUSED_IGNORES,
|
66
|
+
"`ignored_errors` is non-empty, but there are no errors to ignore. Remove `ignored_errors`"
|
67
|
+
)
|
68
|
+
]
|
62
69
|
end
|
63
70
|
else
|
64
|
-
|
65
|
-
if ENV["NO_IGNORED_ERRORS"]
|
66
|
-
|
67
|
-
unfiltered_validation_errors
|
71
|
+
reported_errors =
|
72
|
+
if ENV["NO_IGNORED_ERRORS"] # let users see what errors are suppressed
|
73
|
+
errors
|
68
74
|
else
|
69
|
-
|
70
|
-
err.tag != UNIGNORABLE && ignored_errors.include?(err.tag)
|
71
|
-
end
|
75
|
+
errors.select { |err| err.tag == UNIGNORABLE || !ignored_tags.include?(err.tag) }
|
72
76
|
end
|
73
77
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
+
# let users know when they can remove an ignore ... unless they don't care (for example for a generic monitor)
|
79
|
+
unless ignored_tags.include?(UNUSED_IGNORES)
|
80
|
+
unused_ignored_tags = ignored_tags - errors.map(&:tag)
|
81
|
+
if unused_ignored_tags.any?
|
82
|
+
reported_errors << ValidationMessage.new(
|
83
|
+
UNUSED_IGNORES,
|
84
|
+
"Unused ignores #{unused_ignored_tags.map(&:inspect).sort.uniq.join(" ")}. Remove these from `ignored_errors`"
|
85
|
+
)
|
86
|
+
end
|
78
87
|
end
|
79
88
|
|
80
|
-
|
89
|
+
reported_errors
|
81
90
|
end
|
82
91
|
end
|
83
92
|
end
|
data/lib/kennel/version.rb
CHANGED
data/lib/kennel.rb
CHANGED
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.141.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: 2023-
|
11
|
+
date: 2023-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: diff-lcs
|