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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e23afaba22ded8b1fb654a9552ed135247cb3a9a5c5610090bfa9096123ffb4
4
- data.tar.gz: 7e4096a43a6fcd52f519131c2238879f2eb551d90c5883a9d5e1e8996f90075c
3
+ metadata.gz: 491e174273f8af94504a4e81d9a2ff7da5820aafe287ea9cb1e78df93d07f0f0
4
+ data.tar.gz: bf918b4be464d408259e0f8204541a0619d1719670a9843a6e564cc4edc968d1
5
5
  SHA512:
6
- metadata.gz: bb7d4a1e6f48f645e220169bcabac5ae8fed56f01033a63be6ea855e5308e62ec3d31a4c9326823d99e973d85056f1151a1924ae33ced07b620224b1e716eb8b
7
- data.tar.gz: 7bb59bc7ca8bd3b024903c61bdc11c4f816d9fca0fc666cae36dcdb6258d660608ba6cfe16ccbf1877957c54eb9e9feda437055d44fe4f5a3e0abe701623680d
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/metric) to create a monitor
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#create/metric) to find a monitor
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/`
@@ -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
- # parts
22
+ # records
21
23
  require "kennel/models/monitor"
22
24
  require "kennel/models/dashboard"
23
25
 
@@ -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::Base::LOCK, "") # avoid double lock icon
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) }
@@ -4,115 +4,10 @@ require "hashdiff"
4
4
  module Kennel
5
5
  module Models
6
6
  class Base
7
- LOCK = "\u{1F512}"
8
- READONLY_ATTRIBUTES = [
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
- class ValidationError < RuntimeError
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 < Base
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 = Base::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 < Base
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 = Base::READONLY_ATTRIBUTES + [:multi]
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 = OptionalValidations.all_keys(data).grep_v(Symbol)
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
@@ -122,12 +122,7 @@ module Kennel
122
122
  end
123
123
 
124
124
  def download_definitions
125
- api_resources = Models::Base.subclasses.map do |m|
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
@@ -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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.55.1"
3
+ VERSION = "1.56.0"
4
4
  end
@@ -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/metric) to create a monitor
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#create/metric) to find a monitor
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.55.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-09-17 00:00:00.000000000 Z
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