kennel 1.138.1 → 1.140.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: 3e344f2fab0aa5769a5f3baee97afed596a11cad4ab70fc395f1f3a752b9d54d
4
- data.tar.gz: 669cf17997c36cbb5f1fa0f8afdf4241adbca108dee8d5a9d7f392ebb0f19098
3
+ metadata.gz: e8a15178bd108647314171a012bafa0c977f998a8488418c0d323de095e0a1b3
4
+ data.tar.gz: 1673aec908b72090afa31e0b3dd8a923dbf505c01ab2f5dfd6211846edab4f7d
5
5
  SHA512:
6
- metadata.gz: ad8d65bc645f5499a4b4da18457100d31a2915ec3103f8daefd0c440879b889521177a425d36e94d8670543a2ef111f2b774a4b17cf741920c61b8e5f6cdf689
7
- data.tar.gz: 13f5f3cce0a002036381de98c58a5002af272f5bfbf5ae710502fb3caeb1ac292d38554e8462cc43dae59936eca351ca47d4cc9f4e8ad23c4c5332263b2c2d51
6
+ metadata.gz: 0343af2d0a40081a7fd8ffe5bb9932344f75ef91f1b688f4b6e4eb72a0bd5f59c9b67cf72f498bb008f76aae7f6df645b0bcd8258b5c1c892cb8cd3ef21cc468
7
+ data.tar.gz: 3a1f1df7f6195ec235713dc173e8c869013654355432159e1bd1e59dd29ea3d849407ee9cf43f31453411a54224ae1ea8b80bc1e01e209176d0cfe28f8fc2bc3
@@ -3,6 +3,7 @@ module Kennel
3
3
  module Models
4
4
  class Dashboard < Record
5
5
  include TemplateVariables
6
+ include TagsValidation
6
7
 
7
8
  READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [
8
9
  :author_handle, :author_name, :modified_at, :deleted_at, :url, :is_read_only, :notify_list, :restricted_roles
@@ -158,7 +159,7 @@ module Kennel
158
159
  template_variables: render_template_variables,
159
160
  template_variable_presets: template_variable_presets,
160
161
  widgets: all_widgets,
161
- tags: tags.grep(TAG_PREFIX).uniq
162
+ tags: tags.grep(TAG_PREFIX)
162
163
  )
163
164
 
164
165
  json[:reflow_type] = reflow_type if reflow_type # setting nil breaks create with "ordered"
@@ -222,8 +223,6 @@ module Kennel
222
223
  def validate_json(data)
223
224
  super
224
225
 
225
- validate_template_variables data
226
-
227
226
  # Avoid diff from datadog presets sorting.
228
227
  presets = data[:template_variable_presets]
229
228
  if presets && presets != presets.sort_by { |p| p[:name] }
@@ -2,6 +2,8 @@
2
2
  module Kennel
3
3
  module Models
4
4
  class Monitor < Record
5
+ include TagsValidation
6
+
5
7
  RENOTIFY_INTERVALS = [0, 10, 20, 30, 40, 50, 60, 90, 120, 180, 240, 300, 360, 720, 1440].freeze # minutes
6
8
  OPTIONAL_SERVICE_CHECK_THRESHOLDS = [:ok, :warning].freeze
7
9
  READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [
@@ -67,7 +69,7 @@ module Kennel
67
69
  type: type,
68
70
  query: query.strip,
69
71
  message: message.strip,
70
- tags: tags.uniq,
72
+ tags: tags,
71
73
  priority: priority,
72
74
  options: {
73
75
  timeout_h: timeout_h,
@@ -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" unless filtered_validation_errors.empty?
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
@@ -2,6 +2,8 @@
2
2
  module Kennel
3
3
  module Models
4
4
  class Slo < Record
5
+ include TagsValidation
6
+
5
7
  READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [:type_id, :monitor_tags, :target_threshold, :timeframe, :warning_threshold]
6
8
  TRACKING_FIELD = :description
7
9
  DEFAULTS = {
@@ -29,7 +31,7 @@ module Kennel
29
31
  description: description,
30
32
  thresholds: thresholds,
31
33
  monitor_ids: monitor_ids,
32
- tags: tags.uniq,
34
+ tags: tags,
33
35
  type: type
34
36
  )
35
37
 
@@ -84,6 +86,13 @@ module Kennel
84
86
  def validate_json(data)
85
87
  super
86
88
 
89
+ # datadog does not allow uppercase tags for slos
90
+ bad_tags = data[:tags].grep(/[A-Z]/)
91
+ if bad_tags.any?
92
+ invalid! :tags_are_upper_case, "Tags must not be upper case (bad tags: #{bad_tags.sort.inspect})"
93
+ end
94
+
95
+ # warning must be <= critical
87
96
  if data[:thresholds].any? { |t| t[:warning] && t[:warning].to_f <= t[:critical].to_f }
88
97
  invalid! :warning_must_be_gt_critical, "Threshold warning must be greater-than critical value"
89
98
  end
@@ -2,6 +2,8 @@
2
2
  module Kennel
3
3
  module Models
4
4
  class SyntheticTest < Record
5
+ include TagsValidation
6
+
5
7
  TRACKING_FIELD = :message
6
8
  DEFAULTS = {}.freeze
7
9
  READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [:status, :monitor_id]
@@ -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
- unless unused_ignores.empty?
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
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Kennel
3
+ module TagsValidation
4
+ def validate_json(data)
5
+ super
6
+
7
+ # ideally we'd avoid duplicated tags, but that happens regularly when importing existing monitors
8
+ data[:tags] = data[:tags].uniq
9
+
10
+ # keep tags clean (TODO: reduce this list)
11
+ bad_tags = data[:tags].grep(/[^A-Za-z:_0-9.\/*@!#-]/)
12
+ invalid! :tags_invalid, "Only use A-Za-z:_0-9./*@!#- in tags (bad tags: #{bad_tags.sort.inspect})" if bad_tags.any?
13
+ end
14
+ end
15
+ end
@@ -16,7 +16,7 @@ module Kennel
16
16
 
17
17
  # check for queries that do not use the variables and would be misleading
18
18
  # TODO: do the same check for apm_query and their group_by
19
- def validate_template_variables(data)
19
+ def validate_json(data)
20
20
  variables = (data[:template_variables] || []).map { |v| "$#{v.fetch(:name)}" }
21
21
  return if variables.empty?
22
22
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.138.1"
3
+ VERSION = "1.140.0"
4
4
  end
data/lib/kennel.rb CHANGED
@@ -13,6 +13,7 @@ require "kennel/filter"
13
13
  require "kennel/parts_serializer"
14
14
  require "kennel/projects_provider"
15
15
  require "kennel/attribute_differ"
16
+ require "kennel/tags_validation"
16
17
  require "kennel/syncer"
17
18
  require "kennel/id_map"
18
19
  require "kennel/api"
@@ -126,7 +127,7 @@ module Kennel
126
127
  Utils.parallel(parts, &:build)
127
128
  end
128
129
 
129
- OptionalValidations.valid?(parts) or raise GenerationAbortedError
130
+ OptionalValidations.valid?(parts) || raise(GenerationAbortedError)
130
131
 
131
132
  parts
132
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.138.1
4
+ version: 1.140.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-19 00:00:00.000000000 Z
11
+ date: 2023-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -116,6 +116,7 @@ files:
116
116
  - lib/kennel/syncer/plan_displayer.rb
117
117
  - lib/kennel/syncer/resolver.rb
118
118
  - lib/kennel/syncer/types.rb
119
+ - lib/kennel/tags_validation.rb
119
120
  - lib/kennel/tasks.rb
120
121
  - lib/kennel/template_variables.rb
121
122
  - lib/kennel/unmuted_alerts.rb