kennel 1.114.1 → 1.116.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: 248d75d411fad53dd8011982ec16d2104547991ec35480bf9d085feaed419285
4
- data.tar.gz: fe11bc68ac149ff7ef989c8bd2b2b497eb465f6f0eccc66d5fbf304de9801666
3
+ metadata.gz: 49677de3c690744756d70810dbe71c49cb0313014c5c325af21aa161463738bc
4
+ data.tar.gz: c52c465238c8043ba7a735712dcaf73edd589875b680b7f45f235477c696cff2
5
5
  SHA512:
6
- metadata.gz: d9888da18e41995160fb03516fc02037cb07f43acae46b298941952ec053df4b2ca62ea918a5b1b17664c76d926bb702b6e28a375af1f3047451e2463f88226c
7
- data.tar.gz: 92917f89e251d9345f1cf580ebb0206b1206882abe036d1e44284835c025ca61822e261d35a120fe7194303ede139e3c56db48b878fdb137f8d176cc4796441f
6
+ metadata.gz: 50968ff4039145263ea380d790ebd8d757afb96bd2966a8b7dddf5a58373dbf3fd2e13f0576ad44bf42ceecd7086095865d4edc7c85bc5df1b1f9cc7b9757638
7
+ data.tar.gz: 7facba1805b0d2690b4081e18d46d314161544326a8e2e772bd17284c9065d7a8d50431690ca7562425510b9a12db70726453e3a4296aac6cbfd00d3e6954172
data/Readme.md CHANGED
@@ -289,23 +289,27 @@ Remove the code that created the resource. The next update will delete it (see a
289
289
  ```
290
290
 
291
291
  ### Updating existing resources with id
292
-
293
292
  Setting `id` makes kennel take over a manually created datadog resource.
294
293
  When manually creating to import, it is best to remove the `id` and delete the manually created resource.
295
294
 
296
295
  When an `id` is set and the original resource is deleted, kennel will fail to update,
297
296
  removing the `id` will cause kennel to create a new resource in datadog.
298
297
 
299
- ### Organizing many projects
300
- Having many projects (and their sub-resources) can quickly get out of hand.
298
+ ### Organizing projects with many resources
299
+ When project files get too long, this structure can keep things bite-sized.
301
300
 
302
- Use this class structure to keep things organized:
303
301
  ```Ruby
304
302
  # projects/project_a/base.rb
305
303
  module ProjectA
306
304
  class Base < Kennel::Models::Project
307
305
  defaults(
308
306
  kennel_id: -> { "project_a" },
307
+ parts: -> {
308
+ [
309
+ Monitors::FooAlert.new(self),
310
+ ...
311
+ ]
312
+ }
309
313
  ...
310
314
 
311
315
  # projects/project_a/monitors/foo_alert.rb
@@ -315,6 +319,17 @@ module ProjectA
315
319
  ...
316
320
  ```
317
321
 
322
+ ### Updating a single project or resource
323
+
324
+ - Use `PROJECT=<kennel_id>` for single project:
325
+
326
+ Use the projects `kennel_id` (and if none is set then snake_case of the class name including modules)
327
+ to refer to the project. For example for `class ProjectA` use `PROJECT=project_a` but for `Foo::ProjectA` use `foo_project_a`.
328
+
329
+ - Use `TRACKING_ID=<project-kennel_id>:<resource-kennel_id>` for single resource:
330
+
331
+ Use the project kennel_id and the resources kennel_id, for example `class ProjectA` and `FooAlert` would give `project_a:foo_alert`.
332
+
318
333
  ### Skipping validations
319
334
  Some validations might be too strict for your usecase or just wrong, please [open an issue](https://github.com/grosser/kennel/issues) and
320
335
  to unblock use the `validate: -> { false }` option.
@@ -335,7 +350,7 @@ so they can be created in a single update and can be re-created if any of them i
335
350
 
336
351
  ### Debugging changes locally
337
352
  - rebase on updated `master` to not undo other changes
338
- - figure out project name by converting the class name to snake-case
353
+ - figure out project name by converting the class name to snake_case
339
354
  - run `PROJECT=foo bundle exec rake kennel:update_datadog` to test changes for a single project (monitors: remove mentions while debugging to avoid alert spam)
340
355
  - use `PROJECT=foo,bar,...` for multiple projects
341
356
 
@@ -19,12 +19,21 @@ module Kennel
19
19
 
20
20
  def validated_parts
21
21
  all = parts
22
+ unless all.is_a?(Array) && all.all? { |part| part.is_a?(Record) }
23
+ invalid! "#parts must return an array of Records"
24
+ end
25
+
22
26
  validate_parts(all)
23
27
  all
24
28
  end
25
29
 
26
30
  private
27
31
 
32
+ # let users know which project/resource failed when something happens during diffing where the backtrace is hidden
33
+ def invalid!(message)
34
+ raise ValidationError, "#{kennel_id} #{message}"
35
+ end
36
+
28
37
  # hook for users to add custom validations via `prepend`
29
38
  def validate_parts(parts)
30
39
  end
@@ -7,12 +7,12 @@ module Kennel
7
7
  def self.progress(name)
8
8
  Kennel.err.print "#{name} ... "
9
9
 
10
- animation = "-\\|/"
11
- count = 0
12
10
  stop = false
13
11
  result = nil
14
12
 
15
13
  spinner = Thread.new do
14
+ animation = "-\\|/"
15
+ count = 0
16
16
  loop do
17
17
  break if stop
18
18
  Kennel.err.print animation[count % animation.size]
@@ -30,7 +30,7 @@ module Kennel
30
30
 
31
31
  result
32
32
  ensure
33
- stop = true
33
+ stop = true # make thread stop without killing it
34
34
  end
35
35
  end
36
36
  end
data/lib/kennel/syncer.rb CHANGED
@@ -4,9 +4,10 @@ module Kennel
4
4
  DELETE_ORDER = ["dashboard", "slo", "monitor", "synthetics/tests"].freeze # dashboards references monitors + slos, slos reference monitors
5
5
  LINE_UP = "\e[1A\033[K" # go up and clear
6
6
 
7
- def initialize(api, expected, project_filter: nil)
7
+ def initialize(api, expected, project_filter: nil, tracking_id_filter: nil)
8
8
  @api = api
9
9
  @project_filter = project_filter
10
+ @tracking_id_filter = tracking_id_filter
10
11
  @expected = Set.new expected # need set to speed up deletion
11
12
  calculate_diff
12
13
  validate_plan
@@ -105,7 +106,7 @@ module Kennel
105
106
 
106
107
  Progress.progress "Diffing" do
107
108
  populate_id_map @expected, actual
108
- filter_actual_by_project! actual
109
+ filter_actual! actual
109
110
  resolve_linked_tracking_ids! @expected # resolve dependencies to avoid diff
110
111
 
111
112
  @expected.each(&:add_tracking_id) # avoid diff with actual
@@ -261,11 +262,12 @@ module Kennel
261
262
  next unless tracking_id = a.fetch(:tracking_id)
262
263
 
263
264
  # ignore when deleted from the codebase
264
- # (when running with project filter we cannot see the other resources in the codebase)
265
+ # (when running with filters we cannot see the other resources in the codebase)
265
266
  api_resource = a.fetch(:klass).api_resource
266
267
  next if
267
268
  !@id_map.get(api_resource, tracking_id) &&
268
- (!project_prefixes || tracking_id.start_with?(*project_prefixes))
269
+ (!project_prefixes || tracking_id.start_with?(*project_prefixes)) &&
270
+ (!@tracking_id_filter || @tracking_id_filter.include?(tracking_id))
269
271
 
270
272
  @id_map.set(api_resource, tracking_id, a.fetch(:id))
271
273
  if a[:klass].api_resource == "synthetics/tests"
@@ -278,12 +280,18 @@ module Kennel
278
280
  list.each { |e| e.resolve_linked_tracking_ids!(@id_map, force: force) }
279
281
  end
280
282
 
281
- def filter_actual_by_project!(actual)
282
- return unless @project_filter
283
- project_prefixes = @project_filter&.map { |p| "#{p}:" }
284
- actual.select! do |a|
285
- tracking_id = a.fetch(:tracking_id)
286
- !tracking_id || tracking_id.start_with?(*project_prefixes)
283
+ def filter_actual!(actual)
284
+ if @tracking_id_filter
285
+ actual.select! do |a|
286
+ tracking_id = a.fetch(:tracking_id)
287
+ !tracking_id || @tracking_id_filter.include?(tracking_id)
288
+ end
289
+ elsif @project_filter
290
+ project_prefixes = @project_filter.map { |p| "#{p}:" }
291
+ actual.select! do |a|
292
+ tracking_id = a.fetch(:tracking_id)
293
+ !tracking_id || tracking_id.start_with?(*project_prefixes)
294
+ end
287
295
  end
288
296
  end
289
297
  end
data/lib/kennel/utils.rb CHANGED
@@ -113,7 +113,7 @@ module Kennel
113
113
  done[i] =
114
114
  begin
115
115
  yield item
116
- rescue StandardError => e
116
+ rescue Exception => e # rubocop:disable Lint/RescueException
117
117
  work.clear
118
118
  e
119
119
  end
@@ -121,7 +121,7 @@ module Kennel
121
121
  end
122
122
  end
123
123
  workers.each(&:join)
124
- done.each { |d| raise d if d.is_a?(StandardError) }
124
+ done.each { |d| raise d if d.is_a?(Exception) }
125
125
  end
126
126
 
127
127
  def natural_order(name)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.114.1"
3
+ VERSION = "1.116.1"
4
4
  end
data/lib/kennel.rb CHANGED
@@ -65,13 +65,22 @@ module Kennel
65
65
 
66
66
  def store(parts)
67
67
  Progress.progress "Storing" do
68
- old = Dir["generated/{#{(project_filter || ["**"]).join(",")}}/*"]
68
+ old = Dir[[
69
+ "generated",
70
+ if project_filter || tracking_id_filter
71
+ [
72
+ "{" + (project_filter || ["*"]).join(",") + "}",
73
+ "{" + (tracking_id_filter || ["*"]).join(",") + "}.json"
74
+ ]
75
+ else
76
+ "**"
77
+ end
78
+ ].join("/")]
69
79
  used = []
70
80
 
71
81
  Utils.parallel(parts, max: 2) do |part|
72
82
  path = "generated/#{part.tracking_id.tr("/", ":").sub(":", "/")}.json"
73
- used << File.dirname(path) # only 1 level of sub folders, so this is safe
74
- used << path
83
+ used.concat [File.dirname(path), path] # only 1 level of sub folders, so this is safe
75
84
  payload = part.as_json.merge(api_resource: part.class.api_resource)
76
85
  write_file_if_necessary(path, JSON.pretty_generate(payload) << "\n")
77
86
  end
@@ -94,7 +103,7 @@ module Kennel
94
103
  end
95
104
 
96
105
  def syncer
97
- @syncer ||= Syncer.new(api, generated, project_filter: project_filter)
106
+ @syncer ||= Syncer.new(api, generated, project_filter: project_filter, tracking_id_filter: tracking_id_filter)
98
107
  end
99
108
 
100
109
  def api
@@ -105,22 +114,12 @@ module Kennel
105
114
  @generated ||= begin
106
115
  Progress.progress "Generating" do
107
116
  load_all
108
- known = []
109
- filter = project_filter
110
-
111
- parts = Utils.parallel(Models::Project.recursive_subclasses) do |project_class|
112
- project = project_class.new
113
- kennel_id = project.kennel_id
114
- if filter
115
- known << kennel_id
116
- next [] unless filter.include?(kennel_id)
117
- end
118
- project.validated_parts
119
- end.flatten(1)
120
-
121
- if filter && parts.empty?
122
- raise "#{filter.join(", ")} does not match any projects, try any of these:\n#{known.uniq.sort.join("\n")}"
123
- end
117
+
118
+ projects = Models::Project.recursive_subclasses.map(&:new)
119
+ filter_resources!(projects, :kennel_id, project_filter, "projects", "PROJECT")
120
+
121
+ parts = Utils.parallel(projects, &:validated_parts).flatten(1)
122
+ filter_resources!(parts, :tracking_id, tracking_id_filter, "resources", "TRACKING_ID")
124
123
 
125
124
  parts.group_by(&:tracking_id).each do |tracking_id, same|
126
125
  next if same.size == 1
@@ -139,7 +138,25 @@ module Kennel
139
138
  end
140
139
 
141
140
  def project_filter
142
- ENV["PROJECT"]&.split(",")
141
+ raise "either use PROJECT= or TRACKING_ID=" if ENV["PROJECT"] && ENV["TRACKING_ID"]
142
+ ENV["PROJECT"]&.split(",") || tracking_id_filter&.map { |id| id.split(":", 2).first }
143
+ end
144
+
145
+ def tracking_id_filter
146
+ (tracking_id = ENV["TRACKING_ID"]) && tracking_id.split(",")
147
+ end
148
+
149
+ def filter_resources!(resources, by, against, name, env)
150
+ return unless against
151
+
152
+ before = resources.dup
153
+ resources.select! { |p| against.include?(p.send(by)) }
154
+ return if resources.size == against.size
155
+
156
+ raise <<~MSG.rstrip
157
+ #{env}=#{against.join(",")} matched #{resources.size} #{name}, try any of these:
158
+ #{before.map(&by).sort.uniq.join("\n")}
159
+ MSG
143
160
  end
144
161
 
145
162
  def load_all
@@ -147,8 +164,9 @@ module Kennel
147
164
  Dir.exist?("teams") && loader.push_dir("teams", namespace: Teams)
148
165
  Dir.exist?("parts") && loader.push_dir("parts")
149
166
  loader.setup
167
+ loader.eager_load # TODO: this should not be needed but we see hanging CI processes when it's not added
150
168
 
151
- # TODO: also do projects and update expected path too
169
+ # TODO: also auto-load projects and update expected path too
152
170
  ["projects"].each do |folder|
153
171
  Dir["#{folder}/**/*.rb"].sort.each { |f| require "./#{f}" }
154
172
  end
data/template/Readme.md CHANGED
@@ -271,23 +271,27 @@ Remove the code that created the resource. The next update will delete it (see a
271
271
  ```
272
272
 
273
273
  ### Updating existing resources with id
274
-
275
274
  Setting `id` makes kennel take over a manually created datadog resource.
276
275
  When manually creating to import, it is best to remove the `id` and delete the manually created resource.
277
276
 
278
277
  When an `id` is set and the original resource is deleted, kennel will fail to update,
279
278
  removing the `id` will cause kennel to create a new resource in datadog.
280
279
 
281
- ### Organizing many projects
282
- Having many projects (and their sub-resources) can quickly get out of hand.
280
+ ### Organizing projects with many resources
281
+ When project files get too long, this structure can keep things bite-sized.
283
282
 
284
- Use this class structure to keep things organized:
285
283
  ```Ruby
286
284
  # projects/project_a/base.rb
287
285
  module ProjectA
288
286
  class Base < Kennel::Models::Project
289
287
  defaults(
290
288
  kennel_id: -> { "project_a" },
289
+ parts: -> {
290
+ [
291
+ Monitors::FooAlert.new(self),
292
+ ...
293
+ ]
294
+ }
291
295
  ...
292
296
 
293
297
  # projects/project_a/monitors/foo_alert.rb
@@ -297,6 +301,17 @@ module ProjectA
297
301
  ...
298
302
  ```
299
303
 
304
+ ### Updating a single project or resource
305
+
306
+ - Use `PROJECT=<kennel_id>` for single project:
307
+
308
+ Use the projects `kennel_id` (and if none is set then snake_case of the class name including modules)
309
+ to refer to the project. For example for `class ProjectA` use `PROJECT=project_a` but for `Foo::ProjectA` use `foo_project_a`.
310
+
311
+ - Use `TRACKING_ID=<project-kennel_id>:<resource-kennel_id>` for single resource:
312
+
313
+ Use the project kennel_id and the resources kennel_id, for example `class ProjectA` and `FooAlert` would give `project_a:foo_alert`.
314
+
300
315
  ### Skipping validations
301
316
  Some validations might be too strict for your usecase or just wrong, please [open an issue](https://github.com/grosser/kennel/issues) and
302
317
  to unblock use the `validate: -> { false }` option.
@@ -317,7 +332,7 @@ so they can be created in a single update and can be re-created if any of them i
317
332
 
318
333
  ### Debugging changes locally
319
334
  - rebase on updated `master` to not undo other changes
320
- - figure out project name by converting the class name to snake-case
335
+ - figure out project name by converting the class name to snake_case
321
336
  - run `PROJECT=foo bundle exec rake kennel:update_datadog` to test changes for a single project (monitors: remove mentions while debugging to avoid alert spam)
322
337
  - use `PROJECT=foo,bar,...` for multiple projects
323
338
 
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.114.1
4
+ version: 1.116.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: 2022-08-13 00:00:00.000000000 Z
11
+ date: 2022-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday