kennel 1.130.0 → 1.131.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/api.rb +2 -0
- data/lib/kennel/file_cache.rb +3 -1
- data/lib/kennel/models/dashboard.rb +1 -1
- data/lib/kennel/models/monitor.rb +1 -1
- data/lib/kennel/models/record.rb +1 -1
- data/lib/kennel/syncer.rb +264 -178
- data/lib/kennel/version.rb +1 -1
- 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: ac3eeb3c1ec0e2b03c7acbf2b2d7c68335207a7e79408775e9b876c6c4acd400
|
4
|
+
data.tar.gz: db96fddb04bce543efcea1a505a1d360447161259e9e9fdd481acfe785bad194
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a710b2e2ae6932cf36bdd12fbb06c34f8b538944e693a484b294e7614b7d9941e7c2da498cf7cdcb56bda255a0b2c874383306437ee5b71456d900ae1e9a5c2f
|
7
|
+
data.tar.gz: 90e767722863bb640f3bd96436937c390d40d8c8b868d3ee51a444ff6e042e36f03048cc1b5c9cec49d96207d53d99df6f529318658c3881fd9078ed55c2b7d8
|
data/lib/kennel/api.rb
CHANGED
data/lib/kennel/file_cache.rb
CHANGED
@@ -57,7 +57,9 @@ module Kennel
|
|
57
57
|
# - delete expired keys
|
58
58
|
# - delete what would be deleted anyway when updating
|
59
59
|
def expire_old_data
|
60
|
-
@data.reject!
|
60
|
+
@data.reject! do |(_api_resource, _id), (_value, (_key_version, cache_version), expires)|
|
61
|
+
expires < @now || cache_version != @cache_version
|
62
|
+
end
|
61
63
|
end
|
62
64
|
end
|
63
65
|
end
|
@@ -137,7 +137,7 @@ module Kennel
|
|
137
137
|
end
|
138
138
|
end
|
139
139
|
|
140
|
-
def validate_update!(
|
140
|
+
def validate_update!(diffs)
|
141
141
|
# ensure type does not change, but not if it's metric->query which is supported and used by importer.rb
|
142
142
|
_, path, from, to = diffs.detect { |_, path, _, _| path == "type" }
|
143
143
|
if path && !(from == "metric alert" && to == "query alert")
|
data/lib/kennel/models/record.rb
CHANGED
data/lib/kennel/syncer.rb
CHANGED
@@ -6,47 +6,43 @@ module Kennel
|
|
6
6
|
LINE_UP = "\e[1A\033[K" # go up and clear
|
7
7
|
|
8
8
|
Plan = Struct.new(:changes, keyword_init: true)
|
9
|
+
|
10
|
+
InternalPlan = Struct.new(:creates, :updates, :deletes) do
|
11
|
+
def empty?
|
12
|
+
creates.empty? && updates.empty? && deletes.empty?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
9
16
|
Change = Struct.new(:type, :api_resource, :tracking_id, :id)
|
10
17
|
|
11
18
|
def initialize(api, expected, actual, strict_imports: true, project_filter: nil, tracking_id_filter: nil)
|
12
19
|
@api = api
|
13
|
-
@expected = Set.new expected # need Set to speed up deletion
|
14
|
-
@actual = actual
|
15
20
|
@strict_imports = strict_imports
|
16
21
|
@project_filter = project_filter
|
17
22
|
@tracking_id_filter = tracking_id_filter
|
18
23
|
|
19
|
-
@
|
24
|
+
@resolver = Resolver.new(expected: expected, project_filter: @project_filter, tracking_id_filter: @tracking_id_filter)
|
20
25
|
|
21
|
-
calculate_changes
|
22
|
-
validate_changes
|
23
|
-
|
26
|
+
internal_plan = calculate_changes(expected: expected, actual: actual)
|
27
|
+
validate_changes(internal_plan)
|
28
|
+
@internal_plan = internal_plan
|
24
29
|
|
25
30
|
@warnings.each { |message| Kennel.out.puts Console.color(:yellow, "Warning: #{message}") }
|
26
31
|
end
|
27
32
|
|
28
33
|
def plan
|
34
|
+
ip = @internal_plan
|
29
35
|
Plan.new(
|
30
|
-
changes:
|
31
|
-
@create.map { |_id, e, _a| Change.new(:create, e.class.api_resource, e.tracking_id, nil) } +
|
32
|
-
@update.map { |id, e, _a| Change.new(:update, e.class.api_resource, e.tracking_id, id) } +
|
33
|
-
@delete.map { |id, _e, a| Change.new(:delete, a.fetch(:klass).api_resource, a.fetch(:tracking_id), id) }
|
36
|
+
changes: (ip.creates + ip.updates + ip.deletes).map(&:change)
|
34
37
|
)
|
35
38
|
end
|
36
39
|
|
37
40
|
def print_plan
|
38
|
-
|
39
|
-
if noop?
|
40
|
-
Kennel.out.puts Console.color(:green, "Nothing to do")
|
41
|
-
else
|
42
|
-
print_changes "Create", @create, :green
|
43
|
-
print_changes "Update", @update, :yellow
|
44
|
-
print_changes "Delete", @delete, :red
|
45
|
-
end
|
41
|
+
PlanDisplayer.new.display(internal_plan)
|
46
42
|
end
|
47
43
|
|
48
44
|
def confirm
|
49
|
-
return false if
|
45
|
+
return false if internal_plan.empty?
|
50
46
|
return true if ENV["CI"] || !STDIN.tty? || !Kennel.err.tty?
|
51
47
|
Console.ask?("Execute Plan ?")
|
52
48
|
end
|
@@ -54,121 +50,77 @@ module Kennel
|
|
54
50
|
def update
|
55
51
|
changes = []
|
56
52
|
|
57
|
-
|
58
|
-
message = "#{
|
53
|
+
internal_plan.deletes.each do |item|
|
54
|
+
message = "#{item.api_resource} #{item.tracking_id} #{item.id}"
|
55
|
+
Kennel.out.puts "Deleting #{message}"
|
56
|
+
@api.delete item.api_resource, item.id
|
57
|
+
changes << item.change
|
58
|
+
Kennel.out.puts "#{LINE_UP}Deleted #{message}"
|
59
|
+
end
|
60
|
+
|
61
|
+
resolver.each_resolved internal_plan.creates do |item|
|
62
|
+
message = "#{item.api_resource} #{item.tracking_id}"
|
59
63
|
Kennel.out.puts "Creating #{message}"
|
60
|
-
reply = @api.create
|
64
|
+
reply = @api.create item.api_resource, item.expected.as_json
|
61
65
|
id = reply.fetch(:id)
|
62
|
-
changes <<
|
63
|
-
|
64
|
-
Kennel.out.puts "#{LINE_UP}Created #{message} #{
|
66
|
+
changes << item.change(id)
|
67
|
+
resolver.add_actual [reply] # allow resolving ids we could previously not resolve
|
68
|
+
Kennel.out.puts "#{LINE_UP}Created #{message} #{item.url(id)}"
|
65
69
|
end
|
66
70
|
|
67
|
-
each_resolved
|
68
|
-
message = "#{
|
71
|
+
resolver.each_resolved internal_plan.updates do |item|
|
72
|
+
message = "#{item.api_resource} #{item.tracking_id} #{item.url}"
|
69
73
|
Kennel.out.puts "Updating #{message}"
|
70
|
-
@api.update
|
71
|
-
changes <<
|
74
|
+
@api.update item.api_resource, item.id, item.expected.as_json
|
75
|
+
changes << item.change
|
72
76
|
Kennel.out.puts "#{LINE_UP}Updated #{message}"
|
73
77
|
end
|
74
78
|
|
75
|
-
@delete.each do |id, _, a|
|
76
|
-
klass = a.fetch(:klass)
|
77
|
-
message = "#{klass.api_resource} #{a.fetch(:tracking_id)} #{id}"
|
78
|
-
Kennel.out.puts "Deleting #{message}"
|
79
|
-
@api.delete klass.api_resource, id
|
80
|
-
changes << Change.new(:delete, klass.api_resource, a.fetch(:tracking_id), id)
|
81
|
-
Kennel.out.puts "#{LINE_UP}Deleted #{message}"
|
82
|
-
end
|
83
|
-
|
84
79
|
Plan.new(changes: changes)
|
85
80
|
end
|
86
81
|
|
87
82
|
private
|
88
83
|
|
89
|
-
|
90
|
-
# this solves cases like composite monitors depending on each other or monitor->monitor slo->slo monitor chains
|
91
|
-
def each_resolved(list)
|
92
|
-
list = list.dup
|
93
|
-
loop do
|
94
|
-
return if list.empty?
|
95
|
-
list.reject! do |id, e|
|
96
|
-
if resolved?(e)
|
97
|
-
yield id, e
|
98
|
-
true
|
99
|
-
else
|
100
|
-
false
|
101
|
-
end
|
102
|
-
end ||
|
103
|
-
assert_resolved(list[0][1]) # resolve something or show a circular dependency error
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
# TODO: optimize by storing an instance variable if already resolved
|
108
|
-
def resolved?(e)
|
109
|
-
assert_resolved e
|
110
|
-
true
|
111
|
-
rescue UnresolvableIdError
|
112
|
-
false
|
113
|
-
end
|
114
|
-
|
115
|
-
# raises UnresolvableIdError when not resolved
|
116
|
-
def assert_resolved(e)
|
117
|
-
resolve_linked_tracking_ids! [e], force: true
|
118
|
-
end
|
119
|
-
|
120
|
-
def noop?
|
121
|
-
@create.empty? && @update.empty? && @delete.empty?
|
122
|
-
end
|
84
|
+
attr_reader :resolver, :internal_plan
|
123
85
|
|
124
|
-
def calculate_changes
|
86
|
+
def calculate_changes(expected:, actual:)
|
125
87
|
@warnings = []
|
126
|
-
@id_map = IdMap.new
|
127
88
|
|
128
89
|
Progress.progress "Diffing" do
|
129
|
-
|
130
|
-
filter_actual!
|
131
|
-
|
132
|
-
@expected.each(&:add_tracking_id) # avoid diff with actual, which has tracking_id
|
90
|
+
resolver.add_actual actual
|
91
|
+
filter_actual! actual
|
92
|
+
resolver.resolve_as_much_as_possible(expected) # resolve as many dependencies as possible to reduce the diff
|
133
93
|
|
134
94
|
# see which expected match the actual
|
135
|
-
matching, unmatched_expected, unmatched_actual =
|
95
|
+
matching, unmatched_expected, unmatched_actual = MatchedExpected.partition(expected, actual)
|
136
96
|
validate_expected_id_not_missing unmatched_expected
|
137
97
|
fill_details! matching # need details to diff later
|
138
98
|
|
139
99
|
# update matching if needed
|
140
|
-
|
100
|
+
updates = matching.map do |e, a|
|
101
|
+
# Refuse to "adopt" existing items into kennel while running with a filter (i.e. on a branch).
|
102
|
+
# Without this, we'd adopt an item, then the next CI run would delete it
|
103
|
+
# (instead of "unadopting" it).
|
104
|
+
e.add_tracking_id unless @project_filter && a.fetch(:tracking_id).nil?
|
141
105
|
id = a.fetch(:id)
|
142
106
|
diff = e.diff(a)
|
143
|
-
[id
|
107
|
+
a[:id] = id
|
108
|
+
Types::PlannedUpdate.new(e, a, diff) if diff.any?
|
144
109
|
end.compact
|
145
110
|
|
146
111
|
# delete previously managed
|
147
|
-
|
112
|
+
deletes = unmatched_actual.map { |a| Types::PlannedDelete.new(a) if a.fetch(:tracking_id) }.compact
|
148
113
|
|
149
114
|
# unmatched expected need to be created
|
150
|
-
|
115
|
+
unmatched_expected.each(&:add_tracking_id)
|
116
|
+
creates = unmatched_expected.map { |e| Types::PlannedCreate.new(e) }
|
151
117
|
|
152
118
|
# order to avoid deadlocks
|
153
|
-
|
154
|
-
|
155
|
-
end
|
156
|
-
end
|
119
|
+
deletes.sort_by! { |item| DELETE_ORDER.index item.api_resource }
|
120
|
+
updates.sort_by! { |item| DELETE_ORDER.index item.api_resource } # slo needs to come before slo alert
|
157
121
|
|
158
|
-
|
159
|
-
|
160
|
-
unmatched_expected = @expected.dup
|
161
|
-
unmatched_actual = []
|
162
|
-
matched = []
|
163
|
-
@actual.each do |a|
|
164
|
-
e = matching_expected(a, lookup_map)
|
165
|
-
if e && unmatched_expected.delete?(e)
|
166
|
-
matched << [e, a]
|
167
|
-
else
|
168
|
-
unmatched_actual << a
|
169
|
-
end
|
170
|
-
end.compact
|
171
|
-
[matched, unmatched_expected, unmatched_actual]
|
122
|
+
InternalPlan.new(creates, updates, deletes)
|
123
|
+
end
|
172
124
|
end
|
173
125
|
|
174
126
|
# fill details of things we need to compare
|
@@ -189,108 +141,242 @@ module Kennel
|
|
189
141
|
end
|
190
142
|
end
|
191
143
|
|
192
|
-
#
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
keys.compact.each do |key|
|
198
|
-
raise "Lookup #{key} is duplicated" if all[key]
|
199
|
-
all[key] = e
|
200
|
-
end
|
144
|
+
# We've already validated the desired objects ('generated') in isolation.
|
145
|
+
# Now that we have made the plan, we can perform some more validation.
|
146
|
+
def validate_changes(internal_plan)
|
147
|
+
internal_plan.updates.each do |item|
|
148
|
+
item.expected.validate_update!(item.diff)
|
201
149
|
end
|
202
150
|
end
|
203
151
|
|
204
|
-
def
|
205
|
-
|
206
|
-
|
152
|
+
def filter_actual!(actual)
|
153
|
+
if @tracking_id_filter
|
154
|
+
actual.select! do |a|
|
155
|
+
tracking_id = a.fetch(:tracking_id)
|
156
|
+
!tracking_id || @tracking_id_filter.include?(tracking_id)
|
157
|
+
end
|
158
|
+
elsif @project_filter
|
159
|
+
project_prefixes = @project_filter.map { |p| "#{p}:" }
|
160
|
+
actual.select! do |a|
|
161
|
+
tracking_id = a.fetch(:tracking_id)
|
162
|
+
!tracking_id || tracking_id.start_with?(*project_prefixes)
|
163
|
+
end
|
164
|
+
end
|
207
165
|
end
|
208
166
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
klass = (e ? e.class : a.fetch(:klass))
|
213
|
-
Kennel.out.puts Console.color(color, "#{step} #{klass.api_resource} #{e&.tracking_id || a.fetch(:tracking_id)}")
|
214
|
-
diff&.each { |args| Kennel.out.puts @attribute_differ.format(*args) } # only for update
|
167
|
+
class PlanDisplayer
|
168
|
+
def initialize
|
169
|
+
@attribute_differ = AttributeDiffer.new
|
215
170
|
end
|
216
|
-
end
|
217
171
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
172
|
+
def display(internal_plan)
|
173
|
+
Kennel.out.puts "Plan:"
|
174
|
+
if internal_plan.empty?
|
175
|
+
Kennel.out.puts Console.color(:green, "Nothing to do")
|
176
|
+
else
|
177
|
+
print_changes "Create", internal_plan.creates, :green
|
178
|
+
print_changes "Update", internal_plan.updates, :yellow
|
179
|
+
print_changes "Delete", internal_plan.deletes, :red
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def print_changes(step, list, color)
|
186
|
+
return if list.empty?
|
187
|
+
list.each do |item|
|
188
|
+
Kennel.out.puts Console.color(color, "#{step} #{item.api_resource} #{item.tracking_id}")
|
189
|
+
if item.class::TYPE == :update
|
190
|
+
item.diff.each { |args| Kennel.out.puts @attribute_differ.format(*args) } # only for update
|
191
|
+
end
|
192
|
+
end
|
223
193
|
end
|
224
194
|
end
|
225
195
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
next true if e.class.parse_tracking_id(field.to_sym => actual) # already has tracking id
|
239
|
-
|
240
|
-
field_diff[3] = e.remove_tracking_id # make `rake plan` output match what we are sending
|
241
|
-
actual != field_diff[3] # discard diff if now nothing changes
|
196
|
+
class Resolver
|
197
|
+
def initialize(expected:, project_filter:, tracking_id_filter:)
|
198
|
+
@id_map = IdMap.new
|
199
|
+
@project_filter = project_filter
|
200
|
+
@tracking_id_filter = tracking_id_filter
|
201
|
+
|
202
|
+
# mark everything as new
|
203
|
+
expected.each do |e|
|
204
|
+
id_map.set(e.class.api_resource, e.tracking_id, IdMap::NEW)
|
205
|
+
if e.class.api_resource == "synthetics/tests"
|
206
|
+
id_map.set(Kennel::Models::Monitor.api_resource, e.tracking_id, IdMap::NEW)
|
207
|
+
end
|
242
208
|
end
|
209
|
+
end
|
243
210
|
|
244
|
-
|
211
|
+
def add_actual(actual)
|
212
|
+
# override resources that exist with their id
|
213
|
+
project_prefixes = project_filter&.map { |p| "#{p}:" }
|
214
|
+
|
215
|
+
actual.each do |a|
|
216
|
+
# ignore when not managed by kennel
|
217
|
+
next unless tracking_id = a.fetch(:tracking_id)
|
218
|
+
|
219
|
+
# ignore when deleted from the codebase
|
220
|
+
# (when running with filters we cannot see the other resources in the codebase)
|
221
|
+
api_resource = a.fetch(:klass).api_resource
|
222
|
+
next if
|
223
|
+
!id_map.get(api_resource, tracking_id) &&
|
224
|
+
(!project_prefixes || tracking_id.start_with?(*project_prefixes)) &&
|
225
|
+
(!tracking_id_filter || tracking_id_filter.include?(tracking_id))
|
226
|
+
|
227
|
+
id_map.set(api_resource, tracking_id, a.fetch(:id))
|
228
|
+
if a.fetch(:klass).api_resource == "synthetics/tests"
|
229
|
+
id_map.set(Kennel::Models::Monitor.api_resource, tracking_id, a.fetch(:monitor_id))
|
230
|
+
end
|
231
|
+
end
|
245
232
|
end
|
246
|
-
end
|
247
233
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
@id_map.set(e.class.api_resource, e.tracking_id, IdMap::NEW)
|
252
|
-
if e.class.api_resource == "synthetics/tests"
|
253
|
-
@id_map.set(Kennel::Models::Monitor.api_resource, e.tracking_id, IdMap::NEW)
|
234
|
+
def resolve_as_much_as_possible(expected)
|
235
|
+
expected.each do |e|
|
236
|
+
e.resolve_linked_tracking_ids!(id_map, force: false)
|
254
237
|
end
|
255
238
|
end
|
256
239
|
|
257
|
-
#
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
if a.fetch(:klass).api_resource == "synthetics/tests"
|
273
|
-
@id_map.set(Kennel::Models::Monitor.api_resource, tracking_id, a.fetch(:monitor_id))
|
240
|
+
# loop over items until everything is resolved or crash when we get stuck
|
241
|
+
# this solves cases like composite monitors depending on each other or monitor->monitor slo->slo monitor chains
|
242
|
+
def each_resolved(list)
|
243
|
+
list = list.dup
|
244
|
+
loop do
|
245
|
+
return if list.empty?
|
246
|
+
list.reject! do |item|
|
247
|
+
if resolved?(item.expected)
|
248
|
+
yield item
|
249
|
+
true
|
250
|
+
else
|
251
|
+
false
|
252
|
+
end
|
253
|
+
end ||
|
254
|
+
assert_resolved(list[0].expected) # resolve something or show a circular dependency error
|
274
255
|
end
|
275
256
|
end
|
257
|
+
|
258
|
+
private
|
259
|
+
|
260
|
+
attr_reader :id_map, :project_filter, :tracking_id_filter
|
261
|
+
|
262
|
+
# TODO: optimize by storing an instance variable if already resolved
|
263
|
+
def resolved?(e)
|
264
|
+
assert_resolved e
|
265
|
+
true
|
266
|
+
rescue UnresolvableIdError
|
267
|
+
false
|
268
|
+
end
|
269
|
+
|
270
|
+
# raises UnresolvableIdError when not resolved
|
271
|
+
def assert_resolved(e)
|
272
|
+
e.resolve_linked_tracking_ids!(id_map, force: true)
|
273
|
+
end
|
276
274
|
end
|
277
275
|
|
278
|
-
|
279
|
-
|
276
|
+
module MatchedExpected
|
277
|
+
class << self
|
278
|
+
def partition(expected, actual)
|
279
|
+
lookup_map = matching_expected_lookup_map(expected)
|
280
|
+
unmatched_expected = Set.new(expected) # for efficient deletion
|
281
|
+
unmatched_actual = []
|
282
|
+
matched = []
|
283
|
+
actual.each do |a|
|
284
|
+
e = matching_expected(a, lookup_map)
|
285
|
+
if e && unmatched_expected.delete?(e)
|
286
|
+
matched << [e, a]
|
287
|
+
else
|
288
|
+
unmatched_actual << a
|
289
|
+
end
|
290
|
+
end.compact
|
291
|
+
[matched, unmatched_expected.to_a, unmatched_actual]
|
292
|
+
end
|
293
|
+
|
294
|
+
private
|
295
|
+
|
296
|
+
# index list by all the thing we look up by: tracking id and actual id
|
297
|
+
def matching_expected_lookup_map(expected)
|
298
|
+
expected.each_with_object({}) do |e, all|
|
299
|
+
keys = [e.tracking_id]
|
300
|
+
keys << "#{e.class.api_resource}:#{e.id}" if e.id
|
301
|
+
keys.compact.each do |key|
|
302
|
+
raise "Lookup #{key} is duplicated" if all[key]
|
303
|
+
all[key] = e
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def matching_expected(a, map)
|
309
|
+
klass = a.fetch(:klass)
|
310
|
+
map["#{klass.api_resource}:#{a.fetch(:id)}"] || map[a.fetch(:tracking_id)]
|
311
|
+
end
|
312
|
+
end
|
280
313
|
end
|
281
314
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
315
|
+
module Types
|
316
|
+
class PlannedChange
|
317
|
+
def initialize(klass, tracking_id)
|
318
|
+
@klass = klass
|
319
|
+
@tracking_id = tracking_id
|
287
320
|
end
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
321
|
+
|
322
|
+
def api_resource
|
323
|
+
klass.api_resource
|
324
|
+
end
|
325
|
+
|
326
|
+
def url(id = nil)
|
327
|
+
klass.url(id || self.id)
|
328
|
+
end
|
329
|
+
|
330
|
+
def change(id = nil)
|
331
|
+
Change.new(self.class::TYPE, api_resource, tracking_id, id)
|
293
332
|
end
|
333
|
+
|
334
|
+
attr_reader :klass, :tracking_id
|
335
|
+
end
|
336
|
+
|
337
|
+
class PlannedCreate < PlannedChange
|
338
|
+
TYPE = :create
|
339
|
+
|
340
|
+
def initialize(expected)
|
341
|
+
super(expected.class, expected.tracking_id)
|
342
|
+
@expected = expected
|
343
|
+
end
|
344
|
+
|
345
|
+
attr_reader :expected
|
346
|
+
end
|
347
|
+
|
348
|
+
class PlannedUpdate < PlannedChange
|
349
|
+
TYPE = :update
|
350
|
+
|
351
|
+
def initialize(expected, actual, diff)
|
352
|
+
super(expected.class, expected.tracking_id)
|
353
|
+
@expected = expected
|
354
|
+
@actual = actual
|
355
|
+
@diff = diff
|
356
|
+
@id = actual.fetch(:id)
|
357
|
+
end
|
358
|
+
|
359
|
+
def change
|
360
|
+
super(id)
|
361
|
+
end
|
362
|
+
|
363
|
+
attr_reader :expected, :actual, :diff, :id
|
364
|
+
end
|
365
|
+
|
366
|
+
class PlannedDelete < PlannedChange
|
367
|
+
TYPE = :delete
|
368
|
+
|
369
|
+
def initialize(actual)
|
370
|
+
super(actual.fetch(:klass), actual.fetch(:tracking_id))
|
371
|
+
@actual = actual
|
372
|
+
@id = actual.fetch(:id)
|
373
|
+
end
|
374
|
+
|
375
|
+
def change
|
376
|
+
super(id)
|
377
|
+
end
|
378
|
+
|
379
|
+
attr_reader :actual, :id
|
294
380
|
end
|
295
381
|
end
|
296
382
|
end
|
data/lib/kennel/version.rb
CHANGED
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.131.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:
|
11
|
+
date: 2023-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: diff-lcs
|