kennel 1.85.1 → 1.88.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/importer.rb +28 -7
- data/lib/kennel/models/dashboard.rb +73 -34
- data/lib/kennel/models/monitor.rb +6 -0
- data/lib/kennel/models/record.rb +33 -2
- data/lib/kennel/models/slo.rb +2 -1
- data/lib/kennel/syncer.rb +33 -63
- data/lib/kennel/template_variables.rb +2 -1
- data/lib/kennel/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ccd6dac941560095109ca60a7a60a58a247605c1c6ec8b658fda12ec6e60ea04
|
4
|
+
data.tar.gz: b17327485c61d273a474e0235c274b48e3f950586441bc676d3da14bcab55afc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b0c6a49acdfc63bfcd86180ab5dfedd96aaf73d55c6cdf4890ca58e1a2408623237ada66b67a1278e09e0d207e8a2b7d9d7a45b7b2da09f3f4c750a35aca4011
|
7
|
+
data.tar.gz: 48ec7d742c67f23192550e567f343213a88bf8372c3f7e69604152bae0cd57e2b5b210ec288527ab6d28bd5a77f58b66f6a5b882e77b0ff413059c1e5622a888
|
data/lib/kennel/importer.rb
CHANGED
@@ -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, *
|
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
|
38
|
-
|
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
|
@@ -60,10 +59,19 @@ module Kennel
|
|
60
59
|
query.sub!(/([><=]) (#{Regexp.escape(critical.to_f.to_s)}|#{Regexp.escape(critical.to_i.to_s)})$/, "\\1 \#{critical}")
|
61
60
|
end
|
62
61
|
|
62
|
+
# using float in query is not allowed, so convert here
|
63
|
+
data[:critical] = data[:critical].to_i if data[:type] == "event alert"
|
64
|
+
|
63
65
|
data[:type] = "query alert" if data[:type] == "metric alert"
|
64
66
|
when "dashboard"
|
65
67
|
widgets = data[:widgets]&.flat_map { |widget| widget.dig(:definition, :widgets) || [widget] }
|
66
|
-
widgets&.each
|
68
|
+
widgets&.each do |widget|
|
69
|
+
convert_widget_to_compact_format!(widget)
|
70
|
+
dry_up_widget_metadata!(widget)
|
71
|
+
(widget.dig(:definition, :markers) || []).each { |m| m[:label]&.delete! " " }
|
72
|
+
end
|
73
|
+
else
|
74
|
+
# noop
|
67
75
|
end
|
68
76
|
|
69
77
|
data.delete(:tags) if data[:tags] == [] # do not create super + [] call
|
@@ -85,7 +93,7 @@ module Kennel
|
|
85
93
|
private
|
86
94
|
|
87
95
|
# reduce duplication in imports by using dry `q: :metadata` when possible
|
88
|
-
def
|
96
|
+
def dry_up_widget_metadata!(widget)
|
89
97
|
(widget.dig(:definition, :requests) || []).each do |request|
|
90
98
|
next unless request.is_a?(Hash)
|
91
99
|
next unless metadata = request[:metadata]
|
@@ -98,6 +106,19 @@ module Kennel
|
|
98
106
|
end
|
99
107
|
end
|
100
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
|
+
|
101
122
|
def pretty_print(hash)
|
102
123
|
sort_widgets hash
|
103
124
|
|
@@ -8,12 +8,61 @@ 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
|
14
15
|
WIDGET_DEFAULTS = {
|
15
|
-
"timeseries" => {
|
16
|
-
|
16
|
+
"timeseries" => {
|
17
|
+
legend_size: "0",
|
18
|
+
markers: [],
|
19
|
+
legend_columns: [
|
20
|
+
"avg",
|
21
|
+
"min",
|
22
|
+
"max",
|
23
|
+
"value",
|
24
|
+
"sum"
|
25
|
+
],
|
26
|
+
legend_layout: "auto",
|
27
|
+
yaxis: {
|
28
|
+
include_zero: true,
|
29
|
+
label: "",
|
30
|
+
scale: "linear",
|
31
|
+
min: "auto",
|
32
|
+
max: "auto"
|
33
|
+
},
|
34
|
+
show_legend: true,
|
35
|
+
time: {},
|
36
|
+
title_align: "left",
|
37
|
+
title_size: "16"
|
38
|
+
},
|
39
|
+
"note" => {
|
40
|
+
show_tick: false,
|
41
|
+
tick_edge: "left",
|
42
|
+
tick_pos: "50%",
|
43
|
+
text_align: "left",
|
44
|
+
has_padding: true,
|
45
|
+
background_color: "white",
|
46
|
+
font_size: "14"
|
47
|
+
},
|
48
|
+
"query_value" => {
|
49
|
+
autoscale: true,
|
50
|
+
time: {},
|
51
|
+
title_align: "left",
|
52
|
+
title_size: "16"
|
53
|
+
},
|
54
|
+
"free_text" => {
|
55
|
+
font_size: "auto"
|
56
|
+
},
|
57
|
+
"check_status" => {
|
58
|
+
title_align: "left",
|
59
|
+
title_size: "16"
|
60
|
+
},
|
61
|
+
"slo" => {
|
62
|
+
global_time_target: "0",
|
63
|
+
title_align: "left",
|
64
|
+
title_size: "16"
|
65
|
+
}
|
17
66
|
}.freeze
|
18
67
|
SUPPORTED_DEFINITION_OPTIONS = [:events, :markers, :precision].freeze
|
19
68
|
|
@@ -40,53 +89,43 @@ module Kennel
|
|
40
89
|
def normalize(expected, actual)
|
41
90
|
super
|
42
91
|
|
43
|
-
ignore_default
|
44
|
-
ignore_default
|
92
|
+
ignore_default expected, actual, DEFAULTS
|
93
|
+
ignore_default expected, actual, reflow_type: "auto" if expected[:layout_type] == "ordered"
|
45
94
|
|
46
95
|
widgets_pairs(expected, actual).each do |pair|
|
47
|
-
|
48
|
-
pair
|
49
|
-
widgets.each do |widget|
|
50
|
-
if formats = widget.dig(:definition, :conditional_formats)
|
51
|
-
widget[:definition][:conditional_formats] = formats.sort_by(&:hash)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
ignore_widget_defaults pair
|
57
|
-
|
96
|
+
pair.each { |w| sort_conditional_formats w }
|
97
|
+
ignore_widget_defaults(*pair)
|
58
98
|
ignore_request_defaults(*pair)
|
59
|
-
|
60
|
-
# ids are kinda random so we always discard them
|
61
|
-
pair.each { |widgets| widgets.each { |w| w.delete(:id) } }
|
99
|
+
pair.each { |widget| widget&.delete(:id) } # ids are kinda random so we always discard them
|
62
100
|
end
|
63
101
|
end
|
64
102
|
|
65
103
|
private
|
66
104
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
next unless defaults = WIDGET_DEFAULTS[types.first]
|
72
|
-
ignore_defaults(pair[0], pair[1], defaults, nesting: :definition)
|
105
|
+
# conditional_formats ordering is randomly changed by datadog, compare a stable ordering
|
106
|
+
def sort_conditional_formats(widget)
|
107
|
+
if formats = widget&.dig(:definition, :conditional_formats)
|
108
|
+
widget[:definition][:conditional_formats] = formats.sort_by(&:hash)
|
73
109
|
end
|
74
110
|
end
|
75
111
|
|
112
|
+
def ignore_widget_defaults(expected, actual)
|
113
|
+
types = [expected&.dig(:definition, :type), actual&.dig(:definition, :type)].uniq.compact
|
114
|
+
return unless types.size == 1
|
115
|
+
return unless defaults = WIDGET_DEFAULTS[types.first]
|
116
|
+
ignore_default expected&.[](:definition) || {}, actual&.[](:definition) || {}, defaults
|
117
|
+
end
|
118
|
+
|
76
119
|
# discard styles/conditional_formats/aggregator if nothing would change when we applied (both are default or nil)
|
77
120
|
def ignore_request_defaults(expected, actual)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
ignore_defaults e_r, a_r, REQUEST_DEFAULTS
|
82
|
-
end
|
121
|
+
a_r = actual&.dig(:definition, :requests) || []
|
122
|
+
e_r = expected&.dig(:definition, :requests) || []
|
123
|
+
ignore_defaults e_r, a_r, REQUEST_DEFAULTS
|
83
124
|
end
|
84
125
|
|
85
|
-
def ignore_defaults(expected, actual, defaults
|
126
|
+
def ignore_defaults(expected, actual, defaults)
|
86
127
|
[expected.size, actual.size].max.times do |i|
|
87
|
-
|
88
|
-
a = actual.dig(i, *nesting) || {}
|
89
|
-
ignore_default(e, a, defaults)
|
128
|
+
ignore_default expected[i] || {}, actual[i] || {}, defaults
|
90
129
|
end
|
91
130
|
end
|
92
131
|
|
@@ -99,7 +138,7 @@ module Kennel
|
|
99
138
|
nested = pair.map { |d| d.dig(:widgets, i, :definition, :widgets) || [] }
|
100
139
|
result << nested if nested.any?(&:any?)
|
101
140
|
end
|
102
|
-
result
|
141
|
+
result.flat_map { |a, e| [a.size, e.size].max.times.map { |i| [a[i], e[i]] } }
|
103
142
|
end
|
104
143
|
end
|
105
144
|
|
@@ -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
|
data/lib/kennel/models/record.rb
CHANGED
@@ -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,
|
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!
|
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
|
data/lib/kennel/models/slo.rb
CHANGED
@@ -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,
|
@@ -63,7 +64,7 @@ module Kennel
|
|
63
64
|
end
|
64
65
|
|
65
66
|
def self.parse_url(url)
|
66
|
-
url[/\/slo\?.*slo_id
|
67
|
+
url[/\/slo(\?.*slo_id=|\/edit\/)([a-z\d]{10,})/, 2]
|
67
68
|
end
|
68
69
|
|
69
70
|
def resolve_linked_tracking_ids!(id_map, **args)
|
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
|
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} #{
|
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} #{
|
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
|
-
|
54
|
-
|
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[:
|
122
|
-
detailed.each { |
|
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
|
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
|
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[:
|
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 = [
|
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
|
-
|
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
|
-
|
176
|
-
Kennel.out.puts Utils.color(color, "#{step} #{api_resource} #{e&.tracking_id ||
|
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
|
-
#
|
203
|
-
#
|
204
|
-
#
|
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 #
|
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,
|
214
|
-
|
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
|
-
|
217
|
-
|
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[
|
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 =
|
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
|
-
|
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
|
data/lib/kennel/version.rb
CHANGED
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.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-
|
11
|
+
date: 2021-06-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -94,14 +94,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
94
|
requirements:
|
95
95
|
- - ">="
|
96
96
|
- !ruby/object:Gem::Version
|
97
|
-
version: 2.
|
97
|
+
version: 2.6.0
|
98
98
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
99
|
requirements:
|
100
100
|
- - ">="
|
101
101
|
- !ruby/object:Gem::Version
|
102
102
|
version: '0'
|
103
103
|
requirements: []
|
104
|
-
rubygems_version: 3.
|
104
|
+
rubygems_version: 3.2.16
|
105
105
|
signing_key:
|
106
106
|
specification_version: 4
|
107
107
|
summary: Keep datadog monitors/dashboards/etc in version control, avoid chaotic management
|