kennel 1.127.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/progress.rb +2 -2
- data/lib/kennel/syncer.rb +70 -135
- data/lib/kennel/tasks.rb +4 -1
- data/lib/kennel/utils.rb +4 -9
- data/lib/kennel/version.rb +1 -1
- data/lib/kennel.rb +31 -16
- 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/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
@@ -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,34 +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,
|
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
|
-
|
18
|
+
|
19
|
+
@attribute_differ = AttributeDiffer.new
|
20
|
+
|
21
|
+
calculate_changes
|
22
|
+
validate_changes
|
21
23
|
prevent_irreversible_partial_updates
|
24
|
+
|
25
|
+
@warnings.each { |message| Kennel.out.puts Utils.color(:yellow, "Warning: #{message}") }
|
22
26
|
end
|
23
27
|
|
24
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
|
25
38
|
Kennel.out.puts "Plan:"
|
26
39
|
if noop?
|
27
40
|
Kennel.out.puts Utils.color(:green, "Nothing to do")
|
28
41
|
else
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
print_plan "Delete", @delete, :red
|
42
|
+
print_changes "Create", @create, :green
|
43
|
+
print_changes "Update", @update, :yellow
|
44
|
+
print_changes "Delete", @delete, :red
|
33
45
|
end
|
34
|
-
|
35
|
-
Plan.new(
|
36
|
-
changes:
|
37
|
-
@create.map { |_id, e, _a| Change.new(:create, e.class.api_resource, e.tracking_id, nil) } +
|
38
|
-
@update.map { |id, e, _a| Change.new(:update, e.class.api_resource, e.tracking_id, id) } +
|
39
|
-
@delete.map { |id, _e, a| Change.new(:delete, a.fetch(:klass).api_resource, a.fetch(:tracking_id), id) }
|
40
|
-
)
|
41
46
|
end
|
42
47
|
|
43
48
|
def confirm
|
@@ -53,7 +58,7 @@ module Kennel
|
|
53
58
|
message = "#{e.class.api_resource} #{e.tracking_id}"
|
54
59
|
Kennel.out.puts "Creating #{message}"
|
55
60
|
reply = @api.create e.class.api_resource, e.as_json
|
56
|
-
|
61
|
+
Utils.inline_resource_metadata reply, e.class
|
57
62
|
id = reply.fetch(:id)
|
58
63
|
changes << Change.new(:create, e.class.api_resource, e.tracking_id, id)
|
59
64
|
populate_id_map [], [reply] # allow resolving ids we could previously no resolve
|
@@ -82,8 +87,6 @@ module Kennel
|
|
82
87
|
|
83
88
|
private
|
84
89
|
|
85
|
-
attr_reader :kennel
|
86
|
-
|
87
90
|
# loop over items until everything is resolved or crash when we get stuck
|
88
91
|
# this solves cases like composite monitors depending on each other or monitor->monitor slo->slo monitor chains
|
89
92
|
def each_resolved(list)
|
@@ -119,73 +122,67 @@ module Kennel
|
|
119
122
|
@create.empty? && @update.empty? && @delete.empty?
|
120
123
|
end
|
121
124
|
|
122
|
-
def
|
125
|
+
def calculate_changes
|
123
126
|
@warnings = []
|
124
|
-
@update = []
|
125
|
-
@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
|
133
|
+
@expected.each(&:add_tracking_id) # avoid diff with actual, which has tracking_id
|
134
134
|
|
135
|
-
|
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
|
136
139
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
[nil, a]
|
144
|
-
end
|
145
|
-
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
|
146
146
|
|
147
|
-
#
|
148
|
-
|
149
|
-
@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
|
150
149
|
|
151
|
-
#
|
152
|
-
|
153
|
-
id = a.fetch(:id)
|
154
|
-
if e
|
155
|
-
diff = e.diff(a) # slow ...
|
156
|
-
if diff.any?
|
157
|
-
@update << [id, e, a, diff]
|
158
|
-
end
|
159
|
-
elsif a.fetch(:tracking_id) # was previously managed
|
160
|
-
@delete << [id, nil, a]
|
161
|
-
end
|
162
|
-
end
|
150
|
+
# unmatched expected need to be created
|
151
|
+
@create = unmatched_expected.map { |e| [nil, e] }
|
163
152
|
|
164
|
-
|
165
|
-
@create = @expected.map { |e| [nil, e] }
|
153
|
+
# order to avoid deadlocks
|
166
154
|
@delete.sort_by! { |_, _, a| DELETE_ORDER.index a.fetch(:klass).api_resource }
|
167
155
|
@update.sort_by! { |_, e, _| DELETE_ORDER.index e.class.api_resource } # slo needs to come before slo alert
|
168
156
|
end
|
169
157
|
end
|
170
158
|
|
171
|
-
def
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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]
|
177
173
|
end
|
178
174
|
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
182
179
|
end
|
183
180
|
|
184
|
-
def
|
185
|
-
|
181
|
+
def validate_expected_id_not_missing(expected)
|
182
|
+
expected.each do |e|
|
186
183
|
next unless id = e.id
|
187
184
|
resource = e.class.api_resource
|
188
|
-
if
|
185
|
+
if @strict_imports
|
189
186
|
raise "Unable to find existing #{resource} with id #{id}\nIf the #{resource} was deleted, remove the `id: -> { #{id} }` line."
|
190
187
|
else
|
191
188
|
@warnings << "#{resource} #{e.tracking_id} specifies id #{id}, but no such #{resource} exists. 'id' will be ignored. Remove the `id: -> { #{id} }` line."
|
@@ -210,79 +207,18 @@ module Kennel
|
|
210
207
|
map["#{klass.api_resource}:#{a.fetch(:id)}"] || map[a.fetch(:tracking_id)]
|
211
208
|
end
|
212
209
|
|
213
|
-
def
|
210
|
+
def print_changes(step, list, color)
|
214
211
|
return if list.empty?
|
215
212
|
list.each do |_, e, a, diff|
|
216
213
|
klass = (e ? e.class : a.fetch(:klass))
|
217
214
|
Kennel.out.puts Utils.color(color, "#{step} #{klass.api_resource} #{e&.tracking_id || a.fetch(:tracking_id)}")
|
218
|
-
|
215
|
+
diff&.each { |args| Kennel.out.puts @attribute_differ.format(*args) } # only for update
|
219
216
|
end
|
220
217
|
end
|
221
218
|
|
222
|
-
def print_diff(diff)
|
223
|
-
diff.each do |type, field, old, new|
|
224
|
-
use_diff = false
|
225
|
-
if type == "+"
|
226
|
-
temp = Utils.pretty_inspect(new)
|
227
|
-
new = Utils.pretty_inspect(old)
|
228
|
-
old = temp
|
229
|
-
elsif old.is_a?(String) && new.is_a?(String) && (old.include?("\n") || new.include?("\n"))
|
230
|
-
use_diff = true
|
231
|
-
else # ~ and -
|
232
|
-
old = Utils.pretty_inspect(old)
|
233
|
-
new = Utils.pretty_inspect(new)
|
234
|
-
end
|
235
|
-
|
236
|
-
message =
|
237
|
-
if use_diff
|
238
|
-
" #{type}#{field}\n" +
|
239
|
-
diff(old, new).map { |l| " #{l}" }.join("\n")
|
240
|
-
elsif (old + new).size > 100
|
241
|
-
" #{type}#{field}\n" \
|
242
|
-
" #{old} ->\n" \
|
243
|
-
" #{new}"
|
244
|
-
else
|
245
|
-
" #{type}#{field} #{old} -> #{new}"
|
246
|
-
end
|
247
|
-
|
248
|
-
Kennel.out.puts truncate_diff(message)
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
# display diff for multi-line strings
|
253
|
-
# must stay readable when color is off too
|
254
|
-
def diff(old, new)
|
255
|
-
Diff::LCS.sdiff(old.split("\n", -1), new.split("\n", -1)).flat_map do |diff|
|
256
|
-
case diff.action
|
257
|
-
when "-"
|
258
|
-
Utils.color(:red, "- #{diff.old_element}")
|
259
|
-
when "+"
|
260
|
-
Utils.color(:green, "+ #{diff.new_element}")
|
261
|
-
when "!"
|
262
|
-
[
|
263
|
-
Utils.color(:red, "- #{diff.old_element}"),
|
264
|
-
Utils.color(:green, "+ #{diff.new_element}")
|
265
|
-
]
|
266
|
-
else
|
267
|
-
" #{diff.old_element}"
|
268
|
-
end
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
272
|
-
def truncate_diff(message)
|
273
|
-
# min '2' because: -1 makes no sense, 0 does not work with * 2 math, 1 says '1 lines'
|
274
|
-
@max_diff_lines ||= [Integer(ENV.fetch("MAX_DIFF_LINES", "50")), 2].max
|
275
|
-
warning = Utils.color(
|
276
|
-
:magenta,
|
277
|
-
" (Diff for this item truncated after #{@max_diff_lines} lines. " \
|
278
|
-
"Rerun with MAX_DIFF_LINES=#{@max_diff_lines * 2} to see more)"
|
279
|
-
)
|
280
|
-
Utils.truncate_lines(message, to: @max_diff_lines, warning: warning)
|
281
|
-
end
|
282
|
-
|
283
219
|
# We've already validated the desired objects ('generated') in isolation.
|
284
220
|
# Now that we have made the plan, we can perform some more validation.
|
285
|
-
def
|
221
|
+
def validate_changes
|
286
222
|
@update.each do |_, expected, actuals, diffs|
|
287
223
|
expected.validate_update!(actuals, diffs)
|
288
224
|
end
|
@@ -290,11 +226,10 @@ module Kennel
|
|
290
226
|
|
291
227
|
# - do not add tracking-id when working with existing ids on a branch,
|
292
228
|
# so resource do not get deleted when running an update on master (for example merge->CI)
|
293
|
-
# - make sure the diff is clean, by kicking out the now noop-update
|
294
229
|
# - ideally we'd never add tracking in the first place, but when adding tracking we do not know the diff yet
|
295
230
|
def prevent_irreversible_partial_updates
|
296
|
-
return unless @project_filter
|
297
|
-
@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
|
298
233
|
next true unless e.id # safe to add tracking when not having id
|
299
234
|
|
300
235
|
diff.select! do |field_diff|
|
@@ -307,7 +242,7 @@ module Kennel
|
|
307
242
|
actual != field_diff[3] # discard diff if now nothing changes
|
308
243
|
end
|
309
244
|
|
310
|
-
|
245
|
+
diff.any?
|
311
246
|
end
|
312
247
|
end
|
313
248
|
|
data/lib/kennel/tasks.rb
CHANGED
@@ -101,8 +101,11 @@ namespace :kennel do
|
|
101
101
|
end
|
102
102
|
|
103
103
|
# also generate parts so users see and commit updated generated automatically
|
104
|
+
# (generate must run after plan to enable parallel .download+.generate inside of .plan)
|
104
105
|
desc "show planned datadog changes (scope with PROJECT=name)"
|
105
|
-
task plan: :
|
106
|
+
task plan: :environment do
|
107
|
+
Kennel::Tasks.kennel.preload
|
108
|
+
Kennel::Tasks.kennel.generate
|
106
109
|
Kennel::Tasks.kennel.plan
|
107
110
|
end
|
108
111
|
|
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,15 +151,9 @@ module Kennel
|
|
150
151
|
end
|
151
152
|
end
|
152
153
|
|
153
|
-
|
154
|
-
|
155
|
-
|
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
|
154
|
+
def inline_resource_metadata(resource, klass)
|
155
|
+
resource[:klass] = klass
|
156
|
+
resource[:tracking_id] = klass.parse_tracking_id(resource)
|
162
157
|
end
|
163
158
|
end
|
164
159
|
end
|
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,24 +52,30 @@ 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
|
-
|
61
|
+
# start generation and download in parallel to make planning faster
|
62
|
+
def preload
|
63
|
+
Utils.parallel([:generated, :definitions]) { |m| send m, plain: true }
|
64
|
+
end
|
60
65
|
|
61
66
|
def generate
|
62
67
|
parts = generated
|
63
|
-
|
68
|
+
PartsSerializer.new(filter: filter).write(parts) if ENV["STORE"] != "false" # quicker when debugging
|
64
69
|
parts
|
65
70
|
end
|
66
71
|
|
67
72
|
def plan
|
73
|
+
syncer.print_plan
|
68
74
|
syncer.plan
|
69
75
|
end
|
70
76
|
|
71
77
|
def update
|
72
|
-
syncer.
|
78
|
+
syncer.print_plan
|
73
79
|
syncer.update if syncer.confirm
|
74
80
|
end
|
75
81
|
|
@@ -80,25 +86,25 @@ module Kennel
|
|
80
86
|
end
|
81
87
|
|
82
88
|
def syncer
|
83
|
-
@syncer ||=
|
89
|
+
@syncer ||= begin
|
90
|
+
preload
|
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
|
+
)
|
97
|
+
end
|
84
98
|
end
|
85
99
|
|
86
100
|
def api
|
87
101
|
@api ||= Api.new
|
88
102
|
end
|
89
103
|
|
90
|
-
def
|
91
|
-
@projects_provider ||= ProjectsProvider.new
|
92
|
-
end
|
93
|
-
|
94
|
-
def parts_serializer
|
95
|
-
@parts_serializer ||= PartsSerializer.new(filter: filter)
|
96
|
-
end
|
97
|
-
|
98
|
-
def generated
|
104
|
+
def generated(**kwargs)
|
99
105
|
@generated ||= begin
|
100
|
-
parts = Progress.progress "Finding parts" do
|
101
|
-
projects =
|
106
|
+
parts = Progress.progress "Finding parts", **kwargs do
|
107
|
+
projects = ProjectsProvider.new.projects
|
102
108
|
projects = filter.filter_projects projects
|
103
109
|
|
104
110
|
parts = Utils.parallel(projects, &:validated_parts).flatten(1)
|
@@ -123,5 +129,14 @@ module Kennel
|
|
123
129
|
parts
|
124
130
|
end
|
125
131
|
end
|
132
|
+
|
133
|
+
def definitions(**kwargs)
|
134
|
+
@definitions ||= Progress.progress("Downloading definitions", **kwargs) do
|
135
|
+
Utils.parallel(Models::Record.subclasses) do |klass|
|
136
|
+
results = api.list(klass.api_resource, with_downtimes: false) # lookup monitors without adding unnecessary downtime information
|
137
|
+
results.each { |a| Utils.inline_resource_metadata(a, klass) }
|
138
|
+
end.flatten(1)
|
139
|
+
end
|
140
|
+
end
|
126
141
|
end
|
127
142
|
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: 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
|