kennel 1.87.1 → 1.88.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/importer.rb +22 -7
- data/lib/kennel/models/dashboard.rb +1 -0
- data/lib/kennel/models/monitor.rb +6 -0
- data/lib/kennel/models/record.rb +33 -2
- data/lib/kennel/models/slo.rb +1 -0
- data/lib/kennel/syncer.rb +33 -63
- data/lib/kennel/template_variables.rb +2 -1
- 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: ccd6dac941560095109ca60a7a60a58a247605c1c6ec8b658fda12ec6e60ea04
|
|
4
|
+
data.tar.gz: b17327485c61d273a474e0235c274b48e3f950586441bc676d3da14bcab55afc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b0c6a49acdfc63bfcd86180ab5dfedd96aaf73d55c6cdf4890ca58e1a2408623237ada66b67a1278e09e0d207e8a2b7d9d7a45b7b2da09f3f4c750a35aca4011
|
|
7
|
+
data.tar.gz: 48ec7d742c67f23192550e567f343213a88bf8372c3f7e69604152bae0cd57e2b5b210ec288527ab6d28bd5a77f58b66f6a5b882e77b0ff413059c1e5622a888
|
data/lib/kennel/importer.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Kennel
|
|
4
4
|
class Importer
|
|
5
5
|
TITLES = [:name, :title].freeze
|
|
6
|
-
SORT_ORDER = [*TITLES, :id, :kennel_id, :type, :tags, :query, *
|
|
6
|
+
SORT_ORDER = [*TITLES, :id, :kennel_id, :type, :tags, :query, *Models::Record.subclasses.map { |k| k::TRACKING_FIELDS }, :template_variables].freeze
|
|
7
7
|
|
|
8
8
|
def initialize(api)
|
|
9
9
|
@api = api
|
|
@@ -31,11 +31,10 @@ module Kennel
|
|
|
31
31
|
title.tr!(Kennel::Models::Record::LOCK, "") # avoid double lock icon
|
|
32
32
|
|
|
33
33
|
# calculate or reuse kennel_id
|
|
34
|
-
# TODO: this is copy-pasted from syncer, need to find a nice way to reuse it
|
|
35
|
-
tracking_field = Syncer::TRACKING_FIELDS.detect { |f| data[f] }
|
|
36
34
|
data[:kennel_id] =
|
|
37
|
-
if
|
|
38
|
-
|
|
35
|
+
if tracking_id = model.parse_tracking_id(data)
|
|
36
|
+
model.remove_tracking_id(data)
|
|
37
|
+
tracking_id.split(":").last
|
|
39
38
|
else
|
|
40
39
|
Kennel::Utils.parameterize(title)
|
|
41
40
|
end
|
|
@@ -67,9 +66,12 @@ module Kennel
|
|
|
67
66
|
when "dashboard"
|
|
68
67
|
widgets = data[:widgets]&.flat_map { |widget| widget.dig(:definition, :widgets) || [widget] }
|
|
69
68
|
widgets&.each do |widget|
|
|
70
|
-
|
|
69
|
+
convert_widget_to_compact_format!(widget)
|
|
70
|
+
dry_up_widget_metadata!(widget)
|
|
71
71
|
(widget.dig(:definition, :markers) || []).each { |m| m[:label]&.delete! " " }
|
|
72
72
|
end
|
|
73
|
+
else
|
|
74
|
+
# noop
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
data.delete(:tags) if data[:tags] == [] # do not create super + [] call
|
|
@@ -91,7 +93,7 @@ module Kennel
|
|
|
91
93
|
private
|
|
92
94
|
|
|
93
95
|
# reduce duplication in imports by using dry `q: :metadata` when possible
|
|
94
|
-
def
|
|
96
|
+
def dry_up_widget_metadata!(widget)
|
|
95
97
|
(widget.dig(:definition, :requests) || []).each do |request|
|
|
96
98
|
next unless request.is_a?(Hash)
|
|
97
99
|
next unless metadata = request[:metadata]
|
|
@@ -104,6 +106,19 @@ module Kennel
|
|
|
104
106
|
end
|
|
105
107
|
end
|
|
106
108
|
|
|
109
|
+
# new api format is very verbose, so use old dry format when possible
|
|
110
|
+
def convert_widget_to_compact_format!(widget)
|
|
111
|
+
(widget.dig(:definition, :requests) || []).each do |request|
|
|
112
|
+
next unless request.is_a?(Hash)
|
|
113
|
+
next if request[:formulas]&.any? { |f| f.keys != [:formula] }
|
|
114
|
+
next if request[:queries]&.size != 1
|
|
115
|
+
next if request[:queries].any? { |q| q[:data_source] != "metrics" }
|
|
116
|
+
request.delete(:formulas)
|
|
117
|
+
request[:type] = request.delete(:response_format)
|
|
118
|
+
request[:q] = request.delete(:queries).first.fetch(:query)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
107
122
|
def pretty_print(hash)
|
|
108
123
|
sort_widgets hash
|
|
109
124
|
|
|
@@ -8,6 +8,7 @@ module Kennel
|
|
|
8
8
|
READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [
|
|
9
9
|
:author_handle, :author_name, :modified_at, :url, :is_read_only, :notify_list
|
|
10
10
|
]
|
|
11
|
+
TRACKING_FIELD = :description
|
|
11
12
|
REQUEST_DEFAULTS = {
|
|
12
13
|
style: { line_width: "normal", palette: "dog_classic", line_type: "solid" }
|
|
13
14
|
}.freeze
|
|
@@ -9,6 +9,7 @@ module Kennel
|
|
|
9
9
|
READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [
|
|
10
10
|
:multi, :matching_downtimes, :overall_state_modified, :overall_state, :restricted_roles
|
|
11
11
|
]
|
|
12
|
+
TRACKING_FIELD = :message
|
|
12
13
|
|
|
13
14
|
MONITOR_DEFAULTS = {
|
|
14
15
|
priority: nil
|
|
@@ -25,6 +26,7 @@ module Kennel
|
|
|
25
26
|
groupby_simple_monitor: false
|
|
26
27
|
}.freeze
|
|
27
28
|
DEFAULT_ESCALATION_MESSAGE = ["", nil].freeze
|
|
29
|
+
ALLOWED_PRIORITY_CLASSES = [NilClass, Integer].freeze
|
|
28
30
|
|
|
29
31
|
settings(
|
|
30
32
|
:query, :name, :message, :escalation_message, :critical, :type, :renotify_interval, :warning, :timeout_h, :evaluation_delay,
|
|
@@ -217,6 +219,10 @@ module Kennel
|
|
|
217
219
|
end
|
|
218
220
|
end
|
|
219
221
|
end
|
|
222
|
+
|
|
223
|
+
unless ALLOWED_PRIORITY_CLASSES.include?(priority.class)
|
|
224
|
+
invalid! "priority needs to be an Integer"
|
|
225
|
+
end
|
|
220
226
|
end
|
|
221
227
|
end
|
|
222
228
|
end
|
data/lib/kennel/models/record.rb
CHANGED
|
@@ -3,8 +3,10 @@ module Kennel
|
|
|
3
3
|
module Models
|
|
4
4
|
class Record < Base
|
|
5
5
|
LOCK = "\u{1F512}"
|
|
6
|
+
TRACKING_FIELDS = [:message, :description].freeze
|
|
6
7
|
READONLY_ATTRIBUTES = [
|
|
7
|
-
:deleted, :id, :created, :created_at, :creator, :org_id, :modified, :modified_at,
|
|
8
|
+
:deleted, :id, :created, :created_at, :creator, :org_id, :modified, :modified_at,
|
|
9
|
+
:klass # added by syncer.rb
|
|
8
10
|
].freeze
|
|
9
11
|
|
|
10
12
|
settings :id, :kennel_id
|
|
@@ -22,6 +24,18 @@ module Kennel
|
|
|
22
24
|
subclasses.map { |s| [s.api_resource, s] }.to_h
|
|
23
25
|
end
|
|
24
26
|
|
|
27
|
+
def parse_tracking_id(a)
|
|
28
|
+
a[self::TRACKING_FIELD].to_s[/-- Managed by kennel (\S+:\S+)/, 1]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# TODO: combine with parse into a single method or a single regex
|
|
32
|
+
def remove_tracking_id(a)
|
|
33
|
+
value = a[self::TRACKING_FIELD]
|
|
34
|
+
a[self::TRACKING_FIELD] =
|
|
35
|
+
value.dup.sub!(/\n?-- Managed by kennel .*/, "") ||
|
|
36
|
+
raise("did not find tracking id in #{value}")
|
|
37
|
+
end
|
|
38
|
+
|
|
25
39
|
private
|
|
26
40
|
|
|
27
41
|
def normalize(_expected, actual)
|
|
@@ -66,13 +80,30 @@ module Kennel
|
|
|
66
80
|
def resolve_linked_tracking_ids!(*)
|
|
67
81
|
end
|
|
68
82
|
|
|
83
|
+
def add_tracking_id
|
|
84
|
+
json = as_json
|
|
85
|
+
if self.class.parse_tracking_id(json)
|
|
86
|
+
invalid! "remove \"-- Managed by kennel\" line it from #{self.class::TRACKING_FIELD} to copy a resource"
|
|
87
|
+
end
|
|
88
|
+
json[self.class::TRACKING_FIELD] =
|
|
89
|
+
"#{json[self.class::TRACKING_FIELD]}\n" \
|
|
90
|
+
"-- Managed by kennel #{tracking_id} in #{project.class.file_location}, do not modify manually".lstrip
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def remove_tracking_id
|
|
94
|
+
self.class.remove_tracking_id(as_json)
|
|
95
|
+
end
|
|
96
|
+
|
|
69
97
|
private
|
|
70
98
|
|
|
71
99
|
def resolve_link(tracking_id, type, id_map, force:)
|
|
72
100
|
id = id_map[tracking_id]
|
|
73
101
|
if id == :new
|
|
74
102
|
if force
|
|
75
|
-
invalid!
|
|
103
|
+
invalid!(
|
|
104
|
+
"#{type} #{tracking_id} was referenced but is also created by the current run.\n" \
|
|
105
|
+
"It could not be created because of a circular dependency, try creating only some of the resources"
|
|
106
|
+
)
|
|
76
107
|
else
|
|
77
108
|
nil # will be re-resolved after the linked object was created
|
|
78
109
|
end
|
data/lib/kennel/models/slo.rb
CHANGED
data/lib/kennel/syncer.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module Kennel
|
|
3
3
|
class Syncer
|
|
4
|
-
TRACKING_FIELDS = [:message, :description].freeze
|
|
5
4
|
DELETE_ORDER = ["dashboard", "slo", "monitor"].freeze # dashboards references monitors + slos, slos reference monitors
|
|
6
5
|
|
|
7
6
|
def initialize(api, expected, project: nil)
|
|
@@ -16,7 +15,7 @@ module Kennel
|
|
|
16
15
|
raise "#{@project_filter} does not match any projects, try any of these:\n#{possible.join("\n")}"
|
|
17
16
|
end
|
|
18
17
|
end
|
|
19
|
-
@expected.each
|
|
18
|
+
@expected.each(&:add_tracking_id)
|
|
20
19
|
calculate_diff
|
|
21
20
|
prevent_irreversible_partial_updates
|
|
22
21
|
end
|
|
@@ -39,19 +38,22 @@ module Kennel
|
|
|
39
38
|
def update
|
|
40
39
|
each_resolved @create do |_, e|
|
|
41
40
|
reply = @api.create e.class.api_resource, e.as_json
|
|
41
|
+
reply[:klass] = e.class # store api resource class for later use
|
|
42
42
|
id = reply.fetch(:id)
|
|
43
43
|
populate_id_map [reply] # allow resolving ids we could previously no resolve
|
|
44
|
-
Kennel.out.puts "Created #{e.class.api_resource} #{
|
|
44
|
+
Kennel.out.puts "Created #{e.class.api_resource} #{e.tracking_id} #{e.class.url(id)}"
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
each_resolved @update do |id, e|
|
|
48
48
|
@api.update e.class.api_resource, id, e.as_json
|
|
49
|
-
Kennel.out.puts "Updated #{e.class.api_resource} #{
|
|
49
|
+
Kennel.out.puts "Updated #{e.class.api_resource} #{e.tracking_id} #{e.class.url(id)}"
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
@delete.each do |id, _, a|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
klass = a.fetch(:klass)
|
|
54
|
+
@api.delete klass.api_resource, id
|
|
55
|
+
tracking_id = klass.parse_tracking_id(a)
|
|
56
|
+
Kennel.out.puts "Deleted #{klass.api_resource} #{tracking_id} #{id}"
|
|
55
57
|
end
|
|
56
58
|
end
|
|
57
59
|
|
|
@@ -118,8 +120,8 @@ module Kennel
|
|
|
118
120
|
|
|
119
121
|
# fill details of things we need to compare
|
|
120
122
|
detailed = Hash.new { |h, k| h[k] = [] }
|
|
121
|
-
items.each { |e, a| detailed[a[:
|
|
122
|
-
detailed.each { |
|
|
123
|
+
items.each { |e, a| detailed[a[:klass]] << a if e }
|
|
124
|
+
detailed.each { |klass, actuals| @api.fill_details! klass.api_resource, actuals }
|
|
123
125
|
|
|
124
126
|
# pick out things to update or delete
|
|
125
127
|
items.each do |e, a|
|
|
@@ -127,7 +129,7 @@ module Kennel
|
|
|
127
129
|
if e
|
|
128
130
|
diff = e.diff(a)
|
|
129
131
|
@update << [id, e, a, diff] if diff.any?
|
|
130
|
-
elsif
|
|
132
|
+
elsif a.fetch(:klass).parse_tracking_id(a) # was previously managed
|
|
131
133
|
@delete << [id, nil, a]
|
|
132
134
|
end
|
|
133
135
|
end
|
|
@@ -136,14 +138,14 @@ module Kennel
|
|
|
136
138
|
@create = @expected.map { |e| [nil, e] }
|
|
137
139
|
end
|
|
138
140
|
|
|
139
|
-
@delete.sort_by! { |_, _, a| DELETE_ORDER.index a.fetch(:api_resource
|
|
141
|
+
@delete.sort_by! { |_, _, a| DELETE_ORDER.index a.fetch(:klass).api_resource }
|
|
140
142
|
end
|
|
141
143
|
|
|
142
144
|
def download_definitions
|
|
143
|
-
Utils.parallel(Models::Record.subclasses
|
|
144
|
-
results = @api.list(api_resource, with_downtimes: false) # lookup monitors without adding unnecessary downtime information
|
|
145
|
+
Utils.parallel(Models::Record.subclasses) do |klass|
|
|
146
|
+
results = @api.list(klass.api_resource, with_downtimes: false) # lookup monitors without adding unnecessary downtime information
|
|
145
147
|
results = results[results.keys.first] if results.is_a?(Hash) # dashboards are nested in {dashboards: []}
|
|
146
|
-
results.each { |c| c[:
|
|
148
|
+
results.each { |c| c[:klass] = klass } # store api resource for later diffing
|
|
147
149
|
end.flatten(1)
|
|
148
150
|
end
|
|
149
151
|
|
|
@@ -158,7 +160,7 @@ module Kennel
|
|
|
158
160
|
def matching_expected(a)
|
|
159
161
|
# index list by all the thing we look up by: tracking id and actual id
|
|
160
162
|
@lookup_map ||= @expected.each_with_object({}) do |e, all|
|
|
161
|
-
keys = [
|
|
163
|
+
keys = [e.tracking_id]
|
|
162
164
|
keys << "#{e.class.api_resource}:#{e.id}" if e.id
|
|
163
165
|
keys.compact.each do |key|
|
|
164
166
|
raise "Lookup #{key} is duplicated" if all[key]
|
|
@@ -166,14 +168,15 @@ module Kennel
|
|
|
166
168
|
end
|
|
167
169
|
end
|
|
168
170
|
|
|
169
|
-
|
|
171
|
+
klass = a.fetch(:klass)
|
|
172
|
+
@lookup_map["#{klass.api_resource}:#{a.fetch(:id)}"] || @lookup_map[klass.parse_tracking_id(a)]
|
|
170
173
|
end
|
|
171
174
|
|
|
172
175
|
def print_plan(step, list, color)
|
|
173
176
|
return if list.empty?
|
|
174
177
|
list.each do |_, e, a, diff|
|
|
175
|
-
|
|
176
|
-
Kennel.out.puts Utils.color(color, "#{step} #{api_resource} #{e&.tracking_id ||
|
|
178
|
+
klass = (e ? e.class : a.fetch(:klass))
|
|
179
|
+
Kennel.out.puts Utils.color(color, "#{step} #{klass.api_resource} #{e&.tracking_id || klass.parse_tracking_id(a)}")
|
|
177
180
|
print_diff(diff) if diff # only for update
|
|
178
181
|
end
|
|
179
182
|
end
|
|
@@ -199,26 +202,23 @@ module Kennel
|
|
|
199
202
|
end
|
|
200
203
|
end
|
|
201
204
|
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
# Note: ideally we'd never add tracking in the first place, but at that point we do not know the diff yet
|
|
205
|
+
# - do not add tracking-id when working with existing ids on a branch,
|
|
206
|
+
# so resource do not get deleted when running an update on master (for example merge->CI)
|
|
207
|
+
# - make sure the diff is clean, by kicking out the now noop-update
|
|
208
|
+
# - ideally we'd never add tracking in the first place, but when adding tracking we do not know the diff yet
|
|
207
209
|
def prevent_irreversible_partial_updates
|
|
208
210
|
return unless @project_filter
|
|
209
211
|
@update.select! do |_, e, _, diff|
|
|
210
|
-
next true unless e.id #
|
|
212
|
+
next true unless e.id # safe to add tracking when not having id
|
|
211
213
|
|
|
212
214
|
diff.select! do |field_diff|
|
|
213
|
-
(_, field,
|
|
214
|
-
|
|
215
|
+
(_, field, actual) = field_diff
|
|
216
|
+
# TODO: refactor this so TRACKING_FIELD stays record-private
|
|
217
|
+
next true if e.class::TRACKING_FIELD != field.to_sym # need to sym here because Hashdiff produces strings
|
|
218
|
+
next true if e.class.parse_tracking_id(field.to_sym => actual) # already has tracking id
|
|
215
219
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
else
|
|
219
|
-
field_diff[3] = remove_tracking_id(e) # make plan output match update
|
|
220
|
-
old != field_diff[3]
|
|
221
|
-
end
|
|
220
|
+
field_diff[3] = e.remove_tracking_id # make `rake plan` output match what we are sending
|
|
221
|
+
actual != field_diff[3] # discard diff if now nothing changes
|
|
222
222
|
end
|
|
223
223
|
|
|
224
224
|
!diff.empty?
|
|
@@ -226,7 +226,7 @@ module Kennel
|
|
|
226
226
|
end
|
|
227
227
|
|
|
228
228
|
def populate_id_map(actual)
|
|
229
|
-
actual.each { |a| @id_map[
|
|
229
|
+
actual.each { |a| @id_map[a.fetch(:klass).parse_tracking_id(a)] = a.fetch(:id) }
|
|
230
230
|
end
|
|
231
231
|
|
|
232
232
|
def resolve_linked_tracking_ids!(list, force: false)
|
|
@@ -236,39 +236,9 @@ module Kennel
|
|
|
236
236
|
def filter_by_project!(definitions)
|
|
237
237
|
return unless @project_filter
|
|
238
238
|
definitions.select! do |a|
|
|
239
|
-
id =
|
|
239
|
+
id = a.fetch(:klass).parse_tracking_id(a)
|
|
240
240
|
!id || id.start_with?("#{@project_filter}:")
|
|
241
241
|
end
|
|
242
242
|
end
|
|
243
|
-
|
|
244
|
-
def add_tracking_id(e)
|
|
245
|
-
json = e.as_json
|
|
246
|
-
field = tracking_field(json)
|
|
247
|
-
raise "remove \"-- Managed by kennel\" line it from #{field} to copy a resource" if tracking_value(json[field])
|
|
248
|
-
json[field] = "#{json[field]}\n-- Managed by kennel #{e.tracking_id} in #{e.project.class.file_location}, do not modify manually".lstrip
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
def remove_tracking_id(e)
|
|
252
|
-
json = e.as_json
|
|
253
|
-
field = tracking_field(json)
|
|
254
|
-
value = json[field]
|
|
255
|
-
json[field] = value.dup.sub!(/\n?-- Managed by kennel .*/, "") || raise("did not find tracking id in #{value}")
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
def tracking_id(a)
|
|
259
|
-
tracking_value a[tracking_field(a)]
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
def tracking_value(content)
|
|
263
|
-
content.to_s[/-- Managed by kennel (\S+:\S+)/, 1]
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
def tracking_field(a)
|
|
267
|
-
TRACKING_FIELDS.detect { |f| a.key?(f) }
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
def tracking_field?(field)
|
|
271
|
-
TRACKING_FIELDS.include?(field.to_sym)
|
|
272
|
-
end
|
|
273
243
|
end
|
|
274
244
|
end
|
|
@@ -36,7 +36,8 @@ module Kennel
|
|
|
36
36
|
|
|
37
37
|
def widget_queries(widget)
|
|
38
38
|
requests = widget.dig(:definition, :requests) || []
|
|
39
|
-
|
|
39
|
+
return requests.values.map { |r| r[:q] } if requests.is_a?(Hash) # hostmap widgets have hash requests
|
|
40
|
+
requests.flat_map { |r| r[:q] || r[:queries]&.map { |q| q[:query] } } # old format with q: or queries: [{query:}]
|
|
40
41
|
end
|
|
41
42
|
end
|
|
42
43
|
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.88.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: 2021-
|
|
11
|
+
date: 2021-06-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|