diff --git a/debs-manifest.conf b/debs-manifest.conf index 6129fb3..4fd8c93 100644 --- a/debs-manifest.conf +++ b/debs-manifest.conf @@ -136,3 +136,22 @@ grub2-xcat=2.12-1 elilo-xcat=3.14-6 xnba-undi=1.21.1-1 xcat-genesis-base=2.* + +# [shared] is NOT a build target. It describes the ONE pool the OpenEmbedded Genesis release is +# published into (pool/main/xcat-genesis-openembedded), which every suite indexes and which no +# [-] section covers. Without it nothing asserted the published pool was COMPLETE: +# its packages were checked only as they were copied, against the release checksums, so a pool that +# lost one afterwards would publish quietly. +# +# The pin tracks the paired xcat-core, like xcat-genesis-base ('2.*'): these are built FROM +# xcat-core, so the version walks with it. A glob, not an EVR floor -- pins in THIS manifest are +# exact-or-glob (version_matches); the '>= epoch:version-release' form is an EL-side feature of +# packages-manifest.conf. The deb names carry the architecture with '_' folded to '-'. +[shared] +xcat-genesis-openembedded-x86=2.* +xcat-genesis-openembedded-x86-64=2.* +xcat-genesis-openembedded-ppc64=2.* +xcat-genesis-openembedded-ppc64le=2.* +xcat-genesis-openembedded-armv7hf=2.* +xcat-genesis-openembedded-aarch64=2.* +xcat-genesis-openembedded-riscv64=2.* diff --git a/sbuild-all.pl b/sbuild-all.pl index b843120..0f566c8 100755 --- a/sbuild-all.pl +++ b/sbuild-all.pl @@ -1019,9 +1019,31 @@ sub install_genesis_release_debs { or die "FATAL: cannot set mode on $pool/$base: $!\n"; XCAT::GenesisRelease::verify_release_file($genesis_release_checksums, $relative, "$pool/$base"); } + verify_shared_pool($pool); return scalar(@files); } +# verify_shared_pool($pool): assert the shared Genesis pool carries every package the manifest's +# [shared] section requires, at a version satisfying its pin. [shared] is not a build target: it +# describes the one pool every suite indexes, which no [-] section covers. Run on +# the SIDE TREE, before it is swapped into place, so an incomplete pool is never published. +# Completeness only -- the release checksums cover the bytes. +sub verify_shared_pool { + my ($pool) = @_; + my %req = %{ $MANIFEST{shared} // {} }; + die "FATAL: no [shared] section in $manifest -- cannot verify the shared Genesis pool\n" + if !%req; + my @names = sort keys %req; + my %present = map { $_ => deb_version($pool, $_) } @names; + my @problems = verify_repo_packages(\%req, \%present); + if (@problems) { + print " - $_\n" for @problems; + die "FATAL: shared Genesis pool INCOMPLETE at $pool (" . scalar(@problems) . " problem(s))\n"; + } + print " [verify-repo] shared pool complete: " . scalar(@names) . " packages present\n"; + return 1; +} + sub assemble_into { my ($dir, $expect) = @_; if ($genesis_release ne '') { diff --git a/t/genesis_openembedded_consumer.t b/t/genesis_openembedded_consumer.t index 6ff5af2..964840f 100644 --- a/t/genesis_openembedded_consumer.t +++ b/t/genesis_openembedded_consumer.t @@ -74,7 +74,7 @@ SKIP: { } SKIP: { - skip 'APT repository tools are not installed', 58 + skip 'APT repository tools are not installed', 59 unless $^O eq 'linux' && command_exists('dpkg-deb') && command_exists('apt-ftparchive'); @@ -279,8 +279,13 @@ sub run_apt_consumer { unless ($manifest) { $manifest = "$args{output}/manifest.conf"; make_path($args{output}); + # plus the shipped [shared] section verbatim: publishing a release gates the shared pool + # against it, and a manifest without it is refused rather than silently ungated. + my $shipped = read_binary("$repo_root/debs-manifest.conf"); + my ($shared) = $shipped =~ /^(\[shared\]\n(?:[^\[]*))/ms; + BAIL_OUT('debs-manifest.conf has no [shared] section') unless $shared; write_binary($manifest, - join('', map { "[$_-amd64]\nxcat-genesis-base=*\n" } @APT_SUITES)); + join('', map { "[$_-amd64]\nxcat-genesis-base=*\n" } @APT_SUITES) . "\n" . $shared); } return run_capture( $args{log}, @@ -370,6 +375,8 @@ sub test_deb_consumer { } architectures(); is_deeply([ genesis_deb_names($shared_pool) ], \@expected_packages, 'shared APT pool contains one complete Genesis release'); + like(read_binary($log), qr/\[verify-repo\] shared pool complete: 7 packages present/, + 'the shared pool is gated against the manifest\'s [shared] section'); my @suite_packages; for my $codename (@APT_SUITES) { push(@suite_packages, map { "$codename/$_" } diff --git a/t/sbuild-all.t b/t/sbuild-all.t index 343b854..8f2075e 100644 --- a/t/sbuild-all.t +++ b/t/sbuild-all.t @@ -324,8 +324,11 @@ SKIP: { # ---- manifest <-> reality consistency (concern #3 + doc drift guard) ----------------------------- { my %m = read_manifest("$RealBin/../debs-manifest.conf"); - my @targets = sort keys %m; + # Not every section is a build target: [shared] describes the ONE pool the OpenEmbedded Genesis + # release is published into, which no builder produces. Target sections are -. + my @targets = grep { /^[a-z]+-(?:amd64|ppc64el)$/ } sort keys %m; cmp_ok(scalar(@targets), '>=', 8, 'manifest has all 8 codename x arch target sections'); + ok(!grep({ $_ eq 'shared' } @targets), 'the shared-pool section is not treated as a build target'); # goconserver is a compiled dep built for EVERY target (both arches, all codenames). my @miss_go = grep { !exists $m{$_}{'goconserver'} } @targets;