kennel 1.123.0 → 1.125.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/kennel/api.rb +4 -4
- data/lib/kennel/importer.rb +1 -1
- data/lib/kennel/models/monitor.rb +14 -5
- data/lib/kennel/models/record.rb +44 -12
- data/lib/kennel/models/slo.rb +1 -1
- data/lib/kennel/optional_validations.rb +58 -1
- data/lib/kennel/syncer.rb +40 -27
- 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: 2464486b37bc4711843991d93789d4844d9c9aac33b40cf8b088ec4b249f39ab
|
4
|
+
data.tar.gz: 794318abfb10fecca670f6a8d2ad22e6f117e8425d0dc964b76163a658593d16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97c3664e939d5776bf4dcc2a8fc72b647af0a8df0112035cadd6105e8117dd116097e85e60838050f96959df5ea0e17d7e00ffe89420712937a32190bcce70ca
|
7
|
+
data.tar.gz: 4a1cf01f15ac6959c7cea683297fb41f3a74791a99b3a16c5391f76af48333d9a1b4ea9a9fc804750844feb75d5c02fae0caced96fc3b85723d128d1ad45e289
|
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
|
@@ -21,7 +21,8 @@ module Kennel
|
|
21
21
|
renotify_interval: 0,
|
22
22
|
notify_audit: false,
|
23
23
|
no_data_timeframe: nil, # this works out ok since if notify_no_data is on, it would never be nil
|
24
|
-
groupby_simple_monitor: false
|
24
|
+
groupby_simple_monitor: false,
|
25
|
+
variables: nil
|
25
26
|
}.freeze
|
26
27
|
DEFAULT_ESCALATION_MESSAGE = ["", nil].freeze
|
27
28
|
ALLOWED_PRIORITY_CLASSES = [NilClass, Integer].freeze
|
@@ -30,7 +31,7 @@ module Kennel
|
|
30
31
|
settings(
|
31
32
|
:query, :name, :message, :escalation_message, :critical, :type, :renotify_interval, :warning, :timeout_h, :evaluation_delay,
|
32
33
|
:ok, :no_data_timeframe, :notify_no_data, :notify_audit, :tags, :critical_recovery, :warning_recovery, :require_full_window,
|
33
|
-
:threshold_windows, :new_host_delay, :new_group_delay, :priority, :validate_using_links
|
34
|
+
:threshold_windows, :new_host_delay, :new_group_delay, :priority, :validate_using_links, :variables
|
34
35
|
)
|
35
36
|
|
36
37
|
defaults(
|
@@ -39,7 +40,7 @@ module Kennel
|
|
39
40
|
renotify_interval: -> { project.team.renotify_interval },
|
40
41
|
warning: -> { nil },
|
41
42
|
ok: -> { nil },
|
42
|
-
notify_no_data: -> { true }, # datadog sets this to false by default, but true is
|
43
|
+
notify_no_data: -> { true }, # datadog UI sets this to false by default, but true is safer
|
43
44
|
no_data_timeframe: -> { 60 },
|
44
45
|
notify_audit: -> { MONITOR_OPTION_DEFAULTS.fetch(:notify_audit) },
|
45
46
|
new_host_delay: -> { MONITOR_OPTION_DEFAULTS.fetch(:new_host_delay) },
|
@@ -50,7 +51,8 @@ module Kennel
|
|
50
51
|
critical_recovery: -> { nil },
|
51
52
|
warning_recovery: -> { nil },
|
52
53
|
threshold_windows: -> { nil },
|
53
|
-
priority: -> { MONITOR_DEFAULTS.fetch(:priority) }
|
54
|
+
priority: -> { MONITOR_DEFAULTS.fetch(:priority) },
|
55
|
+
variables: -> { MONITOR_OPTION_DEFAULTS.fetch(:variables) }
|
54
56
|
)
|
55
57
|
|
56
58
|
def build_json
|
@@ -73,7 +75,8 @@ module Kennel
|
|
73
75
|
escalation_message: Utils.presence(escalation_message.strip),
|
74
76
|
evaluation_delay: evaluation_delay,
|
75
77
|
locked: false, # setting this to true prevents any edit and breaks updates when using replace workflow
|
76
|
-
renotify_interval: renotify_interval || 0
|
78
|
+
renotify_interval: renotify_interval || 0,
|
79
|
+
variables: variables
|
77
80
|
}
|
78
81
|
)
|
79
82
|
|
@@ -99,6 +102,12 @@ module Kennel
|
|
99
102
|
end
|
100
103
|
end
|
101
104
|
|
105
|
+
# setting this via the api breaks the UI with
|
106
|
+
# "The no_data_timeframe option is not allowed for log alert monitors"
|
107
|
+
if data.fetch(:type) == "log alert"
|
108
|
+
options.delete :no_data_timeframe
|
109
|
+
end
|
110
|
+
|
102
111
|
if windows = threshold_windows
|
103
112
|
options[:threshold_windows] = windows
|
104
113
|
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)
|
data/lib/kennel/models/slo.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
module Kennel
|
3
3
|
module Models
|
4
4
|
class Slo < Record
|
5
|
-
READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [:type_id, :monitor_tags]
|
5
|
+
READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [:type_id, :monitor_tags, :target_threshold, :timeframe, :warning_threshold]
|
6
6
|
TRACKING_FIELD = :description
|
7
7
|
DEFAULTS = {
|
8
8
|
description: nil,
|
@@ -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
@@ -7,11 +7,12 @@ module Kennel
|
|
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
9
|
|
10
|
-
Plan = Struct.new(:
|
11
|
-
|
10
|
+
Plan = Struct.new(:changes, keyword_init: true)
|
11
|
+
Change = Struct.new(:type, :api_resource, :tracking_id, :id)
|
12
12
|
|
13
|
-
def initialize(api, expected, project_filter: nil, tracking_id_filter: nil)
|
13
|
+
def initialize(api, expected, kennel:, project_filter: nil, tracking_id_filter: nil)
|
14
14
|
@api = api
|
15
|
+
@kennel = kennel
|
15
16
|
@project_filter = project_filter
|
16
17
|
@tracking_id_filter = tracking_id_filter
|
17
18
|
@expected = Set.new expected # need set to speed up deletion
|
@@ -32,11 +33,10 @@ module Kennel
|
|
32
33
|
end
|
33
34
|
|
34
35
|
Plan.new(
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
delete: @delete
|
36
|
+
changes:
|
37
|
+
@create.map { |_id, e, _a| Change.new(:create, e.class.api_resource, e.tracking_id, nil) } +
|
38
|
+
@update.map { |id, e, _a| Change.new(:update, e.class.api_resource, e.tracking_id, id) } +
|
39
|
+
@delete.map { |id, _e, a| Change.new(:delete, a.fetch(:klass).api_resource, a.fetch(:tracking_id), id) }
|
40
40
|
)
|
41
41
|
end
|
42
42
|
|
@@ -47,7 +47,7 @@ module Kennel
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def update
|
50
|
-
|
50
|
+
changes = []
|
51
51
|
|
52
52
|
each_resolved @create do |_, e|
|
53
53
|
message = "#{e.class.api_resource} #{e.tracking_id}"
|
@@ -55,7 +55,7 @@ module Kennel
|
|
55
55
|
reply = @api.create e.class.api_resource, e.as_json
|
56
56
|
cache_metadata reply, e.class
|
57
57
|
id = reply.fetch(:id)
|
58
|
-
|
58
|
+
changes << Change.new(:create, e.class.api_resource, e.tracking_id, id)
|
59
59
|
populate_id_map [], [reply] # allow resolving ids we could previously no resolve
|
60
60
|
Kennel.out.puts "#{LINE_UP}Created #{message} #{e.class.url(id)}"
|
61
61
|
end
|
@@ -64,7 +64,7 @@ module Kennel
|
|
64
64
|
message = "#{e.class.api_resource} #{e.tracking_id} #{e.class.url(id)}"
|
65
65
|
Kennel.out.puts "Updating #{message}"
|
66
66
|
@api.update e.class.api_resource, id, e.as_json
|
67
|
-
|
67
|
+
changes << Change.new(:update, e.class.api_resource, e.tracking_id, id)
|
68
68
|
Kennel.out.puts "#{LINE_UP}Updated #{message}"
|
69
69
|
end
|
70
70
|
|
@@ -73,15 +73,17 @@ module Kennel
|
|
73
73
|
message = "#{klass.api_resource} #{a.fetch(:tracking_id)} #{id}"
|
74
74
|
Kennel.out.puts "Deleting #{message}"
|
75
75
|
@api.delete klass.api_resource, id
|
76
|
-
|
76
|
+
changes << Change.new(:delete, klass.api_resource, a.fetch(:tracking_id), id)
|
77
77
|
Kennel.out.puts "#{LINE_UP}Deleted #{message}"
|
78
78
|
end
|
79
79
|
|
80
|
-
|
80
|
+
Plan.new(changes: changes)
|
81
81
|
end
|
82
82
|
|
83
83
|
private
|
84
84
|
|
85
|
+
attr_reader :kennel
|
86
|
+
|
85
87
|
# loop over items until everything is resolved or crash when we get stuck
|
86
88
|
# this solves cases like composite monitors depending on each other or monitor->monitor slo->slo monitor chains
|
87
89
|
def each_resolved(list)
|
@@ -121,7 +123,6 @@ module Kennel
|
|
121
123
|
@warnings = []
|
122
124
|
@update = []
|
123
125
|
@delete = []
|
124
|
-
@no_change = []
|
125
126
|
@id_map = IdMap.new
|
126
127
|
|
127
128
|
actual = Progress.progress("Downloading definitions") { download_definitions }
|
@@ -154,8 +155,6 @@ module Kennel
|
|
154
155
|
diff = e.diff(a) # slow ...
|
155
156
|
if diff.any?
|
156
157
|
@update << [id, e, a, diff]
|
157
|
-
else
|
158
|
-
@no_change << [id, e, a]
|
159
158
|
end
|
160
159
|
elsif a.fetch(:tracking_id) # was previously managed
|
161
160
|
@delete << [id, nil, a]
|
@@ -186,7 +185,7 @@ module Kennel
|
|
186
185
|
@expected.each do |e|
|
187
186
|
next unless id = e.id
|
188
187
|
resource = e.class.api_resource
|
189
|
-
if
|
188
|
+
if kennel.strict_imports
|
190
189
|
raise "Unable to find existing #{resource} with id #{id}\nIf the #{resource} was deleted, remove the `id: -> { #{id} }` line."
|
191
190
|
else
|
192
191
|
@warnings << "#{resource} #{e.tracking_id} specifies id #{id}, but no such #{resource} exists. 'id' will be ignored. Remove the `id: -> { #{id} }` line."
|
@@ -234,16 +233,19 @@ module Kennel
|
|
234
233
|
new = Utils.pretty_inspect(new)
|
235
234
|
end
|
236
235
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
236
|
+
message =
|
237
|
+
if use_diff
|
238
|
+
" #{type}#{field}\n" +
|
239
|
+
diff(old, new).map { |l| " #{l}" }.join("\n")
|
240
|
+
elsif (old + new).size > 100
|
241
|
+
" #{type}#{field}\n" \
|
242
|
+
" #{old} ->\n" \
|
243
|
+
" #{new}"
|
244
|
+
else
|
245
|
+
" #{type}#{field} #{old} -> #{new}"
|
246
|
+
end
|
247
|
+
|
248
|
+
Kennel.out.puts truncate_diff(message)
|
247
249
|
end
|
248
250
|
end
|
249
251
|
|
@@ -267,6 +269,17 @@ module Kennel
|
|
267
269
|
end
|
268
270
|
end
|
269
271
|
|
272
|
+
def truncate_diff(message)
|
273
|
+
# min '2' because: -1 makes no sense, 0 does not work with * 2 math, 1 says '1 lines'
|
274
|
+
@max_diff_lines ||= [Integer(ENV.fetch("MAX_DIFF_LINES", "50")), 2].max
|
275
|
+
warning = Utils.color(
|
276
|
+
:magenta,
|
277
|
+
" (Diff for this item truncated after #{@max_diff_lines} lines. " \
|
278
|
+
"Rerun with MAX_DIFF_LINES=#{@max_diff_lines * 2} to see more)"
|
279
|
+
)
|
280
|
+
Utils.truncate_lines(message, to: @max_diff_lines, warning: warning)
|
281
|
+
end
|
282
|
+
|
270
283
|
# We've already validated the desired objects ('generated') in isolation.
|
271
284
|
# Now that we have made the plan, we can perform some more validation.
|
272
285
|
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.125.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-30 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
|