mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-10-21 07:08:12 +00:00
Merge master into fix/remove-xcat-genesis-builder
Master merged #7835, which changed the Genesis base files under xCAT-genesis-builder/, the directory this branch removes. The merge keeps master's content at this branch's paths: - GenesisBuildRoot.pm, genesis_payload_verification.t and genesis_ubuntu_build_root.t take master's newer versions, pointed at xCAT-genesis-base/. - verify-genesis-payload and GenesisPayload.pm are identical on both sides; the copies at the old path are dropped. - buildrpms.pl keeps stage_genesis_base_sources(), which already stages what master added inline. - builddebs.pl and the new genesis_deb_per_codename.t now use xCAT-genesis-base/. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
@@ -36,6 +36,9 @@ our @EXPORT_OK = qw(
|
||||
rewrite_file write_script read_line
|
||||
buildinfo_text
|
||||
targetarch_from_target
|
||||
genesis_chroot_name genesis_target_arch genesis_build_plan
|
||||
genesis_log_errors genesis_log_deny_rules deb_belongs_to_dist
|
||||
genesis_dists genesis_dist_reason
|
||||
);
|
||||
|
||||
# Both builders echo the commands they run under --verbose. Set once, after
|
||||
@@ -740,4 +743,135 @@ sub targetarch_from_target {
|
||||
return $parts[-1];
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ Genesis --
|
||||
#
|
||||
# The Genesis image carries the kernel and the kernel modules of the release it boots,
|
||||
# because dracut copies them out of the root it runs in. So the Ubuntu Genesis deb is built
|
||||
# once per Ubuntu codename, inside that codename's chroot. A single build on the build host
|
||||
# gives every codename the build host's kernel.
|
||||
|
||||
# genesis_chroot_name: the schroot chroot that builds the Genesis deb for one codename.
|
||||
#
|
||||
# Same name xcat-dep's sbuild-all.pl ensure_chroots creates, so both repositories build in
|
||||
# the same chroots and neither has to bootstrap a second set.
|
||||
sub genesis_chroot_name {
|
||||
my ($codename, $arch) = @_;
|
||||
die "genesis_chroot_name: a codename is required\n"
|
||||
unless defined $codename && length $codename;
|
||||
die "genesis_chroot_name: an architecture is required\n"
|
||||
unless defined $arch && length $arch;
|
||||
return "$codename-$arch-sbuild";
|
||||
}
|
||||
|
||||
# genesis_target_arch: the directory xCAT reads the Genesis image from, for a deb
|
||||
# architecture. mknb reads /opt/xcat/share/xcat/netboot/genesis/<arch>, and that name is
|
||||
# the rpm architecture, not the deb one.
|
||||
my %GENESIS_TARGET_ARCH = (
|
||||
amd64 => 'x86_64',
|
||||
ppc64el => 'ppc64',
|
||||
);
|
||||
|
||||
sub genesis_target_arch {
|
||||
my ($arch) = @_;
|
||||
my $target = $GENESIS_TARGET_ARCH{ $arch // '' };
|
||||
die "genesis_target_arch: no Genesis image directory for '" . ($arch // '') . "'\n"
|
||||
unless $target;
|
||||
return $target;
|
||||
}
|
||||
|
||||
# genesis_build_plan: one Genesis build per codename, for one architecture.
|
||||
#
|
||||
# The set of codenames comes from the caller, so a pipeline builds exactly the releases it
|
||||
# publishes.
|
||||
sub genesis_build_plan {
|
||||
my ($dists, $arch) = @_;
|
||||
my @dists = @{ $dists || [] };
|
||||
die "genesis_build_plan: at least one codename is required\n" unless @dists;
|
||||
die "genesis_build_plan: an architecture is required\n"
|
||||
unless defined $arch && length $arch;
|
||||
|
||||
my %seen;
|
||||
return map {
|
||||
{
|
||||
codename => $_,
|
||||
arch => $arch,
|
||||
chroot => genesis_chroot_name($_, $arch),
|
||||
package => "xcat-genesis-base-$arch",
|
||||
target => genesis_target_arch($arch),
|
||||
}
|
||||
} grep { !$seen{$_}++ } @dists;
|
||||
}
|
||||
|
||||
# genesis_log_deny_rules / genesis_log_errors: what a Genesis build log says when the build
|
||||
# failed but the exit status did not.
|
||||
#
|
||||
# dracut reports a command it cannot install with a FAILED: line and returns 0. apt has the
|
||||
# same shape: a missing package leaves a diagnostic and a zero status behind a `|| true`. So
|
||||
# the log is the gate, not the exit status.
|
||||
my @GENESIS_LOG_DENY = (
|
||||
[ qr/\bFAILED:/ => 'dracut could not install a command' ],
|
||||
[ qr/Cannot find module/ => 'a kernel module the build names is absent' ],
|
||||
[ qr/dracut: Cannot/ => 'dracut refused the request' ],
|
||||
[ qr/command not found/ => 'the build root has no such command' ],
|
||||
[ qr/E: Unable to locate package/ => 'apt has no such package' ],
|
||||
[ qr/Unable to correct problems/ => 'apt could not resolve the build root' ],
|
||||
);
|
||||
|
||||
sub genesis_log_deny_rules { return @GENESIS_LOG_DENY; }
|
||||
|
||||
# The package built once per codename. It is the only one: it carries the kernel and the kernel
|
||||
# modules of the root that built it.
|
||||
my $GENESIS_IMAGE_DEB = qr{\Axcat-genesis-base-};
|
||||
|
||||
# Releases whose stock chroot cannot build that package, and why. focal ships debhelper 12 and
|
||||
# xCAT-genesis-base declares debhelper-compat (= 13), so sbuild stops on the build dependencies
|
||||
# before dracut runs.
|
||||
my %GENESIS_DIST_UNSUPPORTED = (
|
||||
focal => 'debhelper 12 cannot satisfy debhelper-compat (= 13)',
|
||||
);
|
||||
|
||||
# genesis_dists: the releases of @dists a Genesis image can be built on.
|
||||
sub genesis_dists {
|
||||
my (@dists) = @_;
|
||||
return grep { !exists $GENESIS_DIST_UNSUPPORTED{$_} } @dists;
|
||||
}
|
||||
|
||||
# genesis_dist_reason: why a release was left out, or undef.
|
||||
sub genesis_dist_reason { return $GENESIS_DIST_UNSUPPORTED{ $_[0] // '' }; }
|
||||
|
||||
# deb_belongs_to_dist: whether a built .deb may be published into one release.
|
||||
#
|
||||
# Almost every xcat-core deb is Architecture:all and the same file serves every release, so the
|
||||
# answer is yes. The Genesis image is not: it is built per codename and carries that codename in
|
||||
# its version (2.19.0-snap...~noble). Publishing all three into every suite lets apt serve the
|
||||
# newest, which is the image of another release.
|
||||
#
|
||||
# Only that package is asked. A ~ in a version is Debian's prerelease separator before it is
|
||||
# anything else, and --release takes whatever the caller gives it, so reading every ~ as a
|
||||
# codename drops a whole `--release 1~rc1` build from every suite.
|
||||
sub deb_belongs_to_dist {
|
||||
my ($deb, $dist) = @_;
|
||||
return 1 unless defined $deb && defined $dist && $dist ne '';
|
||||
my $base = basename($deb);
|
||||
my ($name) = $base =~ /\A([^_]+)_/;
|
||||
return 1 unless defined $name && $name =~ $GENESIS_IMAGE_DEB;
|
||||
return 1 unless $base =~ /_[^_]*~([A-Za-z0-9.]+)_[^_]*\.deb\z/;
|
||||
return $1 eq $dist ? 1 : 0;
|
||||
}
|
||||
|
||||
sub genesis_log_errors {
|
||||
my ($text) = @_;
|
||||
return () unless defined $text && length $text;
|
||||
my @found;
|
||||
for my $line (split /\n/, $text) {
|
||||
for my $rule (@GENESIS_LOG_DENY) {
|
||||
my ($pattern, $why) = @{$rule};
|
||||
next unless $line =~ $pattern;
|
||||
push @found, { line => $line, why => $why };
|
||||
last;
|
||||
}
|
||||
}
|
||||
return @found;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
+202
-13
@@ -8,9 +8,10 @@
|
||||
# The central fact this design rests on: xcat-core debs are Perl. They are byte-identical
|
||||
# for every Ubuntu release, so they are built ONCE and the same files are published into
|
||||
# every codename. Only xCAT, xCATsn and xCAT-genesis-scripts carry an architecture, and
|
||||
# even there the difference is packaging metadata, not compiled output. That is why this
|
||||
# needs no sbuild and no per-codename chroot -- unlike xcat-dep, whose compiled packages
|
||||
# genuinely differ per release.
|
||||
# even there the difference is packaging metadata, not compiled output.
|
||||
#
|
||||
# The Genesis image is the one exception. --genesis builds it once per codename, inside
|
||||
# that codename's sbuild chroot.
|
||||
use strict;
|
||||
use warnings;
|
||||
use feature 'say';
|
||||
@@ -38,6 +39,8 @@ use XCAT::BuildUtils qw(
|
||||
reprepro_distributions reprepro_options
|
||||
lock_id_for take_build_lock sh_quote
|
||||
sh sh_or_die usage rewrite_file write_script read_line buildinfo_text
|
||||
genesis_build_plan genesis_log_errors deb_belongs_to_dist
|
||||
genesis_dists genesis_dist_reason
|
||||
);
|
||||
|
||||
# The xcat-core packages that ship as debs. xCAT-openbmc-py, xCAT-rmc and xCAT-release
|
||||
@@ -61,10 +64,14 @@ my @PACKAGES = qw(
|
||||
my @DISTS = default_dists();
|
||||
|
||||
my %opts;
|
||||
my (@cli_packages, @cli_dists);
|
||||
my (@cli_packages, @cli_dists, @cli_genesis_dists);
|
||||
GetOptions(
|
||||
"dist=s@" => \@cli_dists,
|
||||
"package=s@" => \@cli_packages,
|
||||
"genesis" => \$opts{genesis},
|
||||
"genesis-only" => \$opts{genesis_only},
|
||||
"genesis-dist=s@" => \@cli_genesis_dists,
|
||||
"genesis-arch=s" => \$opts{genesis_arch},
|
||||
"dest=s" => \$opts{dest},
|
||||
"builddir=s" => \$opts{builddir},
|
||||
"release=s" => \$opts{release},
|
||||
@@ -83,6 +90,23 @@ $opts{packages} = @cli_packages ? \@cli_packages : \@PACKAGES;
|
||||
$opts{dists} = @cli_dists ? \@cli_dists : \@DISTS;
|
||||
$opts{gpg_key_name} //= 'xCAT Signing Key';
|
||||
|
||||
# The Genesis step is off unless it is asked for, so today's runs keep their behaviour.
|
||||
$opts{genesis} = 1 if $opts{genesis_only};
|
||||
# A release named on the command line is built as asked. The default list is the one the rest
|
||||
# of the build uses, and not every release on it can build the image, so those are dropped and
|
||||
# named rather than failing the run on its first codename.
|
||||
if (@cli_genesis_dists) {
|
||||
$opts{genesis_dists} = \@cli_genesis_dists;
|
||||
} else {
|
||||
$opts{genesis_dists} = [ genesis_dists($opts{dists}->@*) ];
|
||||
for my $dist ($opts{dists}->@*) {
|
||||
my $why = genesis_dist_reason($dist) or next;
|
||||
say "genesis: leaving out $dist -- $why";
|
||||
}
|
||||
}
|
||||
die "FATAL: --genesis-dist needs --genesis\n" if @cli_genesis_dists && !$opts{genesis};
|
||||
die "FATAL: --genesis-arch needs --genesis\n" if $opts{genesis_arch} && !$opts{genesis};
|
||||
|
||||
for my $pkg ($opts{packages}->@*) {
|
||||
die "FATAL: unknown package '$pkg'. Known: @PACKAGES\n"
|
||||
unless grep { $_ eq $pkg } @PACKAGES;
|
||||
@@ -268,6 +292,132 @@ sub collect_debs {
|
||||
return $moved;
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------- the Genesis deb --
|
||||
#
|
||||
# dracut copies the kernel, the kernel modules and every command out of the root it runs in,
|
||||
# so the Genesis image belongs to the release that built it. One build on the build host
|
||||
# serves every codename with the build host's kernel. This step builds one image per
|
||||
# codename, inside that codename's sbuild chroot.
|
||||
#
|
||||
# The chroots are the ones xcat-dep's sbuild-all.pl creates on the Ubuntu build host
|
||||
# (<codename>-<arch>-sbuild). A session is a disposable overlay, so the next codename starts
|
||||
# from the pristine base.
|
||||
|
||||
# Where the build runs inside the chroot. A directory of its own at the chroot root, because
|
||||
# every other candidate is shared: /build and /opt/xcat-ci-shared are bind mounts the sbuild
|
||||
# chroots give to every session.
|
||||
my $GENESIS_STAGE = '/xcat-genesis-build';
|
||||
|
||||
sub host_deb_arch {
|
||||
my $arch = `dpkg --print-architecture 2>/dev/null` // '';
|
||||
chomp $arch;
|
||||
die "FATAL: dpkg does not report a host architecture\n" unless $arch;
|
||||
return $arch;
|
||||
}
|
||||
|
||||
# begin_chroot_session: start a disposable schroot session and return its id and its root.
|
||||
sub begin_chroot_session {
|
||||
my ($chroot) = @_;
|
||||
my $id = `schroot --begin-session --chroot @{[ sh_quote($chroot) ]} 2>&1` // '';
|
||||
my $rc = $? >> 8;
|
||||
chomp $id;
|
||||
die "FATAL: cannot start a session in chroot '$chroot' (exit $rc): $id\n"
|
||||
. " sbuild-all.pl ensure_chroots creates it; run it on this host first.\n"
|
||||
if $rc != 0 || $id !~ /\A\S+\z/;
|
||||
|
||||
my $root = `schroot --location -c @{[ sh_quote("session:$id") ]} 2>/dev/null` // '';
|
||||
chomp $root;
|
||||
unless ($root && -d $root) {
|
||||
sh("schroot --end-session -c " . sh_quote("session:$id") . " >/dev/null 2>&1");
|
||||
die "FATAL: schroot reports no location for session:$id\n";
|
||||
}
|
||||
return ($id, $root);
|
||||
}
|
||||
|
||||
# genesis_build_log_problems: what the log says went wrong when the exit status did not.
|
||||
#
|
||||
# Report the first few offending lines only, so a build console stays readable. The message
|
||||
# names the log file that has the rest.
|
||||
sub genesis_build_log_problems {
|
||||
my ($logfile) = @_;
|
||||
my $text = -f $logfile ? read_text($logfile) : '';
|
||||
my @errors = genesis_log_errors($text);
|
||||
return '' unless @errors;
|
||||
my $shown = @errors > 10 ? 10 : scalar @errors;
|
||||
my $report = "FATAL: the Genesis build log reports " . scalar(@errors) . " error(s):\n";
|
||||
$report .= " $_->{line}\n ($_->{why})\n" for @errors[0 .. $shown - 1];
|
||||
$report .= " ... " . (@errors - $shown) . " more\n" if @errors > $shown;
|
||||
$report .= " the whole log is at $logfile\n";
|
||||
return $report;
|
||||
}
|
||||
|
||||
sub build_one_genesis_deb {
|
||||
my ($step, $pkgdir) = @_;
|
||||
my ($codename, $chroot) = ($step->{codename}, $step->{chroot});
|
||||
say "Building $step->{package} for $codename in $chroot";
|
||||
|
||||
my ($id, $root) = begin_chroot_session($chroot);
|
||||
my $logfile = "$pkgdir/$step->{package}-$codename.buildlog";
|
||||
my $err;
|
||||
|
||||
eval {
|
||||
# Copy the builder in rather than bind-mount the checkout: the build rewrites
|
||||
# debian/control and debian/changelog, and it must not rewrite them in the tree the
|
||||
# pipeline builds from.
|
||||
my $stage = "$root$GENESIS_STAGE";
|
||||
sh_or_die("rm -rf " . sh_quote($stage) . " && mkdir -p "
|
||||
. sh_quote("$stage/xCAT-genesis-base"),
|
||||
"FATAL: cannot make the build directory in session:$id\n");
|
||||
sh_or_die("cp -a " . sh_quote("$ROOT/xCAT-genesis-base") . "/. "
|
||||
. sh_quote("$stage/xCAT-genesis-base") . "/",
|
||||
"FATAL: cannot copy xCAT-genesis-base into session:$id\n");
|
||||
copy("$ROOT/Version", "$stage/Version")
|
||||
or die "FATAL: cannot copy Version into session:$id: $!\n";
|
||||
write_text("$stage/Release", "$RELEASE\n");
|
||||
|
||||
# --expect-codename is the guard that keeps the image and the root together: the
|
||||
# builder stops when the root it woke up in is not the release it was asked for.
|
||||
my $cmd = join ' ',
|
||||
'schroot', '--run-session', '-c', sh_quote("session:$id"), '-u', 'root', '-d', '/',
|
||||
'--', '/bin/bash', "$GENESIS_STAGE/xCAT-genesis-base/builddeb-genesis-base",
|
||||
'--expect-codename', sh_quote($codename), '--outdir', "$GENESIS_STAGE/out";
|
||||
my $rc = sh("$cmd > " . sh_quote($logfile) . " 2>&1");
|
||||
|
||||
# The log is read whether or not the command failed: dracut exits 0 with FAILED:
|
||||
# lines in its log.
|
||||
my $problems = genesis_build_log_problems($logfile);
|
||||
if ($rc != 0) {
|
||||
die "FATAL: the Genesis build for $codename failed (exit $rc); log: $logfile\n"
|
||||
. $problems;
|
||||
}
|
||||
die $problems if $problems;
|
||||
|
||||
my @debs = glob("$root$GENESIS_STAGE/out/*.deb");
|
||||
die "FATAL: the Genesis build for $codename produced no .deb; log: $logfile\n"
|
||||
unless @debs;
|
||||
for my $deb (@debs) {
|
||||
my $dest = "$pkgdir/" . basename($deb);
|
||||
copy($deb, $dest) or die "FATAL: cannot collect $deb: $!\n";
|
||||
say " $dest";
|
||||
}
|
||||
1;
|
||||
} or $err = $@;
|
||||
|
||||
sh("schroot --end-session -c " . sh_quote("session:$id") . " >/dev/null 2>&1");
|
||||
die $err if $err;
|
||||
return;
|
||||
}
|
||||
|
||||
sub build_genesis_debs {
|
||||
my ($pkgdir) = @_;
|
||||
my $arch = $opts{genesis_arch} || host_deb_arch();
|
||||
my @plan = genesis_build_plan($opts{genesis_dists}, $arch);
|
||||
say "Genesis: @{[ scalar @plan ]} build(s) for $arch: "
|
||||
. join(' ', map { $_->{codename} } @plan);
|
||||
build_one_genesis_deb($_, $pkgdir) for @plan;
|
||||
return scalar @plan;
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------- apt assembly --
|
||||
sub gpg_key_id {
|
||||
my ($name) = @_;
|
||||
@@ -304,6 +454,9 @@ sub assemble_repo {
|
||||
for my $deb (@debs) {
|
||||
# A release that predates an architecture must not be handed its packages.
|
||||
next if basename($deb) =~ /_(\w+)\.deb\z/ && $1 ne 'all' && !$ok{$1};
|
||||
# Nor an image built for another release: the Genesis deb carries the codename it
|
||||
# was built on, because it carries that release's kernel.
|
||||
next unless deb_belongs_to_dist($deb, $dist);
|
||||
sh_or_die("cd " . sh_quote($repodir) . " && reprepro -b ./ includedeb "
|
||||
. sh_quote($dist) . ' ' . sh_quote($deb),
|
||||
"FATAL: reprepro could not add $deb to $dist\n");
|
||||
@@ -369,16 +522,25 @@ unlink glob("$ROOT/*.deb"), glob("$ROOT/*.buildinfo"), glob("$ROOT/*.changes"),
|
||||
say "xcat-core $PKGVER -> $dest";
|
||||
say "releases: @{[ join ' ', $opts{dists}->@* ]}";
|
||||
|
||||
for my $pkg ($opts{packages}->@*) {
|
||||
for my $arch (deb_package_arches($pkg)) {
|
||||
build_package($pkg, $arch, $pkgdir);
|
||||
unless ($opts{genesis_only}) {
|
||||
for my $pkg ($opts{packages}->@*) {
|
||||
for my $arch (deb_package_arches($pkg)) {
|
||||
build_package($pkg, $arch, $pkgdir);
|
||||
}
|
||||
collect_debs($pkg, $pkgdir);
|
||||
}
|
||||
collect_debs($pkg, $pkgdir);
|
||||
}
|
||||
|
||||
my $count = assemble_repo($pkgdir, $repo);
|
||||
write_repo_metadata($repo);
|
||||
say "published $count package(s) into @{[ scalar $opts{dists}->@* ]} release(s) at $repo";
|
||||
build_genesis_debs($pkgdir) if $opts{genesis};
|
||||
|
||||
if ($opts{genesis_only}) {
|
||||
say "Genesis debs are in $pkgdir";
|
||||
}
|
||||
else {
|
||||
my $count = assemble_repo($pkgdir, $repo);
|
||||
write_repo_metadata($repo);
|
||||
say "published $count package(s) into @{[ scalar $opts{dists}->@* ]} release(s) at $repo";
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
@@ -399,8 +561,15 @@ xcat-core packages are Perl. The same binary serves every Ubuntu release, so eac
|
||||
package is built B<once> and the resulting C<.deb> files are published into every
|
||||
codename the repository declares. Only C<xCAT>, C<xCATsn> and C<xCAT-genesis-scripts>
|
||||
carry an architecture, and there the difference is packaging metadata rather than
|
||||
compiled output. Consequently this builder needs no C<sbuild> and no per-codename
|
||||
chroot. (xcat-dep is different: its packages are compiled, so it builds per codename.)
|
||||
compiled output.
|
||||
|
||||
C<xcat-genesis-base> is the exception. dracut copies the kernel, the kernel modules
|
||||
and every command out of the root it runs in, so the Genesis image belongs to the
|
||||
release that built it. With C<--genesis> this builder makes one image per codename,
|
||||
each inside that codename's C<< <codename>-<arch>-sbuild >> schroot -- the chroots
|
||||
xcat-dep's C<sbuild-all.pl> creates on the Ubuntu build host. The build refuses to run
|
||||
in a root of another release, and it reads its own log: dracut reports a command it
|
||||
cannot install with a C<FAILED:> line and still exits 0.
|
||||
|
||||
Replaced C<build-ubunturepo>, removed in 2.19. The GSA upload paths, the C<PROMOTE>/C<PREGA> release
|
||||
flows and the C<-d> xcat-dep repository mode were not carried over: publishing is done
|
||||
@@ -418,6 +587,25 @@ Publish into this release. Repeatable. Defaults to focal, jammy, noble and resol
|
||||
|
||||
Build only this package. Repeatable. Defaults to every xcat-core deb package.
|
||||
|
||||
=item B<--genesis>
|
||||
|
||||
Also build C<xcat-genesis-base-E<lt>archE<gt>>, one C<.deb> per codename, each inside
|
||||
that codename's schroot. Off by default.
|
||||
|
||||
=item B<--genesis-only>
|
||||
|
||||
Build the Genesis debs and nothing else, and assemble no repository. This is what
|
||||
xcat-dep needs: it consumes the debs with C<sbuild-all.pl --genesis-deb>.
|
||||
|
||||
=item B<--genesis-dist>=I<CODENAME>
|
||||
|
||||
Build the Genesis image for this release. Repeatable. Defaults to the C<--dist> list.
|
||||
|
||||
=item B<--genesis-arch>=I<ARCH>
|
||||
|
||||
Build the Genesis image for this Debian architecture. Defaults to the architecture of
|
||||
the build host, because the chroot has to match it.
|
||||
|
||||
=item B<--dest>=I<DIR>
|
||||
|
||||
Write the build under this directory: packages in C<debs/>, the apt repository in
|
||||
@@ -459,5 +647,6 @@ This message.
|
||||
./builddebs.pl
|
||||
./builddebs.pl --dist noble --package perl-xCAT
|
||||
./builddebs.pl --dest /srv/out --gpg-sign --gpg-home /keys/xcat-gpg-home
|
||||
./builddebs.pl --genesis-only --genesis-dist jammy --genesis-dist noble --dest /srv/out
|
||||
|
||||
=cut
|
||||
|
||||
@@ -63,6 +63,20 @@ ppc64el, and every release the repository serves declares the architecture;
|
||||
``xCAT-genesis-scripts`` keeps the two architectures it has control files for,
|
||||
because riscv64 Genesis ships as an OpenEmbedded package instead.
|
||||
|
||||
The Genesis image is the exception, and it is off unless it is asked for::
|
||||
|
||||
./builddebs.pl --genesis-only --genesis-dist jammy --genesis-dist noble
|
||||
|
||||
``dracut`` copies the kernel, the kernel modules and every command out of the root
|
||||
it runs in, so the image belongs to the release that built it. ``--genesis`` builds
|
||||
one image per ``--genesis-dist`` codename inside that codename's
|
||||
``<codename>-<arch>-sbuild`` schroot -- the chroots xcat-dep's ``sbuild-all.pl``
|
||||
creates on the Ubuntu build host -- for the architecture of the build host. It
|
||||
refuses to run in a root of another release, it fails the build on an error in the
|
||||
log even when the exit status is 0, and it checks the extracted payload against the
|
||||
commands the dracut module installs. ``--genesis-only`` builds the images and
|
||||
nothing else, which is what xcat-dep consumes with ``--genesis-deb``.
|
||||
|
||||
Helpers shared by both builders live in ``build-utils/lib/XCAT/BuildUtils.pm``.
|
||||
|
||||
``buildcore.sh`` builds the architecture specific packages (``xCAT``, ``xCATsn``,
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
#!/bin/bash
|
||||
# Build xcat-genesis-base .deb package natively on Ubuntu.
|
||||
# Must run as root on an Ubuntu system (22.04, 24.04, or 26.04).
|
||||
# Parallel to buildrpm for EL targets.
|
||||
# Build the xcat-genesis-base .deb for ONE Ubuntu codename.
|
||||
#
|
||||
# dracut copies the kernel, the kernel modules and every command out of the root it runs
|
||||
# in, so this must run inside a root of the target codename. builddebs.pl --genesis starts
|
||||
# it in that codename's <codename>-<arch>-sbuild chroot, one build per codename.
|
||||
# --expect-codename is what stops a run on the build host: a single build there gives every
|
||||
# Ubuntu release the build host's kernel.
|
||||
#
|
||||
# Parallel to xCAT-genesis-base.spec, which does the same for EL.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DIR=$(readlink -f "$(dirname "$0")")
|
||||
OS_RELEASE=${OS_RELEASE:-/etc/os-release}
|
||||
expect_codename=""
|
||||
outdir=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--expect-codename) expect_codename=${2:-}; shift 2 ;;
|
||||
--expect-codename=*) expect_codename=${1#*=}; shift ;;
|
||||
--outdir) outdir=${2:-}; shift 2 ;;
|
||||
--outdir=*) outdir=${1#*=}; shift ;;
|
||||
-h|--help)
|
||||
echo "usage: builddeb-genesis-base [--expect-codename <codename>] [--outdir <dir>]"
|
||||
exit 0 ;;
|
||||
*) echo "ERROR: unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
BUILDARCH=$(dpkg --print-architecture)
|
||||
TRIPLET=$(dpkg-architecture -qDEB_HOST_MULTIARCH)
|
||||
|
||||
case "$BUILDARCH" in
|
||||
amd64) TARCH=x86_64 ;;
|
||||
@@ -31,7 +53,20 @@ rewrite_control() {
|
||||
|
||||
VERSION=$(cat "$DIR/../Version" 2>/dev/null || echo "2.18.0")
|
||||
RELEASE=$(cat "$DIR/../Release" 2>/dev/null || echo "snap$(date +%Y%m%d%H%M)")
|
||||
CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME")
|
||||
CODENAME=$(. "$OS_RELEASE" && echo "${VERSION_CODENAME:-}")
|
||||
|
||||
if [ -z "$CODENAME" ]; then
|
||||
echo "ERROR: $OS_RELEASE names no VERSION_CODENAME" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The image is only valid for the release whose kernel it carries. Refuse to build a
|
||||
# codename's image anywhere but in that codename's root.
|
||||
if [ -n "$expect_codename" ] && [ "$expect_codename" != "$CODENAME" ]; then
|
||||
echo "ERROR: this root is $CODENAME, not $expect_codename." >&2
|
||||
echo " The image would carry the $CODENAME kernel and boot under $expect_codename." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Building xcat-genesis-base for $BUILDARCH ($TARCH) on Ubuntu $CODENAME"
|
||||
|
||||
@@ -45,6 +80,18 @@ REQUIRED_PACKAGES=$(perl -I"$DIR/lib" -MXCAT::GenesisBuildRoot=required_packages
|
||||
-e 'print "$_\n" for required_packages(@ARGV)' "$BUILDARCH" "$CODENAME")
|
||||
apt-get install -y --no-install-recommends $REQUIRED_PACKAGES
|
||||
|
||||
# dpkg-architecture comes from dpkg-dev, which the line above installs.
|
||||
TRIPLET=$(dpkg-architecture -qDEB_HOST_MULTIARCH)
|
||||
|
||||
# ncurses-base put its terminfo entries under /lib/terminfo before 24.04, and both dracut's own
|
||||
# terminfo module and the 97xcat module ask for /usr/share/terminfo. On jammy the build stopped
|
||||
# on /usr/share/terminfo/l/linux. Give this build root the path they ask for; the chroot is
|
||||
# thrown away when the build ends.
|
||||
if [ -d /lib/terminfo ]; then
|
||||
mkdir -p /usr/share/terminfo
|
||||
cp -an /lib/terminfo/. /usr/share/terminfo/ 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Set up dracut module
|
||||
if [ -d /usr/lib/dracut/modules.d ]; then
|
||||
DRACUT_PARENT=/usr/lib/dracut/modules.d
|
||||
@@ -76,6 +123,8 @@ if [ "$BUILDARCH" != "amd64" ]; then
|
||||
sed -i '/efibootmgr dmidecode/d' "$DRACUTMODDIR/module-setup.sh"
|
||||
fi
|
||||
|
||||
# linux-image-generic pulls exactly one kernel into this root, and this root belongs to
|
||||
# $CODENAME, so this is the target release's kernel.
|
||||
KERNELVERSION=$(ls -1 /lib/modules | sort -V | tail -n 1)
|
||||
if [ -z "$KERNELVERSION" ]; then
|
||||
echo "ERROR: no kernel modules found in /lib/modules" >&2
|
||||
@@ -128,24 +177,53 @@ if [ ! -e "$KERNEL_IMAGE" ]; then
|
||||
for candidate in \
|
||||
"/boot/vmlinux-$KERNELVERSION" \
|
||||
"/usr/lib/modules/$KERNELVERSION/vmlinuz" \
|
||||
"/lib/modules/$KERNELVERSION/vmlinuz" \
|
||||
"$(find /usr/lib/modules/"$KERNELVERSION" -maxdepth 2 -name 'vmlinuz*' -o -name 'vmlinux*' 2>/dev/null | head -n 1)" \
|
||||
"$(find /lib/modules/"$KERNELVERSION" -maxdepth 2 -name 'vmlinuz*' -o -name 'vmlinux*' 2>/dev/null | head -n 1)" \
|
||||
"$(ls -1 /boot/vmlinuz-* /boot/vmlinux-* 2>/dev/null | sort -V | tail -n 1)"
|
||||
"/lib/modules/$KERNELVERSION/vmlinuz"
|
||||
do
|
||||
if [ -n "$candidate" ] && [ -e "$candidate" ]; then
|
||||
if [ -e "$candidate" ]; then
|
||||
KERNEL_IMAGE="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [ ! -e "$KERNEL_IMAGE" ]; then
|
||||
echo "ERROR: cannot find kernel image" >&2
|
||||
echo "ERROR: cannot find the image of kernel $KERNELVERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Adding kernel $KERNEL_IMAGE"
|
||||
cp "$KERNEL_IMAGE" "$GENESIS_ROOT/kernel"
|
||||
|
||||
# dracut_install reports a missing command and returns, so a hole reaches the .deb with
|
||||
# nothing in the log but one line. Read the commands back from the module and check them
|
||||
# against the payload, the way xCAT-genesis-base.spec does for EL.
|
||||
bash "$DIR/verify-genesis-payload" --commands-from "$DRACUTMODDIR/module-setup.sh" "$GENESIS_FS" \
|
||||
usr/sbin/dhclient bin/sh sbin/xcatroot sbin/dhclient-script etc/rsyslog.conf
|
||||
|
||||
# What the verifier cannot know: that this image belongs to this chroot's kernel. An image
|
||||
# built against another root's /lib/modules boots and then finds no driver for its NIC.
|
||||
if [ ! -d "$GENESIS_FS/lib/modules/$KERNELVERSION" ]; then
|
||||
echo "ERROR: the image carries no /lib/modules/$KERNELVERSION" >&2
|
||||
ls -1 "$GENESIS_FS/lib/modules" 2>/dev/null >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$(find "$GENESIS_FS/lib/modules/$KERNELVERSION" -name '*.ko*' -print -quit)" ]; then
|
||||
echo "ERROR: /lib/modules/$KERNELVERSION in the image holds no kernel module" >&2
|
||||
exit 1
|
||||
fi
|
||||
for stray in "$GENESIS_FS"/lib/modules/*; do
|
||||
[ -d "$stray" ] || continue
|
||||
if [ "$(basename "$stray")" != "$KERNELVERSION" ]; then
|
||||
echo "ERROR: the image carries $(basename "$stray"), not the $KERNELVERSION of this root" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# The 97xcat hooks are what makes the image xCAT's. dracut drops a module whose check()
|
||||
# fails without a word, and the image then boots to a plain dracut shell.
|
||||
if [ -z "$(find "$GENESIS_FS" -path '*/hooks/cmdline/*xcat-cmdline.sh' -print -quit)" ]; then
|
||||
echo "ERROR: the image carries no 97xcat cmdline hook" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
find "$GENESIS_TMPDIR" -type c -delete
|
||||
|
||||
# Stage for dpkg-buildpackage
|
||||
@@ -154,6 +232,11 @@ cp -a "$GENESIS_TMPDIR/opt" "$DIR/"
|
||||
|
||||
# Adjust control file for target arch
|
||||
rewrite_control "$DIR/debian/control" "$BUILDARCH"
|
||||
# debian/dirs names the image directory, which is the rpm architecture.
|
||||
echo "/opt/xcat/share/xcat/netboot/genesis/$TARCH/" > "$DIR/debian/dirs"
|
||||
|
||||
# dch reads debian/control from the current directory, not from the file it writes.
|
||||
cd "$DIR"
|
||||
|
||||
PKG_VERSION="${VERSION}-${RELEASE}~${CODENAME}"
|
||||
rm -f "$DIR/debian/changelog"
|
||||
@@ -163,8 +246,14 @@ dch --create --package "xcat-genesis-base-$BUILDARCH" \
|
||||
"Native Ubuntu build on $CODENAME $BUILDARCH"
|
||||
|
||||
echo "Building .deb package..."
|
||||
cd "$DIR"
|
||||
dpkg-buildpackage -rfakeroot -uc -us -b
|
||||
|
||||
echo "Build complete. .deb files:"
|
||||
ls -la "$DIR/../"xcat-genesis-base*.deb 2>/dev/null || echo "Check parent directory for .deb files"
|
||||
if [ -n "$outdir" ]; then
|
||||
mkdir -p "$outdir"
|
||||
mv "$DIR/../"xcat-genesis-base-*_*.deb "$outdir/"
|
||||
echo "Build complete. .deb files in $outdir:"
|
||||
ls -la "$outdir"
|
||||
else
|
||||
echo "Build complete. .deb files:"
|
||||
ls -la "$DIR/../"xcat-genesis-base*.deb 2>/dev/null || echo "Check parent directory for .deb files"
|
||||
fi
|
||||
|
||||
@@ -16,13 +16,13 @@ my @BASE_PACKAGES = qw(
|
||||
dosfstools e2fsprogs lvm2 mdadm net-tools
|
||||
bc psmisc rsync wget cpio
|
||||
isc-dhcp-client ifenslave
|
||||
systemd-sysv hwdata btrfs-progs netcat-openbsd iputils-ping fdisk ncurses-term
|
||||
dpkg-dev debhelper fakeroot devscripts vim-tiny
|
||||
);
|
||||
|
||||
# hwclock moved out of util-linux into util-linux-extra. focal and jammy have no such package,
|
||||
# and naming it there fails the whole install. util-linux only Suggests it, and the build
|
||||
# passes --no-install-recommends, so the releases that split it must name it.
|
||||
my @OPTIONAL_PACKAGES = qw(util-linux-extra);
|
||||
# Two commands the image needs changed package between releases: nslookup left dnsutils for
|
||||
# bind9-dnsutils in 22.04, and hwclock left util-linux for util-linux-extra in 23.04.
|
||||
my @RENAMED_PACKAGES = ([qw(bind9-dnsutils dnsutils)], [qw(util-linux-extra util-linux)]);
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
@@ -48,14 +48,18 @@ sub apt_carries {
|
||||
=head3 required_packages
|
||||
|
||||
Descriptions: list the packages the Genesis build root needs.
|
||||
An optional package is listed only when the release carries it.
|
||||
For a renamed package, apt says which name the release carries, so the list does
|
||||
not branch on the codename. 26.04 moved the backward-compatibility zone names that
|
||||
the dracut module installs out of tzdata into tzdata-legacy.
|
||||
Arguments:
|
||||
$arch: the dpkg architecture (amd64, ppc64el)
|
||||
$codename: the release name
|
||||
$codename: the release name, used in the error message
|
||||
$carries: optional code ref. It takes a package name and returns true when the
|
||||
release carries that package. The default is apt_carries.
|
||||
Returns:
|
||||
the package names, in install order.
|
||||
Dies with "ERROR: <codename> carries none of these packages: ..." when the release
|
||||
carries neither name of a renamed package.
|
||||
|
||||
=cut
|
||||
|
||||
@@ -66,7 +70,18 @@ sub required_packages {
|
||||
|
||||
my @packages = @BASE_PACKAGES;
|
||||
push @packages, qw(dmidecode efibootmgr) if $arch eq 'amd64';
|
||||
push @packages, grep { $carries->($_) } @OPTIONAL_PACKAGES;
|
||||
|
||||
RENAMED:
|
||||
for my $alternatives (@RENAMED_PACKAGES) {
|
||||
for my $package (@$alternatives) {
|
||||
if ($carries->($package)) {
|
||||
push @packages, $package;
|
||||
next RENAMED;
|
||||
}
|
||||
}
|
||||
die "ERROR: $codename carries none of these packages: @$alternatives\n";
|
||||
}
|
||||
push @packages, 'tzdata-legacy' if $carries->('tzdata-legacy');
|
||||
return @packages;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env perl
|
||||
# The Genesis image carries the kernel and the kernel modules of the root that built it, so
|
||||
# builddebs.pl --genesis builds one image per codename, in that codename's chroot. Each
|
||||
# assertion here reads the value the code returns.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Slurper qw(read_text write_text);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use lib "$FindBin::Bin/../../build-utils/lib";
|
||||
use Test::More;
|
||||
|
||||
use XCAT::Test::File qw(repo_path);
|
||||
|
||||
require XCAT::BuildUtils;
|
||||
|
||||
my @WANTED = qw(genesis_chroot_name genesis_target_arch genesis_build_plan genesis_log_errors);
|
||||
for my $sub (@WANTED) {
|
||||
ok(XCAT::BuildUtils->can($sub), "XCAT::BuildUtils provides $sub");
|
||||
}
|
||||
unless (scalar(grep { XCAT::BuildUtils->can($_) } @WANTED) == scalar @WANTED) {
|
||||
diag('builddebs.pl has no Genesis step: the per-codename build does not exist yet');
|
||||
done_testing();
|
||||
exit;
|
||||
}
|
||||
|
||||
# --- one build per codename, in that codename's chroot ---------------------------------
|
||||
my @dists = qw(jammy noble resolute);
|
||||
my @plan = XCAT::BuildUtils::genesis_build_plan(\@dists, 'amd64');
|
||||
|
||||
is(scalar @plan, scalar @dists, 'one Genesis build per codename');
|
||||
is_deeply([ map { $_->{codename} } @plan ], \@dists, 'the plan keeps the codename order');
|
||||
is_deeply([ map { $_->{chroot} } @plan ],
|
||||
[ 'jammy-amd64-sbuild', 'noble-amd64-sbuild', 'resolute-amd64-sbuild' ],
|
||||
'each codename builds in its own sbuild chroot');
|
||||
is_deeply([ map { $_->{package} } @plan ],
|
||||
[ ('xcat-genesis-base-amd64') x 3 ],
|
||||
'every codename produces the same package name');
|
||||
|
||||
my @ppc = XCAT::BuildUtils::genesis_build_plan(['noble'], 'ppc64el');
|
||||
is($ppc[0]{chroot}, 'noble-ppc64el-sbuild', 'the architecture selects the chroot');
|
||||
is($ppc[0]{package}, 'xcat-genesis-base-ppc64el', 'the architecture is in the package name');
|
||||
is($ppc[0]{target}, 'ppc64', 'ppc64el reads its image from the ppc64 directory');
|
||||
is(XCAT::BuildUtils::genesis_target_arch('amd64'), 'x86_64',
|
||||
'amd64 reads its image from the x86_64 directory');
|
||||
|
||||
is(scalar(() = XCAT::BuildUtils::genesis_build_plan([qw(noble noble)], 'amd64')), 1,
|
||||
'a repeated codename does not build twice');
|
||||
|
||||
ok(!eval { XCAT::BuildUtils::genesis_build_plan([], 'amd64'); 1 },
|
||||
'a plan with no codename is an error');
|
||||
ok(!eval { XCAT::BuildUtils::genesis_target_arch('riscv64'); 1 },
|
||||
'an architecture with no Genesis image directory is an error');
|
||||
|
||||
# --- a per-codename image reaches only its own suite --------------------------------------
|
||||
ok(XCAT::BuildUtils->can('deb_belongs_to_dist'),
|
||||
'XCAT::BuildUtils decides which suite a deb belongs to');
|
||||
if (XCAT::BuildUtils->can('deb_belongs_to_dist')) {
|
||||
my $noble = 'xcat-genesis-base-amd64_2.19.0-snap202609121200~noble_all.deb';
|
||||
ok(XCAT::BuildUtils::deb_belongs_to_dist($noble, 'noble'),
|
||||
'the noble image is published into noble');
|
||||
ok(!XCAT::BuildUtils::deb_belongs_to_dist($noble, 'jammy'),
|
||||
'the noble image is not published into jammy');
|
||||
# Everything else in xcat-core is the same file for every release.
|
||||
ok(XCAT::BuildUtils::deb_belongs_to_dist('perl-xcat_2.19.0-snap1_all.deb', 'jammy'),
|
||||
'a deb with no codename in its version reaches every suite');
|
||||
ok(XCAT::BuildUtils::deb_belongs_to_dist('xcat_2.19.0-snap1_amd64.deb', 'resolute'),
|
||||
'an architecture deb reaches every suite');
|
||||
|
||||
# A `~` in a version is not a codename. Debian uses it for a prerelease, and --release
|
||||
# takes whatever the caller gives it, so `--release 1~rc1` puts one in every package
|
||||
# name. Reading it as a codename drops the whole build from every suite, and the run
|
||||
# still reports the packages it published.
|
||||
for my $dist (qw(focal jammy noble resolute)) {
|
||||
ok(XCAT::BuildUtils::deb_belongs_to_dist('xcat-client_2.19.0-1~rc1_all.deb', $dist),
|
||||
"a prerelease version reaches $dist");
|
||||
}
|
||||
ok(XCAT::BuildUtils::deb_belongs_to_dist('xcat-genesis-scripts-amd64_2.19.0-1~rc1_amd64.deb', 'noble'),
|
||||
'a prerelease Genesis SCRIPTS deb reaches every suite: only the image is per codename');
|
||||
|
||||
# And the rule the exclusion exists for still holds under a prerelease version.
|
||||
ok(XCAT::BuildUtils::deb_belongs_to_dist('xcat-genesis-base-amd64_2.19.0-1~rc1~noble_amd64.deb', 'noble'),
|
||||
'a prerelease Genesis image reaches its own suite');
|
||||
ok(!XCAT::BuildUtils::deb_belongs_to_dist('xcat-genesis-base-amd64_2.19.0-1~rc1~noble_amd64.deb', 'jammy'),
|
||||
'and no other');
|
||||
}
|
||||
|
||||
# --- the releases a Genesis image can be built on ------------------------------------------
|
||||
# A plain --genesis run takes the release list the rest of the build uses. focal is on it and
|
||||
# cannot build the package: it ships debhelper 12.10 and xCAT-genesis-base declares
|
||||
# debhelper-compat (= 13), so sbuild stops on the build dependencies before dracut runs and
|
||||
# the run ends on its first release.
|
||||
ok(XCAT::BuildUtils->can('genesis_dists'),
|
||||
'XCAT::BuildUtils says which releases a Genesis image can be built on');
|
||||
if (XCAT::BuildUtils->can('genesis_dists')) {
|
||||
is_deeply([ XCAT::BuildUtils::genesis_dists(XCAT::BuildUtils::default_dists()) ],
|
||||
[qw(jammy noble resolute)],
|
||||
'the default plan leaves out the release whose chroot cannot build the package');
|
||||
is_deeply([ XCAT::BuildUtils::genesis_dists(qw(jammy noble)) ], [qw(jammy noble)],
|
||||
'a list with none of them is unchanged');
|
||||
is_deeply([ XCAT::BuildUtils::genesis_dists('focal') ], [],
|
||||
'a list of only that release plans nothing');
|
||||
}
|
||||
|
||||
# --- the log guard ---------------------------------------------------------------------
|
||||
#
|
||||
# dracut prints FAILED: for a command it cannot install and exits 0.
|
||||
my $dracut_log = <<'LOG';
|
||||
Installing build dependencies...
|
||||
dracut: Executing: /usr/bin/dracut --compress gzip -m xcat base -N -f /tmp/genesis.rfs 6.8.0-45-generic
|
||||
dracut-install: ERROR: installing 'dhclient'
|
||||
dracut: FAILED: /usr/lib/dracut/dracut-install -D /var/tmp/dracut.XXXX -a dhclient
|
||||
dracut: *** Creating initramfs image file '/tmp/genesis.rfs' done ***
|
||||
Extracting initramfs...
|
||||
LOG
|
||||
|
||||
my @errors = XCAT::BuildUtils::genesis_log_errors($dracut_log);
|
||||
ok(scalar @errors, 'a dracut log with a FAILED: line is an error');
|
||||
like($errors[0]{line}, qr/FAILED:/, 'the offending line is reported');
|
||||
ok(length $errors[0]{why}, 'the reason is named');
|
||||
|
||||
is_deeply([ XCAT::BuildUtils::genesis_log_errors(<<'LOG') ], [], 'a clean build log is not an error');
|
||||
Installing build dependencies...
|
||||
dracut: *** Creating initramfs image file '/tmp/genesis.rfs' done ***
|
||||
Extracting initramfs...
|
||||
dpkg-deb: building package 'xcat-genesis-base-amd64'
|
||||
LOG
|
||||
|
||||
for my $case (
|
||||
[ 'E: Unable to locate package isc-dhcp-client' => 'a package apt cannot find' ],
|
||||
[ 'dracut: Cannot find module directory /lib/modules/6.8.0' => 'a module directory dracut cannot find' ],
|
||||
[ '/build/builddeb-genesis-base: line 9: dch: command not found' => 'a command the build root lacks' ],
|
||||
[ 'E: Unable to correct problems, you have held broken packages.' => 'a build root apt cannot resolve' ],
|
||||
)
|
||||
{
|
||||
my ($line, $what) = @{$case};
|
||||
ok(scalar XCAT::BuildUtils::genesis_log_errors("before\n$line\nafter\n"),
|
||||
"the log guard catches $what");
|
||||
}
|
||||
|
||||
is_deeply([ XCAT::BuildUtils::genesis_log_errors(undef) ], [], 'no log is not an error');
|
||||
|
||||
# --- the builder refuses a root of another release -------------------------------------
|
||||
my $builder = repo_path('xCAT-genesis-base/builddeb-genesis-base');
|
||||
if (!-f $builder) {
|
||||
fail('xCAT-genesis-base/builddeb-genesis-base is missing');
|
||||
done_testing();
|
||||
exit;
|
||||
}
|
||||
|
||||
my $tmp = tempdir(CLEANUP => 1);
|
||||
mkdir "$tmp/bin";
|
||||
# dpkg is shadowed so the guard is tested on any host, and so the test cannot reach apt.
|
||||
write_text("$tmp/bin/dpkg", "#!/bin/sh\necho amd64\n");
|
||||
chmod 0755, "$tmp/bin/dpkg";
|
||||
|
||||
sub build_in_a_root_of {
|
||||
my ($codename, $expected) = @_;
|
||||
write_text("$tmp/os-release", "ID=ubuntu\nVERSION_CODENAME=$codename\n");
|
||||
my $err = "$tmp/err";
|
||||
my $cmd = sprintf('PATH=%s:$PATH OS_RELEASE=%s /bin/bash %s --expect-codename %s >/dev/null 2>%s',
|
||||
"'$tmp/bin'", "'$tmp/os-release'", "'$builder'", "'$expected'", "'$err'");
|
||||
system('/bin/bash', '-c', $cmd);
|
||||
return ($? >> 8, -f $err ? read_text($err) : '');
|
||||
}
|
||||
|
||||
my ($rc, $err) = build_in_a_root_of('jammy', 'noble');
|
||||
isnt($rc, 0, 'the builder refuses to build noble in a jammy root');
|
||||
like($err, qr/jammy/, 'the message names the root it woke up in');
|
||||
like($err, qr/noble/, 'the message names the release that was asked for');
|
||||
|
||||
done_testing();
|
||||
@@ -5,10 +5,13 @@
|
||||
#
|
||||
# XCAT::GenesisBuildRoot::required_packages lists the packages of the build root, and
|
||||
# XCAT::GenesisPayload::module_commands reads the mandatory commands from the module, as
|
||||
# verify-genesis-payload does.
|
||||
# verify-genesis-payload does. The refusal of a root of another release is in
|
||||
# genesis_deb_per_codename.t.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Slurper qw(read_text);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use lib "$FindBin::Bin/../../xCAT-genesis-base/lib";
|
||||
@@ -18,13 +21,16 @@ use XCAT::GenesisBuildRoot qw(required_packages);
|
||||
use XCAT::GenesisPayload qw(module_commands);
|
||||
use XCAT::Test::File qw(repo_path);
|
||||
|
||||
my $module = repo_path('xCAT-genesis-base/dracut_105/ubuntu/module-setup.sh');
|
||||
my $builder = repo_path('xCAT-genesis-base/builddeb-genesis-base');
|
||||
my $module = repo_path('xCAT-genesis-base/dracut_105/ubuntu/module-setup.sh');
|
||||
|
||||
# Mandatory commands a minimal Ubuntu server root does NOT already provide, and the package
|
||||
# that supplies each one on every release xCAT builds for.
|
||||
my %PACKAGE_FOR = (
|
||||
dhclient => 'isc-dhcp-client',
|
||||
ifenslave => 'ifenslave',
|
||||
# Mandatory commands a minimal Ubuntu server root does NOT already provide, and the packages
|
||||
# that supply each one. hwclock has two names: it left util-linux for util-linux-extra in
|
||||
# 23.04.
|
||||
my %PACKAGES_FOR = (
|
||||
dhclient => ['isc-dhcp-client'],
|
||||
ifenslave => ['ifenslave'],
|
||||
hwclock => [ 'util-linux-extra', 'util-linux' ],
|
||||
);
|
||||
|
||||
# A release carries exactly the names in its list.
|
||||
@@ -32,48 +38,54 @@ sub release {
|
||||
my %carried = map { $_ => 1 } @_;
|
||||
return sub { $carried{ $_[0] } };
|
||||
}
|
||||
my $NOBLE = release(qw(bind9-dnsutils dnsutils util-linux-extra util-linux tzdata-legacy));
|
||||
my $FOCAL = release(qw(dnsutils util-linux));
|
||||
|
||||
# hwclock is not in that list because the package that carries it moved. Measured on the
|
||||
# four Ubuntu management nodes: focal and jammy have it in util-linux, which is essential
|
||||
# and always in the build root, and no util-linux-extra exists to install; noble and
|
||||
# resolute have it in util-linux-extra. util-linux only Suggests that package, and this
|
||||
# build passes --no-install-recommends, so the releases that split it must name it and the
|
||||
# releases that did not must not.
|
||||
my @noble = required_packages('amd64', 'noble', release('util-linux-extra'));
|
||||
my @jammy = required_packages('amd64', 'jammy', release());
|
||||
my @noble = required_packages('amd64', 'noble', $NOBLE);
|
||||
is_deeply([ @noble[ -5 .. -1 ] ],
|
||||
[qw(dmidecode efibootmgr bind9-dnsutils util-linux-extra tzdata-legacy)],
|
||||
'amd64 adds dmidecode and efibootmgr, then the first name the release carries');
|
||||
|
||||
my @focal = required_packages('amd64', 'focal', $FOCAL);
|
||||
is_deeply([ @focal[ -2 .. -1 ] ], [qw(dnsutils util-linux)],
|
||||
'a release without the new names gets dnsutils and util-linux, and no tzdata-legacy');
|
||||
|
||||
my @ppc = required_packages('ppc64el', 'noble', $NOBLE);
|
||||
is_deeply([ @ppc[ -3 .. -1 ] ], [qw(bind9-dnsutils util-linux-extra tzdata-legacy)],
|
||||
'ppc64el gets neither dmidecode nor efibootmgr');
|
||||
is_deeply([ @ppc[ 0 .. $#ppc - 3 ] ], [ @noble[ 0 .. $#noble - 5 ] ],
|
||||
'ppc64el and amd64 share the base packages');
|
||||
|
||||
my @asked;
|
||||
required_packages('amd64', 'noble', sub { push @asked, $_[0]; $NOBLE->($_[0]) });
|
||||
is_deeply(\@asked, [qw(bind9-dnsutils util-linux-extra tzdata-legacy)],
|
||||
'apt is asked for the older name only when the newer one is absent');
|
||||
|
||||
ok(!eval { required_packages('amd64', 'oddball', release('bind9-dnsutils')); 1 },
|
||||
'a release that carries no hwclock package fails');
|
||||
is($@, "ERROR: oddball carries none of these packages: util-linux-extra util-linux\n",
|
||||
'the failure names the release and the missing alternatives');
|
||||
|
||||
# --- every mandatory command has a package in the build root -----------------------------
|
||||
# An absolute path is a data file, not a command.
|
||||
my %mandatory = map { $_ => 1 } grep { !m{^/} } module_commands($module);
|
||||
my %packages = map { $_ => 1 } @jammy;
|
||||
|
||||
for my $command (sort keys %PACKAGE_FOR) {
|
||||
my %packages = map { $_ => 1 } @noble;
|
||||
for my $command (sort keys %PACKAGES_FOR) {
|
||||
my @provider = @{ $PACKAGES_FOR{$command} };
|
||||
ok($mandatory{$command}, "the Ubuntu dracut module installs '$command' unconditionally");
|
||||
ok($packages{ $PACKAGE_FOR{$command} },
|
||||
"the build root installs $PACKAGE_FOR{$command}, which provides '$command'");
|
||||
ok(scalar(grep { $packages{$_} } @provider),
|
||||
"the build root installs @{[ join ' or ', @provider ]}, which provides '$command'");
|
||||
}
|
||||
ok(grep({ $_ eq 'util-linux' } @focal), 'a release before 23.04 gets hwclock from util-linux');
|
||||
|
||||
# doxcat asks dhclient for the provisioning lease.
|
||||
ok($mandatory{dhclient} && $packages{'isc-dhcp-client'},
|
||||
'the Genesis image can obtain a DHCP lease');
|
||||
|
||||
ok($mandatory{hwclock}, "the Ubuntu dracut module installs 'hwclock' unconditionally");
|
||||
|
||||
# Naming a package apt cannot locate fails the whole install, and the script runs under
|
||||
# set -e, so an unconditional util-linux-extra stops the build on focal and jammy.
|
||||
is_deeply([ grep { $_ eq 'util-linux-extra' } @jammy ], [],
|
||||
'a release without util-linux-extra does not get it');
|
||||
is_deeply([ @noble[ 0 .. $#noble - 1 ] ], \@jammy,
|
||||
'a release that carries util-linux-extra gets the same list ...');
|
||||
is($noble[-1], 'util-linux-extra', '... with util-linux-extra last');
|
||||
|
||||
my @ppc = required_packages('ppc64el', 'noble', release('util-linux-extra'));
|
||||
is_deeply([ @noble[ -3 .. -1 ] ], [qw(dmidecode efibootmgr util-linux-extra)],
|
||||
'amd64 adds dmidecode and efibootmgr');
|
||||
is_deeply([ @ppc[ 0 .. $#ppc - 1 ] ], [ @noble[ 0 .. $#noble - 3 ] ],
|
||||
'ppc64el gets neither dmidecode nor efibootmgr');
|
||||
|
||||
my @asked;
|
||||
required_packages('amd64', 'noble', sub { push @asked, $_[0]; 1 });
|
||||
is_deeply(\@asked, ['util-linux-extra'], 'apt is asked only about the optional package');
|
||||
# The build cannot run here, so the call to the payload gate is read from the script. The
|
||||
# gate itself is exercised by genesis_payload_verification.t.
|
||||
like(read_text($builder),
|
||||
qr{^bash "\$DIR/verify-genesis-payload" --commands-from "\$DRACUTMODDIR/module-setup\.sh" "\$GENESIS_FS"}m,
|
||||
'builddeb-genesis-base verifies the payload it packages against the module');
|
||||
|
||||
done_testing();
|
||||
|
||||
Reference in New Issue
Block a user