kennel 2.6.1 → 2.8.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: bb95ceae817bc98d083407c5a6b19483e12e1932a98209c13fd097cd9bcf7ebd
4
- data.tar.gz: 1a0c92f146ab14ffe80bccc86cb12f1561e8d874faefbe7f48f550a4c108cb6e
3
+ metadata.gz: bebf47eb6fde98d7122a199324dca2c205b94a65e844193d7917f2e384fbc029
4
+ data.tar.gz: c3631e51b4d7ef363b7fd177e42d688e00f7ea9816b9161b27dd2f48753fff93
5
5
  SHA512:
6
- metadata.gz: 2b43e7160e79a8174ae60d929d998322cfda2730116500ce8e6ddbfd4114c2217b0d75c5f326ffafbc94c5976155810a858f6d43423430223fa9325207373a75
7
- data.tar.gz: b9c53626a278cea303b617ec12f792f89b5a5ca858a20dd75f244367a38065ed436e5309184b04e8c5ba5f7060a5c5dab472837b2729c2313ab6cb730d6f0bef
6
+ metadata.gz: 7029a570da24413841e71dc380a7f07d476c9cff3b83aee05a032478ed1707771b96f071949e2284003ef51777f955174c9d241a0e11c2d6fbd6aadc1d4ccece
7
+ data.tar.gz: b5df9cfcd85ff76b762c9c8a96e630f1b3cf509eb8509e71a42fb2d5c8b24efd6588a88033abe38c2d1ff5b37f8087d2e0074be171f3e78066c1796df53f72b4
data/lib/kennel/api.rb CHANGED
@@ -73,6 +73,7 @@ module Kennel
73
73
  end
74
74
  end
75
75
 
76
+ # fill the resource with the full response from the `show` if `list` does not return it
76
77
  def fill_details!(api_resource, list)
77
78
  details_cache do |cache|
78
79
  Utils.parallel(list) { |a| fill_detail!(api_resource, a, cache) }
@@ -208,9 +208,10 @@ module Kennel
208
208
  end
209
209
  end
210
210
 
211
- def validate_update!(diffs)
212
- _, path, from, to = diffs.find { |diff| diff[1] == "layout_type" }
213
- invalid_update!(path, from, to) if path
211
+ def allowed_update_error(actual)
212
+ actual_type = actual[:layout_type]
213
+ return if actual_type == layout_type
214
+ "cannot update layout_type from #{actual_type} to #{layout_type}"
214
215
  end
215
216
 
216
217
  private
@@ -180,12 +180,11 @@ module Kennel
180
180
  end
181
181
  end
182
182
 
183
- def validate_update!(diffs)
184
- # ensure type does not change, but not if it's metric->query which is supported and used by importer.rb
185
- _, path, from, to = diffs.detect { |_, path, _, _| path == "type" }
186
- if path && !(from == "metric alert" && to == "query alert")
187
- invalid_update!(path, from, to)
188
- end
183
+ # ensure type does not change, but not if it's metric->query which is supported and used by importer.rb
184
+ def allowed_update_error(actual)
185
+ actual_type = actual[:type]
186
+ return if actual_type == type || (actual_type == "metric alert" && type == "query alert")
187
+ "cannot update type from #{actual_type} to #{type}"
189
188
  end
190
189
 
191
190
  def self.api_resource
@@ -18,7 +18,7 @@ module Kennel
18
18
  end
19
19
 
20
20
  def validated_parts
21
- all = parts
21
+ all = filter_parts(parts)
22
22
  unless all.is_a?(Array) && all.all? { |part| part.is_a?(Record) }
23
23
  raise "Project #{kennel_id} #parts must return an array of Records"
24
24
  end
@@ -29,6 +29,11 @@ module Kennel
29
29
 
30
30
  private
31
31
 
32
+ # hook for users to add custom filtering via `prepend`
33
+ def filter_parts(parts)
34
+ parts
35
+ end
36
+
32
37
  # hook for users to add custom validations via `prepend`
33
38
  def validate_parts(parts)
34
39
  end
@@ -142,12 +142,8 @@ module Kennel
142
142
  @as_json
143
143
  end
144
144
 
145
- # Can raise DisallowedUpdateError
146
- def validate_update!(_diffs)
147
- end
148
-
149
- def invalid_update!(field, old_value, new_value)
150
- raise DisallowedUpdateError, "#{safe_tracking_id} Datadog does not allow update of #{field} (#{old_value.inspect} -> #{new_value.inspect})"
145
+ def allowed_update_error(_a)
146
+ nil
151
147
  end
152
148
 
153
149
  # For use during error handling
@@ -36,7 +36,13 @@ module Kennel
36
36
 
37
37
  def matching_expected(a, map)
38
38
  klass = a.fetch(:klass)
39
- map["#{klass.api_resource}:#{a.fetch(:id)}"] || map[a.fetch(:tracking_id)]
39
+ full_id = "#{klass.api_resource}:#{a.fetch(:id)}"
40
+ if (e = map[full_id]) # we try to update and the user has set the id
41
+ return e unless (error = e.allowed_update_error(a))
42
+ raise DisallowedUpdateError, "#{full_id} Datadog does not allow update: #{error}"
43
+ elsif (e = map[a.fetch(:tracking_id)])
44
+ e.allowed_update_error(a) ? nil : e # force a re-create if we can't update
45
+ end
40
46
  end
41
47
  end
42
48
  end
@@ -12,9 +12,9 @@ module Kennel
12
12
  if plan.empty?
13
13
  Kennel.out.puts Console.color(:green, "Nothing to do")
14
14
  else
15
+ print_changes "Delete", plan.deletes, :red
15
16
  print_changes "Create", plan.creates, :green
16
17
  print_changes "Update", plan.updates, :yellow
17
- print_changes "Delete", plan.deletes, :red
18
18
  end
19
19
  end
20
20
 
data/lib/kennel/syncer.rb CHANGED
@@ -13,14 +13,13 @@ module Kennel
13
13
 
14
14
  attr_reader :plan
15
15
 
16
- def initialize(api, expected, actual, filter:, strict_imports: true)
16
+ def initialize(api, expected:, actual:, filter:, strict_imports: true)
17
17
  @api = api
18
18
  @strict_imports = strict_imports
19
19
  @filter = filter
20
20
 
21
21
  @resolver = Resolver.new(expected: expected, filter: filter)
22
22
  @plan = Plan.new(*calculate_changes(expected: expected, actual: actual))
23
- validate_changes
24
23
  end
25
24
 
26
25
  def print_plan
@@ -126,27 +125,19 @@ module Kennel
126
125
  # if there is a new item that has the same name or title as an "to be deleted" item,
127
126
  # update it instead to avoid old urls from becoming invalid
128
127
  # - careful with unmatched_actual being huge since it has all api resources
129
- # - don't do it when a monitor type is changing since that would block the update
130
- # - don't do it when a dashboard layout_type is changing since that would block the update
131
- # - when using a filter and updating the kennel_id of an existing item, old and new must be in the filter
128
+ # - don't do it when update is not allowed
129
+ # - when using a filter and updating the kennel_id of an existing item, old and new must be in the filter (PROJECT= works, but not TRACKING_ID)
132
130
  def convert_replace_into_update!(matching, unmatched_actual, unmatched_expected)
133
131
  unmatched_expected.reject! do |e|
134
- e_field, e_value = Kennel::Models::Record::TITLE_FIELDS.detect do |field|
135
- next unless (value = e.as_json[field])
136
- break [field, value]
137
- end
138
- raise unless e_field # uncovered: should never happen ...
139
- # TODO: ideally reuse invalid_update! logic and just put the fields somewhere
140
- e_monitor_type = e.as_json[:type]
141
- e_dashboard_layout_type = e.as_json[:layout_type]
142
-
132
+ # find actual by title
133
+ e_field, e_value = title_field_and_value(e)
143
134
  actual = unmatched_actual.detect do |a|
144
135
  a[:klass].api_resource == e.class.api_resource &&
145
- a[e_field] == e_value &&
146
- a[:type] == e_monitor_type &&
147
- a[:layout_type] == e_dashboard_layout_type
136
+ a[e_field] == e_value
148
137
  end
149
- next false unless actual # keep in unmatched
138
+
139
+ # keep unmatched if we could not find or can't update
140
+ next false if !actual || e.allowed_update_error(actual)
150
141
 
151
142
  # add as update and remove from unmatched
152
143
  unmatched_actual.delete(actual)
@@ -156,10 +147,18 @@ module Kennel
156
147
  end
157
148
  end
158
149
 
159
- # fill details of things we need to compare
160
- def fill_details!(details_needed)
161
- details_needed = details_needed.map { |e, a| a if e && e.class.api_resource == "dashboard" }.compact
162
- @api.fill_details! "dashboard", details_needed
150
+ def title_field_and_value(e)
151
+ Kennel::Models::Record::TITLE_FIELDS.detect do |field|
152
+ next unless (value = e.as_json[field])
153
+ return [field, value]
154
+ end
155
+ raise # uncovered: should never happen ...
156
+ end
157
+
158
+ # fill details of things we need to compare, so diff works even though we cannot mass-fetch definitions
159
+ def fill_details!(matching)
160
+ dashboards = matching.filter_map { |e, a| a if e && e.class.api_resource == "dashboard" }
161
+ @api.fill_details! "dashboard", dashboards
163
162
  end
164
163
 
165
164
  def validate_expected_id_not_missing(expected)
@@ -175,14 +174,6 @@ module Kennel
175
174
  end
176
175
  end
177
176
 
178
- # We've already validated the desired objects ('generated') in isolation.
179
- # Now that we have made the plan, we can perform some more validation.
180
- def validate_changes
181
- @plan.updates.each do |item|
182
- item.expected.validate_update!(item.diff)
183
- end
184
- end
185
-
186
177
  def filter_actual!(actual)
187
178
  return unless filter.filtering? # minor optimization
188
179
 
data/lib/kennel/tasks.rb CHANGED
@@ -109,7 +109,7 @@ namespace :kennel do
109
109
  end
110
110
  end
111
111
 
112
- desc "generate local definitions"
112
+ desc "store definitions in generated/"
113
113
  task generate: :environment do
114
114
  Kennel::Tasks.kennel.generate
115
115
  end
data/lib/kennel/utils.rb CHANGED
@@ -24,13 +24,14 @@ module Kennel
24
24
  workers = Array.new(threads).map do
25
25
  Thread.new do
26
26
  loop do
27
- item, i = work.pop
27
+ item, i = work.shift
28
28
  break unless i
29
29
  done[i] =
30
30
  begin
31
31
  yield item
32
32
  rescue Exception => e # rubocop:disable Lint/RescueException
33
- work.clear
33
+ work.clear # prevent new work
34
+ (workers - [Thread.current]).each(&:kill) # stop ongoing work
34
35
  e
35
36
  end
36
37
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "2.6.1"
3
+ VERSION = "2.8.0"
4
4
  end
data/lib/kennel.rb CHANGED
@@ -56,7 +56,7 @@ module Kennel
56
56
  self.err = $stderr
57
57
 
58
58
  class Engine
59
- attr_accessor :strict_imports
59
+ attr_accessor :strict_imports # TODO: rename to :enforce_expected_ids_exist
60
60
 
61
61
  def initialize
62
62
  @strict_imports = true
@@ -93,7 +93,9 @@ module Kennel
93
93
  @syncer ||= begin
94
94
  preload
95
95
  Syncer.new(
96
- api, generated, definitions,
96
+ api,
97
+ expected: generated,
98
+ actual: definitions,
97
99
  filter: filter,
98
100
  strict_imports: strict_imports
99
101
  )
@@ -129,7 +131,7 @@ module Kennel
129
131
  end
130
132
  end
131
133
 
132
- # performance: this takes ~100ms on large codebases, tried rewriting with Set or Hash but it was slower
134
+ # performance: this takes ~100ms on large codebases, tried rewriting with Set or Hash, but it was slower
133
135
  def validate_unique_tracking_ids(parts)
134
136
  bad = parts.group_by(&:tracking_id).select { |_, same| same.size > 1 }
135
137
  return if bad.empty?
@@ -143,7 +145,10 @@ module Kennel
143
145
  def definitions(**kwargs)
144
146
  @definitions ||= Progress.progress("Downloading definitions", **kwargs) do
145
147
  Utils.parallel(Models::Record.subclasses) do |klass|
146
- api.list(klass.api_resource, with_downtimes: false) # lookup monitors without adding unnecessary downtime information
148
+ # lookup monitors without adding unnecessary downtime information
149
+ params = (klass.api_resource == "monitor" ? { with_downtimes: false } : {})
150
+
151
+ api.list(klass.api_resource, params)
147
152
  end.flatten(1)
148
153
  end
149
154
  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: 2.6.1
4
+ version: 2.8.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: 2025-10-30 00:00:00.000000000 Z
11
+ date: 2025-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs