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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2464486b37bc4711843991d93789d4844d9c9aac33b40cf8b088ec4b249f39ab
4
- data.tar.gz: 794318abfb10fecca670f6a8d2ad22e6f117e8425d0dc964b76163a658593d16
3
+ metadata.gz: d8254e03670c1874a6f9b85da5fe453200cdcd9aabe2f82dafc424a6301d8416
4
+ data.tar.gz: 2d1c0b444762941f79fcba893f5cddc9d0119138c5cf0fc04d6fc871f2caf7a8
5
5
  SHA512:
6
- metadata.gz: 97c3664e939d5776bf4dcc2a8fc72b647af0a8df0112035cadd6105e8117dd116097e85e60838050f96959df5ea0e17d7e00ffe89420712937a32190bcce70ca
7
- data.tar.gz: 4a1cf01f15ac6959c7cea683297fb41f3a74791a99b3a16c5391f76af48333d9a1b4ea9a9fc804750844feb75d5c02fae0caced96fc3b85723d128d1ad45e289
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
- invalid! "template_variable_presets must be sorted by name" if presets && presets != presets.sort_by { |p| p[:name] }
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
@@ -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)
@@ -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 :validate
8
- base.defaults(validate: -> { true })
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 validate
43
- unfiltered_validation_errors
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
- mode = ENV.fetch("SUPPRESSED_ERRORS", "ignore")
63
-
64
- if mode == "fail"
65
- unfiltered_validation_errors
66
- else
67
- if mode == "show"
68
- unfiltered_validation_errors.each do |err|
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
@@ -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) unless Kennel.err.tty?
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
- cache_metadata reply, e.class
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: :generate do
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
- "queries #{queries.join(", ")} must use the template variables #{variables.join(", ")}\n" \
33
- "If that is not possible, add `validate: -> { false } # query foo in bar does not have baz tag`"
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
@@ -160,6 +160,11 @@ module Kennel
160
160
  end
161
161
  string
162
162
  end
163
+
164
+ def inline_resource_metadata(resource, klass)
165
+ resource[:klass] = klass
166
+ resource[:tracking_id] = klass.parse_tracking_id(resource)
167
+ end
163
168
  end
164
169
  end
165
170
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.125.0"
3
+ VERSION = "1.126.0"
4
4
  end
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 ||= Syncer.new(api, generated, kennel: self, project_filter: filter.project_filter, tracking_id_filter: filter.tracking_id_filter)
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.125.0
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-30 00:00:00.000000000 Z
11
+ date: 2022-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs