kennel 1.118.2 → 1.119.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: 73ea25369567c46bcfd2af7ce3ac6c5b64754f9a16f5506d46c23edf9f8fbadf
4
- data.tar.gz: b07ae565f4c905e60b784688d15fa704bcbce470283632ff249e26ca51924a1a
3
+ metadata.gz: 67b43081a187354fb146269d929da674d533263b44b3f2cee00b2baa7cc8bc57
4
+ data.tar.gz: c14b369716513a92791496a92c29c7648499d0a06f05e34d36c628f4a9bb9305
5
5
  SHA512:
6
- metadata.gz: '079bf981abd354955807478b3e7ee4e90cdeb418053b1054271c5460421ba7b175afeb77c2b512e835890eff5cbbb121abf2be9ae3f4897376ff6d4cb2f0c772'
7
- data.tar.gz: 51444f447bec09ff09b5b854b0d759d270b8b72f9145d9e5d1bb40647215841384cad9a7d4e4079678fdedf7485dcea83c2b91a1af9de577ebdddf06d0b27424
6
+ metadata.gz: b0eb58d97d4be73227f791379f623a188ab45256749cd4b11a969444a8598d8b30f70490c12dbad12ff0a46d42f2ff1820998304b7167cc11a9876e8aaef06cc
7
+ data.tar.gz: cb38270a791164a92a06522921234838fe4e944e54ebd53a54c00939d531861847de0d4b6cef8d3605a9b2692874e9ac5715ce4fa4df07550f26094e810be845
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kennel
4
+ class Filter
5
+ def initialize
6
+ project_filter
7
+ tracking_id_filter
8
+ end
9
+
10
+ def project_filter
11
+ projects = ENV["PROJECT"]&.split(",")&.sort&.uniq
12
+ tracking_projects = tracking_id_filter&.map { |id| id.split(":", 2).first }&.sort&.uniq
13
+ if projects && tracking_projects && projects != tracking_projects
14
+ raise "do not set PROJECT= when using TRACKING_ID="
15
+ end
16
+ (projects || tracking_projects)
17
+ end
18
+
19
+ def tracking_id_filter
20
+ (tracking_id = ENV["TRACKING_ID"]) && tracking_id.split(",").sort.uniq
21
+ end
22
+
23
+ def self.filter_resources!(resources, by, against, name, env)
24
+ return unless against
25
+
26
+ against = against.uniq
27
+ before = resources.dup
28
+ resources.select! { |p| against.include?(p.send(by)) }
29
+ keeping = resources.uniq(&by).size
30
+ return if keeping == against.size
31
+
32
+ raise <<~MSG.rstrip
33
+ #{env}=#{against.join(",")} matched #{keeping} #{name}, try any of these:
34
+ #{before.map(&by).sort.uniq.join("\n")}
35
+ MSG
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kennel
4
+ class PartsSerializer
5
+ def initialize(filter:)
6
+ @filter = filter
7
+ end
8
+
9
+ def write(parts)
10
+ Progress.progress "Storing" do
11
+ if filter.tracking_id_filter
12
+ write_changed(parts)
13
+ else
14
+ old = old_paths
15
+ used = write_changed(parts)
16
+ (old - used).uniq.each { |p| FileUtils.rm_rf(p) }
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :filter
24
+
25
+ def write_changed(parts)
26
+ used = []
27
+
28
+ Utils.parallel(parts, max: 2) do |part|
29
+ path = "generated/#{part.tracking_id.tr("/", ":").sub(":", "/")}.json"
30
+
31
+ used << File.dirname(path) # only 1 level of sub folders, so this is enough
32
+ used << path
33
+
34
+ payload = part.as_json.merge(api_resource: part.class.api_resource)
35
+ write_file_if_necessary(path, JSON.pretty_generate(payload) << "\n")
36
+ end
37
+
38
+ used
39
+ end
40
+
41
+ def directories_to_clean_up
42
+ if filter.project_filter
43
+ filter.project_filter.map { |project| "generated/#{project}" }
44
+ else
45
+ ["generated"]
46
+ end
47
+ end
48
+
49
+ def old_paths
50
+ Dir["{#{directories_to_clean_up.join(",")}}/**/*"]
51
+ end
52
+
53
+ def write_file_if_necessary(path, content)
54
+ # 99% case
55
+ begin
56
+ return if File.read(path) == content
57
+ rescue Errno::ENOENT
58
+ FileUtils.mkdir_p(File.dirname(path))
59
+ end
60
+
61
+ # slow 1% case
62
+ File.write(path, content)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ module Kennel
3
+ class ProjectsProvider
4
+ def projects
5
+ load_all
6
+ Models::Project.recursive_subclasses.map(&:new)
7
+ end
8
+
9
+ private
10
+
11
+ def load_all
12
+ # load_all's purpose is to "require" all the .rb files under './projects',
13
+ # also with reference to ./teams and ./parts. What happens if you call it
14
+ # more than once?
15
+ #
16
+ # For a reason yet to be investigated, Zeitwerk rejects second and subsequent calls.
17
+ # But even if we skip over the Zeitwerk part, the nature of 'require' is
18
+ # somewhat one-way: we're not providing any mechanism to *un*load things.
19
+ # As long as the contents of `./projects`, `./teams` and `./parts` doesn't
20
+ # change between calls, then simply by no-op'ing subsequent calls to `load_all`
21
+ # we can have `load_all` appear to be idempotent.
22
+ loader = Zeitwerk::Loader.new
23
+ Dir.exist?("teams") && loader.push_dir("teams", namespace: Teams)
24
+ Dir.exist?("parts") && loader.push_dir("parts")
25
+ loader.setup
26
+ loader.eager_load # TODO: this should not be needed but we see hanging CI processes when it's not added
27
+
28
+ # TODO: also auto-load projects and update expected path too
29
+ ["projects"].each do |folder|
30
+ Dir["#{folder}/**/*.rb"].sort.each { |f| require "./#{f}" }
31
+ end
32
+ rescue NameError => e
33
+ message = e.message
34
+ raise unless klass = message[/uninitialized constant (.*)/, 1]
35
+
36
+ # inverse of zeitwerk lib/zeitwerk/inflector.rb
37
+ path = klass.gsub("::", "/").gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase + ".rb"
38
+ expected_path = (path.start_with?("teams/") ? path : "parts/#{path}")
39
+
40
+ # TODO: prefer to raise a new exception with the old backtrace attacked
41
+ e.define_singleton_method(:message) do
42
+ "\n" + <<~MSG.gsub(/^/, " ")
43
+ #{message}
44
+ Unable to load #{klass} from #{expected_path}
45
+ - Option 1: rename the constant or the file it lives in, to make them match
46
+ - Option 2: Use `require` or `require_relative` to load the constant
47
+ MSG
48
+ end
49
+
50
+ raise
51
+ end
52
+ end
53
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.118.2"
3
+ VERSION = "1.119.0"
4
4
  end
data/lib/kennel.rb CHANGED
@@ -8,6 +8,9 @@ require "kennel/version"
8
8
  require "kennel/compatibility"
9
9
  require "kennel/utils"
10
10
  require "kennel/progress"
11
+ require "kennel/filter"
12
+ require "kennel/parts_serializer"
13
+ require "kennel/projects_provider"
11
14
  require "kennel/syncer"
12
15
  require "kennel/id_map"
13
16
  require "kennel/api"
@@ -54,9 +57,9 @@ module Kennel
54
57
  attr_accessor :out, :err, :strict_imports
55
58
 
56
59
  def generate
57
- out = generated
58
- store out if ENV["STORE"] != "false" # quicker when debugging
59
- out
60
+ parts = generated
61
+ parts_serializer.write(parts) if ENV["STORE"] != "false" # quicker when debugging
62
+ parts
60
63
  end
61
64
 
62
65
  def plan
@@ -74,63 +77,34 @@ module Kennel
74
77
 
75
78
  private
76
79
 
77
- def store(parts)
78
- Progress.progress "Storing" do
79
- old = Dir[[
80
- "generated",
81
- if project_filter || tracking_id_filter
82
- [
83
- "{" + (project_filter || ["*"]).join(",") + "}",
84
- "{" + (tracking_id_filter || ["*"]).join(",") + "}.json"
85
- ]
86
- else
87
- "**"
88
- end
89
- ].join("/")]
90
- used = []
91
-
92
- Utils.parallel(parts, max: 2) do |part|
93
- path = "generated/#{part.tracking_id.tr("/", ":").sub(":", "/")}.json"
94
- used.concat [File.dirname(path), path] # only 1 level of sub folders, so this is safe
95
- payload = part.as_json.merge(api_resource: part.class.api_resource)
96
- write_file_if_necessary(path, JSON.pretty_generate(payload) << "\n")
97
- end
98
-
99
- # deleting all is slow, so only delete the extras
100
- (old - used).each { |p| FileUtils.rm_rf(p) }
101
- end
102
- end
103
-
104
- def write_file_if_necessary(path, content)
105
- # 99% case
106
- begin
107
- return if File.read(path) == content
108
- rescue Errno::ENOENT
109
- FileUtils.mkdir_p(File.dirname(path))
110
- end
111
-
112
- # slow 1% case
113
- File.write(path, content)
80
+ def filter
81
+ @filter ||= Filter.new
114
82
  end
115
83
 
116
84
  def syncer
117
- @syncer ||= Syncer.new(api, generated, project_filter: project_filter, tracking_id_filter: tracking_id_filter)
85
+ @syncer ||= Syncer.new(api, generated, project_filter: filter.project_filter, tracking_id_filter: filter.tracking_id_filter)
118
86
  end
119
87
 
120
88
  def api
121
89
  @api ||= Api.new(ENV.fetch("DATADOG_APP_KEY"), ENV.fetch("DATADOG_API_KEY"))
122
90
  end
123
91
 
92
+ def projects_provider
93
+ @projects_provider ||= ProjectsProvider.new
94
+ end
95
+
96
+ def parts_serializer
97
+ @parts_serializer ||= PartsSerializer.new(filter: filter)
98
+ end
99
+
124
100
  def generated
125
101
  @generated ||= begin
126
102
  Progress.progress "Generating" do
127
- load_all
128
-
129
- projects = Models::Project.recursive_subclasses.map(&:new)
130
- filter_resources!(projects, :kennel_id, project_filter, "projects", "PROJECT")
103
+ projects = projects_provider.projects
104
+ Kennel::Filter.filter_resources!(projects, :kennel_id, filter.project_filter, "projects", "PROJECT")
131
105
 
132
106
  parts = Utils.parallel(projects, &:validated_parts).flatten(1)
133
- filter_resources!(parts, :tracking_id, tracking_id_filter, "resources", "TRACKING_ID")
107
+ Kennel::Filter.filter_resources!(parts, :tracking_id, filter.tracking_id_filter, "resources", "TRACKING_ID")
134
108
 
135
109
  parts.group_by(&:tracking_id).each do |tracking_id, same|
136
110
  next if same.size == 1
@@ -147,74 +121,5 @@ module Kennel
147
121
  end
148
122
  end
149
123
  end
150
-
151
- def project_filter
152
- projects = ENV["PROJECT"]&.split(",")
153
- tracking_projects = tracking_id_filter&.map { |id| id.split(":", 2).first }
154
- if projects && tracking_projects && projects != tracking_projects
155
- raise "do not set PROJECT= when using TRACKING_ID="
156
- end
157
- projects || tracking_projects
158
- end
159
-
160
- def tracking_id_filter
161
- (tracking_id = ENV["TRACKING_ID"]) && tracking_id.split(",")
162
- end
163
-
164
- def filter_resources!(resources, by, against, name, env)
165
- return unless against
166
-
167
- before = resources.dup
168
- resources.select! { |p| against.include?(p.send(by)) }
169
- keeping = resources.uniq(&by).size
170
- return if keeping == against.size
171
-
172
- raise <<~MSG.rstrip
173
- #{env}=#{against.join(",")} matched #{keeping} #{name}, try any of these:
174
- #{before.map(&by).sort.uniq.join("\n")}
175
- MSG
176
- end
177
-
178
- def load_all
179
- # load_all's purpose is to "require" all the .rb files under './projects',
180
- # also with reference to ./teams and ./parts. What happens if you call it
181
- # more than once?
182
- #
183
- # For a reason yet to be investigated, Zeitwerk rejects second and subsequent calls.
184
- # But even if we skip over the Zeitwerk part, the nature of 'require' is
185
- # somewhat one-way: we're not providing any mechanism to *un*load things.
186
- # As long as the contents of `./projects`, `./teams` and `./parts` doesn't
187
- # change between calls, then simply by no-op'ing subsequent calls to `load_all`
188
- # we can have `load_all` appear to be idempotent.
189
- loader = Zeitwerk::Loader.new
190
- Dir.exist?("teams") && loader.push_dir("teams", namespace: Teams)
191
- Dir.exist?("parts") && loader.push_dir("parts")
192
- loader.setup
193
- loader.eager_load # TODO: this should not be needed but we see hanging CI processes when it's not added
194
-
195
- # TODO: also auto-load projects and update expected path too
196
- ["projects"].each do |folder|
197
- Dir["#{folder}/**/*.rb"].sort.each { |f| require "./#{f}" }
198
- end
199
- rescue NameError => e
200
- message = e.message
201
- raise unless klass = message[/uninitialized constant (.*)/, 1]
202
-
203
- # inverse of zeitwerk lib/zeitwerk/inflector.rb
204
- path = klass.gsub("::", "/").gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase + ".rb"
205
- expected_path = (path.start_with?("teams/") ? path : "parts/#{path}")
206
-
207
- # TODO: prefer to raise a new exception with the old backtrace attacked
208
- e.define_singleton_method(:message) do
209
- "\n" + <<~MSG.gsub(/^/, " ")
210
- #{message}
211
- Unable to load #{klass} from #{expected_path}
212
- - Option 1: rename the constant or the file it lives in, to make them match
213
- - Option 2: Use `require` or `require_relative` to load the constant
214
- MSG
215
- end
216
-
217
- raise
218
- end
219
124
  end
220
125
  end
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.118.2
4
+ version: 1.119.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: 2022-09-23 00:00:00.000000000 Z
11
+ date: 2022-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -91,6 +91,7 @@ files:
91
91
  - lib/kennel/api.rb
92
92
  - lib/kennel/compatibility.rb
93
93
  - lib/kennel/file_cache.rb
94
+ - lib/kennel/filter.rb
94
95
  - lib/kennel/github_reporter.rb
95
96
  - lib/kennel/id_map.rb
96
97
  - lib/kennel/importer.rb
@@ -103,7 +104,9 @@ files:
103
104
  - lib/kennel/models/synthetic_test.rb
104
105
  - lib/kennel/models/team.rb
105
106
  - lib/kennel/optional_validations.rb
107
+ - lib/kennel/parts_serializer.rb
106
108
  - lib/kennel/progress.rb
109
+ - lib/kennel/projects_provider.rb
107
110
  - lib/kennel/settings_as_methods.rb
108
111
  - lib/kennel/subclass_tracking.rb
109
112
  - lib/kennel/syncer.rb