kennel 1.125.0 → 1.126.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/kennel/models/dashboard.rb +3 -1
- data/lib/kennel/models/monitor.rb +9 -9
- data/lib/kennel/models/record.rb +2 -2
- data/lib/kennel/models/slo.rb +1 -1
- data/lib/kennel/optional_validations.rb +36 -30
- data/lib/kennel/progress.rb +2 -2
- data/lib/kennel/syncer.rb +6 -20
- data/lib/kennel/tasks.rb +2 -1
- data/lib/kennel/template_variables.rb +2 -2
- data/lib/kennel/utils.rb +5 -0
- data/lib/kennel/version.rb +1 -1
- data/lib/kennel.rb +16 -4
- 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: d8254e03670c1874a6f9b85da5fe453200cdcd9aabe2f82dafc424a6301d8416
|
|
4
|
+
data.tar.gz: 2d1c0b444762941f79fcba893f5cddc9d0119138c5cf0fc04d6fc871f2caf7a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 32c9cbe7957e0f0652180d9b743e4498f3624231e96907f2aaafe5889b9d2f5cfcac98b21dce058c7e06cd6b719ae21b91d01b478eb21028a333b6e0383732a1
|
|
7
|
+
data.tar.gz: '068566cb8e89e9c9d993ed451cd174a301ce3976c2240aa8c12c1ebf20b602b9cf245b0eb28267dc9263930ad4659163cb75fee67af1d9c1094a649129fb7e17'
|
|
@@ -230,7 +230,9 @@ module Kennel
|
|
|
230
230
|
|
|
231
231
|
# Avoid diff from datadog presets sorting.
|
|
232
232
|
presets = data[:template_variable_presets]
|
|
233
|
-
|
|
233
|
+
if presets && presets != presets.sort_by { |p| p[:name] }
|
|
234
|
+
invalid! :template_variable_presets_must_be_sorted, "template_variable_presets must be sorted by name"
|
|
235
|
+
end
|
|
234
236
|
end
|
|
235
237
|
|
|
236
238
|
def render_definitions(definitions)
|
|
@@ -214,26 +214,26 @@ module Kennel
|
|
|
214
214
|
super
|
|
215
215
|
|
|
216
216
|
if data[:name]&.start_with?(" ")
|
|
217
|
-
invalid! "name cannot start with a space"
|
|
217
|
+
invalid! :name_must_not_start_with_space, "name cannot start with a space"
|
|
218
218
|
end
|
|
219
219
|
|
|
220
220
|
type = data.fetch(:type)
|
|
221
221
|
|
|
222
222
|
# do not allow deprecated type that will be coverted by datadog and then produce a diff
|
|
223
223
|
if type == "metric alert"
|
|
224
|
-
invalid! "type 'metric alert' is deprecated, please set to a different type (e.g. 'query alert')"
|
|
224
|
+
invalid! :metric_alert_is_deprecated, "type 'metric alert' is deprecated, please set to a different type (e.g. 'query alert')"
|
|
225
225
|
end
|
|
226
226
|
|
|
227
227
|
# verify query includes critical value
|
|
228
228
|
if query_value = data.fetch(:query)[/\s*[<>]=?\s*(\d+(\.\d+)?)\s*$/, 1]
|
|
229
229
|
if Float(query_value) != Float(data.dig(:options, :thresholds, :critical))
|
|
230
|
-
invalid! "critical and value used in query must match"
|
|
230
|
+
invalid! :critical_does_not_match_query, "critical and value used in query must match"
|
|
231
231
|
end
|
|
232
232
|
end
|
|
233
233
|
|
|
234
234
|
# verify renotify interval is valid
|
|
235
235
|
unless RENOTIFY_INTERVALS.include? data.dig(:options, :renotify_interval)
|
|
236
|
-
invalid! "renotify_interval must be one of #{RENOTIFY_INTERVALS.join(", ")}"
|
|
236
|
+
invalid! :invalid_renotify_interval, "renotify_interval must be one of #{RENOTIFY_INTERVALS.join(", ")}"
|
|
237
237
|
end
|
|
238
238
|
|
|
239
239
|
if ["query alert", "service check"].include?(type) # TODO: most likely more types need this
|
|
@@ -243,15 +243,15 @@ module Kennel
|
|
|
243
243
|
validate_using_links(data)
|
|
244
244
|
|
|
245
245
|
if type == "service check" && !data[:query].to_s.include?(".by(")
|
|
246
|
-
invalid! "query must include a .by() at least .by(\"*\")"
|
|
246
|
+
invalid! :query_must_include_by, "query must include a .by() at least .by(\"*\")"
|
|
247
247
|
end
|
|
248
248
|
|
|
249
249
|
unless ALLOWED_PRIORITY_CLASSES.include?(priority.class)
|
|
250
|
-
invalid! "priority needs to be an Integer"
|
|
250
|
+
invalid! :invalid_priority, "priority needs to be an Integer"
|
|
251
251
|
end
|
|
252
252
|
|
|
253
253
|
if data.dig(:options, :timeout_h)&.> 24
|
|
254
|
-
invalid! "timeout_h must be <= 24"
|
|
254
|
+
invalid! :invalid_timeout_h, "timeout_h must be <= 24"
|
|
255
255
|
end
|
|
256
256
|
end
|
|
257
257
|
|
|
@@ -286,7 +286,7 @@ module Kennel
|
|
|
286
286
|
forbidden = used - allowed
|
|
287
287
|
return if forbidden.empty?
|
|
288
288
|
|
|
289
|
-
invalid! <<~MSG.rstrip
|
|
289
|
+
invalid! :invalid_variable_used_in_message, <<~MSG.rstrip
|
|
290
290
|
Used #{forbidden.join(", ")} in the message, but can only be used with #{allowed.join(", ")}.
|
|
291
291
|
Group or filter the query by #{forbidden.map { |f| f.sub(".name", "") }.join(", ")} to use it.
|
|
292
292
|
MSG
|
|
@@ -298,7 +298,7 @@ module Kennel
|
|
|
298
298
|
ids = data[:query].tr("-", "_").scan(/\b\d+\b/)
|
|
299
299
|
ids.reject! { |id| ALLOWED_UNLINKED.include?([tracking_id, id]) }
|
|
300
300
|
if ids.any?
|
|
301
|
-
invalid! <<~MSG.rstrip
|
|
301
|
+
invalid! :links_must_be_via_tracking_id, <<~MSG.rstrip
|
|
302
302
|
Used #{ids} in the query, but should only use links in the form of %{<project id>:<monitor id>}
|
|
303
303
|
If that is not possible, add `validate_using_links: ->(*){} # linked monitors are not in kennel
|
|
304
304
|
MSG
|
data/lib/kennel/models/record.rb
CHANGED
|
@@ -211,8 +211,8 @@ module Kennel
|
|
|
211
211
|
end
|
|
212
212
|
end
|
|
213
213
|
|
|
214
|
-
def invalid!(message)
|
|
215
|
-
unfiltered_validation_errors << ValidationMessage.new(message)
|
|
214
|
+
def invalid!(tag, message)
|
|
215
|
+
unfiltered_validation_errors << ValidationMessage.new(tag || OptionalValidations::UNIGNORABLE, message)
|
|
216
216
|
end
|
|
217
217
|
|
|
218
218
|
def raise_with_location(error, message)
|
data/lib/kennel/models/slo.rb
CHANGED
|
@@ -85,7 +85,7 @@ module Kennel
|
|
|
85
85
|
super
|
|
86
86
|
|
|
87
87
|
if data[:thresholds].any? { |t| t[:warning] && t[:warning].to_f <= t[:critical].to_f }
|
|
88
|
-
invalid! "Threshold warning must be greater-than critical value"
|
|
88
|
+
invalid! :warning_must_be_gt_critical, "Threshold warning must be greater-than critical value"
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
end
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module Kennel
|
|
3
3
|
module OptionalValidations
|
|
4
|
-
ValidationMessage = Struct.new(:text)
|
|
4
|
+
ValidationMessage = Struct.new(:tag, :text)
|
|
5
|
+
|
|
6
|
+
UNIGNORABLE = :unignorable
|
|
5
7
|
|
|
6
8
|
def self.included(base)
|
|
7
|
-
base.settings :
|
|
8
|
-
base.defaults(
|
|
9
|
+
base.settings :ignored_errors
|
|
10
|
+
base.defaults(ignored_errors: -> { [] })
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def self.valid?(parts)
|
|
@@ -15,14 +17,26 @@ module Kennel
|
|
|
15
17
|
|
|
16
18
|
return true if parts_with_errors.empty?
|
|
17
19
|
|
|
20
|
+
example_tag = nil
|
|
21
|
+
|
|
18
22
|
Kennel.err.puts
|
|
19
23
|
parts_with_errors.sort_by(&:safe_tracking_id).each do |part|
|
|
20
24
|
part.filtered_validation_errors.each do |err|
|
|
21
|
-
Kennel.err.puts "#{part.safe_tracking_id} #{err.text}"
|
|
25
|
+
Kennel.err.puts "#{part.safe_tracking_id} [#{err.tag.inspect}] #{err.text.gsub("\n", " ")}"
|
|
26
|
+
example_tag = err.tag unless err.tag == :unignorable
|
|
22
27
|
end
|
|
23
28
|
end
|
|
24
29
|
Kennel.err.puts
|
|
25
30
|
|
|
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
|
+
)
|
|
37
|
+
|
|
38
|
+
MESSAGE
|
|
39
|
+
|
|
26
40
|
false
|
|
27
41
|
end
|
|
28
42
|
|
|
@@ -32,6 +46,7 @@ module Kennel
|
|
|
32
46
|
bad = Kennel::Utils.all_keys(data).grep_v(Symbol).sort.uniq
|
|
33
47
|
return if bad.empty?
|
|
34
48
|
invalid!(
|
|
49
|
+
:hash_keys_must_be_symbols,
|
|
35
50
|
"Only use Symbols as hash keys to avoid permanent diffs when updating.\n" \
|
|
36
51
|
"Change these keys to be symbols (usually 'foo' => 1 --> 'foo': 1)\n" \
|
|
37
52
|
"#{bad.map(&:inspect).join("\n")}"
|
|
@@ -39,39 +54,30 @@ module Kennel
|
|
|
39
54
|
end
|
|
40
55
|
|
|
41
56
|
def filter_validation_errors
|
|
42
|
-
if
|
|
43
|
-
|
|
44
|
-
elsif unfiltered_validation_errors.empty?
|
|
45
|
-
msg = "`validate` is set to false, but there are no validation errors to suppress. Remove `validate: false`"
|
|
46
|
-
|
|
47
|
-
mode = ENV.fetch("UNNECESSARY_VALIDATE_FALSE") do
|
|
48
|
-
if ENV.key?("PROJECT") || ENV.key?("TRACKING_ID")
|
|
49
|
-
"fail"
|
|
50
|
-
else
|
|
51
|
-
nil
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
if mode == "fail"
|
|
56
|
-
[ValidationMessage.new(msg)]
|
|
57
|
-
else
|
|
58
|
-
Kennel.out.puts "#{safe_tracking_id} #{msg}" if mode == "show"
|
|
57
|
+
if unfiltered_validation_errors.empty?
|
|
58
|
+
if ignored_errors.empty?
|
|
59
59
|
[]
|
|
60
|
+
else
|
|
61
|
+
[ValidationMessage.new(UNIGNORABLE, "`ignored_errors` is non-empty, but there are no errors to ignore. Remove `ignored_errors`")]
|
|
60
62
|
end
|
|
61
63
|
else
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Kennel.out.puts "#{safe_tracking_id} `validate: false` suppressing error: #{err.text.gsub("\n", " ")}"
|
|
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
|
|
68
|
+
else
|
|
69
|
+
unfiltered_validation_errors.reject do |err|
|
|
70
|
+
err.tag != UNIGNORABLE && ignored_errors.include?(err.tag)
|
|
70
71
|
end
|
|
71
72
|
end
|
|
72
73
|
|
|
73
|
-
|
|
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`")
|
|
74
78
|
end
|
|
79
|
+
|
|
80
|
+
to_report
|
|
75
81
|
end
|
|
76
82
|
end
|
|
77
83
|
end
|
data/lib/kennel/progress.rb
CHANGED
|
@@ -4,8 +4,8 @@ require "benchmark"
|
|
|
4
4
|
module Kennel
|
|
5
5
|
class Progress
|
|
6
6
|
# print what we are doing and a spinner until it is done ... then show how long it took
|
|
7
|
-
def self.progress(name, interval: 0.2, &block)
|
|
8
|
-
return progress_no_tty(name, &block)
|
|
7
|
+
def self.progress(name, interval: 0.2, plain: false, &block)
|
|
8
|
+
return progress_no_tty(name, &block) if plain || !Kennel.err.tty?
|
|
9
9
|
|
|
10
10
|
Kennel.err.print "#{name} ... "
|
|
11
11
|
|
data/lib/kennel/syncer.rb
CHANGED
|
@@ -10,12 +10,13 @@ module Kennel
|
|
|
10
10
|
Plan = Struct.new(:changes, keyword_init: true)
|
|
11
11
|
Change = Struct.new(:type, :api_resource, :tracking_id, :id)
|
|
12
12
|
|
|
13
|
-
def initialize(api, expected, kennel:, project_filter: nil, tracking_id_filter: nil)
|
|
13
|
+
def initialize(api, expected, actual, kennel:, project_filter: nil, tracking_id_filter: nil)
|
|
14
14
|
@api = api
|
|
15
15
|
@kennel = kennel
|
|
16
16
|
@project_filter = project_filter
|
|
17
17
|
@tracking_id_filter = tracking_id_filter
|
|
18
18
|
@expected = Set.new expected # need set to speed up deletion
|
|
19
|
+
@actual = actual
|
|
19
20
|
calculate_diff
|
|
20
21
|
validate_plan
|
|
21
22
|
prevent_irreversible_partial_updates
|
|
@@ -53,7 +54,7 @@ module Kennel
|
|
|
53
54
|
message = "#{e.class.api_resource} #{e.tracking_id}"
|
|
54
55
|
Kennel.out.puts "Creating #{message}"
|
|
55
56
|
reply = @api.create e.class.api_resource, e.as_json
|
|
56
|
-
|
|
57
|
+
Utils.inline_resource_metadata reply, e.class
|
|
57
58
|
id = reply.fetch(:id)
|
|
58
59
|
changes << Change.new(:create, e.class.api_resource, e.tracking_id, id)
|
|
59
60
|
populate_id_map [], [reply] # allow resolving ids we could previously no resolve
|
|
@@ -125,17 +126,15 @@ module Kennel
|
|
|
125
126
|
@delete = []
|
|
126
127
|
@id_map = IdMap.new
|
|
127
128
|
|
|
128
|
-
actual = Progress.progress("Downloading definitions") { download_definitions }
|
|
129
|
-
|
|
130
129
|
Progress.progress "Diffing" do
|
|
131
|
-
populate_id_map @expected, actual
|
|
132
|
-
filter_actual! actual
|
|
130
|
+
populate_id_map @expected, @actual
|
|
131
|
+
filter_actual! @actual
|
|
133
132
|
resolve_linked_tracking_ids! @expected # resolve dependencies to avoid diff
|
|
134
133
|
|
|
135
134
|
@expected.each(&:add_tracking_id) # avoid diff with actual
|
|
136
135
|
|
|
137
136
|
lookup_map = matching_expected_lookup_map
|
|
138
|
-
items = actual.map do |a|
|
|
137
|
+
items = @actual.map do |a|
|
|
139
138
|
e = matching_expected(a, lookup_map)
|
|
140
139
|
if e && @expected.delete?(e)
|
|
141
140
|
[e, a]
|
|
@@ -168,19 +167,6 @@ module Kennel
|
|
|
168
167
|
end
|
|
169
168
|
end
|
|
170
169
|
|
|
171
|
-
def download_definitions
|
|
172
|
-
Utils.parallel(Models::Record.subclasses) do |klass|
|
|
173
|
-
results = @api.list(klass.api_resource, with_downtimes: false) # lookup monitors without adding unnecessary downtime information
|
|
174
|
-
results = results[results.keys.first] if results.is_a?(Hash) # dashboards are nested in {dashboards: []}
|
|
175
|
-
results.each { |a| cache_metadata(a, klass) }
|
|
176
|
-
end.flatten(1)
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def cache_metadata(a, klass)
|
|
180
|
-
a[:klass] = klass
|
|
181
|
-
a[:tracking_id] = a.fetch(:klass).parse_tracking_id(a)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
170
|
def ensure_all_ids_found
|
|
185
171
|
@expected.each do |e|
|
|
186
172
|
next unless id = e.id
|
data/lib/kennel/tasks.rb
CHANGED
|
@@ -102,8 +102,9 @@ namespace :kennel do
|
|
|
102
102
|
|
|
103
103
|
# also generate parts so users see and commit updated generated automatically
|
|
104
104
|
desc "show planned datadog changes (scope with PROJECT=name)"
|
|
105
|
-
task plan: :
|
|
105
|
+
task plan: :environment do
|
|
106
106
|
Kennel::Tasks.kennel.plan
|
|
107
|
+
Kennel::Tasks.kennel.generate
|
|
107
108
|
end
|
|
108
109
|
|
|
109
110
|
desc "update datadog (scope with PROJECT=name)"
|
|
@@ -29,8 +29,8 @@ module Kennel
|
|
|
29
29
|
return if queries.empty?
|
|
30
30
|
|
|
31
31
|
invalid!(
|
|
32
|
-
|
|
33
|
-
"
|
|
32
|
+
:queries_must_use_template_variables,
|
|
33
|
+
"queries #{queries.join(", ")} must use the template variables #{variables.join(", ")}"
|
|
34
34
|
)
|
|
35
35
|
end
|
|
36
36
|
|
data/lib/kennel/utils.rb
CHANGED
data/lib/kennel/version.rb
CHANGED
data/lib/kennel.rb
CHANGED
|
@@ -59,7 +59,7 @@ module Kennel
|
|
|
59
59
|
attr_accessor :strict_imports
|
|
60
60
|
|
|
61
61
|
def generate
|
|
62
|
-
parts = generated
|
|
62
|
+
parts = generated(plain: false)
|
|
63
63
|
parts_serializer.write(parts) if ENV["STORE"] != "false" # quicker when debugging
|
|
64
64
|
parts
|
|
65
65
|
end
|
|
@@ -75,12 +75,24 @@ module Kennel
|
|
|
75
75
|
|
|
76
76
|
private
|
|
77
77
|
|
|
78
|
+
def download_definitions
|
|
79
|
+
Progress.progress("Downloading definitions", plain: true) do
|
|
80
|
+
Utils.parallel(Models::Record.subclasses) do |klass|
|
|
81
|
+
results = api.list(klass.api_resource, with_downtimes: false) # lookup monitors without adding unnecessary downtime information
|
|
82
|
+
results.each { |a| Utils.inline_resource_metadata(a, klass) }
|
|
83
|
+
end.flatten(1)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
78
87
|
def filter
|
|
79
88
|
@filter ||= Filter.new
|
|
80
89
|
end
|
|
81
90
|
|
|
82
91
|
def syncer
|
|
83
|
-
@syncer ||=
|
|
92
|
+
@syncer ||= begin
|
|
93
|
+
expected, actual = Utils.parallel([:generated, :download_definitions]) { |m| send m }
|
|
94
|
+
Syncer.new(api, expected, actual, kennel: self, project_filter: filter.project_filter, tracking_id_filter: filter.tracking_id_filter)
|
|
95
|
+
end
|
|
84
96
|
end
|
|
85
97
|
|
|
86
98
|
def api
|
|
@@ -95,9 +107,9 @@ module Kennel
|
|
|
95
107
|
@parts_serializer ||= PartsSerializer.new(filter: filter)
|
|
96
108
|
end
|
|
97
109
|
|
|
98
|
-
def generated
|
|
110
|
+
def generated(plain: true)
|
|
99
111
|
@generated ||= begin
|
|
100
|
-
parts = Progress.progress "Finding parts" do
|
|
112
|
+
parts = Progress.progress "Finding parts", plain: plain do
|
|
101
113
|
projects = projects_provider.projects
|
|
102
114
|
projects = filter.filter_projects projects
|
|
103
115
|
|
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.126.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: 2022-
|
|
11
|
+
date: 2022-12-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|