kennel 1.123.0 → 1.124.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/api.rb +4 -4
- data/lib/kennel/importer.rb +1 -1
- data/lib/kennel/models/monitor.rb +7 -1
- data/lib/kennel/models/record.rb +44 -12
- data/lib/kennel/optional_validations.rb +58 -1
- data/lib/kennel/syncer.rb +39 -28
- data/lib/kennel/tasks.rb +16 -12
- data/lib/kennel/utils.rb +6 -0
- data/lib/kennel/version.rb +1 -1
- data/lib/kennel.rb +30 -29
- metadata +2 -3
- data/lib/kennel/compatibility.rb +0 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 31edba322106d5b2f1942c57640f19eff6a265ea970b5bb2cc7f4ef02d1889fd
|
|
4
|
+
data.tar.gz: f47474f8b7d745714fb49231755ed2bf41fae62cf8b5ff3817017c8cd8277713
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3dc6b1b72e834b7b7663b577c830dfa23da87a197752b04988edd2366ac9089685ff01e19a1e3c8244ac9a41f4217c75ec34bc6198ca83cf545bf6079069d71e
|
|
7
|
+
data.tar.gz: 1dffcf6adbd06fe80117d9a5f9bc3b55fac90bc21abb4fdd4ebeb5b726ade944c799edbbcca3eabbc5a28beff6a302011ecfcce18f041b4ad2228076e164c2d6
|
data/lib/kennel/api.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# encapsulates knowledge around how the api works
|
|
3
|
-
# especially 1-off weirdness that should not
|
|
3
|
+
# especially 1-off weirdness that should not leak into other parts of the code
|
|
4
4
|
module Kennel
|
|
5
5
|
class Api
|
|
6
6
|
CACHE_FILE = "tmp/cache/details"
|
|
7
7
|
|
|
8
|
-
def initialize(app_key, api_key)
|
|
9
|
-
@app_key = app_key
|
|
10
|
-
@api_key = api_key
|
|
8
|
+
def initialize(app_key = nil, api_key = nil)
|
|
9
|
+
@app_key = app_key || ENV.fetch("DATADOG_APP_KEY")
|
|
10
|
+
@api_key = api_key || ENV.fetch("DATADOG_API_KEY")
|
|
11
11
|
url = Utils.path_to_url("", subdomain: "app")
|
|
12
12
|
@client = Faraday.new(url: url) { |c| c.adapter :net_http_persistent }
|
|
13
13
|
end
|
data/lib/kennel/importer.rb
CHANGED
|
@@ -98,7 +98,7 @@ module Kennel
|
|
|
98
98
|
def link_composite_monitors(data)
|
|
99
99
|
if data[:type] == "composite"
|
|
100
100
|
data[:query].gsub!(/\d+/) do |id|
|
|
101
|
-
object =
|
|
101
|
+
object = @api.show("monitor", id)
|
|
102
102
|
tracking_id = Kennel::Models::Monitor.parse_tracking_id(object)
|
|
103
103
|
tracking_id ? "%{#{tracking_id}}" : id
|
|
104
104
|
rescue StandardError # monitor not found
|
|
@@ -39,7 +39,7 @@ module Kennel
|
|
|
39
39
|
renotify_interval: -> { project.team.renotify_interval },
|
|
40
40
|
warning: -> { nil },
|
|
41
41
|
ok: -> { nil },
|
|
42
|
-
notify_no_data: -> { true }, # datadog sets this to false by default, but true is
|
|
42
|
+
notify_no_data: -> { true }, # datadog UI sets this to false by default, but true is safer
|
|
43
43
|
no_data_timeframe: -> { 60 },
|
|
44
44
|
notify_audit: -> { MONITOR_OPTION_DEFAULTS.fetch(:notify_audit) },
|
|
45
45
|
new_host_delay: -> { MONITOR_OPTION_DEFAULTS.fetch(:new_host_delay) },
|
|
@@ -99,6 +99,12 @@ module Kennel
|
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
+
# setting this via the api breaks the UI with
|
|
103
|
+
# "The no_data_timeframe option is not allowed for log alert monitors"
|
|
104
|
+
if data.fetch(:type) == "log alert"
|
|
105
|
+
options.delete :no_data_timeframe
|
|
106
|
+
end
|
|
107
|
+
|
|
102
108
|
if windows = threshold_windows
|
|
103
109
|
options[:threshold_windows] = windows
|
|
104
110
|
end
|
data/lib/kennel/models/record.rb
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
module Kennel
|
|
3
3
|
module Models
|
|
4
4
|
class Record < Base
|
|
5
|
+
class PrepareError < StandardError
|
|
6
|
+
def initialize(tracking_id)
|
|
7
|
+
super("Error while preparing #{tracking_id}")
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
UnvalidatedRecordError = Class.new(StandardError)
|
|
12
|
+
|
|
5
13
|
include OptionalValidations
|
|
6
14
|
|
|
7
15
|
# Apart from if you just don't like the default for some reason,
|
|
@@ -76,7 +84,7 @@ module Kennel
|
|
|
76
84
|
end
|
|
77
85
|
end
|
|
78
86
|
|
|
79
|
-
attr_reader :project
|
|
87
|
+
attr_reader :project, :unfiltered_validation_errors, :filtered_validation_errors
|
|
80
88
|
|
|
81
89
|
def initialize(project, *args)
|
|
82
90
|
raise ArgumentError, "First argument must be a project, not #{project.class}" unless project.is_a?(Project)
|
|
@@ -100,7 +108,7 @@ module Kennel
|
|
|
100
108
|
@tracking_id ||= begin
|
|
101
109
|
id = "#{project.kennel_id}:#{kennel_id}"
|
|
102
110
|
unless id.match?(ALLOWED_KENNEL_ID_REGEX) # <-> parse_tracking_id
|
|
103
|
-
raise "#{id} must match #{ALLOWED_KENNEL_ID_REGEX}"
|
|
111
|
+
raise "Bad kennel/tracking id: #{id.inspect} must match #{ALLOWED_KENNEL_ID_REGEX}"
|
|
104
112
|
end
|
|
105
113
|
id
|
|
106
114
|
end
|
|
@@ -112,7 +120,7 @@ module Kennel
|
|
|
112
120
|
def add_tracking_id
|
|
113
121
|
json = as_json
|
|
114
122
|
if self.class.parse_tracking_id(json)
|
|
115
|
-
raise "#{
|
|
123
|
+
raise "#{safe_tracking_id} Remove \"-- #{MARKER_TEXT}\" line from #{self.class::TRACKING_FIELD} to copy a resource"
|
|
116
124
|
end
|
|
117
125
|
json[self.class::TRACKING_FIELD] =
|
|
118
126
|
"#{json[self.class::TRACKING_FIELD]}\n" \
|
|
@@ -129,13 +137,31 @@ module Kennel
|
|
|
129
137
|
}.compact
|
|
130
138
|
end
|
|
131
139
|
|
|
140
|
+
def build
|
|
141
|
+
@unfiltered_validation_errors = []
|
|
142
|
+
json = nil
|
|
143
|
+
|
|
144
|
+
begin
|
|
145
|
+
json = build_json
|
|
146
|
+
(id = json.delete(:id)) && json[:id] = id
|
|
147
|
+
validate_json(json)
|
|
148
|
+
rescue StandardError
|
|
149
|
+
if unfiltered_validation_errors.empty?
|
|
150
|
+
@unfiltered_validation_errors = nil
|
|
151
|
+
raise PrepareError, safe_tracking_id
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
@filtered_validation_errors = filter_validation_errors
|
|
156
|
+
@as_json = json # Only valid if filtered_validation_errors.empty?
|
|
157
|
+
end
|
|
158
|
+
|
|
132
159
|
def as_json
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
end
|
|
160
|
+
# A courtesy to those tests that still expect as_json to perform validation and raise on error
|
|
161
|
+
build if @unfiltered_validation_errors.nil?
|
|
162
|
+
raise UnvalidatedRecordError, "#{safe_tracking_id} as_json called on invalid part" unless filtered_validation_errors.empty?
|
|
163
|
+
|
|
164
|
+
@as_json
|
|
139
165
|
end
|
|
140
166
|
|
|
141
167
|
# Can raise DisallowedUpdateError
|
|
@@ -143,7 +169,14 @@ module Kennel
|
|
|
143
169
|
end
|
|
144
170
|
|
|
145
171
|
def invalid_update!(field, old_value, new_value)
|
|
146
|
-
raise DisallowedUpdateError, "#{
|
|
172
|
+
raise DisallowedUpdateError, "#{safe_tracking_id} Datadog does not allow update of #{field} (#{old_value.inspect} -> #{new_value.inspect})"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# For use during error handling
|
|
176
|
+
def safe_tracking_id
|
|
177
|
+
tracking_id
|
|
178
|
+
rescue StandardError
|
|
179
|
+
"<unknown; #tracking_id crashed>"
|
|
147
180
|
end
|
|
148
181
|
|
|
149
182
|
private
|
|
@@ -178,9 +211,8 @@ module Kennel
|
|
|
178
211
|
end
|
|
179
212
|
end
|
|
180
213
|
|
|
181
|
-
# let users know which project/resource failed when something happens during diffing where the backtrace is hidden
|
|
182
214
|
def invalid!(message)
|
|
183
|
-
|
|
215
|
+
unfiltered_validation_errors << ValidationMessage.new(message)
|
|
184
216
|
end
|
|
185
217
|
|
|
186
218
|
def raise_with_location(error, message)
|
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module Kennel
|
|
3
3
|
module OptionalValidations
|
|
4
|
+
ValidationMessage = Struct.new(:text)
|
|
5
|
+
|
|
4
6
|
def self.included(base)
|
|
5
7
|
base.settings :validate
|
|
6
8
|
base.defaults(validate: -> { true })
|
|
7
9
|
end
|
|
8
10
|
|
|
11
|
+
def self.valid?(parts)
|
|
12
|
+
parts_with_errors = parts.reject do |part|
|
|
13
|
+
part.filtered_validation_errors.empty?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
return true if parts_with_errors.empty?
|
|
17
|
+
|
|
18
|
+
Kennel.err.puts
|
|
19
|
+
parts_with_errors.sort_by(&:safe_tracking_id).each do |part|
|
|
20
|
+
part.filtered_validation_errors.each do |err|
|
|
21
|
+
Kennel.err.puts "#{part.safe_tracking_id} #{err.text}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
Kennel.err.puts
|
|
25
|
+
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
|
|
9
29
|
private
|
|
10
30
|
|
|
11
31
|
def validate_json(data)
|
|
12
|
-
bad = Kennel::Utils.all_keys(data).grep_v(Symbol)
|
|
32
|
+
bad = Kennel::Utils.all_keys(data).grep_v(Symbol).sort.uniq
|
|
13
33
|
return if bad.empty?
|
|
14
34
|
invalid!(
|
|
15
35
|
"Only use Symbols as hash keys to avoid permanent diffs when updating.\n" \
|
|
@@ -17,5 +37,42 @@ module Kennel
|
|
|
17
37
|
"#{bad.map(&:inspect).join("\n")}"
|
|
18
38
|
)
|
|
19
39
|
end
|
|
40
|
+
|
|
41
|
+
def filter_validation_errors
|
|
42
|
+
if validate
|
|
43
|
+
unfiltered_validation_errors
|
|
44
|
+
elsif unfiltered_validation_errors.empty?
|
|
45
|
+
msg = "`validate` is set to false, but there are no validation errors to suppress. Remove `validate: false`"
|
|
46
|
+
|
|
47
|
+
mode = ENV.fetch("UNNECESSARY_VALIDATE_FALSE") do
|
|
48
|
+
if ENV.key?("PROJECT") || ENV.key?("TRACKING_ID")
|
|
49
|
+
"fail"
|
|
50
|
+
else
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if mode == "fail"
|
|
56
|
+
[ValidationMessage.new(msg)]
|
|
57
|
+
else
|
|
58
|
+
Kennel.out.puts "#{safe_tracking_id} #{msg}" if mode == "show"
|
|
59
|
+
[]
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
mode = ENV.fetch("SUPPRESSED_ERRORS", "ignore")
|
|
63
|
+
|
|
64
|
+
if mode == "fail"
|
|
65
|
+
unfiltered_validation_errors
|
|
66
|
+
else
|
|
67
|
+
if mode == "show"
|
|
68
|
+
unfiltered_validation_errors.each do |err|
|
|
69
|
+
Kennel.out.puts "#{safe_tracking_id} `validate: false` suppressing error: #{err.text.gsub("\n", " ")}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
[]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
20
77
|
end
|
|
21
78
|
end
|
data/lib/kennel/syncer.rb
CHANGED
|
@@ -6,12 +6,11 @@ module Kennel
|
|
|
6
6
|
class Syncer
|
|
7
7
|
DELETE_ORDER = ["dashboard", "slo", "monitor", "synthetics/tests"].freeze # dashboards references monitors + slos, slos reference monitors
|
|
8
8
|
LINE_UP = "\e[1A\033[K" # go up and clear
|
|
9
|
+
Plan = Struct.new(:changes, keyword_init: true)
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
Update = Struct.new(:update_log, keyword_init: true)
|
|
12
|
-
|
|
13
|
-
def initialize(api, expected, project_filter: nil, tracking_id_filter: nil)
|
|
11
|
+
def initialize(api, expected, kennel:, project_filter: nil, tracking_id_filter: nil)
|
|
14
12
|
@api = api
|
|
13
|
+
@kennel = kennel
|
|
15
14
|
@project_filter = project_filter
|
|
16
15
|
@tracking_id_filter = tracking_id_filter
|
|
17
16
|
@expected = Set.new expected # need set to speed up deletion
|
|
@@ -32,11 +31,10 @@ module Kennel
|
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
Plan.new(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
delete: @delete
|
|
34
|
+
changes:
|
|
35
|
+
@create.map { |_id, e| [:create, e.class.api_resource, e.tracking_id, nil] } +
|
|
36
|
+
@update.map { |_id, e| [:create, e.class.api_resource, e.tracking_id, nil] } +
|
|
37
|
+
@delete.map { |_id, _e, a| [:delete, a.fetch(:klass).api_resource, a.fetch(:tracking_id), a.fetch(:id)] }
|
|
40
38
|
)
|
|
41
39
|
end
|
|
42
40
|
|
|
@@ -47,7 +45,7 @@ module Kennel
|
|
|
47
45
|
end
|
|
48
46
|
|
|
49
47
|
def update
|
|
50
|
-
|
|
48
|
+
changes = []
|
|
51
49
|
|
|
52
50
|
each_resolved @create do |_, e|
|
|
53
51
|
message = "#{e.class.api_resource} #{e.tracking_id}"
|
|
@@ -55,7 +53,7 @@ module Kennel
|
|
|
55
53
|
reply = @api.create e.class.api_resource, e.as_json
|
|
56
54
|
cache_metadata reply, e.class
|
|
57
55
|
id = reply.fetch(:id)
|
|
58
|
-
|
|
56
|
+
changes << [:create, e.class.api_resource, e.tracking_id, id]
|
|
59
57
|
populate_id_map [], [reply] # allow resolving ids we could previously no resolve
|
|
60
58
|
Kennel.out.puts "#{LINE_UP}Created #{message} #{e.class.url(id)}"
|
|
61
59
|
end
|
|
@@ -64,7 +62,7 @@ module Kennel
|
|
|
64
62
|
message = "#{e.class.api_resource} #{e.tracking_id} #{e.class.url(id)}"
|
|
65
63
|
Kennel.out.puts "Updating #{message}"
|
|
66
64
|
@api.update e.class.api_resource, id, e.as_json
|
|
67
|
-
|
|
65
|
+
changes << [:update, e.class.api_resource, e.tracking_id, id]
|
|
68
66
|
Kennel.out.puts "#{LINE_UP}Updated #{message}"
|
|
69
67
|
end
|
|
70
68
|
|
|
@@ -73,15 +71,17 @@ module Kennel
|
|
|
73
71
|
message = "#{klass.api_resource} #{a.fetch(:tracking_id)} #{id}"
|
|
74
72
|
Kennel.out.puts "Deleting #{message}"
|
|
75
73
|
@api.delete klass.api_resource, id
|
|
76
|
-
|
|
74
|
+
changes << [:delete, klass.api_resource, a.fetch(:tracking_id), id]
|
|
77
75
|
Kennel.out.puts "#{LINE_UP}Deleted #{message}"
|
|
78
76
|
end
|
|
79
77
|
|
|
80
|
-
|
|
78
|
+
Plan.new(changes: changes)
|
|
81
79
|
end
|
|
82
80
|
|
|
83
81
|
private
|
|
84
82
|
|
|
83
|
+
attr_reader :kennel
|
|
84
|
+
|
|
85
85
|
# loop over items until everything is resolved or crash when we get stuck
|
|
86
86
|
# this solves cases like composite monitors depending on each other or monitor->monitor slo->slo monitor chains
|
|
87
87
|
def each_resolved(list)
|
|
@@ -121,7 +121,6 @@ module Kennel
|
|
|
121
121
|
@warnings = []
|
|
122
122
|
@update = []
|
|
123
123
|
@delete = []
|
|
124
|
-
@no_change = []
|
|
125
124
|
@id_map = IdMap.new
|
|
126
125
|
|
|
127
126
|
actual = Progress.progress("Downloading definitions") { download_definitions }
|
|
@@ -154,8 +153,6 @@ module Kennel
|
|
|
154
153
|
diff = e.diff(a) # slow ...
|
|
155
154
|
if diff.any?
|
|
156
155
|
@update << [id, e, a, diff]
|
|
157
|
-
else
|
|
158
|
-
@no_change << [id, e, a]
|
|
159
156
|
end
|
|
160
157
|
elsif a.fetch(:tracking_id) # was previously managed
|
|
161
158
|
@delete << [id, nil, a]
|
|
@@ -186,7 +183,7 @@ module Kennel
|
|
|
186
183
|
@expected.each do |e|
|
|
187
184
|
next unless id = e.id
|
|
188
185
|
resource = e.class.api_resource
|
|
189
|
-
if
|
|
186
|
+
if kennel.strict_imports
|
|
190
187
|
raise "Unable to find existing #{resource} with id #{id}\nIf the #{resource} was deleted, remove the `id: -> { #{id} }` line."
|
|
191
188
|
else
|
|
192
189
|
@warnings << "#{resource} #{e.tracking_id} specifies id #{id}, but no such #{resource} exists. 'id' will be ignored. Remove the `id: -> { #{id} }` line."
|
|
@@ -234,16 +231,19 @@ module Kennel
|
|
|
234
231
|
new = Utils.pretty_inspect(new)
|
|
235
232
|
end
|
|
236
233
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
234
|
+
message =
|
|
235
|
+
if use_diff
|
|
236
|
+
" #{type}#{field}\n" +
|
|
237
|
+
diff(old, new).map { |l| " #{l}" }.join("\n")
|
|
238
|
+
elsif (old + new).size > 100
|
|
239
|
+
" #{type}#{field}\n" \
|
|
240
|
+
" #{old} ->\n" \
|
|
241
|
+
" #{new}"
|
|
242
|
+
else
|
|
243
|
+
" #{type}#{field} #{old} -> #{new}"
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
Kennel.out.puts truncate_diff(message)
|
|
247
247
|
end
|
|
248
248
|
end
|
|
249
249
|
|
|
@@ -267,6 +267,17 @@ module Kennel
|
|
|
267
267
|
end
|
|
268
268
|
end
|
|
269
269
|
|
|
270
|
+
def truncate_diff(message)
|
|
271
|
+
# min '2' because: -1 makes no sense, 0 does not work with * 2 math, 1 says '1 lines'
|
|
272
|
+
@max_diff_lines ||= [Integer(ENV.fetch("MAX_DIFF_LINES", "50")), 2].max
|
|
273
|
+
warning = Utils.color(
|
|
274
|
+
:magenta,
|
|
275
|
+
" (Diff for this item truncated after #{@max_diff_lines} lines. " \
|
|
276
|
+
"Rerun with MAX_DIFF_LINES=#{@max_diff_lines * 2} to see more)"
|
|
277
|
+
)
|
|
278
|
+
Utils.truncate_lines(message, to: @max_diff_lines, warning: warning)
|
|
279
|
+
end
|
|
280
|
+
|
|
270
281
|
# We've already validated the desired objects ('generated') in isolation.
|
|
271
282
|
# Now that we have made the plan, we can perform some more validation.
|
|
272
283
|
def validate_plan
|
data/lib/kennel/tasks.rb
CHANGED
|
@@ -8,6 +8,10 @@ require "json"
|
|
|
8
8
|
module Kennel
|
|
9
9
|
module Tasks
|
|
10
10
|
class << self
|
|
11
|
+
def kennel
|
|
12
|
+
@kennel ||= Kennel::Engine.new
|
|
13
|
+
end
|
|
14
|
+
|
|
11
15
|
def abort(message = nil)
|
|
12
16
|
Kennel.err.puts message if message
|
|
13
17
|
raise SystemExit.new(1), message
|
|
@@ -35,10 +39,10 @@ module Kennel
|
|
|
35
39
|
load_environment
|
|
36
40
|
|
|
37
41
|
if on_default_branch? && git_push?
|
|
38
|
-
Kennel.strict_imports = false
|
|
39
|
-
Kennel.update
|
|
42
|
+
Kennel::Tasks.kennel.strict_imports = false
|
|
43
|
+
Kennel::Tasks.kennel.update
|
|
40
44
|
else
|
|
41
|
-
Kennel.plan # show plan in CI logs
|
|
45
|
+
Kennel::Tasks.kennel.plan # show plan in CI logs
|
|
42
46
|
end
|
|
43
47
|
end
|
|
44
48
|
|
|
@@ -66,7 +70,7 @@ namespace :kennel do
|
|
|
66
70
|
# https://help.datadoghq.com/hc/en-us/requests/254114 for automatic validation
|
|
67
71
|
desc "Verify that all used monitor mentions are valid"
|
|
68
72
|
task validate_mentions: :environment do
|
|
69
|
-
known = Kennel.
|
|
73
|
+
known = Kennel::Api.new
|
|
70
74
|
.send(:request, :get, "/monitor/notifications")
|
|
71
75
|
.fetch(:handles)
|
|
72
76
|
.values
|
|
@@ -93,18 +97,18 @@ namespace :kennel do
|
|
|
93
97
|
|
|
94
98
|
desc "generate local definitions"
|
|
95
99
|
task generate: :environment do
|
|
96
|
-
Kennel.generate
|
|
100
|
+
Kennel::Tasks.kennel.generate
|
|
97
101
|
end
|
|
98
102
|
|
|
99
103
|
# also generate parts so users see and commit updated generated automatically
|
|
100
104
|
desc "show planned datadog changes (scope with PROJECT=name)"
|
|
101
105
|
task plan: :generate do
|
|
102
|
-
Kennel.plan
|
|
106
|
+
Kennel::Tasks.kennel.plan
|
|
103
107
|
end
|
|
104
108
|
|
|
105
109
|
desc "update datadog (scope with PROJECT=name)"
|
|
106
110
|
task update_datadog: :environment do
|
|
107
|
-
Kennel.update
|
|
111
|
+
Kennel::Tasks.kennel.update
|
|
108
112
|
end
|
|
109
113
|
|
|
110
114
|
desc "update on push to the default branch, otherwise show plan"
|
|
@@ -115,13 +119,13 @@ namespace :kennel do
|
|
|
115
119
|
desc "show unmuted alerts filtered by TAG, for example TAG=team:foo"
|
|
116
120
|
task alerts: :environment do
|
|
117
121
|
tag = ENV["TAG"] || Kennel::Tasks.abort("Call with TAG=foo:bar")
|
|
118
|
-
Kennel::UnmutedAlerts.print(Kennel.
|
|
122
|
+
Kennel::UnmutedAlerts.print(Kennel::Api.new, tag)
|
|
119
123
|
end
|
|
120
124
|
|
|
121
125
|
desc "show monitors with no data by TAG, for example TAG=team:foo [THRESHOLD_DAYS=7] [FORMAT=json]"
|
|
122
126
|
task nodata: :environment do
|
|
123
127
|
tag = ENV["TAG"] || Kennel::Tasks.abort("Call with TAG=foo:bar")
|
|
124
|
-
monitors = Kennel.
|
|
128
|
+
monitors = Kennel::Api.new.list("monitor", monitor_tags: tag, group_states: "no data")
|
|
125
129
|
monitors.select! { |m| m[:overall_state] == "No Data" }
|
|
126
130
|
monitors.reject! { |m| m[:tags].include? "nodata:ignore" }
|
|
127
131
|
if monitors.any?
|
|
@@ -179,7 +183,7 @@ namespace :kennel do
|
|
|
179
183
|
Kennel::Tasks.abort("Call with URL= or call with RESOURCE=#{possible_resources.join(" or ")} and ID=")
|
|
180
184
|
end
|
|
181
185
|
|
|
182
|
-
Kennel.out.puts Kennel::Importer.new(Kennel.
|
|
186
|
+
Kennel.out.puts Kennel::Importer.new(Kennel::Api.new).import(resource, id)
|
|
183
187
|
end
|
|
184
188
|
|
|
185
189
|
desc "Dump ALL of datadog config as raw json ... useful for grep/search [TYPE=slo|monitor|dashboard]"
|
|
@@ -190,7 +194,7 @@ namespace :kennel do
|
|
|
190
194
|
else
|
|
191
195
|
Kennel::Models::Record.api_resource_map.keys
|
|
192
196
|
end
|
|
193
|
-
api = Kennel.
|
|
197
|
+
api = Kennel::Api.new
|
|
194
198
|
list = nil
|
|
195
199
|
first = true
|
|
196
200
|
|
|
@@ -240,7 +244,7 @@ namespace :kennel do
|
|
|
240
244
|
klass =
|
|
241
245
|
Kennel::Models::Record.subclasses.detect { |s| s.api_resource == resource } ||
|
|
242
246
|
raise("resource #{resource} not know")
|
|
243
|
-
object = Kennel.
|
|
247
|
+
object = Kennel::Api.new.show(resource, id)
|
|
244
248
|
Kennel.out.puts klass.parse_tracking_id(object)
|
|
245
249
|
end
|
|
246
250
|
|
data/lib/kennel/utils.rb
CHANGED
|
@@ -57,6 +57,12 @@ module Kennel
|
|
|
57
57
|
"\e[#{COLORS.fetch(color)}m#{text}\e[0m"
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
def truncate_lines(text, to:, warning:)
|
|
61
|
+
lines = text.split(/\n/, to + 1)
|
|
62
|
+
lines[-1] = warning if lines.size > to
|
|
63
|
+
lines.join("\n")
|
|
64
|
+
end
|
|
65
|
+
|
|
60
66
|
def capture_stdout
|
|
61
67
|
old = Kennel.out
|
|
62
68
|
Kennel.out = StringIO.new
|
data/lib/kennel/version.rb
CHANGED
data/lib/kennel.rb
CHANGED
|
@@ -5,7 +5,6 @@ require "zeitwerk"
|
|
|
5
5
|
require "English"
|
|
6
6
|
|
|
7
7
|
require "kennel/version"
|
|
8
|
-
require "kennel/compatibility"
|
|
9
8
|
require "kennel/utils"
|
|
10
9
|
require "kennel/progress"
|
|
11
10
|
require "kennel/filter"
|
|
@@ -40,22 +39,24 @@ module Teams
|
|
|
40
39
|
end
|
|
41
40
|
|
|
42
41
|
module Kennel
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
UnresolvableIdError = Class.new(StandardError)
|
|
43
|
+
DisallowedUpdateError = Class.new(StandardError)
|
|
44
|
+
GenerationAbortedError = Class.new(StandardError)
|
|
45
|
+
UpdateResult = Struct.new(:plan, :update, keyword_init: true)
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
class << self
|
|
48
|
+
attr_accessor :out, :err
|
|
49
|
+
end
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
self.out = $stdout
|
|
52
|
+
self.err = $stderr
|
|
50
53
|
|
|
51
54
|
class Engine
|
|
52
55
|
def initialize
|
|
53
|
-
@out = $stdout
|
|
54
|
-
@err = $stderr
|
|
55
56
|
@strict_imports = true
|
|
56
57
|
end
|
|
57
58
|
|
|
58
|
-
attr_accessor :
|
|
59
|
+
attr_accessor :strict_imports
|
|
59
60
|
|
|
60
61
|
def generate
|
|
61
62
|
parts = generated
|
|
@@ -68,12 +69,8 @@ module Kennel
|
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
def update
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
UpdateResult.new(
|
|
74
|
-
plan: the_plan,
|
|
75
|
-
update: the_update
|
|
76
|
-
)
|
|
72
|
+
syncer.plan
|
|
73
|
+
syncer.update if syncer.confirm
|
|
77
74
|
end
|
|
78
75
|
|
|
79
76
|
private
|
|
@@ -83,11 +80,11 @@ module Kennel
|
|
|
83
80
|
end
|
|
84
81
|
|
|
85
82
|
def syncer
|
|
86
|
-
@syncer ||= Syncer.new(api, generated, project_filter: filter.project_filter, tracking_id_filter: filter.tracking_id_filter)
|
|
83
|
+
@syncer ||= Syncer.new(api, generated, kennel: self, project_filter: filter.project_filter, tracking_id_filter: filter.tracking_id_filter)
|
|
87
84
|
end
|
|
88
85
|
|
|
89
86
|
def api
|
|
90
|
-
@api ||= Api.new
|
|
87
|
+
@api ||= Api.new
|
|
91
88
|
end
|
|
92
89
|
|
|
93
90
|
def projects_provider
|
|
@@ -100,26 +97,30 @@ module Kennel
|
|
|
100
97
|
|
|
101
98
|
def generated
|
|
102
99
|
@generated ||= begin
|
|
103
|
-
Progress.progress "
|
|
100
|
+
parts = Progress.progress "Finding parts" do
|
|
104
101
|
projects = projects_provider.projects
|
|
105
102
|
projects = filter.filter_projects projects
|
|
106
103
|
|
|
107
104
|
parts = Utils.parallel(projects, &:validated_parts).flatten(1)
|
|
108
|
-
|
|
105
|
+
filter.filter_parts parts
|
|
106
|
+
end
|
|
109
107
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
108
|
+
parts.group_by(&:tracking_id).each do |tracking_id, same|
|
|
109
|
+
next if same.size == 1
|
|
110
|
+
raise <<~ERROR
|
|
111
|
+
#{tracking_id} is defined #{same.size} times
|
|
112
|
+
use a different `kennel_id` when defining multiple projects/monitors/dashboards to avoid this conflict
|
|
113
|
+
ERROR
|
|
114
|
+
end
|
|
117
115
|
|
|
116
|
+
Progress.progress "Building json" do
|
|
118
117
|
# trigger json caching here so it counts into generating
|
|
119
|
-
Utils.parallel(parts, &:
|
|
120
|
-
|
|
121
|
-
parts
|
|
118
|
+
Utils.parallel(parts, &:build)
|
|
122
119
|
end
|
|
120
|
+
|
|
121
|
+
OptionalValidations.valid?(parts) or raise GenerationAbortedError
|
|
122
|
+
|
|
123
|
+
parts
|
|
123
124
|
end
|
|
124
125
|
end
|
|
125
126
|
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.
|
|
4
|
+
version: 1.124.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: 2022-
|
|
11
|
+
date: 2022-11-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|
|
@@ -89,7 +89,6 @@ files:
|
|
|
89
89
|
- Readme.md
|
|
90
90
|
- lib/kennel.rb
|
|
91
91
|
- lib/kennel/api.rb
|
|
92
|
-
- lib/kennel/compatibility.rb
|
|
93
92
|
- lib/kennel/file_cache.rb
|
|
94
93
|
- lib/kennel/filter.rb
|
|
95
94
|
- lib/kennel/github_reporter.rb
|
data/lib/kennel/compatibility.rb
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Kennel
|
|
4
|
-
module Compatibility
|
|
5
|
-
def self.included(into)
|
|
6
|
-
class << into
|
|
7
|
-
%I[out out= err err= strict_imports strict_imports= generate plan update].each do |sym|
|
|
8
|
-
define_method(sym) { |*args| instance.public_send(sym, *args) }
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def build_default
|
|
12
|
-
Kennel::Engine.new
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def instance
|
|
16
|
-
@instance ||= build_default
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
def api
|
|
22
|
-
instance.send(:api)
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|