kennel 1.98.3 → 1.102.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/id_map.rb +26 -0
- data/lib/kennel/models/dashboard.rb +6 -9
- data/lib/kennel/models/monitor.rb +36 -16
- data/lib/kennel/models/record.rb +14 -3
- data/lib/kennel/models/slo.rb +1 -1
- data/lib/kennel/syncer.rb +11 -3
- data/lib/kennel/tasks.rb +36 -6
- data/lib/kennel/version.rb +1 -1
- data/lib/kennel.rb +3 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 424978ab5e025d68c1b30adee8dcf9bbae531c34da1ce02abcc1a97165accb2f
|
|
4
|
+
data.tar.gz: 762d7b47d15937d0ea9b2bb7d9bb1c79d88b34828a0b455c05aa4d7498fd8f04
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
192
|
+
resolve(id, :monitor, id_map, **args) || id
|
|
193
193
|
end
|
|
194
194
|
end
|
|
195
195
|
when "alert_graph"
|
|
196
|
-
if
|
|
197
|
-
|
|
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
|
|
201
|
-
definition[:slo_id] = (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/kennel/models/record.rb
CHANGED
|
@@ -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
|
-
|
|
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)"
|
data/lib/kennel/models/slo.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
data/lib/kennel/version.rb
CHANGED
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
|
-
|
|
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.
|
|
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-
|
|
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
|