kennel 1.78.1 → 1.80.0

Sign up to get free protection for your applications and to get access to all the features.
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