kennel 1.78.3 → 1.81.1

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: 1606ccb73f8c7b4e4a0ac2d6c4cf7473d320ece51d38bab7d7b574366086476d
4
- data.tar.gz: 1ce0a1b1edd186c0acaf392ca5dd668419ccf891388e197cdbb40b611ec6cfc4
3
+ metadata.gz: 4ecb9c89748e6eeee268d0d744622b536242af1c1d7f615bf77fc5963e1630d8
4
+ data.tar.gz: e6cae4da66e0947163d1f441bce2afb19f1818dac0ed9418143b3f2a2480b220
5
5
  SHA512:
6
- metadata.gz: f39e476825364d1d6eca54cefab1173008894c02bcb749457c2c2b3d8a0995553b6bfbc44f281e61f9e031f316d1a8c06bf81f2bfaefea618258bf536bfcb555
7
- data.tar.gz: 8588a48b7a5886b04d4ba9030e1cd6b27fc7ba3271150edab3447d7aae977e3b47100218b5c1ac2597d4176f969194c326a28434d825311b69710ec14bcb8ef5
6
+ metadata.gz: 7a3342551a926a55c580571524cadbb32bf7b02b77a914f32e1edf2a07b6772091373286c36871d9651a57899134364d8c9c5894a8ef1e46f72ad091ca241990
7
+ data.tar.gz: 6eb25f647a3a7d47c0858ca03f0649e151f6437d711701ea24f1dd3b742b5adbfbd3f0318a531076b2236063dda194bbdec72237eaf3f1159f447ac86a95ee68
data/Readme.md CHANGED
@@ -84,7 +84,7 @@ end
84
84
  - `cp .env.example .env`
85
85
  - open [Datadog API Settings](https://app.datadoghq.com/account/settings#api)
86
86
  - create a `API Key` or get an existing one from an admin, then 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=`
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
  -->
@@ -212,14 +212,20 @@ removing the `id` will cause kennel to create a new resource in datadog.
212
212
  Some validations might be too strict for your usecase or just wrong, please [open an issue](https://github.com/grosser/kennel/issues) and
213
213
  to unblock use the `validate: -> { false }` option.
214
214
 
215
- ### Linking with kennel_ids
215
+ ### Linking resources with kennel_id
216
216
 
217
- To link to existing monitors via their kennel_id `projects kennel_id` + `:` + `monitors kennel id`
217
+ Link resources with their kennel_id in the format `project kennel_id` + `:` + `resource kennel_id`,
218
+ this should be used to create dependent resources like monitor + slos,
219
+ so they can be created in a single update and can be re-created if any of them is deleted.
218
220
 
219
- - Screens `uptime` widgets can use `monitor: {id: "foo:bar"}`
220
- - Screens `alert_graph` widgets can use `alert_id: "foo:bar"`
221
- - Monitors `composite` can use `query: -> { "%{foo:bar} || %{foo:baz}" }`
222
- - Slos can use `monitor_ids: -> ["foo:bar"]`
221
+ |Resource|Type|Syntax|
222
+ |---|---|---|
223
+ |Dashboard|uptime|`monitor: {id: "foo:bar"}`|
224
+ |Dashboard|alert_graph|`alert_id: "foo:bar"`|
225
+ |Dashboard|slo|`slo_id: "foo:bar"`|
226
+ |Monitor|composite|`query: -> { "%{foo:bar} && %{foo:baz}" }`|
227
+ |Monitor|slo alert|`query: -> { "error_budget(\"%{foo:bar}\").over(\"7d\") > 123.0" }`|
228
+ |Slo|monitor|`monitor_ids: -> ["foo:bar"]`|
223
229
 
224
230
  ### Debugging changes locally
225
231
 
@@ -296,5 +302,5 @@ Author
296
302
  [Michael Grosser](http://grosser.it)<br/>
297
303
  michael@grosser.it<br/>
298
304
  License: MIT<br/>
299
- [![Build Status](https://travis-ci.org/grosser/kennel.png)](https://travis-ci.org/grosser/kennel)
305
+ ![CI](https://github.com/grosser/kennel/workflows/CI/badge.svg)
300
306
  <!-- 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 = {
@@ -140,16 +140,16 @@ module Kennel
140
140
  when "uptime"
141
141
  if ids = definition[:monitor_ids]
142
142
  definition[:monitor_ids] = ids.map do |id|
143
- tracking_id?(id) ? resolve_link(id, :monitor, id_map, **args) : id
143
+ tracking_id?(id) ? (resolve_link(id, :monitor, id_map, **args) || id) : id
144
144
  end
145
145
  end
146
146
  when "alert_graph"
147
147
  if (id = definition[:alert_id]) && tracking_id?(id)
148
- definition[:alert_id] = resolve_link(id, :monitor, id_map, **args).to_s
148
+ definition[:alert_id] = (resolve_link(id, :monitor, id_map, **args) || id).to_s
149
149
  end
150
150
  when "slo"
151
151
  if (id = definition[:slo_id]) && tracking_id?(id)
152
- definition[:slo_id] = resolve_link(id, :slo, id_map, **args).to_s
152
+ definition[:slo_id] = (resolve_link(id, :slo, id_map, **args) || id).to_s
153
153
  end
154
154
  end
155
155
  end
@@ -185,7 +185,7 @@ module Kennel
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
 
@@ -65,19 +65,18 @@ module Kennel
65
65
 
66
66
  private
67
67
 
68
- def resolve_link(id, type, id_map, force:)
69
- value = id_map[id]
70
- if value == :new
68
+ def resolve_link(tracking_id, type, id_map, force:)
69
+ id = id_map[tracking_id]
70
+ if id == :new
71
71
  if force
72
- # TODO: remove the need for this by sorting monitors by missing resolutions
73
- invalid! "#{id} needs to already exist, try again"
72
+ invalid! "#{type} #{tracking_id} was referenced but is also created by the current run.\nIt could not be created because of a circular dependency, try creating only some of the resources"
74
73
  else
75
- id # will be re-resolved by syncer after the linked object was created
74
+ nil # will be re-resolved after the linked object was created
76
75
  end
77
- elsif value
78
- value
76
+ elsif id
77
+ id
79
78
  else
80
- invalid! "Unable to find #{type} #{id} (does not exist and is not being created by the current run)"
79
+ invalid! "Unable to find #{type} #{tracking_id} (does not exist and is not being created by the current run)"
81
80
  end
82
81
  end
83
82
 
@@ -69,7 +69,7 @@ module Kennel
69
69
  def resolve_linked_tracking_ids!(id_map, **args)
70
70
  return unless as_json[:monitor_ids] # ignore_default can remove it
71
71
  as_json[:monitor_ids] = as_json[:monitor_ids].map do |id|
72
- id.is_a?(String) ? resolve_link(id, :monitor, id_map, **args) : id
72
+ id.is_a?(String) ? (resolve_link(id, :monitor, id_map, **args) || id) : id
73
73
  end
74
74
  end
75
75
 
@@ -38,23 +38,14 @@ module Kennel
38
38
  end
39
39
 
40
40
  def update
41
- changed = (@create + @update).map { |_, e| e }
42
-
43
- @create.each do |_, e|
44
- e.resolve_linked_tracking_ids!({}, force: true)
45
-
41
+ each_resolved @create do |_, e|
46
42
  reply = @api.create e.class.api_resource, e.as_json
47
43
  id = reply.fetch(:id)
48
-
49
- # resolve ids we could previously no resolve
50
- changed.delete e
51
- resolve_linked_tracking_ids! from: [reply], to: changed
52
-
44
+ populate_id_map [reply] # allow resolving ids we could previously no resolve
53
45
  Kennel.out.puts "Created #{e.class.api_resource} #{tracking_id(e.as_json)} #{e.url(id)}"
54
46
  end
55
47
 
56
- @update.each do |id, e|
57
- e.resolve_linked_tracking_ids!({}, force: true)
48
+ each_resolved @update do |id, e|
58
49
  @api.update e.class.api_resource, id, e.as_json
59
50
  Kennel.out.puts "Updated #{e.class.api_resource} #{tracking_id(e.as_json)} #{e.url(id)}"
60
51
  end
@@ -67,6 +58,37 @@ module Kennel
67
58
 
68
59
  private
69
60
 
61
+ # loop over items until everything is resolved or crash when we get stuck
62
+ # this solves cases like composite monitors depending on each other or monitor->monitor slo->slo monitor chains
63
+ def each_resolved(list)
64
+ list = list.dup
65
+ loop do
66
+ return if list.empty?
67
+ list.reject! do |id, e|
68
+ if resolved?(e)
69
+ yield id, e
70
+ true
71
+ else
72
+ false
73
+ end
74
+ end ||
75
+ assert_resolved(list[0][1]) # resolve something or show a circular dependency error
76
+ end
77
+ end
78
+
79
+ # TODO: optimize by storing an instance variable if already resolved
80
+ def resolved?(e)
81
+ assert_resolved e
82
+ true
83
+ rescue ValidationError
84
+ false
85
+ end
86
+
87
+ # raises ValidationError when not resolved
88
+ def assert_resolved(e)
89
+ resolve_linked_tracking_ids! [e], force: true
90
+ end
91
+
70
92
  def noop?
71
93
  @create.empty? && @update.empty? && @delete.empty?
72
94
  end
@@ -74,9 +96,15 @@ module Kennel
74
96
  def calculate_diff
75
97
  @update = []
76
98
  @delete = []
99
+ @id_map = {}
77
100
 
78
101
  actual = Progress.progress("Downloading definitions") { download_definitions }
79
- resolve_linked_tracking_ids! from: actual, to: @expected
102
+
103
+ # resolve dependencies to avoid diff
104
+ populate_id_map actual
105
+ @expected.each { |e| @id_map[e.tracking_id] ||= :new }
106
+ resolve_linked_tracking_ids! @expected
107
+
80
108
  filter_by_project! actual
81
109
 
82
110
  Progress.progress "Diffing" do
@@ -107,7 +135,6 @@ module Kennel
107
135
 
108
136
  ensure_all_ids_found
109
137
  @create = @expected.map { |e| [nil, e] }
110
- @create.sort_by! { |_, e| -DELETE_ORDER.index(e.class.api_resource) }
111
138
  end
112
139
 
113
140
  @delete.sort_by! { |_, _, a| DELETE_ORDER.index a.fetch(:api_resource) }
@@ -214,10 +241,12 @@ module Kennel
214
241
  end
215
242
  end
216
243
 
217
- def resolve_linked_tracking_ids!(from:, to:)
218
- map = from.each_with_object({}) { |a, lookup| lookup[tracking_id(a)] = a.fetch(:id) }
219
- to.each { |e| map[e.tracking_id] ||= :new }
220
- to.each { |e| e.resolve_linked_tracking_ids!(map, force: false) }
244
+ def populate_id_map(actual)
245
+ actual.each { |a| @id_map[tracking_id(a)] = a.fetch(:id) }
246
+ end
247
+
248
+ def resolve_linked_tracking_ids!(list, force: false)
249
+ list.each { |e| e.resolve_linked_tracking_ids!(@id_map, force: force) }
221
250
  end
222
251
 
223
252
  def filter_by_project!(definitions)
@@ -23,6 +23,11 @@ module Kennel
23
23
  .downcase
24
24
  end
25
25
 
26
+ # for child projects, not used internally
27
+ def title_case(string)
28
+ string.split(/[\s_]/).map(&:capitalize) * " "
29
+ end
30
+
26
31
  # simplified version of https://apidock.com/rails/ActiveSupport/Inflector/parameterize
27
32
  def parameterize(string)
28
33
  string
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.78.3"
3
+ VERSION = "1.81.1"
4
4
  end
@@ -67,7 +67,7 @@ end
67
67
  - `cp .env.example .env`
68
68
  - open [Datadog API Settings](https://app.datadoghq.com/account/settings#api)
69
69
  - create a `API Key` or get an existing one from an admin, then 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=`
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
 
@@ -196,12 +196,17 @@ to unblock use the `validate: -> { false }` option.
196
196
 
197
197
  ### Linking with kennel_ids
198
198
 
199
- To link to existing monitors via their kennel_id `projects kennel_id` + `:` + `monitors kennel id`
200
-
201
- - Screens `uptime` widgets can use `monitor: {id: "foo:bar"}`
202
- - Screens `alert_graph` widgets can use `alert_id: "foo:bar"`
203
- - Monitors `composite` can use `query: -> { "%{foo:bar} || %{foo:baz}" }`
204
- - Slos can use `monitor_ids: -> ["foo:bar"]`
199
+ Link resources via their kennel_id `projects kennel_id` + `:` + `monitors kennel id`,
200
+ this should be used to create dependent resources like monitor + slos.
201
+
202
+ |Resource|Type|Syntax|
203
+ |---|---|---|
204
+ |Dashboard|uptime|`monitor: {id: "foo:bar"}`|
205
+ |Dashboard|alert_graph|`alert_id: "foo:bar"`|
206
+ |Dashboard|slo|`slo_id: "foo:bar"`|
207
+ |Monitor|composite|`query: -> { "%{foo:bar} && %{foo:baz}" }`|
208
+ |Monitor|slo alert|`query: -> { "error_budget(\"%{foo:bar}\").over(\"7d\") > 123.0" }`|
209
+ |Slo|monitor|`monitor_ids: -> ["foo:bar"]`|
205
210
 
206
211
  ### Debugging changes locally
207
212
 
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.3
4
+ version: 1.81.1
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-12-09 00:00:00.000000000 Z
11
+ date: 2021-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday