rake-gem-maintenance 0.1.4 → 0.1.6
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/.woodpecker/publish.yml +52 -0
- data/.woodpecker/renew_api_key.yml +47 -0
- data/.woodpecker/verify.yml +38 -0
- data/CLAUDE.md +14 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +53 -30
- data/README.md +75 -0
- data/lib/rake/gem/maintenance/api_key_renewer.rb +48 -0
- data/lib/rake/gem/maintenance/ci_environment.rb +12 -0
- data/lib/rake/gem/maintenance/credential_store.rb +65 -0
- data/lib/rake/gem/maintenance/gem_publisher.rb +18 -7
- data/lib/rake/gem/maintenance/gem_push.rb +62 -0
- data/lib/rake/gem/maintenance/install_tasks.rb +8 -0
- data/lib/rake/gem/maintenance/otp_provider.rb +45 -0
- data/lib/rake/gem/maintenance/renew_api_key_task.rb +139 -0
- data/lib/rake/gem/maintenance/repos.rb +46 -7
- data/lib/rake/gem/maintenance/ruby_gems_api_key_creator.rb +51 -0
- data/lib/rake/gem/maintenance/upgrade_task.rb +23 -0
- data/lib/rake/gem/maintenance/version.rb +1 -1
- data/lib/rake/gem/maintenance/woodpecker_secret_store.rb +89 -0
- data/lib/rake/gem/maintenance.rb +8 -0
- data/scripts/ci_publish_rubygems.rb +29 -0
- metadata +34 -9
- data/rake-gem-maintenance.gemspec +0 -32
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6dc9985cb348ee5f79a3170eb6ef534b5fe8bc1aad6361be15178f883ba22767
|
|
4
|
+
data.tar.gz: e819c2d3f58a936c59fb8caf620f84e92dc68bf59cee8f592bb852c03d02de62
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 43b4d7dec762bcefd6cd816f4c7a8c05a5e091929937f6e329801a6d4ff211fabdac4631f449d39501807b408f0f1411dc97637519857da49911cafa5bd108f6
|
|
7
|
+
data.tar.gz: 81d529473f1404940f0cc45d1011af5d1930506f972b2485cbe190f184cd53a820cbfaa683201ec0b8ef85e63f2d50714cf337780087a94704071a5ef82d735c
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# publish.yml — build and publish rake-gem-maintenance to rubygems.org
|
|
2
|
+
#
|
|
3
|
+
# Triggers:
|
|
4
|
+
# - push to main (auto-publish on every merge)
|
|
5
|
+
# - manual (ad-hoc publish on demand)
|
|
6
|
+
#
|
|
7
|
+
# Required Woodpecker secrets (org-level cbp-org):
|
|
8
|
+
# rubygems_api_key — rubygems.org API key (set at https://ci.cbp-org.internal)
|
|
9
|
+
# rubygems_otp_seed — base32 TOTP seed for rubygems.org 2FA (already registered)
|
|
10
|
+
# badge_service_token — write token for badge.cbp-org.internal
|
|
11
|
+
|
|
12
|
+
when:
|
|
13
|
+
event: [push, manual]
|
|
14
|
+
branch: main
|
|
15
|
+
|
|
16
|
+
labels:
|
|
17
|
+
platform: linux
|
|
18
|
+
|
|
19
|
+
clone:
|
|
20
|
+
git:
|
|
21
|
+
image: woodpeckerci/plugin-git:2.8.0
|
|
22
|
+
settings:
|
|
23
|
+
skip_verify: true
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- name: publish-gem
|
|
27
|
+
image: ruby:4.0.2-alpine
|
|
28
|
+
backend_options:
|
|
29
|
+
docker:
|
|
30
|
+
network_mode: host
|
|
31
|
+
environment:
|
|
32
|
+
GEM_HOST_API_KEY:
|
|
33
|
+
from_secret: rubygems_api_key
|
|
34
|
+
RUBYGEMS_OTP_SEED:
|
|
35
|
+
from_secret: rubygems_otp_seed
|
|
36
|
+
commands:
|
|
37
|
+
- apk add --no-cache build-base
|
|
38
|
+
- bundle install --jobs 4 --retry 3
|
|
39
|
+
- ruby scripts/ci_publish_rubygems.rb
|
|
40
|
+
|
|
41
|
+
- name: badge
|
|
42
|
+
image: docker-registry.cbp-org.internal/badge-reporter:latest
|
|
43
|
+
when:
|
|
44
|
+
status: [success, failure]
|
|
45
|
+
backend_options:
|
|
46
|
+
docker:
|
|
47
|
+
network_mode: host
|
|
48
|
+
environment:
|
|
49
|
+
BADGE_SECRET:
|
|
50
|
+
from_secret: badge_service_token
|
|
51
|
+
commands:
|
|
52
|
+
- badge report publish
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
when:
|
|
2
|
+
- event: cron
|
|
3
|
+
cron: monthly-renew-api-key
|
|
4
|
+
- event: manual
|
|
5
|
+
|
|
6
|
+
labels:
|
|
7
|
+
platform: linux
|
|
8
|
+
|
|
9
|
+
clone:
|
|
10
|
+
git:
|
|
11
|
+
image: woodpeckerci/plugin-git:2.8.0
|
|
12
|
+
settings:
|
|
13
|
+
skip_verify: true
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: renew-api-key
|
|
17
|
+
image: ruby:4.0.2-alpine
|
|
18
|
+
backend_options:
|
|
19
|
+
docker:
|
|
20
|
+
network_mode: host
|
|
21
|
+
environment:
|
|
22
|
+
RUBYGEMS_USERNAME:
|
|
23
|
+
from_secret: rubygems_username
|
|
24
|
+
RUBYGEMS_PASSWORD:
|
|
25
|
+
from_secret: rubygems_password
|
|
26
|
+
RUBYGEMS_OTP_SEED:
|
|
27
|
+
from_secret: rubygems_otp_seed
|
|
28
|
+
WOODPECKER_TOKEN:
|
|
29
|
+
from_secret: woodpecker_api_token
|
|
30
|
+
WOODPECKER_SERVER: "https://ci.cbp-org.internal"
|
|
31
|
+
commands:
|
|
32
|
+
- apk add --no-cache build-base
|
|
33
|
+
- bundle install --jobs 4 --retry 3
|
|
34
|
+
- bundle exec rake upgrade:renew_api_key
|
|
35
|
+
|
|
36
|
+
- name: badge
|
|
37
|
+
image: docker-registry.cbp-org.internal/badge-reporter:latest
|
|
38
|
+
when:
|
|
39
|
+
status: [success, failure]
|
|
40
|
+
backend_options:
|
|
41
|
+
docker:
|
|
42
|
+
network_mode: host
|
|
43
|
+
environment:
|
|
44
|
+
BADGE_SECRET:
|
|
45
|
+
from_secret: badge_service_token
|
|
46
|
+
commands:
|
|
47
|
+
- badge report renew-api-key
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
when:
|
|
2
|
+
event: [push, pull_request, manual]
|
|
3
|
+
|
|
4
|
+
labels:
|
|
5
|
+
platform: linux
|
|
6
|
+
|
|
7
|
+
clone:
|
|
8
|
+
git:
|
|
9
|
+
image: woodpeckerci/plugin-git:2.8.0
|
|
10
|
+
settings:
|
|
11
|
+
skip_verify: true
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- name: verify
|
|
15
|
+
image: ruby:4.0.2-alpine
|
|
16
|
+
backend_options:
|
|
17
|
+
docker:
|
|
18
|
+
network_mode: host
|
|
19
|
+
environment:
|
|
20
|
+
CUCUMBER_PUBLISH_QUIET: "true"
|
|
21
|
+
commands:
|
|
22
|
+
- apk add --no-cache build-base
|
|
23
|
+
- mkdir -p /root/.local/share/ruby-advisory-db/gems
|
|
24
|
+
- bundle install --jobs 4 --retry 3
|
|
25
|
+
- bundle exec rake verify
|
|
26
|
+
|
|
27
|
+
- name: badge
|
|
28
|
+
image: docker-registry.cbp-org.internal/badge-reporter:latest
|
|
29
|
+
when:
|
|
30
|
+
status: [success, failure]
|
|
31
|
+
backend_options:
|
|
32
|
+
docker:
|
|
33
|
+
network_mode: host
|
|
34
|
+
environment:
|
|
35
|
+
BADGE_SECRET:
|
|
36
|
+
from_secret: badge_service_token
|
|
37
|
+
commands:
|
|
38
|
+
- badge report verify
|
data/CLAUDE.md
CHANGED
|
@@ -54,6 +54,20 @@ Rake::GemMaintenance::UpgradeTask.new do |t|
|
|
|
54
54
|
end
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
## Repository & CI Workflow
|
|
58
|
+
|
|
59
|
+
**GitHub** (`github.com/cbroult/rake-gem-maintenance`) is the canonical source.
|
|
60
|
+
**Forgejo** (`git.cbp-org.internal/forgejo-admin/rake-gem-maintenance`) is a read-only pull mirror.
|
|
61
|
+
|
|
62
|
+
- All pull requests go on **GitHub only**. Never open PRs on Forgejo.
|
|
63
|
+
- Forgejo mirrors GitHub automatically every 10 minutes — never push to the `forgejo` remote manually.
|
|
64
|
+
- **Woodpecker CI** (`ci.cbp-org.internal`) watches Forgejo and runs verify/publish/renew pipelines.
|
|
65
|
+
- Local clone only needs the `origin` (GitHub) remote. Remove `forgejo` if present: `git remote remove forgejo`.
|
|
66
|
+
|
|
67
|
+
## Code Style
|
|
68
|
+
|
|
69
|
+
- Never add `# rubocop:disable` comments without explicit user permission.
|
|
70
|
+
|
|
57
71
|
## Commit Conventions
|
|
58
72
|
|
|
59
73
|
- Use conventional commit format: `type(scope): subject`
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
rake-gem-maintenance (0.1.
|
|
4
|
+
rake-gem-maintenance (0.1.6)
|
|
5
5
|
bundler-audit
|
|
6
|
-
gem-release
|
|
7
|
-
rake
|
|
6
|
+
gem-release
|
|
7
|
+
rake
|
|
8
|
+
rotp
|
|
8
9
|
|
|
9
10
|
GEM
|
|
10
11
|
remote: https://rubygems.org/
|
|
11
12
|
specs:
|
|
12
|
-
aruba (2.
|
|
13
|
+
aruba (2.4.0)
|
|
13
14
|
bundler (>= 1.17)
|
|
14
15
|
contracts (>= 0.16.0, < 0.18.0)
|
|
15
|
-
cucumber (>= 8.0, <
|
|
16
|
+
cucumber (>= 8.0, < 12.0)
|
|
17
|
+
irb (~> 1.16)
|
|
16
18
|
rspec-expectations (>= 3.4, < 5.0)
|
|
17
19
|
thor (~> 1.0)
|
|
18
20
|
ast (2.4.3)
|
|
@@ -24,18 +26,18 @@ GEM
|
|
|
24
26
|
thor (~> 1.0)
|
|
25
27
|
coderay (1.1.3)
|
|
26
28
|
contracts (0.17.3)
|
|
27
|
-
cucumber (
|
|
29
|
+
cucumber (11.0.0)
|
|
28
30
|
base64 (~> 0.2)
|
|
29
31
|
builder (~> 3.2)
|
|
30
32
|
cucumber-ci-environment (> 9, < 12)
|
|
31
|
-
cucumber-core (
|
|
33
|
+
cucumber-core (>= 16.2.0, < 17)
|
|
32
34
|
cucumber-cucumber-expressions (> 17, < 20)
|
|
33
|
-
cucumber-html-formatter (> 21, <
|
|
35
|
+
cucumber-html-formatter (> 21, < 24)
|
|
34
36
|
diff-lcs (~> 1.5)
|
|
35
37
|
logger (~> 1.6)
|
|
36
38
|
mini_mime (~> 1.1)
|
|
37
39
|
multi_test (~> 1.1)
|
|
38
|
-
sys-uname (~> 1.
|
|
40
|
+
sys-uname (~> 1.5)
|
|
39
41
|
cucumber-ci-environment (11.0.0)
|
|
40
42
|
cucumber-core (16.2.0)
|
|
41
43
|
cucumber-gherkin (> 36, < 40)
|
|
@@ -43,14 +45,15 @@ GEM
|
|
|
43
45
|
cucumber-tag-expressions (> 6, < 9)
|
|
44
46
|
cucumber-cucumber-expressions (19.0.0)
|
|
45
47
|
bigdecimal
|
|
46
|
-
cucumber-gherkin (39.
|
|
48
|
+
cucumber-gherkin (39.1.0)
|
|
47
49
|
cucumber-messages (>= 31, < 33)
|
|
48
|
-
cucumber-html-formatter (
|
|
50
|
+
cucumber-html-formatter (23.1.0)
|
|
49
51
|
cucumber-messages (> 23, < 33)
|
|
50
52
|
cucumber-messages (32.3.1)
|
|
51
53
|
cucumber-tag-expressions (8.1.0)
|
|
54
|
+
date (3.5.1)
|
|
52
55
|
diff-lcs (1.6.2)
|
|
53
|
-
|
|
56
|
+
erb (6.0.4)
|
|
54
57
|
ffi (1.17.4-x86_64-linux-gnu)
|
|
55
58
|
formatador (1.2.3)
|
|
56
59
|
reline
|
|
@@ -81,7 +84,12 @@ GEM
|
|
|
81
84
|
guard (~> 2.0)
|
|
82
85
|
rubocop (< 2.0)
|
|
83
86
|
io-console (0.8.2)
|
|
84
|
-
|
|
87
|
+
irb (1.18.0)
|
|
88
|
+
pp (>= 0.6.0)
|
|
89
|
+
prism (>= 1.3.0)
|
|
90
|
+
rdoc (>= 4.0.0)
|
|
91
|
+
reline (>= 0.4.2)
|
|
92
|
+
json (2.19.5)
|
|
85
93
|
language_server-protocol (3.17.0.5)
|
|
86
94
|
lint_roller (1.1.0)
|
|
87
95
|
listen (3.10.0)
|
|
@@ -103,20 +111,31 @@ GEM
|
|
|
103
111
|
parser (3.3.11.1)
|
|
104
112
|
ast (~> 2.4.1)
|
|
105
113
|
racc
|
|
114
|
+
pp (0.6.3)
|
|
115
|
+
prettyprint
|
|
116
|
+
prettyprint (0.2.0)
|
|
106
117
|
prism (1.9.0)
|
|
107
118
|
pry (0.16.0)
|
|
108
119
|
coderay (~> 1.1)
|
|
109
120
|
method_source (~> 1.0)
|
|
110
121
|
reline (>= 0.6.0)
|
|
122
|
+
psych (5.3.1)
|
|
123
|
+
date
|
|
124
|
+
stringio
|
|
111
125
|
racc (1.8.1)
|
|
112
126
|
rainbow (3.1.1)
|
|
113
127
|
rake (13.4.2)
|
|
114
128
|
rb-fsevent (0.11.2)
|
|
115
129
|
rb-inotify (0.11.1)
|
|
116
130
|
ffi (~> 1.0)
|
|
131
|
+
rdoc (7.2.0)
|
|
132
|
+
erb
|
|
133
|
+
psych (>= 4.0.0)
|
|
134
|
+
tsort
|
|
117
135
|
regexp_parser (2.12.0)
|
|
118
136
|
reline (0.6.3)
|
|
119
137
|
io-console (~> 0.5)
|
|
138
|
+
rotp (6.3.0)
|
|
120
139
|
rspec (3.13.2)
|
|
121
140
|
rspec-core (~> 3.13.0)
|
|
122
141
|
rspec-expectations (~> 3.13.0)
|
|
@@ -130,7 +149,7 @@ GEM
|
|
|
130
149
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
131
150
|
rspec-support (~> 3.13.0)
|
|
132
151
|
rspec-support (3.13.7)
|
|
133
|
-
rubocop (1.86.
|
|
152
|
+
rubocop (1.86.2)
|
|
134
153
|
json (~> 2.3)
|
|
135
154
|
language_server-protocol (~> 3.17.0.2)
|
|
136
155
|
lint_roller (~> 1.1.0)
|
|
@@ -152,21 +171,17 @@ GEM
|
|
|
152
171
|
rubocop (~> 1.81)
|
|
153
172
|
ruby-progressbar (1.13.0)
|
|
154
173
|
shellany (0.0.1)
|
|
174
|
+
stringio (3.2.0)
|
|
155
175
|
sys-uname (1.5.1)
|
|
156
176
|
ffi (~> 1.1)
|
|
157
177
|
memoist3 (~> 1.0.0)
|
|
158
|
-
sys-uname (1.5.1-universal-mingw32)
|
|
159
|
-
ffi (~> 1.1)
|
|
160
|
-
memoist3 (~> 1.0.0)
|
|
161
|
-
win32ole
|
|
162
178
|
thor (1.5.0)
|
|
179
|
+
tsort (0.2.0)
|
|
163
180
|
unicode-display_width (3.2.0)
|
|
164
181
|
unicode-emoji (~> 4.1)
|
|
165
182
|
unicode-emoji (4.2.0)
|
|
166
|
-
win32ole (1.9.3)
|
|
167
183
|
|
|
168
184
|
PLATFORMS
|
|
169
|
-
x64-mingw-ucrt
|
|
170
185
|
x86_64-linux
|
|
171
186
|
|
|
172
187
|
DEPENDENCIES
|
|
@@ -179,13 +194,14 @@ DEPENDENCIES
|
|
|
179
194
|
guard-rubocop
|
|
180
195
|
ostruct
|
|
181
196
|
rake-gem-maintenance!
|
|
197
|
+
rdoc
|
|
182
198
|
rspec
|
|
183
199
|
rubocop
|
|
184
200
|
rubocop-rake
|
|
185
201
|
rubocop-rspec
|
|
186
202
|
|
|
187
203
|
CHECKSUMS
|
|
188
|
-
aruba (2.
|
|
204
|
+
aruba (2.4.0) sha256=92bb696efb06b1aa1dc3ff8b13ca71cd727e59d27572f9264216b9722b558299
|
|
189
205
|
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
190
206
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
191
207
|
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
|
|
@@ -193,16 +209,17 @@ CHECKSUMS
|
|
|
193
209
|
bundler-audit (0.9.3) sha256=81c8766c71e47d0d28a0f98c7eed028539f21a6ea3cd8f685eb6f42333c9b4e9
|
|
194
210
|
coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b
|
|
195
211
|
contracts (0.17.3) sha256=e72e626413ea47099becb7b5683beb1c2ea902c69f5bad55c9258fe2b48314d7
|
|
196
|
-
cucumber (
|
|
212
|
+
cucumber (11.0.0) sha256=14bb964cc172999e9010fece2fad9f104044b055b3199230091894637a2a784c
|
|
197
213
|
cucumber-ci-environment (11.0.0) sha256=0df79a9e1d0b015b3d9def680f989200d96fef206f4d19ccf86a338c4f71d1e2
|
|
198
214
|
cucumber-core (16.2.0) sha256=592b58a95cf42feef8e5a349f68e363784ba3b6568ffbcf6776e38e136cf970b
|
|
199
215
|
cucumber-cucumber-expressions (19.0.0) sha256=33208ff204732ac9bed42b46993a0a243054f71ece08579d57e53df6a1c9d93a
|
|
200
|
-
cucumber-gherkin (39.
|
|
201
|
-
cucumber-html-formatter (
|
|
216
|
+
cucumber-gherkin (39.1.0) sha256=aed12a0c955d8563d80a012633c1a72075525f4d64d4cc983001df2181b379ed
|
|
217
|
+
cucumber-html-formatter (23.1.0) sha256=7789b4a792c876394b9604aeb66aa5cf4c61514473b7e712c76d5eaedcdd8cdf
|
|
202
218
|
cucumber-messages (32.3.1) sha256=ddc88e4c1cf7afb96c06005b92a4a6f221a2fa435a8b4ca04677d215fd82771c
|
|
203
219
|
cucumber-tag-expressions (8.1.0) sha256=9bd8c4b6654f8e5bf2a9c99329b6f32136a75e50cd39d4cfb3927d0fa9f52e21
|
|
220
|
+
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
|
|
204
221
|
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
205
|
-
|
|
222
|
+
erb (6.0.4) sha256=38e3803694be357fe2bfe312487c74beaf9fb4e5beb3e22498952fe1645b95d9
|
|
206
223
|
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
|
|
207
224
|
formatador (1.2.3) sha256=19fa898133c2c26cdbb5d09f6998c1e137ad9427a046663e55adfe18b950d894
|
|
208
225
|
gem-release (2.2.4) sha256=2f11124c1580c811507c3b47e875e420cf3ed792a98105b49df11971e6e94db3
|
|
@@ -213,7 +230,8 @@ CHECKSUMS
|
|
|
213
230
|
guard-rspec (4.7.3) sha256=a47ba03cbd1e3c71e6ae8645cea97e203098a248aede507461a43e906e2f75ca
|
|
214
231
|
guard-rubocop (1.5.0) sha256=3041d796dcb5ee31e352de74732250826f5f235b4ff48df9dbf424a6dc736251
|
|
215
232
|
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
|
|
216
|
-
|
|
233
|
+
irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
|
|
234
|
+
json (2.19.5) sha256=218a18553e4801d579ca7e0f5bc72bafd776d7397238a1fb4e74db5b0a812c59
|
|
217
235
|
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
|
218
236
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
219
237
|
listen (3.10.0) sha256=c6e182db62143aeccc2e1960033bebe7445309c7272061979bb098d03760c9d2
|
|
@@ -228,33 +246,38 @@ CHECKSUMS
|
|
|
228
246
|
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
|
|
229
247
|
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
|
|
230
248
|
parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
|
|
249
|
+
pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
|
|
250
|
+
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
|
|
231
251
|
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
232
252
|
pry (0.16.0) sha256=d76c69065698ed1f85e717bd33d7942c38a50868f6b0673c636192b3d1b6054e
|
|
253
|
+
psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
|
|
233
254
|
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
234
255
|
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
|
235
256
|
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
|
|
236
|
-
rake-gem-maintenance (0.1.
|
|
257
|
+
rake-gem-maintenance (0.1.6)
|
|
237
258
|
rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe
|
|
238
259
|
rb-inotify (0.11.1) sha256=a0a700441239b0ff18eb65e3866236cd78613d6b9f78fea1f9ac47a85e47be6e
|
|
260
|
+
rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192
|
|
239
261
|
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
|
|
240
262
|
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
|
|
263
|
+
rotp (6.3.0) sha256=75d40087e65ed0d8022c33055a6306c1c400d1c12261932533b5d6cbcd868854
|
|
241
264
|
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
|
|
242
265
|
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
|
|
243
266
|
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
|
244
267
|
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
|
|
245
268
|
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
|
246
|
-
rubocop (1.86.
|
|
269
|
+
rubocop (1.86.2) sha256=bb2e97f635eda42c448f2588f4a6ff78f221b8bdfdf65b1e9b07fbd57521b45d
|
|
247
270
|
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
|
|
248
271
|
rubocop-rake (0.7.1) sha256=3797f2b6810c3e9df7376c26d5f44f3475eda59eb1adc38e6f62ecf027cbae4d
|
|
249
272
|
rubocop-rspec (3.9.0) sha256=8fa70a3619408237d789aeecfb9beef40576acc855173e60939d63332fdb55e2
|
|
250
273
|
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
|
251
274
|
shellany (0.0.1) sha256=0e127a9132698766d7e752e82cdac8250b6adbd09e6c0a7fbbb6f61964fedee7
|
|
275
|
+
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
|
|
252
276
|
sys-uname (1.5.1) sha256=784d7e6491b0393c25cbbe5ac38324ac7be9fda083a6094832648af669386d7b
|
|
253
|
-
sys-uname (1.5.1-universal-mingw32) sha256=aceb618e3276da5eae0ce368e9f6fae8c1f3e9ef23a0595cb88db7b6ecd45f62
|
|
254
277
|
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
|
|
278
|
+
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
|
|
255
279
|
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
|
|
256
280
|
unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
|
|
257
|
-
win32ole (1.9.3) sha256=01f43dc5dc13806e6e58204f538b4a28f3d85968ea89074abc9a3cd118e94d96
|
|
258
281
|
|
|
259
282
|
BUNDLED WITH
|
|
260
283
|
4.0.4
|
data/README.md
CHANGED
|
@@ -46,6 +46,81 @@ Rake::GemMaintenance::VersionBumpTask.new do |t|
|
|
|
46
46
|
end
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
## Automated Publishing to rubygems.org
|
|
50
|
+
|
|
51
|
+
Set two environment variables and `gem push` runs fully unattended — including TOTP 2FA code
|
|
52
|
+
generation if your rubygems.org account has MFA enabled.
|
|
53
|
+
|
|
54
|
+
| Env var | Purpose |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `GEM_HOST_API_KEY` | rubygems.org API key (scoped to push) |
|
|
57
|
+
| `RUBYGEMS_OTP_SEED` | Base32 TOTP seed — auto-generates the 2FA code; omit if MFA is disabled |
|
|
58
|
+
|
|
59
|
+
### Quick setup
|
|
60
|
+
|
|
61
|
+
`require "rake/gem_maintenance/install_tasks"` pre-configures both env var names automatically —
|
|
62
|
+
no extra Ruby needed. See [features/install_tasks.feature](features/install_tasks.feature) for
|
|
63
|
+
the full workflow.
|
|
64
|
+
|
|
65
|
+
### Custom env var names
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
require "rake/gem/maintenance"
|
|
69
|
+
|
|
70
|
+
Rake::GemMaintenance::Repos.rubygems_api_key_env_var = "MY_RUBYGEMS_KEY"
|
|
71
|
+
Rake::GemMaintenance::Repos.rubygems_otp_seed_env_var = "MY_OTP_SEED"
|
|
72
|
+
|
|
73
|
+
Rake::GemMaintenance::UpgradeTask.new
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
See [features/upgrade_task/repos_configuration.feature](features/upgrade_task/repos_configuration.feature)
|
|
77
|
+
for all configuration options including geminabox and dual publishing.
|
|
78
|
+
|
|
79
|
+
### Local credential store
|
|
80
|
+
|
|
81
|
+
After the first successful `upgrade:renew_api_key` run, the API key and OTP seed are saved to:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
~/.config/rake-gem-maintenance/credentials.yml # Linux / Mac (respects $XDG_CONFIG_HOME)
|
|
85
|
+
%APPDATA%\rake-gem-maintenance\credentials.yml # Windows
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The file is created with `0600` permissions (owner-read-only on Unix). It stores `username`,
|
|
89
|
+
`gem_host_api_key`, and `rubygems_otp_seed` — **never the password**. Any project using
|
|
90
|
+
`require "rake/gem_maintenance/install_tasks"` automatically loads the key and OTP seed from
|
|
91
|
+
this file at startup, so `gem push` works without any manual env-var setup.
|
|
92
|
+
|
|
93
|
+
See [features/upgrade_task/credential_store.feature](features/upgrade_task/credential_store.feature)
|
|
94
|
+
for the full behaviour specification.
|
|
95
|
+
|
|
96
|
+
### API key renewal
|
|
97
|
+
|
|
98
|
+
API keys can be rotated in two ways:
|
|
99
|
+
|
|
100
|
+
**Automatic** — when `gem push` returns a 401/403, the publisher transparently obtains a new
|
|
101
|
+
key using `RUBYGEMS_USERNAME` + `RUBYGEMS_PASSWORD` (+ TOTP from `RUBYGEMS_OTP_SEED` if MFA is
|
|
102
|
+
enabled), then retries the push once. No intervention needed.
|
|
103
|
+
|
|
104
|
+
**On-demand** — run the task explicitly to rotate ahead of expiry:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
rake upgrade:renew_api_key
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Locally this prompts for credentials interactively. In CI, supply all three env vars for
|
|
111
|
+
unattended operation:
|
|
112
|
+
|
|
113
|
+
| Env var | Purpose |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `RUBYGEMS_USERNAME` | rubygems.org account username or email |
|
|
116
|
+
| `RUBYGEMS_PASSWORD` | rubygems.org account password |
|
|
117
|
+
| `RUBYGEMS_OTP_SEED` | Same TOTP seed as above — reused here to authenticate the key-creation request |
|
|
118
|
+
|
|
119
|
+
The new key is written back to the `GEM_HOST_API_KEY` CI secret automatically (requires
|
|
120
|
+
`WOODPECKER_TOKEN` and `WOODPECKER_SERVER` when running under Woodpecker CI).
|
|
121
|
+
|
|
122
|
+
See [features/upgrade_task/renew_api_key.feature](features/upgrade_task/renew_api_key.feature).
|
|
123
|
+
|
|
49
124
|
## License
|
|
50
125
|
|
|
51
126
|
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rake
|
|
4
|
+
module GemMaintenance
|
|
5
|
+
# Renews a rubygems.org API key using credentials from env vars and
|
|
6
|
+
# persists the new key to Woodpecker CI when server details are available.
|
|
7
|
+
class ApiKeyRenewer
|
|
8
|
+
def initialize(otp_provider:,
|
|
9
|
+
username_env_var: "RUBYGEMS_USERNAME",
|
|
10
|
+
password_env_var: "RUBYGEMS_PASSWORD")
|
|
11
|
+
@otp_provider = otp_provider
|
|
12
|
+
@username_env_var = username_env_var
|
|
13
|
+
@password_env_var = password_env_var
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def renew(repository)
|
|
17
|
+
username = env_credential(@username_env_var)
|
|
18
|
+
password = env_credential(@password_env_var)
|
|
19
|
+
return nil if username.nil? || password.nil?
|
|
20
|
+
|
|
21
|
+
otp = @otp_provider.otp_for(repository[:name], otp_seed_env_var: repository[:otp_seed_env_var])
|
|
22
|
+
new_key = RubyGemsApiKeyCreator.new(host: repository.fetch(:url, "https://rubygems.org"))
|
|
23
|
+
.create(username, password, otp: otp)
|
|
24
|
+
persist_to_woodpecker(new_key)
|
|
25
|
+
new_key
|
|
26
|
+
rescue StandardError
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def env_credential(var)
|
|
33
|
+
value = ENV.fetch(var, nil)
|
|
34
|
+
value&.empty? ? nil : value
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def persist_to_woodpecker(new_key)
|
|
38
|
+
server = ENV.fetch("WOODPECKER_SERVER", nil)
|
|
39
|
+
token = ENV.fetch("WOODPECKER_TOKEN", nil)
|
|
40
|
+
return unless server && token
|
|
41
|
+
|
|
42
|
+
org = ENV.fetch("WOODPECKER_ORG", "cbp-org")
|
|
43
|
+
WoodpeckerSecretStore.new(server: server, org: org, token: token)
|
|
44
|
+
.store("rubygems_api_key", new_key)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "rubygems"
|
|
6
|
+
|
|
7
|
+
module Rake
|
|
8
|
+
module GemMaintenance
|
|
9
|
+
# Persists rubygems.org credentials (username, API key, OTP seed) to a local config file.
|
|
10
|
+
class CredentialStore
|
|
11
|
+
def self.default_path
|
|
12
|
+
base = if Gem.win_platform?
|
|
13
|
+
ENV.fetch("APPDATA", File.expand_path("~"))
|
|
14
|
+
else
|
|
15
|
+
ENV.fetch("XDG_CONFIG_HOME", File.join(Dir.home, ".config"))
|
|
16
|
+
end
|
|
17
|
+
File.join(base, "rake-gem-maintenance", "credentials.yml")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(path: self.class.default_path)
|
|
21
|
+
@path = path
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :path
|
|
25
|
+
|
|
26
|
+
def read
|
|
27
|
+
return {} unless File.exist?(@path)
|
|
28
|
+
|
|
29
|
+
YAML.safe_load_file(@path, symbolize_names: true) || {}
|
|
30
|
+
rescue StandardError
|
|
31
|
+
{}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def write(credentials)
|
|
35
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
|
36
|
+
File.write(@path, credentials.transform_keys(&:to_s).to_yaml)
|
|
37
|
+
File.chmod(0o600, @path) unless Gem.win_platform?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def apply_to_env(username_env_var:, api_key_env_var:)
|
|
41
|
+
creds = read
|
|
42
|
+
set_env_if_absent(username_env_var, creds[:username])
|
|
43
|
+
set_env_if_absent("RUBYGEMS_OTP_SEED", creds[:rubygems_otp_seed])
|
|
44
|
+
set_env_if_absent(api_key_env_var, creds[:gem_host_api_key])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def update(username:, api_key:, api_key_env_var:)
|
|
48
|
+
otp_seed = ENV.fetch("RUBYGEMS_OTP_SEED", nil)
|
|
49
|
+
updated = read.merge(username: username, gem_host_api_key: api_key)
|
|
50
|
+
updated[:rubygems_otp_seed] = otp_seed if otp_seed && !otp_seed.empty?
|
|
51
|
+
write(updated)
|
|
52
|
+
ENV[api_key_env_var] = api_key
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def set_env_if_absent(env_var, value)
|
|
58
|
+
return unless value && !value.empty?
|
|
59
|
+
return if (existing = ENV.fetch(env_var, nil)) && !existing.empty?
|
|
60
|
+
|
|
61
|
+
ENV[env_var] = value
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -9,8 +9,12 @@ module Rake
|
|
|
9
9
|
class GemPublisher
|
|
10
10
|
attr_reader :repositories, :warnings, :failed_repositories, :successful_repos
|
|
11
11
|
|
|
12
|
-
def initialize(repositories = default_repositories
|
|
12
|
+
def initialize(repositories = default_repositories,
|
|
13
|
+
otp_provider: OtpProvider.new,
|
|
14
|
+
ci_environment: CIEnvironment)
|
|
13
15
|
@repositories = repositories
|
|
16
|
+
@otp_provider = otp_provider
|
|
17
|
+
@ci_environment = ci_environment
|
|
14
18
|
@warnings = []
|
|
15
19
|
@failed_pushes = []
|
|
16
20
|
@failed_repositories = []
|
|
@@ -76,16 +80,23 @@ module Rake
|
|
|
76
80
|
end
|
|
77
81
|
|
|
78
82
|
def push(gem_file, repository:)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@successful_repos << repository[:name]
|
|
84
|
-
end
|
|
83
|
+
result = GemPush.new(gem_file, repository, @otp_provider).attempt
|
|
84
|
+
return record_success(gem_file, repository) if result.success
|
|
85
|
+
|
|
86
|
+
record_push_failure(repository, result.error)
|
|
85
87
|
rescue StandardError => e
|
|
86
88
|
@failed_pushes << { repository: repository[:name], error: e.message }
|
|
87
89
|
end
|
|
88
90
|
|
|
91
|
+
def record_success(gem_file, repository)
|
|
92
|
+
@published_files << gem_file
|
|
93
|
+
@successful_repos << repository[:name]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def record_push_failure(repository, message)
|
|
97
|
+
@failed_pushes << { repository: repository[:name], error: message.strip }
|
|
98
|
+
end
|
|
99
|
+
|
|
89
100
|
def version_exists_on_all_repos?(gem_name, version)
|
|
90
101
|
repositories.all? do |repo|
|
|
91
102
|
versions_on_repository(gem_name, repo).include?(version.to_s)
|