kennel 1.78.1 → 1.80.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: d12735939d42da2f660773d9ca734a279fdc5c29b6619f6722b163f07f299236
4
- data.tar.gz: 14f40ae5314abc2ff5b58672ad096a9d1898b793c1dee490a45d9deefc67e513
3
+ metadata.gz: 0cc9d71dff0d4c4bf8a29d9c58c1b62efbb739f3ebdc0d63fb7bd1a3e1cfec7c
4
+ data.tar.gz: 312fbda2e1577d071ba7cad7a6c5571b6b3913517cc3e5e9b92f7858637f96dc
5
5
  SHA512:
6
- metadata.gz: b5340adfc8b564f648caa8f656b8b10fe9cd48177a6b15e21944adac24209344aeb81aa0bc5190e7a5997e8ebc0dc49af0304daf947abf99112dc8d56f6a9485
7
- data.tar.gz: 368fb6dffbc42be889f8692b876e867818cde5eefd04b982151efc7d9dc74a378a046f12a7764f448df5818ebca950e55f22e36d45822c68ed3e8a3442850162
6
+ metadata.gz: 8d07c6d405178e7114c1ebaf67ce3f92a09a411208d20353bf7d07f453093e322b98f2e8e2f501cf902a601fafa1b352b5fa19bff9d54f1d042e87fc7ef81277
7
+ data.tar.gz: 9d681c94f1d61bbc60a098ea0b6fe12e3efc5b6e3c771ec49743db8e6edaae1c83a64ae322a9f5759fc677465b199842f0401c8a38973d1bbaa14157f53f94da
data/Readme.md CHANGED
@@ -83,8 +83,8 @@ end
83
83
  - `gem install bundler && bundle install`
84
84
  - `cp .env.example .env`
85
85
  - open [Datadog API Settings](https://app.datadoghq.com/account/settings#api)
86
- - copy any `API Key` and add it to `.env` as `DATADOG_API_KEY`
87
- - find or create (check last page) your personal "Application Key" and add it to `.env` as `DATADOG_APP_KEY=`
86
+ - create a `API Key` or get an existing one from an admin, then add it to `.env` as `DATADOG_API_KEY`
87
+ - open [Datadog API Settings](https://app.datadoghq.com/access/application-keys) and create a new key, then add it to `.env` as `DATADOG_APP_KEY=`
88
88
  - change the `DATADOG_SUBDOMAIN=app` in `.env` to your companies subdomain if you have one
89
89
  - verify it works by running `rake plan`, it might show some diff, but should not crash
90
90
  -->
@@ -219,6 +219,7 @@ To link to existing monitors via their kennel_id `projects kennel_id` + `:` + `m
219
219
  - Screens `uptime` widgets can use `monitor: {id: "foo:bar"}`
220
220
  - Screens `alert_graph` widgets can use `alert_id: "foo:bar"`
221
221
  - Monitors `composite` can use `query: -> { "%{foo:bar} || %{foo:baz}" }`
222
+ - Monitors `slo alert` can use `query: -> { "error_budget(\"%{foo:bar}\").over(\"7d\") > 123.0" }`
222
223
  - Slos can use `monitor_ids: -> ["foo:bar"]`
223
224
 
224
225
  ### Debugging changes locally
@@ -296,5 +297,5 @@ Author
296
297
  [Michael Grosser](http://grosser.it)<br/>
297
298
  michael@grosser.it<br/>
298
299
  License: MIT<br/>
299
- [![Build Status](https://travis-ci.org/grosser/kennel.png)](https://travis-ci.org/grosser/kennel)
300
+ ![CI](https://github.com/grosser/kennel/workflows/CI/badge.svg)
300
301
  <!-- NOT IN -->
@@ -39,12 +39,7 @@ module Kennel
39
39
  attr_accessor :out, :err
40
40
 
41
41
  def generate
42
- FileUtils.rm_rf("generated")
43
- generated.each do |part|
44
- path = "generated/#{part.tracking_id.sub(":", "/")}.json"
45
- FileUtils.mkdir_p(File.dirname(path))
46
- File.write(path, JSON.pretty_generate(part.as_json) << "\n")
47
- end
42
+ store generated
48
43
  end
49
44
 
50
45
  def plan
@@ -58,6 +53,35 @@ module Kennel
58
53
 
59
54
  private
60
55
 
56
+ def store(parts)
57
+ Progress.progress "Storing" do
58
+ old = Dir["generated/**/*"]
59
+ used = []
60
+
61
+ Utils.parallel(parts, max: 2) do |part|
62
+ path = "generated/#{part.tracking_id.tr("/", ":").sub(":", "/")}.json"
63
+ used << File.dirname(path) # only 1 level of sub folders, so this is safe
64
+ used << path
65
+ write_file_if_necessary(path, JSON.pretty_generate(part.as_json) << "\n")
66
+ end
67
+
68
+ # deleting all is slow, so only delete the extras
69
+ (old - used).each { |p| FileUtils.rm_rf(p) }
70
+ end
71
+ end
72
+
73
+ def write_file_if_necessary(path, content)
74
+ # 99% case
75
+ begin
76
+ return if File.read(path) == content
77
+ rescue Errno::ENOENT
78
+ FileUtils.mkdir_p(File.dirname(path))
79
+ end
80
+
81
+ # slow 1% case
82
+ File.write(path, content)
83
+ end
84
+
61
85
  def syncer
62
86
  @syncer ||= Syncer.new(api, generated, project: ENV["PROJECT"])
63
87
  end
@@ -73,8 +97,12 @@ module Kennel
73
97
  parts = Models::Project.recursive_subclasses.flat_map do |project_class|
74
98
  project_class.new.validated_parts
75
99
  end
76
- parts.map(&:tracking_id).group_by { |id| id }.select do |id, same|
77
- raise "#{id} is defined #{same.size} times" if same.size != 1
100
+ parts.group_by(&:tracking_id).each do |tracking_id, same|
101
+ next if same.size == 1
102
+ raise <<~ERROR
103
+ #{tracking_id} is defined #{same.size} times
104
+ use a different `kennel_id` when defining multiple projects/monitors/dashboards to avoid this conflict
105
+ ERROR
78
106
  end
79
107
  parts
80
108
  end
@@ -105,7 +105,7 @@ module Kennel
105
105
 
106
106
  def as_json
107
107
  return @json if @json
108
- all_widgets = render_definitions + widgets
108
+ all_widgets = render_definitions(definitions) + widgets
109
109
  expand_q all_widgets
110
110
 
111
111
  @json = {
@@ -178,14 +178,14 @@ module Kennel
178
178
  def validate_json(data)
179
179
  super
180
180
 
181
- validate_template_variables data, :widgets
181
+ validate_template_variables data
182
182
 
183
183
  # Avoid diff from datadog presets sorting.
184
184
  presets = data[:template_variable_presets]
185
185
  invalid! "template_variable_presets must be sorted by name" if presets && presets != presets.sort_by { |p| p[:name] }
186
186
  end
187
187
 
188
- def render_definitions
188
+ def render_definitions(definitions)
189
189
  definitions.map do |title, type, display_type, queries, options = {}, ignored = nil|
190
190
  # validate inputs
191
191
  if ignored || (!title || !type || !queries || !options.is_a?(Hash))
@@ -12,6 +12,10 @@ module Kennel
12
12
  :multi, :matching_downtimes, :overall_state_modified, :overall_state, :restricted_roles
13
13
  ]
14
14
 
15
+ MONITOR_DEFAULTS = {
16
+ priority: nil
17
+ }.freeze
18
+
15
19
  # defaults that datadog uses when options are not sent, so safe to leave out if our values match their defaults
16
20
  MONITOR_OPTION_DEFAULTS = {
17
21
  evaluation_delay: nil,
@@ -27,7 +31,7 @@ module Kennel
27
31
  settings(
28
32
  :query, :name, :message, :escalation_message, :critical, :type, :renotify_interval, :warning, :timeout_h, :evaluation_delay,
29
33
  :ok, :no_data_timeframe, :notify_no_data, :notify_audit, :tags, :critical_recovery, :warning_recovery, :require_full_window,
30
- :threshold_windows, :new_host_delay
34
+ :threshold_windows, :new_host_delay, :priority
31
35
  )
32
36
 
33
37
  defaults(
@@ -46,7 +50,8 @@ module Kennel
46
50
  evaluation_delay: -> { MONITOR_OPTION_DEFAULTS.fetch(:evaluation_delay) },
47
51
  critical_recovery: -> { nil },
48
52
  warning_recovery: -> { nil },
49
- threshold_windows: -> { nil }
53
+ threshold_windows: -> { nil },
54
+ priority: -> { MONITOR_DEFAULTS.fetch(:priority) }
50
55
  )
51
56
 
52
57
  def as_json
@@ -57,6 +62,7 @@ module Kennel
57
62
  query: query.strip,
58
63
  message: message.strip,
59
64
  tags: tags.uniq,
65
+ priority: priority,
60
66
  options: {
61
67
  timeout_h: timeout_h,
62
68
  notify_no_data: notify_no_data,
@@ -106,9 +112,11 @@ module Kennel
106
112
  end
107
113
 
108
114
  def resolve_linked_tracking_ids!(id_map, **args)
109
- if as_json[:type] == "composite"
110
- as_json[:query] = as_json[:query].gsub(/%\{(.*?)\}/) do
111
- resolve_link($1, :monitor, id_map, **args)
115
+ case as_json[:type]
116
+ when "composite", "slo alert"
117
+ type = (as_json[:type] == "composite" ? :monitor : :slo)
118
+ as_json[:query] = as_json[:query].gsub(/%{(.*?)}/) do
119
+ resolve_link($1, type, id_map, **args)
112
120
  end
113
121
  end
114
122
  end
@@ -129,6 +137,9 @@ module Kennel
129
137
 
130
138
  def self.normalize(expected, actual)
131
139
  super
140
+
141
+ ignore_default(expected, actual, MONITOR_DEFAULTS)
142
+
132
143
  options = actual.fetch(:options)
133
144
  options.delete(:silenced) # we do not manage silenced, so ignore it when diffing
134
145
 
@@ -209,7 +220,10 @@ module Kennel
209
220
  .map! { |w| %("#{w}.name") }
210
221
  used.uniq.each do |match, group|
211
222
  next if allowed.include?(group)
212
- invalid! "#{match} used with #{group}, but can only be used with #{allowed.join(", ")}. Add more groupings or fix the #{match}"
223
+ invalid!(
224
+ "#{match} used with #{group}, but can only be used with #{allowed.join(", ")}. " \
225
+ "Group the query by #{group.sub(".name", "").tr('"', "")} or change the #{match}"
226
+ )
213
227
  end
214
228
  end
215
229
  end
@@ -16,18 +16,22 @@ module Kennel
16
16
 
17
17
  # check for queries that do not use the variables and would be misleading
18
18
  # TODO: do the same check for apm_query and their group_by
19
- def validate_template_variables(data, key)
19
+ def validate_template_variables(data)
20
20
  variables = (data[:template_variables] || []).map { |v| "$#{v.fetch(:name)}" }
21
- queries = data[key].flat_map do |widget|
21
+ return if variables.empty?
22
+
23
+ queries = data[:widgets].flat_map do |widget|
22
24
  ([widget] + (widget.dig(:definition, :widgets) || [])).flat_map { |w| widget_queries(w) }
23
25
  end.compact
24
- bad = queries.grep_v(/(#{variables.map { |v| Regexp.escape(v) }.join("|")})\b/)
25
- if bad.any?
26
- invalid!(
27
- "queries #{bad.join(", ")} must use the template variables #{variables.join(", ")}\n" \
28
- "If that is not possible, add `validate: -> { false } # query foo in bar does not have baz tag`"
29
- )
30
- end
26
+
27
+ matches = variables.map { |v| Regexp.new "#{Regexp.escape(v)}\\b" }
28
+ queries.reject! { |q| matches.all? { |m| q.match? m } }
29
+ return if queries.empty?
30
+
31
+ invalid!(
32
+ "queries #{queries.join(", ")} must use the template variables #{variables.join(", ")}\n" \
33
+ "If that is not possible, add `validate: -> { false } # query foo in bar does not have baz tag`"
34
+ )
31
35
  end
32
36
 
33
37
  def widget_queries(widget)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.78.1"
3
+ VERSION = "1.80.0"
4
4
  end
@@ -66,8 +66,8 @@ end
66
66
  - `gem install bundler && bundle install`
67
67
  - `cp .env.example .env`
68
68
  - open [Datadog API Settings](https://app.datadoghq.com/account/settings#api)
69
- - copy any `API Key` and add it to `.env` as `DATADOG_API_KEY`
70
- - find or create (check last page) your personal "Application Key" and add it to `.env` as `DATADOG_APP_KEY=`
69
+ - create a `API Key` or get an existing one from an admin, then add it to `.env` as `DATADOG_API_KEY`
70
+ - open [Datadog API Settings](https://app.datadoghq.com/access/application-keys) and create a new key, then add it to `.env` as `DATADOG_APP_KEY=`
71
71
  - change the `DATADOG_SUBDOMAIN=app` in `.env` to your companies subdomain if you have one
72
72
  - verify it works by running `rake plan`, it might show some diff, but should not crash
73
73
 
@@ -201,6 +201,7 @@ To link to existing monitors via their kennel_id `projects kennel_id` + `:` + `m
201
201
  - Screens `uptime` widgets can use `monitor: {id: "foo:bar"}`
202
202
  - Screens `alert_graph` widgets can use `alert_id: "foo:bar"`
203
203
  - Monitors `composite` can use `query: -> { "%{foo:bar} || %{foo:baz}" }`
204
+ - Monitors `slo alert` can use `query: -> { "error_budget(\"%{foo:bar}\").over(\"7d\") > 123.0" }`
204
205
  - Slos can use `monitor_ids: -> ["foo:bar"]`
205
206
 
206
207
  ### Debugging changes locally
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.78.1
4
+ version: 1.80.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: 2020-10-20 00:00:00.000000000 Z
11
+ date: 2021-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday