2
0
mirror of https://github.com/xcat2/xcat-dep.git synced 2026-09-12 04:26:25 +00:00

feat(xcat-dep): gate the shared Genesis pool on a manifest section of its own

Every package in a suite pool is gated against debs-manifest.conf, but the
OpenEmbedded Genesis release is published into pool/main/xcat-genesis-openembedded
-- one pool every suite indexes, described by no [<codename>-<arch>] section. So
nothing asserted the published pool was complete: its packages were checked only
as they were copied, against the release checksums, and a pool that lost one
afterwards would publish quietly.

[shared] describes that pool -- all seven architectures, pinned '2.*' like
xcat-genesis-base, because they are built FROM xcat-core and their version walks
with it. A glob rather than an EVR floor: pins in this manifest are exact-or-glob
(version_matches), and the '>= epoch:version-release' form is an EL-side feature
of packages-manifest.conf. verify_shared_pool runs on the side tree before the
swap, so an incomplete pool is never published.

[shared] is not a build target, so the manifest now has two kinds of section. No
code iterates sections blindly, but t/sbuild-all.t did -- twice -- so it now
selects <codename>-<arch> sections and asserts the shared-pool section is not
treated as a target. The consumer fixtures carry the shipped [shared] section
verbatim: publishing a release against a manifest that lacks it is refused, not
silently ungated.

Verified the gate fails when removed, and when [shared] and the pool disagree.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
Daniel Hilst
2026-08-26 18:31:59 -03:00
parent 73ad97d5a2
commit a35343f60e
4 changed files with 54 additions and 3 deletions
+19
View File
@@ -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
# [<codename>-<arch>] 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.*
+22
View File
@@ -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 [<codename>-<arch>] 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 '') {
+9 -2
View File
@@ -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/$_" }
+4 -1
View File
@@ -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 <codename>-<arch>.
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;