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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33d7570c6b4d70c6d50f89d0111d8db96e6c0c0ba9273d00bb0975acfef12526
4
- data.tar.gz: ea89fdf62138917a204e9c619a7a1182acca1e8d898f22e16707d0a02685cd26
3
+ metadata.gz: e82dacccbb0c14e8b8bedd9545b782b7405ca057a02724e89aa03f0a624fa3e4
4
+ data.tar.gz: f70302b436c823c6cbcb765edbd56da1103b83fadf398c2c904372a8f73b36a7
5
5
  SHA512:
6
- metadata.gz: de01f19064531cb09615b63056b1eaa82118a4734fbdd9afa44eef56019923a736b45f9f4440da584cd0939231f7d7928f9a819cbfdb2f014336e82ee0f66c7e
7
- data.tar.gz: ec59ee486794d91f5af93e8b3f77134ff206b6f653cc424c3e337b6893fefef1a6fb92a401029e2f5fd4ca83db49c1d284bf556105ad6d38667b3825667bb973
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, :validate_using_links, :variables, :on_missing_data,
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
- Used #{ids} in the query, but should only use links in the form of %{<project id>:<monitor id>}
327
- If that is not possible, add `validate_using_links: ->(*){} # linked monitors are not in kennel
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
@@ -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, :unfiltered_validation_errors, :filtered_validation_errors
80
+ attr_reader :project, :as_json
89
81
 
90
- def initialize(project, *args)
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(*args)
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
- @unfiltered_validation_errors = []
145
- json = nil
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 self.valid?(parts)
14
- parts_with_errors = parts.reject do |part|
15
- part.filtered_validation_errors.empty?
16
- end
15
+ def initialize(...)
16
+ super
17
+ @validation_errors = []
18
+ end
17
19
 
18
- return true if parts_with_errors.empty?
20
+ def invalid!(tag, message)
21
+ validation_errors << ValidationMessage.new(tag || OptionalValidations::UNIGNORABLE, message)
22
+ end
19
23
 
20
- example_tag = nil
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(&:safe_tracking_id).each do |part|
24
- part.filtered_validation_errors.each do |err|
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
- Kennel.err.puts <<~MESSAGE if example_tag
32
- If a particular error cannot be fixed, it can be marked as ignored via `ignored_errors`, e.g.:
33
- Kennel::Models::Monitor.new(
34
- ...,
35
- ignored_errors: [#{example_tag.inspect}]
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
- MESSAGE
48
+ MESSAGE
49
+ end
39
50
 
40
51
  false
41
52
  end
42
53
 
43
- private
44
-
45
- def validate_json(data)
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
- def filter_validation_errors
57
- if unfiltered_validation_errors.empty?
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
- [ValidationMessage.new(UNIGNORABLE, "`ignored_errors` is non-empty, but there are no errors to ignore. Remove `ignored_errors`")]
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
- to_report =
65
- if ENV["NO_IGNORED_ERRORS"]
66
- # Turn off all suppressions, to see what errors are actually being suppressed
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
- unfiltered_validation_errors.reject do |err|
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
- unused_ignores = ignored_errors - unfiltered_validation_errors.map(&:tag)
75
-
76
- if unused_ignores.any?
77
- to_report << ValidationMessage.new(UNIGNORABLE, "Unused ignores #{unused_ignores.map(&:inspect).sort.uniq.join(" ")}. Remove these from `ignored_errors`")
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
- to_report
89
+ reported_errors
81
90
  end
82
91
  end
83
92
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.139.0"
3
+ VERSION = "1.141.0"
4
4
  end
data/lib/kennel.rb CHANGED
@@ -127,7 +127,7 @@ module Kennel
127
127
  Utils.parallel(parts, &:build)
128
128
  end
129
129
 
130
- OptionalValidations.valid?(parts) or raise GenerationAbortedError
130
+ OptionalValidations.valid?(parts) || raise(GenerationAbortedError)
131
131
 
132
132
  parts
133
133
  end
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.139.0
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-05-26 00:00:00.000000000 Z
11
+ date: 2023-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs