kennel 1.87.1 → 1.88.0

Sign up to get free protection for your applications and to get access to all the features.
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