kettle-dev 3.0.0 → 3.0.2
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 +35 -1
- data/README.md +1 -1
- data/exe/kettle-pre-release +4 -0
- data/lib/kettle/dev/interactive_release_command.rb +56 -27
- data/lib/kettle/dev/lockfile_reset.rb +18 -3
- data/lib/kettle/dev/pre_release_cli.rb +6 -2
- data/lib/kettle/dev/release_cli.rb +36 -4
- data/lib/kettle/dev/release_secrets.rb +95 -5
- data/lib/kettle/dev/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +6 -6
- 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: ac5e19991ef314add0cbb6f8c764a0389df6003ea291e5ea08bb76bf32c2786a
|
|
4
|
+
data.tar.gz: 894c0f1521ec987771056394037f3aa2108634bea11830c47f630b43b65727e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2bbb2b78730584bfdddbf66664679393ec7c282ea8e24130b01019bc4e61c48da2d4be751d354679c0a76f6ac4ed23a28bfcf60efae081468ffa98cfbe66dfb5
|
|
7
|
+
data.tar.gz: cc6156801f9ef0b6cd6e39ee614b63729acff1862e83cd4390319a98a6978ef06e148d82c255beb38a0d68a09531e435a4d57c7f315a9e4f4cb1cd929b16edff
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,36 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [3.0.2] - 2026-08-10
|
|
34
|
+
|
|
35
|
+
- TAG: [v3.0.2][3.0.2t]
|
|
36
|
+
- COVERAGE: 91.56% -- 5629/6148 lines in 47 files
|
|
37
|
+
- BRANCH COVERAGE: 76.14% -- 2128/2795 branches in 47 files
|
|
38
|
+
- 51.00% documented
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- Stop polling 1Password during release monitoring and support family-owned release secret brokers.
|
|
43
|
+
|
|
44
|
+
- Allow pre-release GitHub Actions pin validation to use the explicitly requested persistent cache when live TLS access is unavailable.
|
|
45
|
+
|
|
46
|
+
- Detect locally installed unreleased workspace gems through an unbundled RubyGems probe during release lockfile normalization.
|
|
47
|
+
|
|
48
|
+
- Isolate standalone kettle-changelog coverage and tests from family release orchestration environment.
|
|
49
|
+
|
|
50
|
+
## [3.0.1] - 2026-08-10
|
|
51
|
+
|
|
52
|
+
- TAG: [v3.0.1][3.0.1t]
|
|
53
|
+
- COVERAGE: 91.87% -- 5602/6098 lines in 47 files
|
|
54
|
+
- BRANCH COVERAGE: 76.29% -- 2117/2775 branches in 47 files
|
|
55
|
+
- 50.64% documented
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- Run 1Password authorization keepalives through the interactive terminal so release prompts remain visible.
|
|
60
|
+
|
|
61
|
+
- Re-normalize release lockfiles after release tasks before commit and CI validation.
|
|
62
|
+
|
|
33
63
|
## [3.0.0] - 2026-08-09
|
|
34
64
|
|
|
35
65
|
- TAG: [v3.0.0][3.0.0t]
|
|
@@ -3401,7 +3431,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
3401
3431
|
- Selecting will run the selected workflow via `act`
|
|
3402
3432
|
- This may move to its own gem in the future.
|
|
3403
3433
|
|
|
3404
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.
|
|
3434
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.2...HEAD
|
|
3435
|
+
[3.0.2]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.1...v3.0.2
|
|
3436
|
+
[3.0.2t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.0.2
|
|
3437
|
+
[3.0.1]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.0...v3.0.1
|
|
3438
|
+
[3.0.1t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.0.1
|
|
3405
3439
|
[3.0.0]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.28...v3.0.0
|
|
3406
3440
|
[3.0.0t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.0.0
|
|
3407
3441
|
[2.5.28]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.27...v2.5.28
|
data/README.md
CHANGED
|
@@ -1007,7 +1007,7 @@ Thanks for RTFM. ☺️
|
|
|
1007
1007
|
[📌gitmoji]: https://gitmoji.dev
|
|
1008
1008
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
1009
1009
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1010
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-6.
|
|
1010
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-6.148-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1011
1011
|
[🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
|
|
1012
1012
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1013
1013
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/exe/kettle-pre-release
CHANGED
|
@@ -59,6 +59,10 @@ parser = OptionParser.new do |opts|
|
|
|
59
59
|
puts " KETTLE_IMAGE_URL_CACHE=/path/to/cache.json Use a custom cache path"
|
|
60
60
|
puts " KETTLE_IMAGE_URL_CACHE=false Disable the image URL cache"
|
|
61
61
|
puts " KETTLE_IMAGE_URL_CACHE_REFRESH=true Ignore cached successes and refresh"
|
|
62
|
+
puts
|
|
63
|
+
puts "GitHub Actions pin cache:"
|
|
64
|
+
puts " Live cache validation is the default and refreshes stale entries from GitHub."
|
|
65
|
+
puts " KETTLE_PRE_RELEASE_GHA_SHA_PINS_OFFLINE=true Trust the persistent pin cache without network access"
|
|
62
66
|
exit(0)
|
|
63
67
|
end
|
|
64
68
|
end
|
|
@@ -1,39 +1,54 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "open3"
|
|
4
|
+
require "shellwords"
|
|
4
5
|
|
|
5
6
|
module Kettle
|
|
6
7
|
module Dev
|
|
7
8
|
class InteractiveReleaseCommand
|
|
8
|
-
def initialize(secrets_provider:, input: $stdin, output: $stdout, error: $stderr, secret_event_handler: nil)
|
|
9
|
+
def initialize(secrets_provider:, input: $stdin, output: $stdout, error: $stderr, secret_event_handler: nil, forward_stdin: false)
|
|
9
10
|
@secrets_provider = secrets_provider
|
|
10
11
|
@input = input
|
|
11
12
|
@output = output
|
|
12
13
|
@error = error
|
|
13
14
|
@secret_event_handler = secret_event_handler
|
|
15
|
+
@forward_stdin = forward_stdin
|
|
14
16
|
@gem_signing_passphrase = nil
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def call(env, cmd)
|
|
18
|
-
|
|
20
|
+
call_argv(env, Shellwords.split(cmd.to_s))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call_argv(env, argv)
|
|
24
|
+
return call_with_pty(env, argv) if pty_available?
|
|
19
25
|
|
|
20
|
-
call_with_open3(env,
|
|
26
|
+
call_with_open3(env, argv)
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
private
|
|
24
30
|
|
|
25
31
|
attr_reader :secrets_provider, :secret_event_handler
|
|
26
32
|
|
|
27
|
-
def call_with_pty(env,
|
|
33
|
+
def call_with_pty(env, argv)
|
|
28
34
|
stdout_str = +""
|
|
35
|
+
prompt_buffer = +""
|
|
29
36
|
status = nil
|
|
30
|
-
PTY.spawn(env,
|
|
37
|
+
PTY.spawn(env, *argv) do |output, input, pid|
|
|
31
38
|
begin
|
|
32
39
|
loop do
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
readers = [output]
|
|
41
|
+
readers << @input if @forward_stdin && @input.tty?
|
|
42
|
+
IO.select(readers).first.each do |reader|
|
|
43
|
+
if reader.equal?(@input)
|
|
44
|
+
input.write(@input.readpartial(1024))
|
|
45
|
+
else
|
|
46
|
+
chunk = output.readpartial(1024)
|
|
47
|
+
stdout_str << chunk
|
|
48
|
+
@output.print(chunk)
|
|
49
|
+
prompt_buffer = handle_prompt(input, chunk, prompt_buffer: prompt_buffer)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
37
52
|
end
|
|
38
53
|
rescue Errno::EIO
|
|
39
54
|
# PTY raises EIO when the child process exits after closing the slave.
|
|
@@ -47,27 +62,33 @@ module Kettle
|
|
|
47
62
|
[stdout_str, "", status]
|
|
48
63
|
end
|
|
49
64
|
|
|
50
|
-
def call_with_open3(env,
|
|
65
|
+
def call_with_open3(env, argv)
|
|
51
66
|
stdout_str = +""
|
|
52
67
|
stderr_str = +""
|
|
68
|
+
prompt_buffer = +""
|
|
53
69
|
status = nil
|
|
54
|
-
Open3.popen3(env,
|
|
70
|
+
Open3.popen3(env, *argv) do |input, output, error, wait_thread|
|
|
55
71
|
readers = [output, error]
|
|
72
|
+
readers << @input if @forward_stdin && @input.tty?
|
|
56
73
|
until readers.empty?
|
|
57
74
|
ready = IO.select(readers)
|
|
58
75
|
ready.first.each do |reader|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
if reader.equal?(@input)
|
|
77
|
+
input.write(@input.readpartial(1024))
|
|
78
|
+
else
|
|
79
|
+
begin
|
|
80
|
+
chunk = reader.readpartial(1024)
|
|
81
|
+
if reader.equal?(output)
|
|
82
|
+
stdout_str << chunk
|
|
83
|
+
@output.print(chunk)
|
|
84
|
+
else
|
|
85
|
+
stderr_str << chunk
|
|
86
|
+
@error.print(chunk)
|
|
87
|
+
end
|
|
88
|
+
prompt_buffer = handle_prompt(input, chunk, prompt_buffer: prompt_buffer)
|
|
89
|
+
rescue EOFError
|
|
90
|
+
readers.delete(reader)
|
|
67
91
|
end
|
|
68
|
-
handle_prompt(input, chunk)
|
|
69
|
-
rescue EOFError
|
|
70
|
-
readers.delete(reader)
|
|
71
92
|
end
|
|
72
93
|
end
|
|
73
94
|
end
|
|
@@ -76,13 +97,21 @@ module Kettle
|
|
|
76
97
|
[stdout_str, stderr_str, status]
|
|
77
98
|
end
|
|
78
99
|
|
|
79
|
-
def handle_prompt(input, chunk)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
100
|
+
def handle_prompt(input, chunk, prompt_buffer: nil)
|
|
101
|
+
prompt_buffer = prompt_buffer.to_s.dup
|
|
102
|
+
prompt_buffer << chunk.to_s
|
|
103
|
+
prompt_buffer = prompt_buffer.byteslice([prompt_buffer.bytesize - 4096, 0].max, 4096).to_s
|
|
104
|
+
|
|
105
|
+
if otp_prompt?(prompt_buffer)
|
|
106
|
+
if otp_response_prompt?(prompt_buffer)
|
|
107
|
+
write_secret(input, label: "RubyGems MFA code", source: "rubygems_otp") { secrets_provider.rubygems_otp }
|
|
108
|
+
return +""
|
|
109
|
+
end
|
|
110
|
+
return prompt_buffer
|
|
83
111
|
end
|
|
84
112
|
|
|
85
|
-
write_secret(input, label: "gem signing passphrase", source: "gem_signing_passphrase") { gem_signing_passphrase } if signing_password_prompt?(
|
|
113
|
+
write_secret(input, label: "gem signing passphrase", source: "gem_signing_passphrase") { gem_signing_passphrase } if signing_password_prompt?(prompt_buffer)
|
|
114
|
+
prompt_buffer
|
|
86
115
|
end
|
|
87
116
|
|
|
88
117
|
def gem_signing_passphrase
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "set"
|
|
4
4
|
require "shellwords"
|
|
5
5
|
require "fileutils"
|
|
6
|
+
require "open3"
|
|
6
7
|
require "bundler"
|
|
7
8
|
|
|
8
9
|
require_relative "bundler_env_guard"
|
|
@@ -418,12 +419,26 @@ module Kettle
|
|
|
418
419
|
end
|
|
419
420
|
|
|
420
421
|
def locally_installed?(name, version)
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
422
|
+
_stdout, _stderr, status = Open3.capture3(
|
|
423
|
+
unbundled_env,
|
|
424
|
+
"gem",
|
|
425
|
+
"specification",
|
|
426
|
+
name,
|
|
427
|
+
"--version",
|
|
428
|
+
version.to_s,
|
|
429
|
+
"--local"
|
|
430
|
+
)
|
|
431
|
+
status.success?
|
|
432
|
+
rescue SystemCallError
|
|
424
433
|
false
|
|
425
434
|
end
|
|
426
435
|
|
|
436
|
+
def unbundled_env
|
|
437
|
+
UNBUNDLED_ENV_KEYS.each_with_object({}) do |key, env|
|
|
438
|
+
env[key] = nil
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
427
442
|
def gem_source_version_available?(name, version, source_urls)
|
|
428
443
|
urls = Array(source_urls).compact.uniq
|
|
429
444
|
return false if urls.empty?
|
|
@@ -316,8 +316,12 @@ module Kettle
|
|
|
316
316
|
# Check 1: Ensure GitHub Actions workflow action refs are current SHA pins.
|
|
317
317
|
# @return [void]
|
|
318
318
|
def check_github_actions_sha_pins!
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
offline = env_truthy?(ENV["KETTLE_PRE_RELEASE_GHA_SHA_PINS_OFFLINE"])
|
|
320
|
+
mode = offline ? "offline cache" : "live cache validation"
|
|
321
|
+
puts "[kettle-pre-release] Check 1: Validate GitHub Actions SHA pins (#{mode})"
|
|
322
|
+
args = ["--root", Dir.pwd, "--check", "--upgrade", "major"]
|
|
323
|
+
args << "--offline" if offline
|
|
324
|
+
status = Kettle::Dev::GhaShaPinsCLI.new(args).run!
|
|
321
325
|
return nil if status.zero?
|
|
322
326
|
|
|
323
327
|
Kettle::Dev::ExitAdapter.abort("GitHub Actions SHA pin validation failed")
|
|
@@ -46,6 +46,23 @@ module Kettle
|
|
|
46
46
|
"BUNDLE_SILENCE_ROOT_WARNING" => "true",
|
|
47
47
|
"BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES" => "true"
|
|
48
48
|
}.freeze
|
|
49
|
+
RELEASE_CHILD_ENV_KEYS = %w[
|
|
50
|
+
K_RELEASE_CI_CONTINUE
|
|
51
|
+
K_RELEASE_REQUIRED_REMOTES
|
|
52
|
+
KETTLE_FAMILY_CONFIG
|
|
53
|
+
KETTLE_PRE_RELEASE_GHA_SHA_PINS_OFFLINE
|
|
54
|
+
KETTLE_RELEASE_SECRETS_PROVIDER
|
|
55
|
+
KETTLE_RELEASE_SECRETS_BROKER
|
|
56
|
+
KETTLE_RELEASE_GEM_SIGNING_PASSPHRASE
|
|
57
|
+
KETTLE_RELEASE_GEM_SIGNING_PASSPHRASE_SOURCE
|
|
58
|
+
KETTLE_RELEASE_1PASSWORD_ACCOUNT
|
|
59
|
+
KETTLE_RELEASE_1PASSWORD_CLI
|
|
60
|
+
KETTLE_RELEASE_1PASSWORD_ITEM
|
|
61
|
+
KETTLE_RELEASE_1PASSWORD_GEM_SIGNING_PASSPHRASE_FIELD
|
|
62
|
+
KETTLE_RELEASE_1PASSWORD_RUBYGEMS_OTP_FIELD
|
|
63
|
+
KETTLE_RELEASE_1PASSWORD_GEM_SIGNING_PASSPHRASE_REFERENCE
|
|
64
|
+
KETTLE_RELEASE_1PASSWORD_RUBYGEMS_OTP_REFERENCE
|
|
65
|
+
].freeze
|
|
49
66
|
DEBUG_TRUE_VALUES = %w[1 true yes on].freeze
|
|
50
67
|
RELEASE_VALIDATION_SOURCE = "https://gem.coop"
|
|
51
68
|
|
|
@@ -110,7 +127,9 @@ module Kettle
|
|
|
110
127
|
env_hash = command_env
|
|
111
128
|
return env_hash unless project_bundle_command?(cmd)
|
|
112
129
|
|
|
113
|
-
env_hash.merge(BundlerEnvGuard.unbundled_env)
|
|
130
|
+
env_hash.merge(BundlerEnvGuard.unbundled_env).merge(
|
|
131
|
+
RELEASE_CHILD_ENV_KEYS.each_with_object({}) { |key, env| env[key] = nil }
|
|
132
|
+
)
|
|
114
133
|
end
|
|
115
134
|
|
|
116
135
|
def project_bundle_command?(cmd)
|
|
@@ -622,7 +641,10 @@ module Kettle
|
|
|
622
641
|
end
|
|
623
642
|
|
|
624
643
|
def prepare_release_lockfiles_for_commit!
|
|
625
|
-
|
|
644
|
+
# Release setup and documentation tasks can re-materialize local PATH
|
|
645
|
+
# sources in the parent development bundle after the preflight reset.
|
|
646
|
+
# Always normalize again at the commit boundary.
|
|
647
|
+
reset_release_lockfiles!(stage: "before release prep commit")
|
|
626
648
|
validate_release_lockfiles!(stage: "before release prep commit")
|
|
627
649
|
end
|
|
628
650
|
|
|
@@ -1167,7 +1189,7 @@ module Kettle
|
|
|
1167
1189
|
Kettle::Dev::CIMonitor.monitor_all!(
|
|
1168
1190
|
restart_hint: restart_hint,
|
|
1169
1191
|
workflows: @ci_workflows,
|
|
1170
|
-
keepalive:
|
|
1192
|
+
keepalive: release_secrets_keepalive_required? ? -> { keep_release_secrets_alive!("CI monitoring") } : nil,
|
|
1171
1193
|
event_recorder: @event_recorder
|
|
1172
1194
|
)
|
|
1173
1195
|
emit_ci_monitor_event(action: "finish", status: "ok", workflows: @ci_workflows, restart_hint: restart_hint)
|
|
@@ -1365,7 +1387,7 @@ module Kettle
|
|
|
1365
1387
|
end
|
|
1366
1388
|
|
|
1367
1389
|
def keep_release_secrets_alive!(purpose)
|
|
1368
|
-
return unless
|
|
1390
|
+
return unless release_secrets_keepalive_required?
|
|
1369
1391
|
return unless @secrets_provider.respond_to?(:keepalive!)
|
|
1370
1392
|
|
|
1371
1393
|
emit_secret_event(action: "keepalive", status: "started", purpose: purpose, source: release_secrets_provider_label, elapsed: release_elapsed_label)
|
|
@@ -1376,6 +1398,16 @@ module Kettle
|
|
|
1376
1398
|
abort("Release secrets provider keepalive failed before #{purpose}: #{error.message}")
|
|
1377
1399
|
end
|
|
1378
1400
|
|
|
1401
|
+
def release_secrets_keepalive_required?
|
|
1402
|
+
return false unless release_secrets_configured?
|
|
1403
|
+
if @secrets_provider.respond_to?(:keepalive_required?)
|
|
1404
|
+
configured = @secrets_provider.keepalive_required?
|
|
1405
|
+
return configured unless configured.nil?
|
|
1406
|
+
end
|
|
1407
|
+
|
|
1408
|
+
true
|
|
1409
|
+
end
|
|
1410
|
+
|
|
1379
1411
|
def release_elapsed_label
|
|
1380
1412
|
return nil unless @started_at
|
|
1381
1413
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "open3"
|
|
4
|
+
require "json"
|
|
5
|
+
require "socket"
|
|
6
|
+
require "kettle/dev/interactive_release_command"
|
|
4
7
|
require "kettle/dev/release_notifier"
|
|
5
8
|
|
|
6
9
|
module Kettle
|
|
@@ -9,6 +12,10 @@ module Kettle
|
|
|
9
12
|
|
|
10
13
|
module ReleaseSecrets
|
|
11
14
|
class Provider
|
|
15
|
+
def keepalive_required?
|
|
16
|
+
true
|
|
17
|
+
end
|
|
18
|
+
|
|
12
19
|
def gem_signing_passphrase
|
|
13
20
|
nil
|
|
14
21
|
end
|
|
@@ -22,6 +29,63 @@ module Kettle
|
|
|
22
29
|
end
|
|
23
30
|
end
|
|
24
31
|
|
|
32
|
+
# Secret provider used by a family-owned release broker. The broker keeps
|
|
33
|
+
# all 1Password lookups in the family process; member release processes
|
|
34
|
+
# never authenticate independently.
|
|
35
|
+
class BrokerClient < Provider
|
|
36
|
+
def initialize(endpoint)
|
|
37
|
+
@endpoint = endpoint.to_s
|
|
38
|
+
raise Kettle::Dev::Error, "release secrets broker endpoint is required" if @endpoint.empty?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def gem_signing_passphrase
|
|
42
|
+
return cached_gem_signing_passphrase if cached_gem_signing_passphrase?
|
|
43
|
+
|
|
44
|
+
request("gem_signing_passphrase")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def rubygems_otp
|
|
48
|
+
request("rubygems_otp")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# A family broker owns authorization. Polling must never start a new
|
|
52
|
+
# authentication lookup in the member process.
|
|
53
|
+
def keepalive!(elapsed: nil)
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def keepalive_required?
|
|
58
|
+
false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def cached_gem_signing_passphrase?
|
|
64
|
+
%w[cached family kettle-family].include?(ENV.fetch("KETTLE_RELEASE_GEM_SIGNING_PASSPHRASE_SOURCE", "").downcase)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def cached_gem_signing_passphrase
|
|
68
|
+
value = ENV.fetch("KETTLE_RELEASE_GEM_SIGNING_PASSPHRASE", "").to_s
|
|
69
|
+
raise Kettle::Dev::Error, "cached gem signing passphrase was requested but KETTLE_RELEASE_GEM_SIGNING_PASSPHRASE is empty" if value.empty?
|
|
70
|
+
|
|
71
|
+
value
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def request(operation)
|
|
75
|
+
socket = UNIXSocket.new(@endpoint)
|
|
76
|
+
socket.write(JSON.generate("operation" => operation))
|
|
77
|
+
socket.write("\n")
|
|
78
|
+
response = JSON.parse(socket.gets.to_s)
|
|
79
|
+
return response.fetch("value").to_s if response["ok"]
|
|
80
|
+
|
|
81
|
+
raise Kettle::Dev::Error, response.fetch("error", "release secrets broker request failed")
|
|
82
|
+
rescue Errno::ENOENT, Errno::ECONNREFUSED => error
|
|
83
|
+
raise Kettle::Dev::Error, "release secrets broker is unavailable: #{error.message}"
|
|
84
|
+
ensure
|
|
85
|
+
socket&.close
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
25
89
|
class OnePassword < Provider
|
|
26
90
|
PROVIDER_NAMES = %w[1password onepassword op].freeze
|
|
27
91
|
DEFAULTS = {
|
|
@@ -70,13 +134,16 @@ module Kettle
|
|
|
70
134
|
end
|
|
71
135
|
|
|
72
136
|
def keepalive!(elapsed: nil)
|
|
73
|
-
|
|
74
|
-
account
|
|
75
|
-
|
|
76
|
-
run_op(argv, purpose: "authorization keepalive", elapsed: elapsed)
|
|
137
|
+
# 1Password CLI authentication is session-scoped. Starting a fresh
|
|
138
|
+
# `op account get` for every release poll does not keep it alive; it
|
|
139
|
+
# only creates repeated authorization prompts and noise.
|
|
77
140
|
true
|
|
78
141
|
end
|
|
79
142
|
|
|
143
|
+
def keepalive_required?
|
|
144
|
+
false
|
|
145
|
+
end
|
|
146
|
+
|
|
80
147
|
private
|
|
81
148
|
|
|
82
149
|
attr_reader :config
|
|
@@ -112,9 +179,11 @@ module Kettle
|
|
|
112
179
|
required_config("cli")
|
|
113
180
|
end
|
|
114
181
|
|
|
115
|
-
def run_op(argv, purpose:, elapsed: nil)
|
|
182
|
+
def run_op(argv, purpose:, elapsed: nil, interactive: false)
|
|
116
183
|
elapsed_suffix = elapsed.to_s.empty? ? "" : " (elapsed #{elapsed})"
|
|
117
184
|
Kettle::Dev::ReleaseNotifier.alert("1Password #{purpose} lookup starting#{elapsed_suffix}; watch for authorization prompt.")
|
|
185
|
+
return run_interactive_op(argv, purpose: purpose) if interactive
|
|
186
|
+
|
|
118
187
|
stdout, stderr, status = Open3.capture3(*argv)
|
|
119
188
|
return stdout.to_s.strip if status.success? && !stdout.to_s.strip.empty?
|
|
120
189
|
|
|
@@ -125,6 +194,23 @@ module Kettle
|
|
|
125
194
|
raise Kettle::Dev::Error, "1Password CLI executable #{argv.first.inspect} was not found"
|
|
126
195
|
end
|
|
127
196
|
|
|
197
|
+
def run_interactive_op(argv, purpose:)
|
|
198
|
+
_stdout, stderr, status = Kettle::Dev::InteractiveReleaseCommand.new(
|
|
199
|
+
secrets_provider: Provider.new,
|
|
200
|
+
input: $stdin,
|
|
201
|
+
output: $stdout,
|
|
202
|
+
error: $stderr,
|
|
203
|
+
forward_stdin: true
|
|
204
|
+
).call_argv({}, argv)
|
|
205
|
+
return if status.success?
|
|
206
|
+
|
|
207
|
+
details = stderr.to_s.strip
|
|
208
|
+
details = "op exited #{status.exitstatus}" if details.empty?
|
|
209
|
+
raise Kettle::Dev::Error, "1Password #{purpose} lookup failed: #{details}"
|
|
210
|
+
rescue Errno::ENOENT
|
|
211
|
+
raise Kettle::Dev::Error, "1Password CLI executable #{argv.first.inspect} was not found"
|
|
212
|
+
end
|
|
213
|
+
|
|
128
214
|
def required_config(key)
|
|
129
215
|
value = string_config(key)
|
|
130
216
|
raise Kettle::Dev::Error, "1Password release secrets require #{key}" if value.empty?
|
|
@@ -157,6 +243,10 @@ module Kettle
|
|
|
157
243
|
provider_name.to_s
|
|
158
244
|
end
|
|
159
245
|
return Provider.new if name.empty? || name == "interactive"
|
|
246
|
+
if %w[family broker].include?(name.downcase)
|
|
247
|
+
endpoint = configured.fetch("endpoint", ENV.fetch("KETTLE_RELEASE_SECRETS_BROKER", ""))
|
|
248
|
+
return BrokerClient.new(endpoint)
|
|
249
|
+
end
|
|
160
250
|
return OnePassword.new(env_config.merge(configured)) if OnePassword.configured?(name)
|
|
161
251
|
|
|
162
252
|
raise Kettle::Dev::Error, "unsupported release secrets provider #{name.inspect}"
|
data/lib/kettle/dev/version.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Kettle
|
|
|
5
5
|
# Version namespace for this gem.
|
|
6
6
|
module Version
|
|
7
7
|
# Current gem version.
|
|
8
|
-
VERSION = "3.0.
|
|
8
|
+
VERSION = "3.0.2"
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
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-dev
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -60,7 +60,7 @@ dependencies:
|
|
|
60
60
|
version: '0.3'
|
|
61
61
|
- - ">="
|
|
62
62
|
- !ruby/object:Gem::Version
|
|
63
|
-
version: 0.3.
|
|
63
|
+
version: 0.3.11
|
|
64
64
|
type: :runtime
|
|
65
65
|
prerelease: false
|
|
66
66
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -70,7 +70,7 @@ dependencies:
|
|
|
70
70
|
version: '0.3'
|
|
71
71
|
- - ">="
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
|
-
version: 0.3.
|
|
73
|
+
version: 0.3.11
|
|
74
74
|
- !ruby/object:Gem::Dependency
|
|
75
75
|
name: kettle-ndjson
|
|
76
76
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -400,10 +400,10 @@ licenses:
|
|
|
400
400
|
- AGPL-3.0-only
|
|
401
401
|
metadata:
|
|
402
402
|
homepage_uri: https://kettle-dev.galtzo.com
|
|
403
|
-
source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v3.0.
|
|
404
|
-
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v3.0.
|
|
403
|
+
source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v3.0.2
|
|
404
|
+
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v3.0.2/CHANGELOG.md
|
|
405
405
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
|
|
406
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/3.0.
|
|
406
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/3.0.2
|
|
407
407
|
funding_uri: https://github.com/sponsors/pboling
|
|
408
408
|
wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
|
|
409
409
|
news_uri: https://www.railsbling.com/tags/kettle-dev
|
metadata.gz.sig
CHANGED
|
Binary file
|