From 17bd87c53c165e17aa2b289f17e5a3f4f773ff94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sat, 22 Aug 2026 02:39:38 -0300 Subject: [PATCH] fix(genesis): harden staged release publication --- build-apt-repo.sh | 17 ++---- genesis-openembedded/package | 2 +- .../rpm/xCAT-genesis-openembedded.spec | 2 +- genesis-openembedded/verify-release | 15 ++++-- mockbuild-all.pl | 53 +++++++++++++------ 5 files changed, 55 insertions(+), 34 deletions(-) diff --git a/build-apt-repo.sh b/build-apt-repo.sh index 67f6102..55ef407 100755 --- a/build-apt-repo.sh +++ b/build-apt-repo.sh @@ -120,12 +120,11 @@ command -v gpg >/dev/null 2>&1 \ || die "gpg not found. Install: sudo apt-get install gnupg" command -v cmp >/dev/null 2>&1 \ || die "cmp not found. Install: sudo apt-get install diffutils" -command -v dpkg-deb >/dev/null 2>&1 \ - || die "dpkg-deb not found. Install: sudo apt-get install dpkg" -command -v sha256sum >/dev/null 2>&1 \ - || die "sha256sum not found. Install: sudo apt-get install coreutils" - if [[ -n "$GENESIS_RELEASE" ]]; then + command -v dpkg-deb >/dev/null 2>&1 \ + || die "dpkg-deb not found. Install: sudo apt-get install dpkg" + command -v sha256sum >/dev/null 2>&1 \ + || die "sha256sum not found. Install: sudo apt-get install coreutils" GENESIS_CHECKSUMS=$(mktemp "${TMPDIR:-/tmp}/xcat-genesis-checksums.XXXXXX") cp -- "$GENESIS_RELEASE/SHA256SUMS" "$GENESIS_CHECKSUMS" "$GENESIS_VERIFIER" --complete --format deb "$GENESIS_RELEASE" @@ -198,12 +197,7 @@ copy_genesis_deb() { name=$(basename "$source") destination="$directory/$name" relative="deb/$name" - if [[ -e "$destination" ]]; then - cmp -s "$source" "$destination" \ - || die "Package collision with different content: $destination" - else - cp -- "$source" "$destination" - fi + cp -- "$source" "$destination" expected=$(awk -v path="$relative" '$2 == path { print $1 }' "$GENESIS_CHECKSUMS") [[ -n "$expected" ]] || die "Missing verified checksum for $relative" actual=$(sha256sum "$destination") @@ -225,7 +219,6 @@ for ver in "${SELECTED_VERS[@]}"; do copy_deb "$deb" "$dst" done if [[ -n "$GENESIS_RELEASE" ]]; then - rm -f -- "$dst"/xcat-genesis-openembedded-*.deb for deb in "$GENESIS_RELEASE"/deb/*.deb; do copy_genesis_deb "$deb" "$dst" done diff --git a/genesis-openembedded/package b/genesis-openembedded/package index cf212a3..977cee8 100755 --- a/genesis-openembedded/package +++ b/genesis-openembedded/package @@ -112,7 +112,7 @@ sub _build_rpm { my $archive = "$rpm_top/SOURCES/$source_name.tar.gz"; _run( - 'tar', '--sort=name', '--owner=0', '--group=0', + 'tar', '--sort=name', '--owner=0', '--group=0', '--numeric-owner', "--mtime=\@$source_date_epoch", '--use-compress-program=gzip -n', '-cf', $archive, '-C', dirname($source_root), basename($source_root), ); diff --git a/genesis-openembedded/rpm/xCAT-genesis-openembedded.spec b/genesis-openembedded/rpm/xCAT-genesis-openembedded.spec index 1e36de0..689b25a 100644 --- a/genesis-openembedded/rpm/xCAT-genesis-openembedded.spec +++ b/genesis-openembedded/rpm/xCAT-genesis-openembedded.spec @@ -3,7 +3,6 @@ Name: xCAT-genesis-openembedded-%{genesis_arch} Version: %{?version}%{!?version:0} Release: %{?release}%{!?release:1} -Epoch: 1 Summary: xCAT OpenEmbedded Genesis netboot image License: Various URL: https://xcat.org/ @@ -24,6 +23,7 @@ service actions before a node boots its installed operating system. rm -rf %{buildroot} install -d -m 0755 %{buildroot}/opt/xcat/share/xcat/netboot/genesis-openembedded/%{genesis_arch} install -m 0644 image/* %{buildroot}/opt/xcat/share/xcat/netboot/genesis-openembedded/%{genesis_arch}/ +# Keep this path stable across RPM build hosts. %%{_docdir} differs on SUSE. install -d -m 0755 %{buildroot}/usr/share/doc/%{name} install -m 0644 xcat-core-revision %{buildroot}/usr/share/doc/%{name}/ diff --git a/genesis-openembedded/verify-release b/genesis-openembedded/verify-release index c555e1e..623a748 100755 --- a/genesis-openembedded/verify-release +++ b/genesis-openembedded/verify-release @@ -61,14 +61,23 @@ sub _verify_rpm { my ($path, $expected_arch, $expected_source) = @{$package}; my $metadata = _capture( 'rpm', '-qp', '--qf', - "%{NAME}\t%{VERSION}\t%{RELEASE}\t%{ARCH}\t%{EPOCHNUM}\t%{SOURCERPM}\n", + "%{NAME}\t%{VERSION}\t%{RELEASE}\t%{ARCH}\t%{EPOCHNUM}\t%{SOURCERPM}" + . "\t%{BUILDHOST}\t%{BUILDTIME}\n", $path, ); my $expected = join( "\t", $name, $manifest->{xcat_version}, $manifest->{xcat_release}, - $expected_arch, '1', $expected_source, + $expected_arch, '0', $expected_source, 'xcat-release', + $manifest->{source_date_epoch}, ); die "Unexpected RPM identity for $path: $metadata\n" unless $metadata eq $expected; + for my $relationship (qw(conflicts obsoletes)) { + my $value = _capture('rpm', '-qp', "--$relationship", $path); + die "Unexpected RPM $relationship for $path: $value\n" if $value ne ''; + } + my $provides = _capture('rpm', '-qp', '--provides', $path); + die "Legacy RPM relationship in $path: $provides\n" + if $provides =~ /xCAT-genesis-base/; } } @@ -87,7 +96,7 @@ sub _verify_deb { 'all', ); die "Unexpected DEB identity for $path: $metadata\n" unless $metadata eq $expected; - for my $field (qw(Conflicts Replaces)) { + for my $field (qw(Breaks Conflicts Provides Replaces)) { my $value = _capture('dpkg-deb', '-f', $path, $field); die "Unexpected DEB $field for $path: $value\n" if $value ne ''; } diff --git a/mockbuild-all.pl b/mockbuild-all.pl index e7917c5..5c26d67 100755 --- a/mockbuild-all.pl +++ b/mockbuild-all.pl @@ -466,7 +466,6 @@ push @collect_roots, @extra_collect_dirs; my @srpm_collect_roots = (!$skip_xcat) ? uniq(@collect_roots, $xcat_srpms_dir) : uniq(@collect_roots); -@srpm_collect_roots = uniq(@srpm_collect_roots); if ($genesis_release && !$dry_run) { remove_genesis_packages($repo_dir, 0); @@ -609,11 +608,21 @@ sub deploy_target { print_step("Deploy $tgt -> $dest"); return if $dry_run; make_path($dest); - remove_genesis_packages($dest, 0) if $genesis_release; for my $rpm (bsd_glob("$src/*.rpm")) { next if $rpm =~ /\.src\.rpm$/; - copy($rpm, "$dest/" . basename($rpm)) - or die "Failed to copy $rpm -> $dest: $!\n"; + my $destination = "$dest/" . basename($rpm); + my $temporary = "$destination.$$.new"; + die "Deployment staging file already exists: $temporary\n" + if -e $temporary || -l $temporary; + copy($rpm, $temporary) + or die "Failed to stage $rpm -> $temporary: $!\n"; + rename($temporary, $destination) + or die "Failed to publish $temporary -> $destination: $!\n"; + } + if ($genesis_release) { + my @keep = map { basename($_) } genesis_release_files('rpm'); + remove_genesis_packages($dest, 0, \@keep); + verify_genesis_release_packages('rpm', $dest); } assert_required_deps($dest); sign_and_index_repo($dest); @@ -935,12 +944,14 @@ sub assert_required_deps { } sub remove_genesis_packages { - my ($directory, $source) = @_; + my ($directory, $source, $keep_names) = @_; return unless -d $directory; + my %keep = map { $_ => 1 } @{ $keep_names // [] }; opendir(my $dh, $directory) or die "Cannot read $directory: $!\n"; my @names = grep { /^xCAT-genesis-openembedded-.*\.rpm\z/ } readdir($dh); closedir($dh) or die "Cannot close $directory: $!\n"; for my $name (@names) { + next if $keep{$name}; my $path = "$directory/$name"; next if $source && $path !~ /\.src\.rpm\z/; next if !$source && $path =~ /\.src\.rpm\z/; @@ -953,11 +964,7 @@ sub install_genesis_release_packages { remove_genesis_packages($destination_root, $prefix eq 'srpm'); my $copied = 0; - for my $relative ( - sort grep { /^\Q$prefix\E\/xCAT-genesis-openembedded-[^\/]+\.rpm\z/ } - keys %{$genesis_release_checksums} - ) - { + for my $relative (genesis_release_files($prefix)) { my $source = "$genesis_release/$relative"; my $destination = "$destination_root/" . basename($relative); copy($source, $destination) @@ -969,6 +976,24 @@ sub install_genesis_release_packages { return $copied; } +sub genesis_release_files { + my ($prefix) = @_; + return sort grep { + /^\Q$prefix\E\/xCAT-genesis-openembedded-[^\/]+\.rpm\z/ + } keys %{$genesis_release_checksums}; +} + +sub verify_genesis_release_packages { + my ($prefix, $destination_root) = @_; + my @files = genesis_release_files($prefix); + die "Genesis release has no $prefix packages\n" unless @files; + for my $relative (@files) { + my $destination = "$destination_root/" . basename($relative); + verify_release_file($genesis_release_checksums, $relative, $destination); + } + return scalar(@files); +} + sub assert_genesis_release_copied { my ($binary_directory, $source_directory) = @_; for my $entry ( @@ -976,13 +1001,7 @@ sub assert_genesis_release_copied { [ 'srpm', $source_directory ], ) { my ($prefix, $destination_root) = @{$entry}; - for my $relative ( - sort grep { /^\Q$prefix\E\/[^\/]+\.rpm\z/ } keys %{$genesis_release_checksums} - ) - { - my $destination = "$destination_root/" . basename($relative); - verify_release_file($genesis_release_checksums, $relative, $destination); - } + verify_genesis_release_packages($prefix, $destination_root); } }