kettle-gha-pins 0.3.8 → 0.3.10
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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +31 -1
- data/README.md +20 -1
- data/lib/kettle/gha/pins/cli.rb +167 -7
- data/lib/kettle/gha/pins/github_client.rb +27 -27
- data/lib/kettle/gha/pins/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +4 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e39d2df910b12d4b40d0978f63481a73d87a5efcdf6ea58c4966b397d3319470
|
|
4
|
+
data.tar.gz: 584ac1b59952635a279806c03aba51460a757e9edd2dbcafc5b7f158b8a302dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 43c208e30cd0c6501479f4cf52a48a3e4df4b9192ada22b05017681a4761ffe01ca4abdb0398cf8c523828a32814ef9fcd51cf57f2be46f7ff55b81c89e05237
|
|
7
|
+
data.tar.gz: a47e19be556a73215a786b4e9fe23b50ac1890e5087ed0b5eea1a14e8e7f9dd1ce8da62d8e95a329949a6e339aacda9c1b8e12cabd0debe167e301ed12ca96f9
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,32 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [0.3.10] - 2026-08-09
|
|
34
|
+
|
|
35
|
+
- TAG: [v0.3.10][0.3.10t]
|
|
36
|
+
- COVERAGE: 98.08% -- 1022/1042 lines in 8 files
|
|
37
|
+
- BRANCH COVERAGE: 88.03% -- 412/468 branches in 8 files
|
|
38
|
+
- 26.17% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- Add JSON action inventory and cache review modes, strict offline pin resolution, and configurable action metadata TTL handling.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- Keep JSON action inventory and review parsing compatible with Ruby 2.5.
|
|
47
|
+
|
|
48
|
+
## [0.3.9] - 2026-08-05
|
|
49
|
+
|
|
50
|
+
- TAG: [v0.3.9][0.3.9t]
|
|
51
|
+
- COVERAGE: 98.36% -- 961/977 lines in 8 files
|
|
52
|
+
- BRANCH COVERAGE: 89.95% -- 394/438 branches in 8 files
|
|
53
|
+
- 25.71% documented
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- Emit NDJSON progress events for family orchestration without replaying the full human report.
|
|
58
|
+
|
|
33
59
|
## [0.3.8] - 2026-08-05
|
|
34
60
|
|
|
35
61
|
- TAG: [v0.3.8][0.3.8t]
|
|
@@ -271,7 +297,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
271
297
|
- kettle-jem-template-20260720-005 - Generated README Support & Community rows
|
|
272
298
|
now include a RubyForum help badge.
|
|
273
299
|
|
|
274
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.
|
|
300
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.10...HEAD
|
|
301
|
+
[0.3.10]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.9...v0.3.10
|
|
302
|
+
[0.3.10t]: https://github.com/kettle-dev/kettle-gha-pins/releases/tag/v0.3.10
|
|
303
|
+
[0.3.9]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.8...v0.3.9
|
|
304
|
+
[0.3.9t]: https://github.com/kettle-dev/kettle-gha-pins/releases/tag/v0.3.9
|
|
275
305
|
[0.3.8]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.7...v0.3.8
|
|
276
306
|
[0.3.8t]: https://github.com/kettle-dev/kettle-gha-pins/releases/tag/v0.3.8
|
|
277
307
|
[0.3.7]: https://github.com/kettle-dev/kettle-gha-pins/compare/v0.3.6...v0.3.7
|
data/README.md
CHANGED
|
@@ -155,6 +155,17 @@ cooldown window. The same default can be supplied with
|
|
|
155
155
|
`KETTLE_GHA_PINS_COOLDOWN_DAYS`. The default is `0`, so checks remain strict
|
|
156
156
|
unless a project opts into a warning window.
|
|
157
157
|
|
|
158
|
+
The action metadata cache uses a one-day TTL by default. Set `--ttl DAYS` to
|
|
159
|
+
change it; `--ttl 0` forces a refresh. `--offline` trusts the cache without
|
|
160
|
+
checking TTL or contacting GitHub and fails if required repository metadata is
|
|
161
|
+
missing.
|
|
162
|
+
|
|
163
|
+
For family orchestration, `kettle-gha-pins --list --json` emits the action
|
|
164
|
+
repositories used by a project without network access. A separate
|
|
165
|
+
`kettle-gha-pins --review --input PATH` refreshes the repositories listed in
|
|
166
|
+
that JSON and updates the shared cache. Review refreshes metadata independently
|
|
167
|
+
of the selected major/minor/patch upgrade policy.
|
|
168
|
+
|
|
158
169
|
`Kettle::Gha::Pins::PersistentActionCache.default_path` intentionally preserves
|
|
159
170
|
the historical `kettle-gha-sha-pins` cache location so command-line tools can
|
|
160
171
|
share action metadata without coupling to each other.
|
|
@@ -188,6 +199,14 @@ plan[:updates]
|
|
|
188
199
|
# => {sha: "...", version: "7.0.0", reason: "upgrade_to_allowed_release"}
|
|
189
200
|
```
|
|
190
201
|
|
|
202
|
+
The family-oriented cache workflow is:
|
|
203
|
+
|
|
204
|
+
```console
|
|
205
|
+
kettle-gha-pins --list --json
|
|
206
|
+
kettle-gha-pins --review --input pins.json
|
|
207
|
+
kettle-gha-pins --offline --write --upgrade major
|
|
208
|
+
```
|
|
209
|
+
|
|
191
210
|
When two version-equivalent tags point at the same SHA, the rubric keeps the
|
|
192
211
|
more explicit release spelling. For example, `v7` and `v7.0.0` pointing at the
|
|
193
212
|
same commit canonicalize to `v7.0.0`; if they point at different SHAs, both
|
|
@@ -563,7 +582,7 @@ Thanks for RTFM. ☺️
|
|
|
563
582
|
[📌gitmoji]: https://gitmoji.dev
|
|
564
583
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
565
584
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
566
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-
|
|
585
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-1.042-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
567
586
|
[🔐security]: https://github.com/kettle-dev/kettle-gha-pins/blob/main/SECURITY.md
|
|
568
587
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
569
588
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/lib/kettle/gha/pins/cli.rb
CHANGED
|
@@ -59,6 +59,7 @@ module Kettle
|
|
|
59
59
|
dry_run: true,
|
|
60
60
|
token: ENV["GITHUB_TOKEN"] || ENV["GH_TOKEN"],
|
|
61
61
|
json: false,
|
|
62
|
+
events: false,
|
|
62
63
|
validate: true,
|
|
63
64
|
write: false,
|
|
64
65
|
check: false,
|
|
@@ -66,9 +67,13 @@ module Kettle
|
|
|
66
67
|
user_agent: "kettle-gha-pins",
|
|
67
68
|
upgrade: DEFAULT_UPGRADE_LEVEL,
|
|
68
69
|
cache_path: ENV["KETTLE_GHA_SHA_PINS_CACHE"] || PersistentActionCache.default_path,
|
|
70
|
+
ttl_days: DEFAULT_CACHE_TTL_SECONDS / 86_400.0,
|
|
69
71
|
cooldown_days: cooldown_days,
|
|
70
72
|
clock: clock,
|
|
71
73
|
refresh_cache: false,
|
|
74
|
+
offline: false,
|
|
75
|
+
mode: :project,
|
|
76
|
+
input: nil,
|
|
72
77
|
reject_patterns: Set.new,
|
|
73
78
|
progress: nil
|
|
74
79
|
}
|
|
@@ -77,20 +82,43 @@ module Kettle
|
|
|
77
82
|
def run!
|
|
78
83
|
parse!
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
return list_workflows! if @options[:mode] == :list
|
|
86
|
+
|
|
87
|
+
client, persistent_cache = build_client
|
|
88
|
+
return review_cache!(client: client, persistent_cache: persistent_cache) if @options[:mode] == :review
|
|
89
|
+
|
|
90
|
+
run_project!(client: client)
|
|
91
|
+
rescue Error, GitHubClient::CacheMissError, GitHubClient::RefreshError => error
|
|
92
|
+
@err.puts("kettle-gha-pins: #{error.message}")
|
|
93
|
+
2
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def build_client
|
|
99
|
+
@options[:token] ||= gh_auth_token if @options[:api_base] == API_BASE && !@options[:offline]
|
|
81
100
|
persistent_cache = if @options[:cache_path].to_s.empty?
|
|
82
101
|
nil
|
|
83
102
|
else
|
|
84
|
-
PersistentActionCache.new(
|
|
103
|
+
PersistentActionCache.new(
|
|
104
|
+
path: @options[:cache_path],
|
|
105
|
+
ttl_seconds: (@options[:ttl_days] * 86_400).to_i,
|
|
106
|
+
clock: @options[:clock]
|
|
107
|
+
)
|
|
85
108
|
end
|
|
86
109
|
client = GitHubClient.new(
|
|
87
110
|
token: @options[:token],
|
|
88
111
|
api_base: @options[:api_base],
|
|
89
112
|
user_agent: @options[:user_agent],
|
|
90
113
|
persistent_cache: persistent_cache,
|
|
91
|
-
refresh_cache: @options[:refresh_cache]
|
|
114
|
+
refresh_cache: @options[:refresh_cache],
|
|
115
|
+
offline: @options[:offline],
|
|
116
|
+
fail_on_refresh_error: @options[:mode] == :review
|
|
92
117
|
)
|
|
118
|
+
[client, persistent_cache]
|
|
119
|
+
end
|
|
93
120
|
|
|
121
|
+
def run_project!(client:)
|
|
94
122
|
state = {
|
|
95
123
|
files_scanned: 0,
|
|
96
124
|
files_with_changes: 0,
|
|
@@ -104,8 +132,10 @@ module Kettle
|
|
|
104
132
|
}
|
|
105
133
|
|
|
106
134
|
progress_message("Discovering workflow files under #{display_path(@options[:root])}...")
|
|
135
|
+
emit_event("start", root: @options[:root])
|
|
107
136
|
workflow_files = discover_workflow_files(@options[:root], @options[:reject_patterns])
|
|
108
137
|
progress_message("Discovered #{workflow_files.length} workflow file(s).")
|
|
138
|
+
emit_event("workflows", total: workflow_files.length)
|
|
109
139
|
|
|
110
140
|
workflows = load_workflows(workflow_files, state)
|
|
111
141
|
action_count = workflows.sum { |workflow| workflow[:uses_nodes].count { |node| classify_action_ref(node[:value].to_s) } }
|
|
@@ -119,6 +149,7 @@ module Kettle
|
|
|
119
149
|
)
|
|
120
150
|
action_plan_cache = {}
|
|
121
151
|
|
|
152
|
+
completed_actions = 0
|
|
122
153
|
workflows.each do |workflow|
|
|
123
154
|
path = workflow.fetch(:path)
|
|
124
155
|
text = workflow.fetch(:text)
|
|
@@ -134,6 +165,7 @@ module Kettle
|
|
|
134
165
|
action = parsed_ref[:action]
|
|
135
166
|
repo_ref = "#{action[:owner]}/#{action[:repo]}"
|
|
136
167
|
old_ref = action[:ref]
|
|
168
|
+
cached_before = action_plan_cache.key?(repo_ref)
|
|
137
169
|
upgrade_plan = resolve_action_plan(
|
|
138
170
|
cache: action_plan_cache,
|
|
139
171
|
client: client,
|
|
@@ -141,6 +173,16 @@ module Kettle
|
|
|
141
173
|
repo_ref: repo_ref,
|
|
142
174
|
old_ref: old_ref
|
|
143
175
|
)
|
|
176
|
+
completed_actions += 1
|
|
177
|
+
emit_event(
|
|
178
|
+
"action",
|
|
179
|
+
action_ref: repo_ref,
|
|
180
|
+
path: path,
|
|
181
|
+
line: node[:line] + 1,
|
|
182
|
+
completed: completed_actions,
|
|
183
|
+
total: action_count,
|
|
184
|
+
cache: (cached_before || (client.respond_to?(:last_versions_cache_hit) && client.last_versions_cache_hit)) ? "hit" : "miss"
|
|
185
|
+
)
|
|
144
186
|
|
|
145
187
|
updates = nil
|
|
146
188
|
if upgrade_plan[:updates]
|
|
@@ -261,6 +303,15 @@ module Kettle
|
|
|
261
303
|
end
|
|
262
304
|
action_progress.stop
|
|
263
305
|
progress_message("Action resolution checks: #{action_progress.cached_count} cached, #{action_progress.live_count} live.") if action_count.positive?
|
|
306
|
+
emit_event(
|
|
307
|
+
"summary",
|
|
308
|
+
files_scanned: state[:files_scanned],
|
|
309
|
+
changed_files: state[:changed_files].length,
|
|
310
|
+
updates: state[:updates],
|
|
311
|
+
failures: state[:failures],
|
|
312
|
+
cached: action_progress.cached_count,
|
|
313
|
+
live: action_progress.live_count
|
|
314
|
+
)
|
|
264
315
|
|
|
265
316
|
print_report(state)
|
|
266
317
|
return 2 unless state[:failures].zero?
|
|
@@ -269,7 +320,79 @@ module Kettle
|
|
|
269
320
|
0
|
|
270
321
|
end
|
|
271
322
|
|
|
272
|
-
|
|
323
|
+
def list_workflows!
|
|
324
|
+
state = {files_scanned: 0, failures: 0, errors: []}
|
|
325
|
+
workflow_files = discover_workflow_files(@options[:root], @options[:reject_patterns])
|
|
326
|
+
workflows = load_workflows(workflow_files, state)
|
|
327
|
+
pins = workflows.flat_map do |workflow|
|
|
328
|
+
workflow.fetch(:uses_nodes).each_with_object([]) do |node, entries|
|
|
329
|
+
parsed = classify_action_ref(node[:value].to_s)
|
|
330
|
+
next unless parsed
|
|
331
|
+
|
|
332
|
+
action = parsed.fetch(:action)
|
|
333
|
+
entries << {
|
|
334
|
+
"repository" => "#{action[:owner]}/#{action[:repo]}",
|
|
335
|
+
"ref" => action[:ref],
|
|
336
|
+
"value" => parsed[:value],
|
|
337
|
+
"path" => workflow[:path],
|
|
338
|
+
"line" => node[:line].to_i + 1
|
|
339
|
+
}
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
repositories = pins.map { |pin| pin.fetch("repository") }.uniq.sort
|
|
343
|
+
puts JSON.pretty_generate(
|
|
344
|
+
"schema_version" => 1,
|
|
345
|
+
"root" => @options[:root],
|
|
346
|
+
"repositories" => repositories,
|
|
347
|
+
"pins" => pins
|
|
348
|
+
)
|
|
349
|
+
state[:failures].zero? ? 0 : 2
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def review_cache!(client:, persistent_cache:)
|
|
353
|
+
raise Error, "--review requires a writable cache; do not use --cache-path \"\"" unless persistent_cache
|
|
354
|
+
|
|
355
|
+
repositories = review_repositories
|
|
356
|
+
refreshed = repositories.map do |repository|
|
|
357
|
+
versions = client.versions_for_repo(repository)
|
|
358
|
+
{
|
|
359
|
+
"repository" => repository,
|
|
360
|
+
"versions" => versions.length,
|
|
361
|
+
"cache" => client.last_versions_cache_hit ? "hit" : "refreshed"
|
|
362
|
+
}
|
|
363
|
+
end
|
|
364
|
+
puts JSON.pretty_generate(
|
|
365
|
+
"schema_version" => 1,
|
|
366
|
+
"mode" => "review",
|
|
367
|
+
"ttl_days" => @options[:ttl_days],
|
|
368
|
+
"repositories" => refreshed
|
|
369
|
+
)
|
|
370
|
+
0
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def review_repositories
|
|
374
|
+
source = @options[:input].to_s
|
|
375
|
+
source = if source.empty? || source == "-"
|
|
376
|
+
$stdin.read
|
|
377
|
+
else
|
|
378
|
+
File.read(source)
|
|
379
|
+
end
|
|
380
|
+
payload = JSON.parse(source)
|
|
381
|
+
entries = if payload.is_a?(Array)
|
|
382
|
+
payload
|
|
383
|
+
elsif payload.is_a?(Hash)
|
|
384
|
+
payload["repositories"] || payload["actions"] || payload["pins"] || []
|
|
385
|
+
else
|
|
386
|
+
[]
|
|
387
|
+
end
|
|
388
|
+
entries.each_with_object([]) do |entry, repositories|
|
|
389
|
+
value = entry.is_a?(Hash) ? (entry["repository"] || entry["action"] || entry["value"]) : entry
|
|
390
|
+
repository = value.to_s.split("@", 2).first
|
|
391
|
+
repositories << repository if repository.match?(%r{\A[^/\s]+/[^/\s]+\z})
|
|
392
|
+
end.uniq.sort
|
|
393
|
+
rescue JSON::ParserError => error
|
|
394
|
+
raise Error, "invalid review JSON: #{error.message}"
|
|
395
|
+
end
|
|
273
396
|
|
|
274
397
|
def parse!
|
|
275
398
|
parser = OptionParser.new do |opt|
|
|
@@ -304,12 +427,36 @@ module Kettle
|
|
|
304
427
|
opt.on("--refresh-cache", "Bypass cached action release data and refresh discovered actions") do
|
|
305
428
|
@options[:refresh_cache] = true
|
|
306
429
|
end
|
|
430
|
+
opt.on("--ttl DAYS", Float, "Cache TTL in days (default: #{@options[:ttl_days]})") do |days|
|
|
431
|
+
Kernel.abort("Invalid --ttl value #{days.inspect}; use a non-negative number") if days.negative?
|
|
432
|
+
|
|
433
|
+
@options[:ttl_days] = days
|
|
434
|
+
end
|
|
435
|
+
opt.on("--offline", "Use only the persistent cache; fail on a cache miss") do
|
|
436
|
+
@options[:offline] = true
|
|
437
|
+
end
|
|
438
|
+
opt.on("--list", "List project action pins as JSON without network access") do
|
|
439
|
+
raise OptionParser::InvalidOption, "--list cannot be combined with --review" if @options[:mode] == :review
|
|
440
|
+
|
|
441
|
+
@options[:mode] = :list
|
|
442
|
+
end
|
|
443
|
+
opt.on("--review", "Refresh action metadata from JSON input and update the cache") do
|
|
444
|
+
raise OptionParser::InvalidOption, "--review cannot be combined with --list" if @options[:mode] == :list
|
|
445
|
+
|
|
446
|
+
@options[:mode] = :review
|
|
447
|
+
end
|
|
448
|
+
opt.on("--input PATH", "Review JSON input path, or - for stdin") do |path|
|
|
449
|
+
@options[:input] = path
|
|
450
|
+
end
|
|
307
451
|
opt.on("--cache-path PATH", "Action release cache path (default: #{@options[:cache_path]})") do |path|
|
|
308
452
|
@options[:cache_path] = path
|
|
309
453
|
end
|
|
310
454
|
opt.on("--json", "Emit JSON report") do
|
|
311
455
|
@options[:json] = true
|
|
312
456
|
end
|
|
457
|
+
opt.on("--events", "Emit line-delimited JSON events") do
|
|
458
|
+
@options[:events] = true
|
|
459
|
+
end
|
|
313
460
|
opt.on("--[no-]progress", "Show progress feedback on STDERR (default: on unless --json)") do |bool|
|
|
314
461
|
@options[:progress] = bool
|
|
315
462
|
end
|
|
@@ -329,6 +476,9 @@ module Kettle
|
|
|
329
476
|
end
|
|
330
477
|
end
|
|
331
478
|
parser.parse!(@argv)
|
|
479
|
+
if @options[:mode] == :review && @options[:offline]
|
|
480
|
+
raise OptionParser::InvalidOption, "--review cannot be combined with --offline"
|
|
481
|
+
end
|
|
332
482
|
end
|
|
333
483
|
|
|
334
484
|
def display_path(path)
|
|
@@ -384,7 +534,13 @@ module Kettle
|
|
|
384
534
|
def progress_enabled?
|
|
385
535
|
return @options[:progress] unless @options[:progress].nil?
|
|
386
536
|
|
|
387
|
-
!@options[:json]
|
|
537
|
+
!@options[:json] && !@options[:events]
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def emit_event(action, payload = {})
|
|
541
|
+
return unless @options[:events]
|
|
542
|
+
|
|
543
|
+
puts JSON.generate({"event_version" => 1, "type" => "gha_sha_pins_#{action}", "action" => action}.merge(payload))
|
|
388
544
|
end
|
|
389
545
|
|
|
390
546
|
def progress_message(message)
|
|
@@ -858,8 +1014,12 @@ module Kettle
|
|
|
858
1014
|
lines << "Recommended fix: kettle-gha-pins --write --upgrade #{@options[:upgrade]}"
|
|
859
1015
|
end
|
|
860
1016
|
|
|
861
|
-
|
|
862
|
-
|
|
1017
|
+
output = lines.join("\n")
|
|
1018
|
+
if @options[:events]
|
|
1019
|
+
@err.puts output
|
|
1020
|
+
else
|
|
1021
|
+
puts output
|
|
1022
|
+
end
|
|
863
1023
|
end
|
|
864
1024
|
end
|
|
865
1025
|
end
|
|
@@ -10,12 +10,17 @@ module Kettle
|
|
|
10
10
|
module Pins
|
|
11
11
|
# Lightweight GitHub API client for commit and release SHA resolution.
|
|
12
12
|
class GitHubClient
|
|
13
|
-
|
|
13
|
+
class CacheMissError < Error; end
|
|
14
|
+
class RefreshError < Error; end
|
|
15
|
+
|
|
16
|
+
def initialize(token:, api_base:, user_agent:, persistent_cache: nil, refresh_cache: false, offline: false, fail_on_refresh_error: false, open_timeout: DEFAULT_HTTP_OPEN_TIMEOUT_SECONDS, read_timeout: DEFAULT_HTTP_READ_TIMEOUT_SECONDS, refresh_timeout: DEFAULT_HTTP_REFRESH_TIMEOUT_SECONDS)
|
|
14
17
|
@token = token
|
|
15
18
|
@api_base = api_base
|
|
16
19
|
@user_agent = user_agent
|
|
17
20
|
@persistent_cache = persistent_cache
|
|
18
21
|
@refresh_cache = refresh_cache
|
|
22
|
+
@offline = offline
|
|
23
|
+
@fail_on_refresh_error = fail_on_refresh_error
|
|
19
24
|
@open_timeout = open_timeout
|
|
20
25
|
@read_timeout = read_timeout
|
|
21
26
|
@refresh_timeout = refresh_timeout
|
|
@@ -34,15 +39,22 @@ module Kettle
|
|
|
34
39
|
return @release_cache[repo_ref]
|
|
35
40
|
end
|
|
36
41
|
|
|
42
|
+
if @offline
|
|
43
|
+
cached = @persistent_cache&.versions_for_repo(repo_ref, fresh: false)
|
|
44
|
+
raise CacheMissError, "offline cache miss for #{repo_ref}" if cached.nil?
|
|
45
|
+
|
|
46
|
+
@last_versions_cache_hit = true
|
|
47
|
+
@release_cache[repo_ref] = cached
|
|
48
|
+
return cached
|
|
49
|
+
end
|
|
50
|
+
|
|
37
51
|
stale = nil
|
|
38
52
|
unless @refresh_cache
|
|
39
53
|
cached = @persistent_cache&.versions_for_repo(repo_ref, fresh: true)
|
|
40
54
|
if cached
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return cached
|
|
45
|
-
end
|
|
55
|
+
@last_versions_cache_hit = true
|
|
56
|
+
@release_cache[repo_ref] = cached
|
|
57
|
+
return cached
|
|
46
58
|
end
|
|
47
59
|
stale = @persistent_cache&.versions_for_repo(repo_ref, fresh: false)
|
|
48
60
|
end
|
|
@@ -50,10 +62,10 @@ module Kettle
|
|
|
50
62
|
releases = nil
|
|
51
63
|
Timeout.timeout(@refresh_timeout) do
|
|
52
64
|
data = request_json("/repos/#{repo_ref}/releases?per_page=100")
|
|
53
|
-
return
|
|
65
|
+
return refresh_failed!(repo_ref, stale, "invalid releases response") unless data.is_a?(Array)
|
|
54
66
|
|
|
55
67
|
tag_shas = tag_ref_shas(repo_ref)
|
|
56
|
-
return
|
|
68
|
+
return refresh_failed!(repo_ref, stale, "invalid tag response") unless tag_shas
|
|
57
69
|
|
|
58
70
|
releases = build_release_versions(data, tag_shas)
|
|
59
71
|
end
|
|
@@ -61,7 +73,7 @@ module Kettle
|
|
|
61
73
|
@release_cache[repo_ref] = releases
|
|
62
74
|
releases
|
|
63
75
|
rescue Timeout::Error
|
|
64
|
-
|
|
76
|
+
refresh_failed!(repo_ref, stale, "GitHub refresh timed out")
|
|
65
77
|
end
|
|
66
78
|
|
|
67
79
|
def commit_sha(repo_ref, ref, refresh: false)
|
|
@@ -71,13 +83,15 @@ module Kettle
|
|
|
71
83
|
return @commit_cache[cache_key] if @commit_cache.key?(cache_key) && !refresh
|
|
72
84
|
|
|
73
85
|
unless @refresh_cache || refresh
|
|
74
|
-
cached = @persistent_cache&.ref_sha(repo_ref, ref, fresh:
|
|
86
|
+
cached = @persistent_cache&.ref_sha(repo_ref, ref, fresh: !@offline)
|
|
75
87
|
if cached
|
|
76
88
|
@commit_cache[cache_key] = cached
|
|
77
89
|
return cached
|
|
78
90
|
end
|
|
79
91
|
end
|
|
80
92
|
|
|
93
|
+
raise CacheMissError, "offline cache miss for #{repo_ref}@#{ref}" if @offline
|
|
94
|
+
|
|
81
95
|
data = request_json("/repos/#{repo_ref}/commits/#{uri_encode(ref)}")
|
|
82
96
|
sha = if data.is_a?(Hash)
|
|
83
97
|
data.fetch("sha", "")[0, 40]
|
|
@@ -106,24 +120,10 @@ module Kettle
|
|
|
106
120
|
versions
|
|
107
121
|
end
|
|
108
122
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# only when that probe identifies a tag or target the cache lacks.
|
|
112
|
-
def cache_requires_refresh?(repo_ref, cached)
|
|
113
|
-
latest = request_json("/repos/#{repo_ref}/releases/latest")
|
|
114
|
-
return false unless latest.is_a?(Hash)
|
|
115
|
-
|
|
116
|
-
tag = latest["tag_name"].to_s
|
|
117
|
-
return false unless VersionRubric.parse(tag)
|
|
118
|
-
|
|
119
|
-
cached_entry = cached.find { |entry| entry[:tag].to_s == tag }
|
|
120
|
-
return true unless cached_entry
|
|
121
|
-
|
|
122
|
-
latest_sha = commit_sha(repo_ref, tag, refresh: true)
|
|
123
|
-
cached_sha = cached_entry[:sha].to_s
|
|
124
|
-
return !latest_sha.to_s.empty? if cached_sha.empty?
|
|
123
|
+
def refresh_failed!(repo_ref, stale, message)
|
|
124
|
+
return cached_versions(repo_ref, stale) unless @fail_on_refresh_error
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
raise RefreshError, "#{message} for #{repo_ref}"
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
def build_release_versions(data, tag_shas)
|
|
@@ -6,7 +6,7 @@ module Kettle
|
|
|
6
6
|
# Version namespace for this gem.
|
|
7
7
|
module Version
|
|
8
8
|
# Current gem version.
|
|
9
|
-
VERSION = "0.3.
|
|
9
|
+
VERSION = "0.3.10"
|
|
10
10
|
end
|
|
11
11
|
# Current gem version exposed at the traditional constant location.
|
|
12
12
|
VERSION = Version::VERSION # Traditional Constant Location
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kettle-gha-pins
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -301,10 +301,10 @@ licenses:
|
|
|
301
301
|
- MIT
|
|
302
302
|
metadata:
|
|
303
303
|
homepage_uri: https://github.com/kettle-dev/kettle-gha-pins
|
|
304
|
-
source_code_uri: https://github.com/kettle-dev/kettle-gha-pins/tree/v0.3.
|
|
305
|
-
changelog_uri: https://github.com/kettle-dev/kettle-gha-pins/blob/v0.3.
|
|
304
|
+
source_code_uri: https://github.com/kettle-dev/kettle-gha-pins/tree/v0.3.10
|
|
305
|
+
changelog_uri: https://github.com/kettle-dev/kettle-gha-pins/blob/v0.3.10/CHANGELOG.md
|
|
306
306
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-gha-pins/issues
|
|
307
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-gha-pins/0.3.
|
|
307
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-gha-pins/0.3.10
|
|
308
308
|
funding_uri: https://github.com/sponsors/pboling
|
|
309
309
|
wiki_uri: https://github.com/kettle-dev/kettle-gha-pins/wiki
|
|
310
310
|
news_uri: https://www.railsbling.com/tags/kettle-gha-pins
|
metadata.gz.sig
CHANGED
|
Binary file
|