kennel 1.128.0 → 1.129.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/attribute_differ.rb +83 -0
- data/lib/kennel/importer.rb +1 -1
- data/lib/kennel/models/record.rb +5 -3
- data/lib/kennel/models/synthetic_test.rb +1 -2
- data/lib/kennel/parts_serializer.rb +18 -17
- data/lib/kennel/syncer.rb +71 -122
- data/lib/kennel/utils.rb +1 -11
- data/lib/kennel/version.rb +1 -1
- data/lib/kennel.rb +13 -15
- metadata +9 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c7690d961a150ea0ba0649edcab513c22ec828988fec2ab631636497274305c7
|
|
4
|
+
data.tar.gz: c5bc7244c4168466f5167e5c322f3a2cdb8e6b9b8ec23a2d12fb343a7648ae06
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 51b47377a8cf688dafd17600b543cc1734eb8511a6ed1b627d8b1180bf70fc66c86f9d4a8e4b2a3ff5e7223edf366e5483d983d9465c5907aaa07f5b9c36ef46
|
|
7
|
+
data.tar.gz: 698c23a4de558382467342b90e7d3eac143a0279653efe9f1b711b3e114a37d8d72f4a9c5d97e652a6646007298f5e93f296c1125caaa4c498195089b0cac730
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "diff/lcs"
|
|
4
|
+
|
|
5
|
+
module Kennel
|
|
6
|
+
class AttributeDiffer
|
|
7
|
+
def initialize
|
|
8
|
+
# min '2' because: -1 makes no sense, 0 does not work with * 2 math, 1 says '1 lines'
|
|
9
|
+
@max_diff_lines = [Integer(ENV.fetch("MAX_DIFF_LINES", "50")), 2].max
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def format(type, field, old, new = nil)
|
|
14
|
+
multiline = false
|
|
15
|
+
if type == "+"
|
|
16
|
+
temp = pretty_inspect(new)
|
|
17
|
+
new = pretty_inspect(old)
|
|
18
|
+
old = temp
|
|
19
|
+
elsif old.is_a?(String) && new.is_a?(String) && (old.include?("\n") || new.include?("\n"))
|
|
20
|
+
multiline = true
|
|
21
|
+
else # ~ and -
|
|
22
|
+
old = pretty_inspect(old)
|
|
23
|
+
new = pretty_inspect(new)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
message =
|
|
27
|
+
if multiline
|
|
28
|
+
" #{type}#{field}\n" +
|
|
29
|
+
multiline_diff(old, new).map { |l| " #{l}" }.join("\n")
|
|
30
|
+
elsif (old + new).size > 100
|
|
31
|
+
" #{type}#{field}\n" \
|
|
32
|
+
" #{old} ->\n" \
|
|
33
|
+
" #{new}"
|
|
34
|
+
else
|
|
35
|
+
" #{type}#{field} #{old} -> #{new}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
truncate(message)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# display diff for multi-line strings
|
|
44
|
+
# must stay readable when color is off too
|
|
45
|
+
def multiline_diff(old, new)
|
|
46
|
+
Diff::LCS.sdiff(old.split("\n", -1), new.split("\n", -1)).flat_map do |diff|
|
|
47
|
+
case diff.action
|
|
48
|
+
when "-"
|
|
49
|
+
Utils.color(:red, "- #{diff.old_element}")
|
|
50
|
+
when "+"
|
|
51
|
+
Utils.color(:green, "+ #{diff.new_element}")
|
|
52
|
+
when "!"
|
|
53
|
+
[
|
|
54
|
+
Utils.color(:red, "- #{diff.old_element}"),
|
|
55
|
+
Utils.color(:green, "+ #{diff.new_element}")
|
|
56
|
+
]
|
|
57
|
+
else
|
|
58
|
+
" #{diff.old_element}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def truncate(message)
|
|
64
|
+
warning = Utils.color(
|
|
65
|
+
:magenta,
|
|
66
|
+
" (Diff for this item truncated after #{@max_diff_lines} lines. " \
|
|
67
|
+
"Rerun with MAX_DIFF_LINES=#{@max_diff_lines * 2} to see more)"
|
|
68
|
+
)
|
|
69
|
+
Utils.truncate_lines(message, to: @max_diff_lines, warning: warning)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# TODO: use awesome-print or similar, but it has too many monkey-patches
|
|
73
|
+
# https://github.com/amazing-print/amazing_print/issues/36
|
|
74
|
+
def pretty_inspect(object)
|
|
75
|
+
string = object.inspect.dup
|
|
76
|
+
string.gsub!(/:([a-z_]+)=>/, "\\1: ")
|
|
77
|
+
10.times do
|
|
78
|
+
string.gsub!(/{(\S.*?\S)}/, "{ \\1 }") || break
|
|
79
|
+
end
|
|
80
|
+
string
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/kennel/importer.rb
CHANGED
data/lib/kennel/models/record.rb
CHANGED
|
@@ -35,8 +35,8 @@ module Kennel
|
|
|
35
35
|
:klass, :tracking_id # added by syncer.rb
|
|
36
36
|
].freeze
|
|
37
37
|
ALLOWED_KENNEL_ID_CHARS = "a-zA-Z_\\d.-"
|
|
38
|
-
ALLOWED_KENNEL_ID_FULL = "[#{ALLOWED_KENNEL_ID_CHARS}]+:[#{ALLOWED_KENNEL_ID_CHARS}]+"
|
|
39
|
-
ALLOWED_KENNEL_ID_REGEX = /\A#{ALLOWED_KENNEL_ID_FULL}\z
|
|
38
|
+
ALLOWED_KENNEL_ID_FULL = "[#{ALLOWED_KENNEL_ID_CHARS}]+:[#{ALLOWED_KENNEL_ID_CHARS}]+".freeze
|
|
39
|
+
ALLOWED_KENNEL_ID_REGEX = /\A#{ALLOWED_KENNEL_ID_FULL}\z/
|
|
40
40
|
|
|
41
41
|
settings :id, :kennel_id
|
|
42
42
|
|
|
@@ -52,7 +52,7 @@ module Kennel
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def api_resource_map
|
|
55
|
-
subclasses.
|
|
55
|
+
subclasses.to_h { |s| [s.api_resource, s] }
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def parse_tracking_id(a)
|
|
@@ -98,6 +98,8 @@ module Kennel
|
|
|
98
98
|
|
|
99
99
|
self.class.send(:normalize, expected, actual)
|
|
100
100
|
|
|
101
|
+
return [] if actual == expected # Hashdiff is slow, this is fast
|
|
102
|
+
|
|
101
103
|
# strict: ignore Integer vs Float
|
|
102
104
|
# similarity: show diff when not 100% similar
|
|
103
105
|
# use_lcs: saner output
|
|
@@ -3,8 +3,7 @@ module Kennel
|
|
|
3
3
|
module Models
|
|
4
4
|
class SyntheticTest < Record
|
|
5
5
|
TRACKING_FIELD = :message
|
|
6
|
-
DEFAULTS = {
|
|
7
|
-
}.freeze
|
|
6
|
+
DEFAULTS = {}.freeze
|
|
8
7
|
READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [:status, :monitor_id]
|
|
9
8
|
LOCATIONS = ["aws:ca-central-1", "aws:eu-north-1", "aws:eu-west-1", "aws:eu-west-3", "aws:eu-west-2", "aws:ap-south-1", "aws:us-west-2", "aws:us-west-1", "aws:sa-east-1", "aws:us-east-2", "aws:ap-northeast-1", "aws:ap-northeast-2", "aws:eu-central-1", "aws:ap-southeast-2", "aws:ap-southeast-1"].freeze
|
|
10
9
|
|
|
@@ -8,13 +8,9 @@ module Kennel
|
|
|
8
8
|
|
|
9
9
|
def write(parts)
|
|
10
10
|
Progress.progress "Storing" do
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
old = old_paths
|
|
15
|
-
used = write_changed(parts)
|
|
16
|
-
(old - used).uniq.each { |p| FileUtils.rm_rf(p) }
|
|
17
|
-
end
|
|
11
|
+
existing = existing_files_and_folders
|
|
12
|
+
used = write_changed(parts)
|
|
13
|
+
FileUtils.rm_rf(existing - used)
|
|
18
14
|
end
|
|
19
15
|
end
|
|
20
16
|
|
|
@@ -26,31 +22,36 @@ module Kennel
|
|
|
26
22
|
used = []
|
|
27
23
|
|
|
28
24
|
Utils.parallel(parts, max: 2) do |part|
|
|
29
|
-
path =
|
|
25
|
+
path = path_for_tracking_id(part.tracking_id)
|
|
30
26
|
|
|
31
|
-
used << File.dirname(path) #
|
|
27
|
+
used << File.dirname(path) # we have 1 level of sub folders, so this is enough
|
|
32
28
|
used << path
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
write_file_if_necessary(path,
|
|
30
|
+
content = part.as_json.merge(api_resource: part.class.api_resource)
|
|
31
|
+
write_file_if_necessary(path, content)
|
|
36
32
|
end
|
|
37
33
|
|
|
38
34
|
used
|
|
39
35
|
end
|
|
40
36
|
|
|
41
|
-
def
|
|
42
|
-
if filter.
|
|
43
|
-
filter.
|
|
37
|
+
def existing_files_and_folders
|
|
38
|
+
if filter.tracking_id_filter
|
|
39
|
+
filter.tracking_id_filter.map { |tracking_id| path_for_tracking_id(tracking_id) }
|
|
40
|
+
elsif filter.project_filter
|
|
41
|
+
filter.project_filter.flat_map { |project| Dir["generated/#{project}/*"] }
|
|
44
42
|
else
|
|
45
|
-
["generated"]
|
|
43
|
+
Dir["generated/**/*"] # also includes folders so we clean up empty directories
|
|
46
44
|
end
|
|
47
45
|
end
|
|
48
46
|
|
|
49
|
-
def
|
|
50
|
-
|
|
47
|
+
def path_for_tracking_id(tracking_id)
|
|
48
|
+
"generated/#{tracking_id.tr("/", ":").sub(":", "/")}.json"
|
|
51
49
|
end
|
|
52
50
|
|
|
53
51
|
def write_file_if_necessary(path, content)
|
|
52
|
+
# NOTE: always generating is faster than JSON.load-ing and comparing
|
|
53
|
+
content = JSON.pretty_generate(content) << "\n"
|
|
54
|
+
|
|
54
55
|
# 99% case
|
|
55
56
|
begin
|
|
56
57
|
return if File.read(path) == content
|
data/lib/kennel/syncer.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "diff/lcs"
|
|
4
|
-
|
|
5
3
|
module Kennel
|
|
6
4
|
class Syncer
|
|
7
5
|
DELETE_ORDER = ["dashboard", "slo", "monitor", "synthetics/tests"].freeze # dashboards references monitors + slos, slos reference monitors
|
|
@@ -10,35 +8,41 @@ module Kennel
|
|
|
10
8
|
Plan = Struct.new(:changes, keyword_init: true)
|
|
11
9
|
Change = Struct.new(:type, :api_resource, :tracking_id, :id)
|
|
12
10
|
|
|
13
|
-
def initialize(api, expected, actual,
|
|
11
|
+
def initialize(api, expected, actual, strict_imports: true, project_filter: nil, tracking_id_filter: nil)
|
|
14
12
|
@api = api
|
|
15
|
-
@
|
|
13
|
+
@expected = Set.new expected # need Set to speed up deletion
|
|
14
|
+
@actual = actual
|
|
15
|
+
@strict_imports = strict_imports
|
|
16
16
|
@project_filter = project_filter
|
|
17
17
|
@tracking_id_filter = tracking_id_filter
|
|
18
|
-
|
|
19
|
-
@
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
|
|
19
|
+
@attribute_differ = AttributeDiffer.new
|
|
20
|
+
|
|
21
|
+
calculate_changes
|
|
22
|
+
validate_changes
|
|
22
23
|
prevent_irreversible_partial_updates
|
|
24
|
+
|
|
25
|
+
@warnings.each { |message| Kennel.out.puts Utils.color(:yellow, "Warning: #{message}") }
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
def plan
|
|
29
|
+
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) }
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def print_plan
|
|
26
38
|
Kennel.out.puts "Plan:"
|
|
27
39
|
if noop?
|
|
28
40
|
Kennel.out.puts Utils.color(:green, "Nothing to do")
|
|
29
41
|
else
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
print_plan "Delete", @delete, :red
|
|
42
|
+
print_changes "Create", @create, :green
|
|
43
|
+
print_changes "Update", @update, :yellow
|
|
44
|
+
print_changes "Delete", @delete, :red
|
|
34
45
|
end
|
|
35
|
-
|
|
36
|
-
Plan.new(
|
|
37
|
-
changes:
|
|
38
|
-
@create.map { |_id, e, _a| Change.new(:create, e.class.api_resource, e.tracking_id, nil) } +
|
|
39
|
-
@update.map { |id, e, _a| Change.new(:update, e.class.api_resource, e.tracking_id, id) } +
|
|
40
|
-
@delete.map { |id, _e, a| Change.new(:delete, a.fetch(:klass).api_resource, a.fetch(:tracking_id), id) }
|
|
41
|
-
)
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
def confirm
|
|
@@ -83,8 +87,6 @@ module Kennel
|
|
|
83
87
|
|
|
84
88
|
private
|
|
85
89
|
|
|
86
|
-
attr_reader :kennel
|
|
87
|
-
|
|
88
90
|
# loop over items until everything is resolved or crash when we get stuck
|
|
89
91
|
# this solves cases like composite monitors depending on each other or monitor->monitor slo->slo monitor chains
|
|
90
92
|
def each_resolved(list)
|
|
@@ -120,58 +122,67 @@ module Kennel
|
|
|
120
122
|
@create.empty? && @update.empty? && @delete.empty?
|
|
121
123
|
end
|
|
122
124
|
|
|
123
|
-
def
|
|
125
|
+
def calculate_changes
|
|
124
126
|
@warnings = []
|
|
125
|
-
@update = []
|
|
126
|
-
@delete = []
|
|
127
127
|
@id_map = IdMap.new
|
|
128
128
|
|
|
129
129
|
Progress.progress "Diffing" do
|
|
130
130
|
populate_id_map @expected, @actual
|
|
131
131
|
filter_actual! @actual
|
|
132
132
|
resolve_linked_tracking_ids! @expected # resolve dependencies to avoid diff
|
|
133
|
+
@expected.each(&:add_tracking_id) # avoid diff with actual, which has tracking_id
|
|
133
134
|
|
|
134
|
-
|
|
135
|
+
# see which expected match the actual
|
|
136
|
+
matching, unmatched_expected, unmatched_actual = partition_matched_expected
|
|
137
|
+
validate_expected_id_not_missing unmatched_expected
|
|
138
|
+
fill_details! matching # need details to diff later
|
|
135
139
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
[nil, a]
|
|
143
|
-
end
|
|
144
|
-
end
|
|
140
|
+
# update matching if needed
|
|
141
|
+
@update = matching.map do |e, a|
|
|
142
|
+
id = a.fetch(:id)
|
|
143
|
+
diff = e.diff(a)
|
|
144
|
+
[id, e, a, diff] if diff.any?
|
|
145
|
+
end.compact
|
|
145
146
|
|
|
146
|
-
#
|
|
147
|
-
|
|
148
|
-
@api.fill_details! "dashboard", details
|
|
147
|
+
# delete previously managed
|
|
148
|
+
@delete = unmatched_actual.map { |a| [a.fetch(:id), nil, a] if a.fetch(:tracking_id) }.compact
|
|
149
149
|
|
|
150
|
-
#
|
|
151
|
-
|
|
152
|
-
id = a.fetch(:id)
|
|
153
|
-
if e
|
|
154
|
-
diff = e.diff(a) # slow ...
|
|
155
|
-
if diff.any?
|
|
156
|
-
@update << [id, e, a, diff]
|
|
157
|
-
end
|
|
158
|
-
elsif a.fetch(:tracking_id) # was previously managed
|
|
159
|
-
@delete << [id, nil, a]
|
|
160
|
-
end
|
|
161
|
-
end
|
|
150
|
+
# unmatched expected need to be created
|
|
151
|
+
@create = unmatched_expected.map { |e| [nil, e] }
|
|
162
152
|
|
|
163
|
-
|
|
164
|
-
@create = @expected.map { |e| [nil, e] }
|
|
153
|
+
# order to avoid deadlocks
|
|
165
154
|
@delete.sort_by! { |_, _, a| DELETE_ORDER.index a.fetch(:klass).api_resource }
|
|
166
155
|
@update.sort_by! { |_, e, _| DELETE_ORDER.index e.class.api_resource } # slo needs to come before slo alert
|
|
167
156
|
end
|
|
168
157
|
end
|
|
169
158
|
|
|
170
|
-
def
|
|
171
|
-
|
|
159
|
+
def partition_matched_expected
|
|
160
|
+
lookup_map = matching_expected_lookup_map
|
|
161
|
+
unmatched_expected = @expected.dup
|
|
162
|
+
unmatched_actual = []
|
|
163
|
+
matched = []
|
|
164
|
+
@actual.each do |a|
|
|
165
|
+
e = matching_expected(a, lookup_map)
|
|
166
|
+
if e && unmatched_expected.delete?(e)
|
|
167
|
+
matched << [e, a]
|
|
168
|
+
else
|
|
169
|
+
unmatched_actual << a
|
|
170
|
+
end
|
|
171
|
+
end.compact
|
|
172
|
+
[matched, unmatched_expected, unmatched_actual]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# fill details of things we need to compare
|
|
176
|
+
def fill_details!(details_needed)
|
|
177
|
+
details_needed = details_needed.map { |e, a| a if e && e.class.api_resource == "dashboard" }.compact
|
|
178
|
+
@api.fill_details! "dashboard", details_needed
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def validate_expected_id_not_missing(expected)
|
|
182
|
+
expected.each do |e|
|
|
172
183
|
next unless id = e.id
|
|
173
184
|
resource = e.class.api_resource
|
|
174
|
-
if
|
|
185
|
+
if @strict_imports
|
|
175
186
|
raise "Unable to find existing #{resource} with id #{id}\nIf the #{resource} was deleted, remove the `id: -> { #{id} }` line."
|
|
176
187
|
else
|
|
177
188
|
@warnings << "#{resource} #{e.tracking_id} specifies id #{id}, but no such #{resource} exists. 'id' will be ignored. Remove the `id: -> { #{id} }` line."
|
|
@@ -196,79 +207,18 @@ module Kennel
|
|
|
196
207
|
map["#{klass.api_resource}:#{a.fetch(:id)}"] || map[a.fetch(:tracking_id)]
|
|
197
208
|
end
|
|
198
209
|
|
|
199
|
-
def
|
|
210
|
+
def print_changes(step, list, color)
|
|
200
211
|
return if list.empty?
|
|
201
212
|
list.each do |_, e, a, diff|
|
|
202
213
|
klass = (e ? e.class : a.fetch(:klass))
|
|
203
214
|
Kennel.out.puts Utils.color(color, "#{step} #{klass.api_resource} #{e&.tracking_id || a.fetch(:tracking_id)}")
|
|
204
|
-
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def print_diff(diff)
|
|
209
|
-
diff.each do |type, field, old, new|
|
|
210
|
-
use_diff = false
|
|
211
|
-
if type == "+"
|
|
212
|
-
temp = Utils.pretty_inspect(new)
|
|
213
|
-
new = Utils.pretty_inspect(old)
|
|
214
|
-
old = temp
|
|
215
|
-
elsif old.is_a?(String) && new.is_a?(String) && (old.include?("\n") || new.include?("\n"))
|
|
216
|
-
use_diff = true
|
|
217
|
-
else # ~ and -
|
|
218
|
-
old = Utils.pretty_inspect(old)
|
|
219
|
-
new = Utils.pretty_inspect(new)
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
message =
|
|
223
|
-
if use_diff
|
|
224
|
-
" #{type}#{field}\n" +
|
|
225
|
-
diff(old, new).map { |l| " #{l}" }.join("\n")
|
|
226
|
-
elsif (old + new).size > 100
|
|
227
|
-
" #{type}#{field}\n" \
|
|
228
|
-
" #{old} ->\n" \
|
|
229
|
-
" #{new}"
|
|
230
|
-
else
|
|
231
|
-
" #{type}#{field} #{old} -> #{new}"
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
Kennel.out.puts truncate_diff(message)
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
# display diff for multi-line strings
|
|
239
|
-
# must stay readable when color is off too
|
|
240
|
-
def diff(old, new)
|
|
241
|
-
Diff::LCS.sdiff(old.split("\n", -1), new.split("\n", -1)).flat_map do |diff|
|
|
242
|
-
case diff.action
|
|
243
|
-
when "-"
|
|
244
|
-
Utils.color(:red, "- #{diff.old_element}")
|
|
245
|
-
when "+"
|
|
246
|
-
Utils.color(:green, "+ #{diff.new_element}")
|
|
247
|
-
when "!"
|
|
248
|
-
[
|
|
249
|
-
Utils.color(:red, "- #{diff.old_element}"),
|
|
250
|
-
Utils.color(:green, "+ #{diff.new_element}")
|
|
251
|
-
]
|
|
252
|
-
else
|
|
253
|
-
" #{diff.old_element}"
|
|
254
|
-
end
|
|
215
|
+
diff&.each { |args| Kennel.out.puts @attribute_differ.format(*args) } # only for update
|
|
255
216
|
end
|
|
256
217
|
end
|
|
257
218
|
|
|
258
|
-
def truncate_diff(message)
|
|
259
|
-
# min '2' because: -1 makes no sense, 0 does not work with * 2 math, 1 says '1 lines'
|
|
260
|
-
@max_diff_lines ||= [Integer(ENV.fetch("MAX_DIFF_LINES", "50")), 2].max
|
|
261
|
-
warning = Utils.color(
|
|
262
|
-
:magenta,
|
|
263
|
-
" (Diff for this item truncated after #{@max_diff_lines} lines. " \
|
|
264
|
-
"Rerun with MAX_DIFF_LINES=#{@max_diff_lines * 2} to see more)"
|
|
265
|
-
)
|
|
266
|
-
Utils.truncate_lines(message, to: @max_diff_lines, warning: warning)
|
|
267
|
-
end
|
|
268
|
-
|
|
269
219
|
# We've already validated the desired objects ('generated') in isolation.
|
|
270
220
|
# Now that we have made the plan, we can perform some more validation.
|
|
271
|
-
def
|
|
221
|
+
def validate_changes
|
|
272
222
|
@update.each do |_, expected, actuals, diffs|
|
|
273
223
|
expected.validate_update!(actuals, diffs)
|
|
274
224
|
end
|
|
@@ -276,11 +226,10 @@ module Kennel
|
|
|
276
226
|
|
|
277
227
|
# - do not add tracking-id when working with existing ids on a branch,
|
|
278
228
|
# so resource do not get deleted when running an update on master (for example merge->CI)
|
|
279
|
-
# - make sure the diff is clean, by kicking out the now noop-update
|
|
280
229
|
# - ideally we'd never add tracking in the first place, but when adding tracking we do not know the diff yet
|
|
281
230
|
def prevent_irreversible_partial_updates
|
|
282
|
-
return unless @project_filter
|
|
283
|
-
@update.select! do |_, e, _, diff|
|
|
231
|
+
return unless @project_filter # full update, so we are not on a branch
|
|
232
|
+
@update.select! do |_, e, _, diff| # ensure clean diff, by removing noop-update
|
|
284
233
|
next true unless e.id # safe to add tracking when not having id
|
|
285
234
|
|
|
286
235
|
diff.select! do |field_diff|
|
|
@@ -293,7 +242,7 @@ module Kennel
|
|
|
293
242
|
actual != field_diff[3] # discard diff if now nothing changes
|
|
294
243
|
end
|
|
295
244
|
|
|
296
|
-
|
|
245
|
+
diff.any?
|
|
297
246
|
end
|
|
298
247
|
end
|
|
299
248
|
|
data/lib/kennel/utils.rb
CHANGED
|
@@ -5,6 +5,7 @@ module Kennel
|
|
|
5
5
|
|
|
6
6
|
class TeeIO < IO
|
|
7
7
|
def initialize(ios)
|
|
8
|
+
super(0) # called with fake file descriptor 0, so we can call super and get a proper class
|
|
8
9
|
@ios = ios
|
|
9
10
|
end
|
|
10
11
|
|
|
@@ -150,17 +151,6 @@ module Kennel
|
|
|
150
151
|
end
|
|
151
152
|
end
|
|
152
153
|
|
|
153
|
-
# TODO: use awesome-print or similar, but it has too many monkey-patches
|
|
154
|
-
# https://github.com/amazing-print/amazing_print/issues/36
|
|
155
|
-
def pretty_inspect(object)
|
|
156
|
-
string = object.inspect.dup
|
|
157
|
-
string.gsub!(/:([a-z_]+)=>/, "\\1: ")
|
|
158
|
-
10.times do
|
|
159
|
-
string.gsub!(/{(\S.*?\S)}/, "{ \\1 }") || break
|
|
160
|
-
end
|
|
161
|
-
string
|
|
162
|
-
end
|
|
163
|
-
|
|
164
154
|
def inline_resource_metadata(resource, klass)
|
|
165
155
|
resource[:klass] = klass
|
|
166
156
|
resource[:tracking_id] = klass.parse_tracking_id(resource)
|
data/lib/kennel/version.rb
CHANGED
data/lib/kennel.rb
CHANGED
|
@@ -10,6 +10,7 @@ require "kennel/progress"
|
|
|
10
10
|
require "kennel/filter"
|
|
11
11
|
require "kennel/parts_serializer"
|
|
12
12
|
require "kennel/projects_provider"
|
|
13
|
+
require "kennel/attribute_differ"
|
|
13
14
|
require "kennel/syncer"
|
|
14
15
|
require "kennel/id_map"
|
|
15
16
|
require "kennel/api"
|
|
@@ -42,7 +43,6 @@ module Kennel
|
|
|
42
43
|
UnresolvableIdError = Class.new(StandardError)
|
|
43
44
|
DisallowedUpdateError = Class.new(StandardError)
|
|
44
45
|
GenerationAbortedError = Class.new(StandardError)
|
|
45
|
-
UpdateResult = Struct.new(:plan, :update, keyword_init: true)
|
|
46
46
|
|
|
47
47
|
class << self
|
|
48
48
|
attr_accessor :out, :err
|
|
@@ -52,12 +52,12 @@ module Kennel
|
|
|
52
52
|
self.err = $stderr
|
|
53
53
|
|
|
54
54
|
class Engine
|
|
55
|
+
attr_accessor :strict_imports
|
|
56
|
+
|
|
55
57
|
def initialize
|
|
56
58
|
@strict_imports = true
|
|
57
59
|
end
|
|
58
60
|
|
|
59
|
-
attr_accessor :strict_imports
|
|
60
|
-
|
|
61
61
|
# start generation and download in parallel to make planning faster
|
|
62
62
|
def preload
|
|
63
63
|
Utils.parallel([:generated, :definitions]) { |m| send m, plain: true }
|
|
@@ -65,16 +65,17 @@ module Kennel
|
|
|
65
65
|
|
|
66
66
|
def generate
|
|
67
67
|
parts = generated
|
|
68
|
-
|
|
68
|
+
PartsSerializer.new(filter: filter).write(parts) if ENV["STORE"] != "false" # quicker when debugging
|
|
69
69
|
parts
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def plan
|
|
73
|
+
syncer.print_plan
|
|
73
74
|
syncer.plan
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
def update
|
|
77
|
-
syncer.
|
|
78
|
+
syncer.print_plan
|
|
78
79
|
syncer.update if syncer.confirm
|
|
79
80
|
end
|
|
80
81
|
|
|
@@ -87,7 +88,12 @@ module Kennel
|
|
|
87
88
|
def syncer
|
|
88
89
|
@syncer ||= begin
|
|
89
90
|
preload
|
|
90
|
-
Syncer.new(
|
|
91
|
+
Syncer.new(
|
|
92
|
+
api, generated, definitions,
|
|
93
|
+
strict_imports: strict_imports,
|
|
94
|
+
project_filter: filter.project_filter,
|
|
95
|
+
tracking_id_filter: filter.tracking_id_filter
|
|
96
|
+
)
|
|
91
97
|
end
|
|
92
98
|
end
|
|
93
99
|
|
|
@@ -95,18 +101,10 @@ module Kennel
|
|
|
95
101
|
@api ||= Api.new
|
|
96
102
|
end
|
|
97
103
|
|
|
98
|
-
def projects_provider
|
|
99
|
-
@projects_provider ||= ProjectsProvider.new
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def parts_serializer
|
|
103
|
-
@parts_serializer ||= PartsSerializer.new(filter: filter)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
104
|
def generated(**kwargs)
|
|
107
105
|
@generated ||= begin
|
|
108
106
|
parts = Progress.progress "Finding parts", **kwargs do
|
|
109
|
-
projects =
|
|
107
|
+
projects = ProjectsProvider.new.projects
|
|
110
108
|
projects = filter.filter_projects projects
|
|
111
109
|
|
|
112
110
|
parts = Utils.parallel(projects, &:validated_parts).flatten(1)
|
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.129.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michael Grosser
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-12-
|
|
11
|
+
date: 2022-12-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|
|
@@ -80,7 +80,7 @@ dependencies:
|
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '2.4'
|
|
83
|
-
description:
|
|
83
|
+
description:
|
|
84
84
|
email: michael@grosser.it
|
|
85
85
|
executables: []
|
|
86
86
|
extensions: []
|
|
@@ -89,6 +89,7 @@ files:
|
|
|
89
89
|
- Readme.md
|
|
90
90
|
- lib/kennel.rb
|
|
91
91
|
- lib/kennel/api.rb
|
|
92
|
+
- lib/kennel/attribute_differ.rb
|
|
92
93
|
- lib/kennel/file_cache.rb
|
|
93
94
|
- lib/kennel/filter.rb
|
|
94
95
|
- lib/kennel/github_reporter.rb
|
|
@@ -119,7 +120,7 @@ homepage: https://github.com/grosser/kennel
|
|
|
119
120
|
licenses:
|
|
120
121
|
- MIT
|
|
121
122
|
metadata: {}
|
|
122
|
-
post_install_message:
|
|
123
|
+
post_install_message:
|
|
123
124
|
rdoc_options: []
|
|
124
125
|
require_paths:
|
|
125
126
|
- lib
|
|
@@ -127,15 +128,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
127
128
|
requirements:
|
|
128
129
|
- - ">="
|
|
129
130
|
- !ruby/object:Gem::Version
|
|
130
|
-
version:
|
|
131
|
+
version: 3.1.0
|
|
131
132
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
133
|
requirements:
|
|
133
134
|
- - ">="
|
|
134
135
|
- !ruby/object:Gem::Version
|
|
135
136
|
version: '0'
|
|
136
137
|
requirements: []
|
|
137
|
-
rubygems_version: 3.
|
|
138
|
-
signing_key:
|
|
138
|
+
rubygems_version: 3.3.26
|
|
139
|
+
signing_key:
|
|
139
140
|
specification_version: 4
|
|
140
141
|
summary: Keep datadog monitors/dashboards/etc in version control, avoid chaotic management
|
|
141
142
|
via UI
|