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 +4 -4
- data/lib/kennel/filter.rb +38 -0
- data/lib/kennel/parts_serializer.rb +65 -0
- data/lib/kennel/projects_provider.rb +53 -0
- data/lib/kennel/version.rb +1 -1
- data/lib/kennel.rb +20 -115
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 67b43081a187354fb146269d929da674d533263b44b3f2cee00b2baa7cc8bc57
|
|
4
|
+
data.tar.gz: c14b369716513a92791496a92c29c7648499d0a06f05e34d36c628f4a9bb9305
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/kennel/version.rb
CHANGED
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
78
|
-
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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
|