Skip to content

Commit 68b8e37

Browse files
committed
fix: harden EOL archive tooling
- Wire up GCloudStorageLock in all four archive scripts so migrations and prunes mutually exclude with the publish workflow's pointer updates. The lock library was already imported but never used. - Wrap all four scripts in begin/ensure cleanup so a failure mid-run no longer leaves the imported GPG signing key on disk. - migrate-apt-to-archive: stop swallowing pool-merge errors via '|| true'. Real I/O/permission failures must surface; only no-clobber skips were ever benign. - migrate-apt-to-archive / migrate-yum-to-archive: comments claimed EOL detection compared against environments/, but the code compares against config.yml. Align the comments with the code. - archiving-eol-packages.md: APT explicit-distros example listed centos-8, which is RPM-only and would fail in the APT script. Replace with debian-10,ubuntu-20.04.
1 parent e523f8a commit 68b8e37

5 files changed

Lines changed: 309 additions & 200 deletions

File tree

dev-handbook/archiving-eol-packages.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ ARCHIVE_REPO_BUCKET_NAME=fsruby-server-edition-apt-repo-archive \
6969
./internal-scripts/ci-cd/archive/migrate-apt-to-archive.rb --dry-run
7070
~~~
7171

72-
The script auto-detects EOL distros by comparing `aptly repo list` output against the distributions defined in `config.yml`. You can also specify distros explicitly:
72+
The script auto-detects EOL distros by comparing `aptly repo list` output against the DEB distributions defined in `config.yml`. You can also specify distros explicitly (DEB-only — RPM distros belong to the YUM script):
7373

7474
~~~bash
75-
./internal-scripts/ci-cd/archive/migrate-apt-to-archive.rb --dry-run --distros centos-8,debian-9
75+
./internal-scripts/ci-cd/archive/migrate-apt-to-archive.rb --dry-run --distros debian-10,ubuntu-20.04
7676
~~~
7777

7878
**Execute the migration** (removes `--dry-run`):

internal-scripts/ci-cd/archive/migrate-apt-to-archive.rb

Lines changed: 76 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# ./internal-scripts/ci-cd/archive/migrate-apt-to-archive.rb [--dry-run] [--distros centos-8,debian-9]
1212
#
1313
# If --distros is not specified, automatically detects EOL distros by comparing
14-
# the Aptly state against the current environments/ directory.
14+
# the Aptly state against the DEB distributions defined in config.yml.
1515

1616
require_relative '../../../lib/gcloud_storage_lock'
1717
require_relative '../../../lib/ci_workflow_support'
@@ -42,55 +42,66 @@ def main
4242
create_temp_dirs
4343
ensure_gpg_state_isolated
4444
activate_wrappers_bin_dir
45+
initialize_locking
4546
initialize_aptly(@main_aptly_config_path, @main_state_path, @main_state_repo_path)
4647
fetch_and_import_signing_key
4748

48-
print_header 'Downloading main repository state'
49-
version = get_latest_production_repo_version
50-
if version == 0
51-
abort 'ERROR: No production repository exists yet'
52-
end
53-
fetch_main_state(version)
54-
55-
print_header 'Identifying EOL distributions'
56-
eol_distros = identify_eol_distros
57-
if eol_distros.empty?
58-
log_notice 'No EOL distributions found to archive'
59-
exit
60-
end
61-
log_notice "EOL distributions to archive: #{eol_distros.join(', ')}"
62-
63-
print_header 'Fetching existing archive state (if any)'
64-
@archive_version = get_latest_archive_version
65-
if @archive_version > 0
66-
log_notice "Existing archive at version #{@archive_version}, will merge"
67-
fetch_archive_state(@archive_version)
68-
else
69-
log_notice 'No existing archive — creating fresh'
70-
end
71-
72-
print_header 'Creating archive repository'
73-
create_archive_state(eol_distros)
74-
75-
print_header 'Trimming main repository state'
76-
trim_main_state(eol_distros)
49+
eol_distros = nil
50+
version = nil
51+
52+
begin
53+
synchronize do
54+
print_header 'Downloading main repository state'
55+
version = get_latest_production_repo_version
56+
if version == 0
57+
abort 'ERROR: No production repository exists yet'
58+
end
59+
fetch_main_state(version)
60+
61+
print_header 'Identifying EOL distributions'
62+
eol_distros = identify_eol_distros
63+
if eol_distros.empty?
64+
log_notice 'No EOL distributions found to archive'
65+
return
66+
end
67+
log_notice "EOL distributions to archive: #{eol_distros.join(', ')}"
68+
69+
print_header 'Fetching existing archive state (if any)'
70+
@archive_version = get_latest_archive_version
71+
if @archive_version > 0
72+
log_notice "Existing archive at version #{@archive_version}, will merge"
73+
fetch_archive_state(@archive_version)
74+
else
75+
log_notice 'No existing archive — creating fresh'
76+
end
77+
78+
print_header 'Creating archive repository'
79+
create_archive_state(eol_distros)
80+
check_lock_health
81+
82+
print_header 'Trimming main repository state'
83+
trim_main_state(eol_distros)
84+
check_lock_health
85+
86+
if @dry_run
87+
log_notice 'DRY RUN — not uploading changes'
88+
print_summary(eol_distros, version)
89+
return
90+
end
91+
92+
print_header 'Uploading archive repository'
93+
upload_archive
94+
check_lock_health
95+
96+
print_header 'Uploading trimmed main repository'
97+
upload_main(version)
98+
end
7799

78-
if @dry_run
79-
log_notice 'DRY RUN — not uploading changes'
80-
print_summary(eol_distros, version)
81-
exit
100+
print_header 'Success!'
101+
print_summary(eol_distros, version) if eol_distros && !eol_distros.empty?
102+
ensure
103+
cleanup
82104
end
83-
84-
print_header 'Uploading archive repository'
85-
upload_archive
86-
87-
print_header 'Uploading trimmed main repository'
88-
upload_main(version)
89-
90-
print_header 'Success!'
91-
print_summary(eol_distros, version)
92-
93-
cleanup
94105
end
95106

96107
private
@@ -161,6 +172,22 @@ def activate_wrappers_bin_dir
161172
ENV['PATH'] = "#{@wrappers_bin_dir}:#{ENV['PATH']}"
162173
end
163174

175+
def initialize_locking
176+
@lock = GCloudStorageLock.new(url: lock_url)
177+
end
178+
179+
def lock_url
180+
"gs://#{ENV['PRODUCTION_REPO_BUCKET_NAME']}/locks/apt"
181+
end
182+
183+
def synchronize(&block)
184+
@lock.synchronize(&block)
185+
end
186+
187+
def check_lock_health
188+
abort 'ERROR: lock is unhealthy. Aborting operation' if !@lock.healthy?
189+
end
190+
164191
def initialize_aptly(config_path, state_path, repo_path)
165192
log_notice "Creating Aptly config: #{config_path}"
166193
File.open(config_path, 'w:utf-8') do |f|
@@ -297,12 +324,13 @@ def create_archive_state(eol_distros)
297324
archive_pool = "#{@archive_state_path}/pool"
298325
if File.exist?(main_pool)
299326
if File.exist?(archive_pool)
300-
# Merge: copy new pool files that don't already exist
327+
# Merge: copy new pool files that don't already exist (-n: no-clobber).
328+
# Real I/O errors must surface — only no-clobber skips are benign.
301329
run_bash(
302-
sprintf('cp -rn %s/* %s/ 2>/dev/null || true',
330+
sprintf('cp -rn %s/* %s/',
303331
Shellwords.escape(main_pool),
304332
Shellwords.escape(archive_pool)),
305-
log_invocation: false, check_error: false, pipefail: false
333+
log_invocation: true, check_error: true, pipefail: false
306334
)
307335
else
308336
FileUtils.cp_r(main_pool, @archive_state_path)

internal-scripts/ci-cd/archive/migrate-yum-to-archive.rb

Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
# ./internal-scripts/ci-cd/archive/migrate-yum-to-archive.rb [--dry-run] [--distros centos-8]
1212
#
1313
# If --distros is not specified, automatically detects EOL distros by comparing
14-
# the repo contents against the current environments/ directory.
14+
# the repo contents against the RPM distributions defined in config.yml.
1515

16+
require_relative '../../../lib/gcloud_storage_lock'
1617
require_relative '../../../lib/ci_workflow_support'
1718
require_relative '../../../lib/shell_scripting_support'
1819
require_relative '../../../lib/publishing_support'
@@ -34,50 +35,59 @@ def main
3435
print_header 'Initializing'
3536
load_config
3637
create_temp_dirs
38+
initialize_locking
3739
fetch_and_import_signing_key
3840

39-
print_header 'Downloading main repository'
40-
version = get_latest_production_repo_version
41-
if version == 0
42-
abort 'ERROR: No production repository exists yet'
43-
end
44-
fetch_main_repo(version)
45-
46-
print_header 'Identifying EOL distributions'
47-
eol_distros = identify_eol_distros
48-
if eol_distros.empty?
49-
log_notice 'No EOL distributions found to archive'
50-
exit
51-
end
52-
log_notice "EOL distributions to archive: #{eol_distros.join(', ')}"
53-
54-
print_header 'Fetching existing archive (if any)'
55-
@archive_version = get_latest_archive_version
56-
if @archive_version > 0
57-
log_notice "Existing archive at version #{@archive_version}, will merge"
58-
fetch_archive_repo(@archive_version)
59-
else
60-
log_notice 'No existing archive — creating fresh'
61-
@archive_repo_path = "#{@temp_dir}/archive-repo"
62-
Dir.mkdir(@archive_repo_path)
63-
end
41+
eol_distros = nil
42+
version = nil
43+
44+
begin
45+
synchronize do
46+
print_header 'Downloading main repository'
47+
version = get_latest_production_repo_version
48+
if version == 0
49+
abort 'ERROR: No production repository exists yet'
50+
end
51+
fetch_main_repo(version)
52+
53+
print_header 'Identifying EOL distributions'
54+
eol_distros = identify_eol_distros
55+
if eol_distros.empty?
56+
log_notice 'No EOL distributions found to archive'
57+
return
58+
end
59+
log_notice "EOL distributions to archive: #{eol_distros.join(', ')}"
60+
61+
print_header 'Fetching existing archive (if any)'
62+
@archive_version = get_latest_archive_version
63+
if @archive_version > 0
64+
log_notice "Existing archive at version #{@archive_version}, will merge"
65+
fetch_archive_repo(@archive_version)
66+
else
67+
log_notice 'No existing archive — creating fresh'
68+
@archive_repo_path = "#{@temp_dir}/archive-repo"
69+
Dir.mkdir(@archive_repo_path)
70+
end
71+
72+
if @dry_run
73+
log_notice 'DRY RUN — not uploading changes'
74+
print_summary(eol_distros, version)
75+
return
76+
end
77+
78+
print_header 'Uploading EOL distros to archive'
79+
upload_archive(eol_distros)
80+
check_lock_health
81+
82+
print_header 'Removing EOL distros from main repository'
83+
remove_from_main(eol_distros, version)
84+
end
6485

65-
if @dry_run
66-
log_notice 'DRY RUN — not uploading changes'
67-
print_summary(eol_distros, version)
68-
exit
86+
print_header 'Success!'
87+
print_summary(eol_distros, version) if eol_distros && !eol_distros.empty?
88+
ensure
89+
cleanup
6990
end
70-
71-
print_header 'Uploading EOL distros to archive'
72-
upload_archive(eol_distros)
73-
74-
print_header 'Removing EOL distros from main repository'
75-
remove_from_main(eol_distros, version)
76-
77-
print_header 'Success!'
78-
print_summary(eol_distros, version)
79-
80-
cleanup
8191
end
8292

8393
private
@@ -101,6 +111,22 @@ def create_temp_dirs
101111
Dir.mkdir(@local_repo_path)
102112
end
103113

114+
def initialize_locking
115+
@lock = GCloudStorageLock.new(url: lock_url)
116+
end
117+
118+
def lock_url
119+
"gs://#{ENV['PRODUCTION_REPO_BUCKET_NAME']}/locks/yum"
120+
end
121+
122+
def synchronize(&block)
123+
@lock.synchronize(&block)
124+
end
125+
126+
def check_lock_health
127+
abort 'ERROR: lock is unhealthy. Aborting operation' if !@lock.healthy?
128+
end
129+
104130
def fetch_and_import_signing_key
105131
log_notice 'Fetching and importing signing key'
106132
File.open(@signing_key_path, 'wb') do |f|

0 commit comments

Comments
 (0)