kennel 1.118.2 → 1.119.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: 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