kennel 1.55.1 → 1.56.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/Readme.md +2 -2
- data/lib/kennel.rb +3 -1
- data/lib/kennel/importer.rb +3 -1
- data/lib/kennel/models/base.rb +3 -155
- data/lib/kennel/models/dashboard.rb +2 -11
- data/lib/kennel/models/monitor.rb +2 -9
- data/lib/kennel/models/record.rb +98 -0
- data/lib/kennel/optional_validations.rb +1 -10
- data/lib/kennel/settings_as_methods.rb +87 -0
- data/lib/kennel/syncer.rb +1 -6
- data/lib/kennel/utils.rb +9 -0
- data/lib/kennel/version.rb +1 -1
- data/template/Readme.md +6 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 491e174273f8af94504a4e81d9a2ff7da5820aafe287ea9cb1e78df93d07f0f0
|
4
|
+
data.tar.gz: bf918b4be464d408259e0f8204541a0619d1719670a9843a6e564cc4edc968d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ff9000e02764618a7e08c7349f315737778ff7afadf8ca303b7dda8db5648d2bd966412a255e7987ebd2416f8bfd264c3c2979bf143ed0d1f6fdd1a759fc85f
|
7
|
+
data.tar.gz: daf144b7fc436cfb317c4cc298dbbd62d61d252b554261746ed00bcaf5279422bf1a4ab185d45c57974665bc38ebbbf61d0b8e28d8269225c011beb11a451d67
|
data/Readme.md
CHANGED
@@ -65,11 +65,11 @@ end
|
|
65
65
|
```
|
66
66
|
|
67
67
|
### Adding a new monitor
|
68
|
-
- use [datadog monitor UI](https://app.datadoghq.com/monitors#create
|
68
|
+
- use [datadog monitor UI](https://app.datadoghq.com/monitors#create) to create a monitor
|
69
69
|
- see below
|
70
70
|
|
71
71
|
### Updating an existing monitor
|
72
|
-
- use [datadog monitor UI](https://app.datadoghq.com/monitors
|
72
|
+
- use [datadog monitor UI](https://app.datadoghq.com/monitors/manage) to find a monitor
|
73
73
|
- get the `id` from the url
|
74
74
|
- run `RESOURCE=monitor ID=12345 bundle exec rake kennel:import` and copy the output
|
75
75
|
- find or create a project in `projects/`
|
data/lib/kennel.rb
CHANGED
@@ -10,14 +10,16 @@ require "kennel/syncer"
|
|
10
10
|
require "kennel/api"
|
11
11
|
require "kennel/github_reporter"
|
12
12
|
require "kennel/subclass_tracking"
|
13
|
+
require "kennel/settings_as_methods"
|
13
14
|
require "kennel/file_cache"
|
14
15
|
require "kennel/template_variables"
|
15
16
|
require "kennel/optional_validations"
|
16
17
|
require "kennel/unmuted_alerts"
|
17
18
|
|
18
19
|
require "kennel/models/base"
|
20
|
+
require "kennel/models/record"
|
19
21
|
|
20
|
-
#
|
22
|
+
# records
|
21
23
|
require "kennel/models/monitor"
|
22
24
|
require "kennel/models/dashboard"
|
23
25
|
|
data/lib/kennel/importer.rb
CHANGED
@@ -28,7 +28,7 @@ module Kennel
|
|
28
28
|
|
29
29
|
title_field = TITLES.detect { |f| data[f] }
|
30
30
|
title = data.fetch(title_field)
|
31
|
-
title.tr!(Kennel::Models::
|
31
|
+
title.tr!(Kennel::Models::Record::LOCK, "") # avoid double lock icon
|
32
32
|
|
33
33
|
# calculate or reuse kennel_id
|
34
34
|
# TODO: this is copy-pasted from syncer, need to find a nice way to reuse it
|
@@ -53,6 +53,8 @@ module Kennel
|
|
53
53
|
if query && critical
|
54
54
|
query.sub!(/([><=]) (#{Regexp.escape(critical.to_f.to_s)}|#{Regexp.escape(critical.to_i.to_s)})$/, "\\1 \#{critical}")
|
55
55
|
end
|
56
|
+
|
57
|
+
data[:type] = "query alert" if data[:type] == "metric alert"
|
56
58
|
elsif resource == "dashboard"
|
57
59
|
widgets = data[:widgets]&.flat_map { |widget| widget.dig(:definition, :widgets) || [widget] }
|
58
60
|
widgets&.each { |widget| dry_up_query!(widget) }
|
data/lib/kennel/models/base.rb
CHANGED
@@ -4,115 +4,10 @@ require "hashdiff"
|
|
4
4
|
module Kennel
|
5
5
|
module Models
|
6
6
|
class Base
|
7
|
-
|
8
|
-
|
9
|
-
:deleted, :matching_downtimes, :id, :created, :created_at, :creator, :org_id, :modified,
|
10
|
-
:overall_state_modified, :overall_state, :api_resource
|
11
|
-
].freeze
|
12
|
-
REQUEST_DEFAULTS = {
|
13
|
-
style: { width: "normal", palette: "dog_classic", type: "solid" },
|
14
|
-
conditional_formats: [],
|
15
|
-
aggregator: "avg"
|
16
|
-
}.freeze
|
17
|
-
OVERRIDABLE_METHODS = [:name, :kennel_id].freeze
|
7
|
+
extend SubclassTracking
|
8
|
+
include SettingsAsMethods
|
18
9
|
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
class << self
|
23
|
-
include SubclassTracking
|
24
|
-
|
25
|
-
def settings(*names)
|
26
|
-
duplicates = (@set & names)
|
27
|
-
if duplicates.any?
|
28
|
-
raise ArgumentError, "Settings #{duplicates.map(&:inspect).join(", ")} are already defined"
|
29
|
-
end
|
30
|
-
|
31
|
-
overrides = ((instance_methods - OVERRIDABLE_METHODS) & names)
|
32
|
-
if overrides.any?
|
33
|
-
raise ArgumentError, "Settings #{overrides.map(&:inspect).join(", ")} are already used as methods"
|
34
|
-
end
|
35
|
-
|
36
|
-
@set.concat names
|
37
|
-
names.each do |name|
|
38
|
-
next if method_defined?(name)
|
39
|
-
define_method name do
|
40
|
-
message = "Trying to call #{name} for #{self.class} but it was never set or passed as option"
|
41
|
-
raise_with_location ArgumentError, message
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def defaults(options)
|
47
|
-
options.each do |name, block|
|
48
|
-
validate_setting_exists name
|
49
|
-
define_method name, &block
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def inherited(child)
|
54
|
-
super
|
55
|
-
child.instance_variable_set(:@set, (@set || []).dup)
|
56
|
-
end
|
57
|
-
|
58
|
-
def validate_setting_exists(name)
|
59
|
-
return if !@set || @set.include?(name)
|
60
|
-
supported = @set.map(&:inspect)
|
61
|
-
raise ArgumentError, "Unsupported setting #{name.inspect}, supported settings are #{supported.join(", ")}"
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def normalize(_expected, actual)
|
67
|
-
self::READONLY_ATTRIBUTES.each { |k| actual.delete k }
|
68
|
-
end
|
69
|
-
|
70
|
-
# discard styles/conditional_formats/aggregator if nothing would change when we applied (both are default or nil)
|
71
|
-
def ignore_request_defaults(expected, actual, level1, level2)
|
72
|
-
actual = actual[level1] || {}
|
73
|
-
expected = expected[level1] || {}
|
74
|
-
[expected.size.to_i, actual.size.to_i].max.times do |i|
|
75
|
-
a_r = actual.dig(i, level2, :requests) || []
|
76
|
-
e_r = expected.dig(i, level2, :requests) || []
|
77
|
-
ignore_defaults e_r, a_r, self::REQUEST_DEFAULTS
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def ignore_defaults(expected, actual, defaults)
|
82
|
-
[expected&.size.to_i, actual&.size.to_i].max.times do |i|
|
83
|
-
e = expected[i] || {}
|
84
|
-
a = actual[i] || {}
|
85
|
-
ignore_default(e, a, defaults)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def ignore_default(expected, actual, defaults)
|
90
|
-
definitions = [actual, expected]
|
91
|
-
defaults.each do |key, default|
|
92
|
-
if definitions.all? { |r| !r.key?(key) || r[key] == default }
|
93
|
-
actual.delete(key)
|
94
|
-
expected.delete(key)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def initialize(options = {})
|
101
|
-
validate_options(options)
|
102
|
-
|
103
|
-
options.each do |name, block|
|
104
|
-
self.class.validate_setting_exists name
|
105
|
-
define_singleton_method name, &block
|
106
|
-
end
|
107
|
-
|
108
|
-
# need expand_path so it works wih rake and when run individually
|
109
|
-
pwd = /^#{Regexp.escape(Dir.pwd)}\//
|
110
|
-
@invocation_location = caller.detect do |l|
|
111
|
-
if found = File.expand_path(l).sub!(pwd, "")
|
112
|
-
break found
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
10
|
+
SETTING_OVERRIDABLE_METHODS = [:name, :kennel_id].freeze
|
116
11
|
|
117
12
|
def kennel_id
|
118
13
|
name = self.class.name
|
@@ -126,56 +21,9 @@ module Kennel
|
|
126
21
|
self.class.name
|
127
22
|
end
|
128
23
|
|
129
|
-
def diff(actual)
|
130
|
-
expected = as_json
|
131
|
-
expected.delete(:id)
|
132
|
-
|
133
|
-
self.class.send(:normalize, expected, actual)
|
134
|
-
|
135
|
-
HashDiff.diff(actual, expected, use_lcs: false)
|
136
|
-
end
|
137
|
-
|
138
|
-
def tracking_id
|
139
|
-
"#{project.kennel_id}:#{kennel_id}"
|
140
|
-
end
|
141
|
-
|
142
24
|
def to_json
|
143
25
|
raise NotImplementedError, "Use as_json"
|
144
26
|
end
|
145
|
-
|
146
|
-
def resolve_linked_tracking_ids(*)
|
147
|
-
end
|
148
|
-
|
149
|
-
private
|
150
|
-
|
151
|
-
def resolve_link(id, id_map, force:)
|
152
|
-
id_map[id] || begin
|
153
|
-
message = "Unable to find #{id} in existing monitors (they need to be created first to link them)"
|
154
|
-
force ? invalid!(message) : Kennel.err.puts(message)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
# let users know which project/resource failed when something happens during diffing where the backtrace is hidden
|
159
|
-
def invalid!(message)
|
160
|
-
raise ValidationError, "#{tracking_id} #{message}"
|
161
|
-
end
|
162
|
-
|
163
|
-
def raise_with_location(error, message)
|
164
|
-
message = message.dup
|
165
|
-
message << " for project #{project.kennel_id}" if defined?(project)
|
166
|
-
message << " on #{@invocation_location}" if @invocation_location
|
167
|
-
raise error, message
|
168
|
-
end
|
169
|
-
|
170
|
-
def validate_options(options)
|
171
|
-
unless options.is_a?(Hash)
|
172
|
-
raise ArgumentError, "Expected #{self.class.name}.new options to be a Hash, got a #{options.class}"
|
173
|
-
end
|
174
|
-
options.each do |k, v|
|
175
|
-
next if v.class == Proc
|
176
|
-
raise ArgumentError, "Expected #{self.class.name}.new option :#{k} to be Proc, for example `#{k}: -> { 12 }`"
|
177
|
-
end
|
178
|
-
end
|
179
27
|
end
|
180
28
|
end
|
181
29
|
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module Kennel
|
3
3
|
module Models
|
4
|
-
class Dashboard <
|
4
|
+
class Dashboard < Record
|
5
5
|
include TemplateVariables
|
6
6
|
include OptionalValidations
|
7
7
|
|
8
8
|
API_LIST_INCOMPLETE = true
|
9
9
|
DASHBOARD_DEFAULTS = { template_variables: [] }.freeze
|
10
|
-
READONLY_ATTRIBUTES =
|
10
|
+
READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [
|
11
11
|
:author_handle, :author_name, :modified_at, :url, :is_read_only, :notify_list
|
12
12
|
]
|
13
13
|
REQUEST_DEFAULTS = {
|
@@ -32,8 +32,6 @@ module Kennel
|
|
32
32
|
def normalize(expected, actual)
|
33
33
|
super
|
34
34
|
|
35
|
-
ignore_default expected, actual, DASHBOARD_DEFAULTS
|
36
|
-
|
37
35
|
base_pairs(expected, actual).each do |pair|
|
38
36
|
# conditional_formats ordering is randomly changed by datadog, compare a stable ordering
|
39
37
|
pair.each do |b|
|
@@ -64,13 +62,6 @@ module Kennel
|
|
64
62
|
end
|
65
63
|
end
|
66
64
|
|
67
|
-
attr_reader :project
|
68
|
-
|
69
|
-
def initialize(project, *args)
|
70
|
-
@project = project
|
71
|
-
super(*args)
|
72
|
-
end
|
73
|
-
|
74
65
|
def as_json
|
75
66
|
return @json if @json
|
76
67
|
all_widgets = render_definitions + widgets
|
@@ -1,14 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module Kennel
|
3
3
|
module Models
|
4
|
-
class Monitor <
|
4
|
+
class Monitor < Record
|
5
5
|
include OptionalValidations
|
6
6
|
|
7
7
|
API_LIST_INCOMPLETE = false
|
8
8
|
RENOTIFY_INTERVALS = [0, 10, 20, 30, 40, 50, 60, 90, 120, 180, 240, 300, 360, 720, 1440].freeze # minutes
|
9
9
|
QUERY_INTERVALS = ["1m", "5m", "10m", "15m", "30m", "1h", "2h", "4h", "1d"].freeze
|
10
10
|
OPTIONAL_SERVICE_CHECK_THRESHOLDS = [:ok, :warning].freeze
|
11
|
-
READONLY_ATTRIBUTES =
|
11
|
+
READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [:multi]
|
12
12
|
|
13
13
|
# defaults that datadog uses when options are not sent, so safe to leave out if our values match their defaults
|
14
14
|
MONITOR_OPTION_DEFAULTS = {
|
@@ -45,13 +45,6 @@ module Kennel
|
|
45
45
|
threshold_windows: -> { nil }
|
46
46
|
)
|
47
47
|
|
48
|
-
attr_reader :project
|
49
|
-
|
50
|
-
def initialize(project, *args)
|
51
|
-
@project = project
|
52
|
-
super(*args)
|
53
|
-
end
|
54
|
-
|
55
48
|
def as_json
|
56
49
|
return @as_json if @as_json
|
57
50
|
data = {
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Kennel
|
3
|
+
module Models
|
4
|
+
class Record < Base
|
5
|
+
LOCK = "\u{1F512}"
|
6
|
+
READONLY_ATTRIBUTES = [
|
7
|
+
:deleted, :matching_downtimes, :id, :created, :created_at, :creator, :org_id, :modified,
|
8
|
+
:overall_state_modified, :overall_state, :api_resource
|
9
|
+
].freeze
|
10
|
+
REQUEST_DEFAULTS = {
|
11
|
+
style: { width: "normal", palette: "dog_classic", type: "solid" },
|
12
|
+
conditional_formats: [],
|
13
|
+
aggregator: "avg"
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
class ValidationError < RuntimeError
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
private
|
21
|
+
|
22
|
+
def normalize(_expected, actual)
|
23
|
+
self::READONLY_ATTRIBUTES.each { |k| actual.delete k }
|
24
|
+
end
|
25
|
+
|
26
|
+
# discard styles/conditional_formats/aggregator if nothing would change when we applied (both are default or nil)
|
27
|
+
def ignore_request_defaults(expected, actual, level1, level2)
|
28
|
+
actual = actual[level1] || {}
|
29
|
+
expected = expected[level1] || {}
|
30
|
+
[expected.size.to_i, actual.size.to_i].max.times do |i|
|
31
|
+
a_r = actual.dig(i, level2, :requests) || []
|
32
|
+
e_r = expected.dig(i, level2, :requests) || []
|
33
|
+
ignore_defaults e_r, a_r, self::REQUEST_DEFAULTS
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def ignore_defaults(expected, actual, defaults)
|
38
|
+
[expected&.size.to_i, actual&.size.to_i].max.times do |i|
|
39
|
+
e = expected[i] || {}
|
40
|
+
a = actual[i] || {}
|
41
|
+
ignore_default(e, a, defaults)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def ignore_default(expected, actual, defaults)
|
46
|
+
definitions = [actual, expected]
|
47
|
+
defaults.each do |key, default|
|
48
|
+
if definitions.all? { |r| !r.key?(key) || r[key] == default }
|
49
|
+
actual.delete(key)
|
50
|
+
expected.delete(key)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_reader :project
|
57
|
+
|
58
|
+
def initialize(project, *args)
|
59
|
+
@project = project
|
60
|
+
super(*args)
|
61
|
+
end
|
62
|
+
|
63
|
+
def diff(actual)
|
64
|
+
expected = as_json
|
65
|
+
expected.delete(:id)
|
66
|
+
|
67
|
+
self.class.send(:normalize, expected, actual)
|
68
|
+
|
69
|
+
HashDiff.diff(actual, expected, use_lcs: false)
|
70
|
+
end
|
71
|
+
|
72
|
+
def tracking_id
|
73
|
+
"#{project.kennel_id}:#{kennel_id}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def resolve_linked_tracking_ids(*)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def resolve_link(id, id_map, force:)
|
82
|
+
id_map[id] || begin
|
83
|
+
message = "Unable to find #{id} in existing monitors (they need to be created first to link them)"
|
84
|
+
force ? invalid!(message) : Kennel.err.puts(message)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# let users know which project/resource failed when something happens during diffing where the backtrace is hidden
|
89
|
+
def invalid!(message)
|
90
|
+
raise ValidationError, "#{tracking_id} #{message}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def raise_with_location(error, message)
|
94
|
+
super error, "#{message} for project #{project.kennel_id}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -6,19 +6,10 @@ module Kennel
|
|
6
6
|
base.defaults(validate: -> { true })
|
7
7
|
end
|
8
8
|
|
9
|
-
# https://stackoverflow.com/questions/20235206/ruby-get-all-keys-in-a-hash-including-sub-keys/53876255#53876255
|
10
|
-
def self.all_keys(items)
|
11
|
-
case items
|
12
|
-
when Hash then items.keys + items.values.flat_map { |v| all_keys(v) }
|
13
|
-
when Array then items.flat_map { |i| all_keys(i) }
|
14
|
-
else []
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
9
|
private
|
19
10
|
|
20
11
|
def validate_json(data)
|
21
|
-
bad =
|
12
|
+
bad = Kennel::Utils.all_keys(data).grep_v(Symbol)
|
22
13
|
return if bad.empty?
|
23
14
|
invalid!(
|
24
15
|
"Only use Symbols as hash keys to avoid permanent diffs when updating.\n" \
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Kennel
|
3
|
+
module SettingsAsMethods
|
4
|
+
SETTING_OVERRIDABLE_METHODS = [].freeze
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
base.instance_variable_set(:@settings, [])
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def settings(*names)
|
13
|
+
duplicates = (@settings & names)
|
14
|
+
if duplicates.any?
|
15
|
+
raise ArgumentError, "Settings #{duplicates.map(&:inspect).join(", ")} are already defined"
|
16
|
+
end
|
17
|
+
|
18
|
+
overrides = ((instance_methods - self::SETTING_OVERRIDABLE_METHODS) & names)
|
19
|
+
if overrides.any?
|
20
|
+
raise ArgumentError, "Settings #{overrides.map(&:inspect).join(", ")} are already used as methods"
|
21
|
+
end
|
22
|
+
|
23
|
+
@settings.concat names
|
24
|
+
|
25
|
+
names.each do |name|
|
26
|
+
next if method_defined?(name)
|
27
|
+
define_method name do
|
28
|
+
message = "Trying to call #{name} for #{self.class} but it was never set or passed as option"
|
29
|
+
raise_with_location ArgumentError, message
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def defaults(options)
|
35
|
+
options.each do |name, block|
|
36
|
+
validate_setting_exist name
|
37
|
+
define_method name, &block
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def validate_setting_exist(name)
|
44
|
+
return if !@settings || @settings.include?(name)
|
45
|
+
supported = @settings.map(&:inspect)
|
46
|
+
raise ArgumentError, "Unsupported setting #{name.inspect}, supported settings are #{supported.join(", ")}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def inherited(child)
|
50
|
+
super
|
51
|
+
child.instance_variable_set(:@settings, (@settings || []).dup)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize(options = {})
|
56
|
+
super()
|
57
|
+
|
58
|
+
unless options.is_a?(Hash)
|
59
|
+
raise ArgumentError, "Expected #{self.class.name}.new options to be a Hash, got a #{options.class}"
|
60
|
+
end
|
61
|
+
|
62
|
+
options.each do |k, v|
|
63
|
+
next if v.class == Proc
|
64
|
+
raise ArgumentError, "Expected #{self.class.name}.new option :#{k} to be Proc, for example `#{k}: -> { 12 }`"
|
65
|
+
end
|
66
|
+
|
67
|
+
options.each do |name, block|
|
68
|
+
self.class.send :validate_setting_exist, name
|
69
|
+
define_singleton_method name, &block
|
70
|
+
end
|
71
|
+
|
72
|
+
# need expand_path so it works wih rake and when run individually
|
73
|
+
pwd = /^#{Regexp.escape(Dir.pwd)}\//
|
74
|
+
@invocation_location = caller.detect do |l|
|
75
|
+
if found = File.expand_path(l).sub!(pwd, "")
|
76
|
+
break found
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def raise_with_location(error, message)
|
82
|
+
message = message.dup
|
83
|
+
message << " on #{@invocation_location}" if @invocation_location
|
84
|
+
raise error, message
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/kennel/syncer.rb
CHANGED
@@ -122,12 +122,7 @@ module Kennel
|
|
122
122
|
end
|
123
123
|
|
124
124
|
def download_definitions
|
125
|
-
|
126
|
-
next unless m.respond_to?(:api_resource)
|
127
|
-
m.api_resource
|
128
|
-
end
|
129
|
-
|
130
|
-
Utils.parallel(api_resources.compact.uniq) do |api_resource|
|
125
|
+
Utils.parallel(Models::Record.subclasses.map(&:api_resource)) do |api_resource|
|
131
126
|
results = @api.list(api_resource, with_downtimes: false) # lookup monitors without adding unnecessary downtime information
|
132
127
|
results = results[results.keys.first] if results.is_a?(Hash) # dashes/screens are nested in {dash: {}}
|
133
128
|
results.each { |c| c[:api_resource] = api_resource } # store api resource for later diffing
|
data/lib/kennel/utils.rb
CHANGED
@@ -133,6 +133,15 @@ module Kennel
|
|
133
133
|
Kennel.err.puts "Error #{e}, #{times} retries left"
|
134
134
|
retry
|
135
135
|
end
|
136
|
+
|
137
|
+
# https://stackoverflow.com/questions/20235206/ruby-get-all-keys-in-a-hash-including-sub-keys/53876255#53876255
|
138
|
+
def all_keys(items)
|
139
|
+
case items
|
140
|
+
when Hash then items.keys + items.values.flat_map { |v| all_keys(v) }
|
141
|
+
when Array then items.flat_map { |i| all_keys(i) }
|
142
|
+
else []
|
143
|
+
end
|
144
|
+
end
|
136
145
|
end
|
137
146
|
end
|
138
147
|
end
|
data/lib/kennel/version.rb
CHANGED
data/template/Readme.md
CHANGED
@@ -47,11 +47,11 @@ end
|
|
47
47
|
```
|
48
48
|
|
49
49
|
### Adding a new monitor
|
50
|
-
- use [datadog monitor UI](https://app.datadoghq.com/monitors#create
|
50
|
+
- use [datadog monitor UI](https://app.datadoghq.com/monitors#create) to create a monitor
|
51
51
|
- see below
|
52
52
|
|
53
53
|
### Updating an existing monitor
|
54
|
-
- use [datadog monitor UI](https://app.datadoghq.com/monitors
|
54
|
+
- use [datadog monitor UI](https://app.datadoghq.com/monitors/manage) to find a monitor
|
55
55
|
- get the `id` from the url
|
56
56
|
- run `RESOURCE=monitor ID=12345 bundle exec rake kennel:import` and copy the output
|
57
57
|
- find or create a project in `projects/`
|
@@ -161,6 +161,10 @@ To link to existing monitors via their kennel_id
|
|
161
161
|
|
162
162
|
Run `rake kennel:alerts TAG=service:my-service` to see all un-muted alerts for a given datadog monitor tag.
|
163
163
|
|
164
|
+
### Validating mentions work
|
165
|
+
|
166
|
+
`rake kennel:validate_mentions` should run as part of CI
|
167
|
+
|
164
168
|
## Examples
|
165
169
|
|
166
170
|
### Reusable monitors/dashes/etc
|
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.56.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: 2019-
|
11
|
+
date: 2019-10-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -68,9 +68,11 @@ files:
|
|
68
68
|
- lib/kennel/models/dashboard.rb
|
69
69
|
- lib/kennel/models/monitor.rb
|
70
70
|
- lib/kennel/models/project.rb
|
71
|
+
- lib/kennel/models/record.rb
|
71
72
|
- lib/kennel/models/team.rb
|
72
73
|
- lib/kennel/optional_validations.rb
|
73
74
|
- lib/kennel/progress.rb
|
75
|
+
- lib/kennel/settings_as_methods.rb
|
74
76
|
- lib/kennel/subclass_tracking.rb
|
75
77
|
- lib/kennel/syncer.rb
|
76
78
|
- lib/kennel/tasks.rb
|