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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42e4d1fd80bfa23ccbed32dcc6cfeb05fda2a2748bc29b302e6f982273302551
4
- data.tar.gz: 6ffc6ddafa5518868cc913fd05add8f11eb49729ba4ca498b357241ab80111f3
3
+ metadata.gz: ccd6dac941560095109ca60a7a60a58a247605c1c6ec8b658fda12ec6e60ea04
4
+ data.tar.gz: b17327485c61d273a474e0235c274b48e3f950586441bc676d3da14bcab55afc
5
5
  SHA512:
6
- metadata.gz: 76f2b875dd8e918db62b0ee13a6ad01acd6a187f11127824176cdee2f8665ec0ffa4d5ad3a0f8fbfd97fbf980faae8039cdbca8f2b078ef81e091a9a34c3ed0e
7
- data.tar.gz: a612a2d02f684f847ef6c167d2a3ef9fc815f80c1fd618106c8824017cede188c77dec3700d1a7afd46c481cd3c8fa7c37358af6a9737c1703a68491d159a5b1
6
+ metadata.gz: b0c6a49acdfc63bfcd86180ab5dfedd96aaf73d55c6cdf4890ca58e1a2408623237ada66b67a1278e09e0d207e8a2b7d9d7a45b7b2da09f3f4c750a35aca4011
7
+ data.tar.gz: 48ec7d742c67f23192550e567f343213a88bf8372c3f7e69604152bae0cd57e2b5b210ec288527ab6d28bd5a77f58b66f6a5b882e77b0ff413059c1e5622a888
@@ -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, *Syncer::TRACKING_FIELDS, :template_variables].freeze
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 tracking_field && data[tracking_field].sub!(/\n?-- Managed by kennel (\S+:\S+).*/, "")
38
- $1.split(":").last
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
- dry_up_query!(widget)
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 dry_up_query!(widget)
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
@@ -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, :api_resource
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! "#{type} #{tracking_id} was referenced but is also created by the current run.\nIt could not be created because of a circular dependency, try creating only some of the resources"
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
@@ -3,6 +3,7 @@ module Kennel
3
3
  module Models
4
4
  class Slo < Record
5
5
  READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [:type_id, :monitor_tags]
6
+ TRACKING_FIELD = :description
6
7
  DEFAULTS = {
7
8
  description: nil,
8
9
  query: nil,
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 { |e| add_tracking_id e }
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} #{tracking_id(e.as_json)} #{e.class.url(id)}"
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} #{tracking_id(e.as_json)} #{e.class.url(id)}"
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
- @api.delete a.fetch(:api_resource), id
54
- Kennel.out.puts "Deleted #{a.fetch(:api_resource)} #{tracking_id(a)} #{id}"
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[:api_resource]] << a if e }
122
- detailed.each { |api_resource, actuals| @api.fill_details! api_resource, actuals }
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 tracking_id(a) # was previously managed
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.map(&:api_resource)) do |api_resource|
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[:api_resource] = api_resource } # store api resource for later diffing
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 = [tracking_id(e.as_json)]
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
- @lookup_map["#{a.fetch(:api_resource)}:#{a.fetch(:id)}"] || @lookup_map[tracking_id(a)]
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
- api_resource = (e ? e.class.api_resource : a.fetch(:api_resource))
176
- Kennel.out.puts Utils.color(color, "#{step} #{api_resource} #{e&.tracking_id || tracking_id(a)}")
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
- # Do not add tracking-id when working with existing ids on a branch,
203
- # so resource do not get deleted from running an update on master (for example merge->CI).
204
- # Also make sure the diff still makes sense, by kicking out the now noop-update.
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 # short circuit for performance
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, old, new) = field_diff
214
- next true unless tracking_field?(field)
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
- if (old_tracking = tracking_value(old))
217
- old_tracking == tracking_value(new) || raise("do not update! (atm unreachable)")
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[tracking_id(a)] = a.fetch(:id) }
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 = tracking_id(a)
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
- (requests.is_a?(Hash) ? requests.values : requests).map { |r| r[:q] } # hostmap widgets have hash requests
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.87.1"
3
+ VERSION = "1.88.0"
4
4
  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.87.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-05-14 00:00:00.000000000 Z
11
+ date: 2021-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday