kennel 1.98.3 → 1.102.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: 1362ca60b0542b017fffad3e47bf1de8baf0b08950cb679a98760de51f0ef9c7
4
- data.tar.gz: 7dacd36972ad7cad574d50236b6463a6be11082373b752c6f5f4530a29230a34
3
+ metadata.gz: 424978ab5e025d68c1b30adee8dcf9bbae531c34da1ce02abcc1a97165accb2f
4
+ data.tar.gz: 762d7b47d15937d0ea9b2bb7d9bb1c79d88b34828a0b455c05aa4d7498fd8f04
5
5
  SHA512:
6
- metadata.gz: '069a8adb906d55515b5b21db21ca7186ab1d132b370bd9319910719df4d641ce71729f613b52e764b42a4127d0b32d0da86fdf9f01d440df870c2ebb52168730'
7
- data.tar.gz: ea453b3fc2afa94e9f9d916dcdec01b9e077816ea4fcb9c886e2b0a9e36c0c2a7623b4c63b0fd5c2014fa4e04198f42dd759d6335980df899c7bf0b6c8e7b42f
6
+ metadata.gz: 2079300fb176ffe4a3c363c56dfb2fed0901b4f3cfce7ff6fd1c656f8981e16a94628eaa660ffee6a53cc86c9df2d7b604ca877c6c34c61eeec13cbe4b0484ee
7
+ data.tar.gz: 0113a4f7a4edf4813d66cb15090909e63a47945cfaa4bce22cd9fe40203f3b6dd3705c2f62652df7e09be9e4b7a7d5de1de2092074e2baa524c8392a3bb9b462
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ module Kennel
3
+ class IdMap
4
+ NEW = :new # will be created during this run
5
+
6
+ def initialize
7
+ @map = Hash.new { |h, k| h[k] = {} }
8
+ end
9
+
10
+ def get(type, tracking_id)
11
+ @map[type][tracking_id]
12
+ end
13
+
14
+ def set(type, tracking_id, id)
15
+ @map[type][tracking_id] = id
16
+ end
17
+
18
+ def set_new(type, tracking_id)
19
+ @map[type][tracking_id] = NEW
20
+ end
21
+
22
+ def new?(type, tracking_id)
23
+ @map[type][tracking_id] == NEW
24
+ end
25
+ end
26
+ end
@@ -189,16 +189,17 @@ module Kennel
189
189
  when "uptime"
190
190
  if ids = definition[:monitor_ids]
191
191
  definition[:monitor_ids] = ids.map do |id|
192
- tracking_id?(id) ? (resolve_link(id, :monitor, id_map, **args) || id) : id
192
+ resolve(id, :monitor, id_map, **args) || id
193
193
  end
194
194
  end
195
195
  when "alert_graph"
196
- if (id = definition[:alert_id]) && tracking_id?(id)
197
- definition[:alert_id] = (resolve_link(id, :monitor, id_map, **args) || id).to_s
196
+ if id = definition[:alert_id]
197
+ resolved = resolve(id, :monitor, id_map, **args) || id
198
+ definition[:alert_id] = resolved.to_s # even though it's a monitor id
198
199
  end
199
200
  when "slo"
200
- if (id = definition[:slo_id]) && tracking_id?(id)
201
- definition[:slo_id] = (resolve_link(id, :slo, id_map, **args) || id).to_s
201
+ if id = definition[:slo_id]
202
+ definition[:slo_id] = resolve(id, :slo, id_map, **args) || id
202
203
  end
203
204
  end
204
205
  end
@@ -206,10 +207,6 @@ module Kennel
206
207
 
207
208
  private
208
209
 
209
- def tracking_id?(id)
210
- id.is_a?(String) && id.include?(":")
211
- end
212
-
213
210
  # creates queries from metadata to avoid having to keep q and expression in sync
214
211
  #
215
212
  # {q: :metadata, metadata: [{expression: "sum:bar", alias_name: "foo"}, ...], }
@@ -121,7 +121,7 @@ module Kennel
121
121
  when "composite", "slo alert"
122
122
  type = (as_json[:type] == "composite" ? :monitor : :slo)
123
123
  as_json[:query] = as_json[:query].gsub(/%{(.*?)}/) do
124
- resolve_link($1, type, id_map, **args) || $&
124
+ resolve($1, type, id_map, **args) || $&
125
125
  end
126
126
  end
127
127
  end
@@ -214,27 +214,47 @@ module Kennel
214
214
  end
215
215
 
216
216
  if ["query alert", "service check"].include?(type) # TODO: most likely more types need this
217
- # verify is_match/is_exact_match uses available variables
218
- message = data.fetch(:message)
219
- used = message.scan(/{{\s*([#^]is(?:_exact)?_match)\s*([^\s}]+)/)
220
- if used.any?
221
- allowed = data.fetch(:query)[/by\s*[({]([^})]+)[})]/, 1]
222
- .to_s.gsub(/["']/, "").split(/\s*,\s*/)
223
- .map! { |w| %("#{w}.name") }
224
- used.uniq.each do |match, group|
225
- next if allowed.include?(group)
226
- invalid!(
227
- "#{match} used with #{group}, but can only be used with #{allowed.join(", ")}. " \
228
- "Group the query by #{group.sub(".name", "").tr('"', "")} or change the #{match}"
229
- )
230
- end
231
- end
217
+ validate_message_variables(data)
232
218
  end
233
219
 
234
220
  unless ALLOWED_PRIORITY_CLASSES.include?(priority.class)
235
221
  invalid! "priority needs to be an Integer"
236
222
  end
237
223
  end
224
+
225
+ # verify is_match/is_exact_match and {{foo.name}} uses available variables
226
+ def validate_message_variables(data)
227
+ message = data.fetch(:message)
228
+
229
+ used =
230
+ message.scan(/{{\s*(?:[#^]is(?:_exact)?_match)\s*"([^\s}]+)"/) + # {{#is_match "environment.name" "production"}}
231
+ message.scan(/{{\s*([^}]+\.name)\s*}}/) # Pod {{pod.name}} failed
232
+ return if used.empty?
233
+ used.flatten!(1)
234
+ used.uniq!
235
+
236
+ # TODO
237
+ # - also match without by
238
+ # - separate parsers for query and service
239
+ # - service must always allow `host`, maybe others
240
+ return unless match = data.fetch(:query).match(/(?:{([^}]*)}\s*)?by\s*[({]([^})]+)[})]/)
241
+
242
+ allowed =
243
+ match[1].to_s.split(/\s*,\s*/).map { |k| k.split(":", 2)[-2] } + # {a:b} -> a TODO: does not work for service check
244
+ match[2].to_s.gsub(/["']/, "").split(/\s*,\s*/) # by {a} -> a
245
+
246
+ allowed.compact!
247
+ allowed.uniq!
248
+ allowed.map! { |w| "#{w.tr('"', "")}.name" }
249
+
250
+ forbidden = used - allowed
251
+ return if forbidden.empty?
252
+
253
+ invalid! <<~MSG.rstrip
254
+ Used #{forbidden.join(", ")} in the message, but can only be used with #{allowed.join(", ")}.
255
+ Group or filter the query by #{forbidden.map { |f| f.sub(".name", "") }.join(", ")} to use it.
256
+ MSG
257
+ end
238
258
  end
239
259
  end
240
260
  end
@@ -100,9 +100,20 @@ module Kennel
100
100
 
101
101
  private
102
102
 
103
+ def resolve(value, type, id_map, force:)
104
+ if tracking_id?(value)
105
+ return resolve_link(value, type, id_map, force: force)
106
+ end
107
+
108
+ value
109
+ end
110
+
111
+ def tracking_id?(id)
112
+ id.is_a?(String) && id.include?(":")
113
+ end
114
+
103
115
  def resolve_link(tracking_id, type, id_map, force:)
104
- id = id_map[tracking_id]
105
- if id == :new
116
+ if id_map.new?(type.to_s, tracking_id)
106
117
  if force
107
118
  invalid!(
108
119
  "#{type} #{tracking_id} was referenced but is also created by the current run.\n" \
@@ -111,7 +122,7 @@ module Kennel
111
122
  else
112
123
  nil # will be re-resolved after the linked object was created
113
124
  end
114
- elsif id
125
+ elsif id = id_map.get(type.to_s, tracking_id)
115
126
  id
116
127
  else
117
128
  invalid! "Unable to find #{type} #{tracking_id} (does not exist and is not being created by the current run)"
@@ -70,7 +70,7 @@ module Kennel
70
70
  def resolve_linked_tracking_ids!(id_map, **args)
71
71
  return unless as_json[:monitor_ids] # ignore_default can remove it
72
72
  as_json[:monitor_ids] = as_json[:monitor_ids].map do |id|
73
- id.is_a?(String) ? (resolve_link(id, :monitor, id_map, **args) || id) : id
73
+ resolve(id, :monitor, id_map, **args) || id
74
74
  end
75
75
  end
76
76
 
data/lib/kennel/syncer.rb CHANGED
@@ -122,7 +122,7 @@ module Kennel
122
122
  def calculate_diff
123
123
  @update = []
124
124
  @delete = []
125
- @id_map = {}
125
+ @id_map = IdMap.new
126
126
 
127
127
  actual = Progress.progress("Downloading definitions") { download_definitions }
128
128
 
@@ -254,11 +254,19 @@ module Kennel
254
254
  end
255
255
 
256
256
  def populate_id_map(expected, actual)
257
+ expected.each do |e|
258
+ @id_map.set_new(e.class.api_resource, e.tracking_id)
259
+ end
260
+
257
261
  actual.each do |a|
258
262
  next unless tracking_id = a.fetch(:tracking_id)
259
- @id_map[tracking_id] = a.fetch(:id)
263
+ next unless @id_map.get(a.fetch(:klass).api_resource, tracking_id)
264
+
265
+ @id_map.set(a.fetch(:klass).api_resource, tracking_id, a.fetch(:id))
266
+ if a[:klass].api_resource == "synthetics/tests"
267
+ @id_map.set(Kennel::Models::Monitor.api_resource, tracking_id, a.fetch(:monitor_id))
268
+ end
260
269
  end
261
- expected.each { |e| @id_map[e.tracking_id] ||= :new }
262
270
  end
263
271
 
264
272
  def resolve_linked_tracking_ids!(list, force: false)
data/lib/kennel/tasks.rb CHANGED
@@ -3,6 +3,7 @@ require "English"
3
3
  require "kennel"
4
4
  require "kennel/unmuted_alerts"
5
5
  require "kennel/importer"
6
+ require "json"
6
7
 
7
8
  module Kennel
8
9
  module Tasks
@@ -89,7 +90,7 @@ namespace :kennel do
89
90
  Kennel::UnmutedAlerts.print(Kennel.send(:api), tag)
90
91
  end
91
92
 
92
- desc "show monitors with no data by TAG, for example TAG=team:foo"
93
+ desc "show monitors with no data by TAG, for example TAG=team:foo [THRESHOLD_DAYS=7] [FORMAT=json]"
93
94
  task nodata: :environment do
94
95
  tag = ENV["TAG"] || Kennel::Tasks.abort("Call with TAG=foo:bar")
95
96
  monitors = Kennel.send(:api).list("monitor", monitor_tags: tag, group_states: "no data")
@@ -97,16 +98,45 @@ namespace :kennel do
97
98
  monitors.reject! { |m| m[:tags].include? "nodata:ignore" }
98
99
  if monitors.any?
99
100
  Kennel.err.puts <<~TEXT
100
- This is a useful task to find monitors that have mis-spelled metrics or never received data at any time.
101
- To ignore monitors with nodata, tag the monitor with "nodata:ignore"
101
+ To ignore monitors with expected nodata, tag it with "nodata:ignore"
102
102
 
103
103
  TEXT
104
104
  end
105
105
 
106
+ now = Time.now
106
107
  monitors.each do |m|
107
- Kennel.out.puts m[:name]
108
- Kennel.out.puts Kennel::Utils.path_to_url("/monitors/#{m[:id]}")
109
- Kennel.out.puts
108
+ m[:days_in_no_data] =
109
+ if m[:overall_state_modified]
110
+ since = Date.parse(m[:overall_state_modified]).to_time
111
+ ((now - since) / (24 * 60 * 60)).to_i
112
+ else
113
+ 999
114
+ end
115
+ end
116
+
117
+ if threshold = ENV["THRESHOLD_DAYS"]
118
+ monitors.select! { |m| m[:days_in_no_data] > Integer(threshold) }
119
+ end
120
+
121
+ monitors.each { |m| m[:url] = Kennel::Utils.path_to_url("/monitors/#{m[:id]}") }
122
+
123
+ if ENV["FORMAT"] == "json"
124
+ report = monitors.map do |m|
125
+ match = m[:message].to_s.match(/-- Managed by kennel (\S+:\S+) in (\S+), /) || []
126
+ m.slice(:url, :name, :tags, :days_in_no_data).merge(
127
+ kennel_tracking_id: match[1],
128
+ kennel_source: match[2]
129
+ )
130
+ end
131
+
132
+ Kennel.out.puts JSON.pretty_generate(report)
133
+ else
134
+ monitors.each do |m|
135
+ Kennel.out.puts m[:name]
136
+ Kennel.out.puts Kennel::Utils.path_to_url("/monitors/#{m[:id]}")
137
+ Kennel.out.puts "No data since #{m[:days_in_no_data]}d"
138
+ Kennel.out.puts
139
+ end
110
140
  end
111
141
  end
112
142
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.98.3"
3
+ VERSION = "1.102.0"
4
4
  end
data/lib/kennel.rb CHANGED
@@ -9,6 +9,7 @@ require "kennel/version"
9
9
  require "kennel/utils"
10
10
  require "kennel/progress"
11
11
  require "kennel/syncer"
12
+ require "kennel/id_map"
12
13
  require "kennel/api"
13
14
  require "kennel/github_reporter"
14
15
  require "kennel/subclass_tracking"
@@ -69,7 +70,8 @@ module Kennel
69
70
  path = "generated/#{part.tracking_id.tr("/", ":").sub(":", "/")}.json"
70
71
  used << File.dirname(path) # only 1 level of sub folders, so this is safe
71
72
  used << path
72
- write_file_if_necessary(path, JSON.pretty_generate(part.as_json) << "\n")
73
+ payload = part.as_json.merge(api_resource: part.class.api_resource)
74
+ write_file_if_necessary(path, JSON.pretty_generate(payload) << "\n")
73
75
  end
74
76
 
75
77
  # deleting all is slow, so only delete the extras
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.98.3
4
+ version: 1.102.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-04 00:00:00.000000000 Z
11
+ date: 2021-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -77,6 +77,7 @@ files:
77
77
  - lib/kennel/api.rb
78
78
  - lib/kennel/file_cache.rb
79
79
  - lib/kennel/github_reporter.rb
80
+ - lib/kennel/id_map.rb
80
81
  - lib/kennel/importer.rb
81
82
  - lib/kennel/models/base.rb
82
83
  - lib/kennel/models/dashboard.rb