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

Merge pull request #68 from VersatusHPC/feat/genesis-s390x

feat(genesis): package s390x images
This commit is contained in:
Daniel Hilst
2026-09-11 18:52:19 -03:00
committed by GitHub
14 changed files with 717 additions and 142 deletions
@@ -48,6 +48,7 @@ jobs:
lib/XCAT/GenesisRelease.pm \
mockbuild-all.pl \
t/build_utils.t \
t/common-repo-gate.t \
t/genesis_openembedded_release.t \
t/genesis_openembedded_consumer.t \
t/lib/XCAT/GenesisReleaseTest.pm
@@ -59,3 +60,4 @@ jobs:
prove -v t/sbuild-all.t
prove -v -It/lib t/genesis_openembedded_release.t
sudo -E prove -v -It/lib t/genesis_openembedded_consumer.t
sudo -E prove -v -It/lib t/common-repo-gate.t
+4 -3
View File
@@ -247,9 +247,10 @@ generation, and signing have succeeded. If a stopped publisher leaves staging
or backup directories behind, rerun it with ``--force-unlock`` to recover the
previous repository before starting a new publication.
Repository publication requires a release containing every supported Genesis
architecture. The packages are `noarch`, and the common repository contains
the full set of target images.
Repository publication requires all eight current architectures. Version 1
release manifests remain readable, but they cannot replace the current
repository because they lack `s390x`. The packages are `noarch`, and each
publication contains the full set.
The release checksums cover the unsigned input packages. If repository signing
is enabled, `rpmsign` changes the deployed RPM bytes after collection.
+1
View File
@@ -185,3 +185,4 @@ xcat-genesis-openembedded-ppc64le=2.*
xcat-genesis-openembedded-armv7hf=2.*
xcat-genesis-openembedded-aarch64=2.*
xcat-genesis-openembedded-riscv64=2.*
xcat-genesis-openembedded-s390x=2.*
+9 -3
View File
@@ -23,7 +23,7 @@ The packaging scripts use `File::Slurper` and `IPC::Cmd`. Install
The default format is `all`, which produces RPM, SRPM, and DEB packages. Use
`--format rpm` or `--format deb` when only one package family is needed. The
supported image architectures are `x86`, `x86_64`, `ppc64`, `ppc64le`,
`armv7hf`, `aarch64`, and `riscv64`.
`armv7hf`, `aarch64`, `riscv64`, and `s390x`.
Use `--architecture` for development builds. Repository publication requires a
complete release built with `--all`.
@@ -88,8 +88,13 @@ APT metadata is assembled in a side tree and swapped onto the published
repository with a single rename, under one global publish lock, so a failed or
interrupted publication leaves the previous repository exactly as it was.
Both consumers require all seven architectures and verify package identities
and checksums before publication. A management node can install an image for a
Both consumers verify package identities and checksums before publication.
Releases containing `s390x` use format version 2. Other releases keep version 1
and remain readable by older tools. Current publishers require all eight
architectures, so a version 1 release cannot replace the shared repository.
Building `s390x` requires an xcat-core revision that reports the target. Merge
or deploy that xcat-core change first. Builds for the other architectures still
accept older xcat-core revisions. A management node can install an image for a
different target architecture.
Without `--genesis-release`, both builders keep their existing behavior. The
@@ -113,4 +118,5 @@ Run the package tests on a Linux builder with RPM, DEB, and repository tools:
prove t/build_utils.t
prove -It/lib t/genesis_openembedded_release.t
sudo -E prove -It/lib t/genesis_openembedded_consumer.t
sudo -E prove -It/lib t/common-repo-gate.t
```
+1 -1
View File
@@ -39,7 +39,7 @@ genesis_run_mknb() {
genesis_activation_main() {
architecture=$1
case "$architecture" in
x86|x86_64|ppc64|ppc64le|armv7hf|aarch64|riscv64) ;;
x86|x86_64|ppc64|ppc64le|armv7hf|aarch64|riscv64|s390x) ;;
*)
echo "Invalid Genesis architecture: $architecture" >&2
return 0
+25 -1
View File
@@ -23,6 +23,7 @@ use XCAT::BuildUtils qw(
use XCAT::GenesisRelease qw(
architectures
deb_package_name
minimum_release_version
rpm_package_name
validate_architecture
validate_release
@@ -77,6 +78,28 @@ if ($xcat_ref ne '') {
die "xcat-core HEAD $revision does not match $xcat_ref ($expected)\n"
unless $revision eq $expected;
}
if ($requested{s390x}) {
my $architecture_reporter = "$xcat_source/xCAT-genesis-builder/oe/build";
my $probe_work = tempdir('xcat-genesis-capabilities.XXXXXX', TMPDIR => 1, CLEANUP => 1);
my $supported_output;
{
local $ENV{XCAT_GENESIS_WORK_DIR} = $probe_work;
local $ENV{TMPDIR} = $probe_work;
$supported_output = eval {
capture_command($architecture_reporter, '--list-architectures');
};
}
my $report_error = $@;
die "xcat-core source does not report supported Genesis architectures: $report_error"
if $report_error;
if (!defined($supported_output) || !length $supported_output) {
die "xcat-core source reported no supported Genesis architectures\n";
}
my %supported_architecture = map { $_ => 1 } split m{\s+}xms, $supported_output;
if (!$supported_architecture{s390x}) {
die "xcat-core source does not support Genesis architecture s390x\n";
}
}
my $version = read_first_line("$xcat_source/Version");
die "Invalid xCAT version: $version\n" unless $version =~ /^\d+(?:\.\d+){1,3}$/;
@@ -140,10 +163,11 @@ for my $architecture (@requested_architectures) {
}
my @formats = $format eq 'all' ? qw(deb rpm) : ($format);
my $manifest_version = minimum_release_version(@requested_architectures);
write_binary(
"$staging/release.manifest",
"format=xcat-genesis-packages\n"
. "version=1\n"
. "version=$manifest_version\n"
. "xcat_version=$version\n"
. "xcat_release=$release\n"
. "xcat_revision=$revision\n"
+53 -5
View File
@@ -8,20 +8,32 @@ use XCAT::BuildUtils qw(digest_file read_lines relative_files);
our @EXPORT_OK = qw(
architectures
deb_package_prefix
deb_package_name
minimum_release_version
read_checksum_manifest
read_release_manifest
rpm_package_prefix
rpm_package_name
validated_release_checksums
validate_architecture
validate_complete_release
validate_export
validate_repository_packages
validate_release
verify_release_file
);
my @ARCHITECTURES = qw(x86 x86_64 ppc64 ppc64le armv7hf aarch64 riscv64);
my @RELEASE_V1_ARCHITECTURES = qw(x86 x86_64 ppc64 ppc64le armv7hf aarch64 riscv64);
my @RELEASE_V2_ARCHITECTURES = (@RELEASE_V1_ARCHITECTURES, 's390x');
my @ARCHITECTURES = @RELEASE_V2_ARCHITECTURES;
my $RPM_PACKAGE_PREFIX = 'xCAT-genesis-openembedded-';
my $DEB_PACKAGE_PREFIX = 'xcat-genesis-openembedded-';
my %ARCHITECTURE = map { $_ => 1 } @ARCHITECTURES;
my %RELEASE_ARCHITECTURES = (
1 => \@RELEASE_V1_ARCHITECTURES,
2 => \@RELEASE_V2_ARCHITECTURES,
);
sub architectures {
return @ARCHITECTURES;
@@ -34,17 +46,48 @@ sub validate_architecture {
return $architecture;
}
sub rpm_package_prefix {
return $RPM_PACKAGE_PREFIX;
}
sub rpm_package_name {
my ($architecture) = @_;
validate_architecture($architecture);
return "xCAT-genesis-openembedded-$architecture";
return $RPM_PACKAGE_PREFIX . $architecture;
}
sub deb_package_prefix {
return $DEB_PACKAGE_PREFIX;
}
sub deb_package_name {
my ($architecture) = @_;
validate_architecture($architecture);
$architecture =~ tr/_/-/;
return "xcat-genesis-openembedded-$architecture";
return $DEB_PACKAGE_PREFIX . $architecture;
}
sub minimum_release_version {
my @architectures = @_;
die "Release format selection requires a Genesis architecture\n" unless @architectures;
validate_architecture($_) for @architectures;
for my $version (sort { $a <=> $b } keys %RELEASE_ARCHITECTURES) {
my %supported = map { $_ => 1 } @{ $RELEASE_ARCHITECTURES{$version} };
return $version unless grep { !$supported{$_} } @architectures;
}
die "No release format supports the requested Genesis architectures\n";
}
sub validate_repository_packages {
my ($packages, $section, $prefix, @supported_names) = @_;
my %supported = map { $_ => 1 } @supported_names;
my @missing = grep { !exists $packages->{$_} } @supported_names;
my @unknown = grep {
index($_, $prefix) == 0 && !$supported{$_}
} sort keys %{$packages};
die "FATAL: [$section] is missing supported packages: @missing\n" if @missing;
die "FATAL: [$section] has unsupported packages: @unknown\n" if @unknown;
return $packages;
}
sub _read_key_values {
@@ -161,7 +204,7 @@ sub _validate_release {
die "Unsupported Genesis package release format\n"
unless $manifest->{format} eq 'xcat-genesis-packages';
die "Unsupported Genesis package release version\n"
unless $manifest->{version} eq '1';
unless $RELEASE_ARCHITECTURES{ $manifest->{version} };
die "Invalid xCAT version in release manifest\n"
unless $manifest->{xcat_version} =~ /^\d+(?:\.\d+){1,3}$/;
die "Invalid xCAT release in release manifest\n"
@@ -172,9 +215,13 @@ sub _validate_release {
unless $manifest->{source_date_epoch} =~ /^\d+$/;
my @architectures = split(/,/, $manifest->{architectures});
my %version_architecture = map { $_ => 1 }
@{ $RELEASE_ARCHITECTURES{ $manifest->{version} } };
my %seen_arch;
for my $architecture (@architectures) {
validate_architecture($architecture);
die "Genesis architecture $architecture is not valid in release version $manifest->{version}\n"
unless $version_architecture{$architecture};
die "Duplicate release architecture: $architecture\n" if $seen_arch{$architecture}++;
}
die "Release manifest has no architectures\n" unless @architectures;
@@ -223,7 +270,8 @@ sub validate_complete_release {
my $manifest = validate_release($directory);
my %present = map { $_ => 1 } split(/,/, $manifest->{architectures});
my @missing = grep { !$present{$_} } @ARCHITECTURES;
die "Genesis release is missing supported architectures: @missing\n" if @missing;
die "Genesis release version $manifest->{version} omits currently supported architectures: @missing\n"
if @missing;
return $manifest;
}
+26 -13
View File
@@ -38,6 +38,10 @@ use XCAT::BuildUtils qw(
shell_quote
);
use XCAT::GenesisRelease qw(
architectures
rpm_package_prefix
rpm_package_name
validate_repository_packages
validated_release_checksums
verify_release_file
);
@@ -373,6 +377,7 @@ if ($genesis_release ne '') {
unless -d $genesis_release;
my $verifier = "$script_dir/genesis-openembedded/verify-release";
die "Genesis release verifier not found: $verifier\n" unless -x $verifier;
common_repository_requirements();
# Checksum, verify, checksum again. The verifier reads the tree it validates, so a
# release rewritten together with its SHA256SUMS while the verifier runs would satisfy
# both the verifier and any single pass taken afterwards; comparing the pass taken
@@ -1131,9 +1136,8 @@ sub publish_genesis_common_repo {
=head3 verify_common_repo
Assert the shared OpenEmbedded Genesis repository carries every package the manifest's [common]
section requires, at a version satisfying its pin. [common] is not a build target: it describes
the one repository published beside the per-EL cells, which no [<target>] section covers.
Assert the shared repository carries every package required by [common]. [common] must
describe every currently supported Genesis architecture.
Arguments:
$dir - the repository to check (the staging directory, before it is swapped into place)
@@ -1145,16 +1149,11 @@ sub publish_genesis_common_repo {
#--------------------------------------------------------------------------------
sub verify_common_repo {
my ($dir) = @_;
my $manifest = "$repo_root/packages-manifest.conf";
my %MAN = read_manifest($manifest);
my %req = %{ $MAN{common} // {} };
die "FATAL: no [common] section in $manifest -- cannot verify the shared Genesis repository\n"
if !%req;
my @names = sort keys %req;
my %present = repo_present_versions($dir, \@names);
my %req = %{ common_repository_requirements() };
my @names = sort keys %req;
my %present = repo_present_versions($dir, \@names);
my %present_evr = map { $_ => rpm_evr($dir, $_) } @names;
my @problems = verify_repo_packages(\%req, \%present, \%present_evr, \&rpm_vercmp_segment);
my @problems = verify_repo_packages(\%req, \%present, \%present_evr, \&rpm_vercmp_segment);
if (@problems) {
print " - $_\n" for @problems;
die "FATAL: shared Genesis repo INCOMPLETE at $dir (" . scalar(@problems) . " problem(s))\n";
@@ -1164,6 +1163,21 @@ sub verify_common_repo {
return 1;
}
sub common_repository_requirements {
my $manifest = "$repo_root/packages-manifest.conf";
my %MAN = read_manifest($manifest);
my %common = %{ $MAN{common} // {} };
die "FATAL: no [common] section in $manifest -- cannot verify the shared Genesis repository\n"
if !%common;
return validate_repository_packages(
\%common,
'common',
rpm_package_prefix(),
map { rpm_package_name($_) } architectures(),
);
}
sub replace_common_repository {
my ($staged, $destination) = @_;
my $backup = "$repo_dep/.common.previous.$$";
@@ -2229,4 +2243,3 @@ sub slurp_chomp {
chomp $line if defined $line;
return $line // '';
}
+1
View File
@@ -189,3 +189,4 @@ xCAT-genesis-openembedded-ppc64le=>= 2.18.0
xCAT-genesis-openembedded-armv7hf=>= 2.18.0
xCAT-genesis-openembedded-aarch64=>= 2.18.0
xCAT-genesis-openembedded-riscv64=>= 2.18.0
xCAT-genesis-openembedded-s390x=>= 2.18.0
+20 -12
View File
@@ -318,6 +318,7 @@ if ($genesis_release ne '') {
# the verifier runs would satisfy both the verifier and any single pass taken afterwards.
require XCAT::BuildUtils;
require XCAT::GenesisRelease;
shared_repository_requirements();
my $before = XCAT::GenesisRelease::validated_release_checksums($genesis_release);
XCAT::BuildUtils::run_command($^X, $verifier, '--complete', '--format', 'deb', $genesis_release);
my $after = XCAT::GenesisRelease::validated_release_checksums($genesis_release);
@@ -1142,16 +1143,9 @@ sub install_genesis_release_debs {
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 %req = %{ shared_repository_requirements() };
my @names = sort keys %req;
my %present = map { $_ => deb_version($pool, $_) } @names;
my @problems = verify_repo_packages(\%req, \%present);
@@ -1163,6 +1157,19 @@ sub verify_shared_pool {
return 1;
}
sub shared_repository_requirements {
my %shared = %{ $MANIFEST{shared} // {} };
die "FATAL: no [shared] section in $manifest -- cannot verify the shared Genesis pool\n"
if !%shared;
return XCAT::GenesisRelease::validate_repository_packages(
\%shared,
'shared',
XCAT::GenesisRelease::deb_package_prefix(),
map { XCAT::GenesisRelease::deb_package_name($_) }
XCAT::GenesisRelease::architectures(),
);
}
sub assemble_into {
my ($dir, $expect) = @_;
if ($genesis_release ne '') {
@@ -1567,10 +1574,11 @@ Publish an B<OpenEmbedded Genesis package release> alongside the packages this r
release is produced separately (see F<genesis-openembedded/README.md>); this option only verifies it
and copies the verified bytes into every selected suite.
The release must be B<complete> (every supported Genesis architecture) and must carry C<deb>
packages. It is validated before any build or publish: its C<SHA256SUMS> is read, the shared
verifier runs, and the checksums are read again -- a release rewritten together with its checksums
while the verifier runs is rejected.
The release must carry C<deb> packages for every currently supported architecture. Version 1
metadata remains readable, but a complete release now requires C<s390x>. The release is validated
before any build or publish: its C<SHA256SUMS> is read, the shared verifier runs, and the checksums
are read again -- a release rewritten together with its checksums while the verifier runs is
rejected.
The packages are published B<once>, into F<pool/main/xcat-genesis-openembedded>, and every suite's
C<Packages> index points at that one copy: they are C<Architecture: all> and identical everywhere,
+178 -25
View File
@@ -10,34 +10,85 @@ use strict;
use warnings;
use Test::More;
use FindBin qw($RealBin);
use File::Basename qw(basename);
use File::Temp qw(tempdir);
use File::Path qw(make_path);
use File::Copy qw(copy);
use lib "$RealBin/..";
use lib "$RealBin/../lib";
use lib "$RealBin/lib";
use MockBuildUtils qw(read_manifest);
use XCAT::BuildUtils qw(command_exists);
use XCAT::GenesisRelease qw(architectures rpm_package_name);
use XCAT::GenesisReleaseTest qw(
build_package_release
copy_tree
write_checksums
write_release_manifest
);
my $SCRIPT = "$RealBin/../mockbuild-all.pl";
my $RELEASE = '/opt/xcat-ci-shared/builds/genesis-openembedded-initial-20260825/release';
my $PACKAGER = "$RealBin/../genesis-openembedded/package";
plan skip_all => 'mockbuild-all.pl not found' unless -f $SCRIPT;
plan skip_all => 'no Genesis release fixture' unless -d "$RELEASE/rpm";
# root, like every other test that drives mockbuild-all.pl: the script refuses to run otherwise,
# and the CI builder (XCAT_GENESIS_CI) is root.
plan skip_all => 'rpm tooling and a root Linux builder required'
unless $^O eq 'linux'
&& $> == 0
&& !system('sh', '-c', 'command -v rpm >/dev/null 2>&1')
&& !system('sh', '-c', 'command -v createrepo_c >/dev/null 2>&1')
&& !system('sh', '-c', 'command -v rpmbuild >/dev/null 2>&1');
my @missing_requirements;
push @missing_requirements, 'Linux' unless $^O eq 'linux';
push @missing_requirements, 'root' unless $> == 0;
for my $command (qw(createrepo_c gzip rpm rpmbuild tar)) {
push @missing_requirements, $command unless command_exists($command);
}
if (command_exists('tar')) {
my $tar_version = `tar --version 2>/dev/null`;
push @missing_requirements, 'GNU tar' unless $tar_version =~ /GNU tar/;
}
if (@missing_requirements) {
my $message = 'requires ' . join(', ', @missing_requirements);
BAIL_OUT($message) if $ENV{XCAT_GENESIS_CI};
plan skip_all => $message;
}
my $tmp = tempdir(CLEANUP => 1);
my $target = 'alma+epel-10-' . do { my $m = `uname -m`; chomp $m; $m };
my @architectures = architectures();
my $xcat_version = '2.19.0';
my $xcat_release = 'snap202609040000';
my $xcat_revision = 'c' x 40;
my $source_date_epoch = 1788476400;
my $RELEASE = build_package_release(
root => "$tmp/release-fixture",
format => 'rpm',
architectures => \@architectures,
packager => $PACKAGER,
version => $xcat_version,
release => $xcat_release,
revision => $xcat_revision,
epoch => $source_date_epoch,
);
my @version_1_architectures = grep { $_ ne 's390x' } @architectures;
my $VERSION_1_RELEASE = build_package_release(
root => "$tmp/version-1-release-fixture",
format => 'rpm',
architectures => \@version_1_architectures,
packager => $PACKAGER,
version => $xcat_version,
release => $xcat_release,
revision => $xcat_revision,
epoch => $source_date_epoch,
);
write_release_manifest(
$VERSION_1_RELEASE, $xcat_version, $xcat_release, $xcat_revision,
$source_date_epoch, join(',', @version_1_architectures), 'rpm', 1,
);
write_checksums($VERSION_1_RELEASE);
# The shipped manifest must describe the shared repo, else nothing can gate it.
{
my %m = read_manifest("$RealBin/../packages-manifest.conf");
ok($m{common} && %{ $m{common} }, 'the shipped manifest has a [common] section');
is(scalar(keys %{ $m{common} // {} }), 7,
'... naming every architecture the release must carry');
my @expected = sort map { rpm_package_name($_) }
qw(x86 x86_64 ppc64 ppc64le armv7hf aarch64 riscv64 s390x);
my @missing = grep { !exists $m{common}{$_} } @expected;
is_deeply(\@missing, [],
'the shared RPM manifest lists every Genesis architecture');
}
# fixture_rpm: a minimal noarch rpm, built once, standing in for a compiled dep.
@@ -69,9 +120,8 @@ SPEC
return $FIXTURE;
}
# run_publish($release_dir) -> ($exit, $output, $common_dir)
sub run_publish {
my ($release, $tag) = @_;
my ($release, $tag, $mutate_common, $skip_repo_verification) = @_;
my $out = "$tmp/$tag";
make_path("$out/root", "$out/collect");
# Something to collect, so the run gets past the "built nothing" guard. It must NOT be an
@@ -83,49 +133,152 @@ sub run_publish {
print $fh "[$target]\nipmitool-xcat=1.8.18\n";
# the shared repo's own section, copied from the shipped manifest so the test uses the real one
my %m = read_manifest("$RealBin/../packages-manifest.conf");
$mutate_common->($m{common}) if $mutate_common;
print $fh "\n[common]\n";
print $fh "$_=$m{common}{$_}\n" for sort keys %{ $m{common} // {} };
close $fh;
my $cmd = join(' ', map { my $x = $_; $x =~ s/'/'"'"'/g; "'$x'" }
my @command =
($^X, $SCRIPT, '--repo-root', "$out/root", '--output', "$out/build",
'--repo-dep', "$out/repo", '--target', $target, '--run-id', $tag,
'--build-timestamp', '1787672536',
'--skip-build', '--skip-genesis', '--skip-xcat-dep', '--skip-perl',
'--skip-tarball',
'--collect-dir', "$out/collect", '--genesis-release', $release)) . ' 2>&1';
'--collect-dir', "$out/collect", '--genesis-release', $release);
push @command, '--no-verify-repo' if $skip_repo_verification;
my $cmd = join(' ', map { my $x = $_; $x =~ s/'/'"'"'/g; "'$x'" } @command)
. ' 2>&1';
my $log = `$cmd`;
return ($? >> 8, $log, "$out/repo/common");
}
{
my $package = rpm_package_name('s390x');
my ($rc, $out, $common) = run_publish(
$RELEASE,
'unsatisfied-common',
sub { $_[0]->{$package} = '>= 99.0.0' },
);
isnt($rc, 0, 'an unsatisfied shared-repository requirement is refused');
like($out, qr/EVR \Q$package\E: repo has .* manifest requires >= 99\.0\.0/,
'the shared-repository gate names the unsatisfied requirement');
ok(!-d $common || !glob("$common/*.rpm"),
'a failed shared-repository gate publishes nothing');
}
# ---- a complete release publishes, and says it was gated -----------------------------------------
{
my ($rc, $out, $common) = run_publish($RELEASE, 'full');
is($rc, 0, 'a complete release publishes') or diag($out);
is(scalar(grep { !/\.src\.rpm$/ } glob("$common/*.rpm")), 7,
is(scalar(grep { !/\.src\.rpm$/ } glob("$common/*.rpm")), 8,
'the published shared repo carries every architecture');
like($out, qr/\[verify-repo\] common complete/, 'the shared repo is gated against [common]');
like($out, qr/\[verify-repo\] common complete: 8 packages present/,
'the shared repo is gated against [common]');
}
{
my ($rc, $out, $common) = run_publish($VERSION_1_RELEASE, 'version-1');
isnt($rc, 0, 'a version 1 release cannot replace the current repository');
like($out, qr/Genesis release version 1 omits currently supported architectures: s390x/,
'version 1 refusal identifies the missing architecture');
ok(!-d $common || !glob("$common/*.rpm"),
'a version 1 release publishes nothing');
}
{
my ($rc, $out, $common) = run_publish(
$RELEASE,
'missing-non-genesis-package',
sub { $_[0]->{'xCAT-release'} = '>= 2.0.0' },
);
isnt($rc, 0, 'every common manifest package is verified');
like($out, qr/MISSING xCAT-release/,
'the common gate identifies a missing non-Genesis package');
ok(!-d $common || !glob("$common/*.rpm"),
'a missing non-Genesis package prevents publication');
}
{
my ($rc, $out, $common) = run_publish(
$RELEASE,
'missing-current-package',
sub { delete $_[0]->{ rpm_package_name('s390x') } },
);
isnt($rc, 0, 'a current release does not hide an incomplete manifest');
like($out, qr/\[common\] is missing supported packages: .*s390x/,
'the manifest failure identifies the missing current package');
ok(!-d $common || !glob("$common/*.rpm"),
'an incomplete current manifest publishes nothing');
}
{
my ($rc, $out, $common) = run_publish(
$RELEASE,
'missing-current-package-without-repo-verification',
sub { delete $_[0]->{ rpm_package_name('s390x') } },
1,
);
isnt($rc, 0, 'repository verification cannot disable the common manifest gate');
like($out, qr/\[common\] is missing supported packages: .*s390x/,
'the mandatory manifest gate identifies the missing package');
ok(!-d $common || !glob("$common/*.rpm"),
'the mandatory manifest gate publishes nothing');
}
{
my ($rc, $out, $common) = run_publish(
$RELEASE,
'unknown-current-package',
sub { $_[0]->{'xCAT-genesis-openembedded-unknown'} = '>= 2.18.0' },
);
isnt($rc, 0, 'an unknown shared manifest package is refused');
like($out, qr/\[common\] has unsupported packages: xCAT-genesis-openembedded-unknown/,
'the manifest failure identifies the unknown package');
ok(!-d $common || !glob("$common/*.rpm"),
'an unknown shared manifest package publishes nothing');
}
# ---- an incomplete release is refused, and publishes nothing --------------------------------------
{
my $partial = "$tmp/partial-release";
my $missing_architecture = 's390x';
my $missing_package = rpm_package_name($missing_architecture);
my @partial_architectures = grep { $_ ne $missing_architecture } @architectures;
make_path("$partial/rpm", "$partial/srpm");
for my $f (glob("$RELEASE/rpm/*.rpm"), glob("$RELEASE/srpm/*.rpm")) {
next if $f =~ /riscv64/; # drop one architecture
next if basename($f) =~ /^\Q$missing_package-\E/;
my ($sub) = $f =~ m{/(rpm|srpm)/[^/]+$};
copy($f, "$partial/$sub/") or die $!;
}
copy("$RELEASE/release.manifest", $partial) or die $!;
# SHA256SUMS without the dropped arch, so the release itself still self-describes consistently
open my $in, '<', "$RELEASE/SHA256SUMS" or die $!;
open my $o, '>', "$partial/SHA256SUMS" or die $!;
while (<$in>) { print {$o} $_ unless /riscv64/ }
close $in; close $o;
write_release_manifest(
$partial, $xcat_version, $xcat_release, $xcat_revision, $source_date_epoch,
join(',', @partial_architectures), 'rpm',
);
write_checksums($partial);
my ($rc, $out, $common) = run_publish($partial, 'partial');
isnt($rc, 0, 'a release missing an architecture is refused');
like($out, qr/omits currently supported architectures: \Q$missing_architecture\E/,
'the completeness gate identifies the missing architecture');
ok(!-d $common || !glob("$common/*.rpm"),
'... and nothing is published into the shared repository');
}
{
my $inconsistent = "$tmp/inconsistent-release";
my $missing_package = rpm_package_name('s390x');
copy_tree($RELEASE, $inconsistent);
for my $directory (qw(rpm srpm)) {
my @packages = glob("$inconsistent/$directory/$missing_package-*");
unlink(@packages) == @packages or die "cannot remove package fixture\n";
}
write_checksums($inconsistent);
my ($rc, $out, $common) = run_publish($inconsistent, 'inconsistent');
isnt($rc, 0, 'a release inconsistent with its manifest is refused');
like($out, qr/Genesis release is missing:/,
'the release-layout gate reports the missing package');
ok(!-d $common || !glob("$common/*.rpm"),
'an inconsistent release publishes nothing');
}
done_testing();
+152 -59
View File
@@ -12,8 +12,10 @@ use POSIX ();
use Test::More;
use Time::HiRes qw(sleep);
use lib "$FindBin::Bin/..";
use lib "$FindBin::Bin/../lib";
use lib "$FindBin::Bin/lib";
use BuildUtils qw(read_manifest);
use XCAT::BuildUtils qw(
capture_command
command_exists
@@ -27,10 +29,10 @@ use XCAT::GenesisRelease qw(
rpm_package_name
);
use XCAT::GenesisReleaseTest qw(
make_export
build_package_release
run_capture
write_forkmanager_stub
write_checksums
write_forkmanager_stub
write_release_manifest
);
@@ -75,11 +77,12 @@ SKIP: {
}
SKIP: {
skip 'APT repository tools are not installed', 59
skip 'APT repository tools are not installed', 63
unless $^O eq 'linux'
&& command_exists('dpkg-deb')
&& command_exists('apt-ftparchive');
test_deb_consumer();
test_version_1_deb_consumer();
test_legacy_deb_consumer();
test_partial_deb_release();
test_publish_lock();
@@ -100,9 +103,13 @@ done_testing();
sub write_target_manifest {
my ($root, $target) = @_;
make_path($root);
my %manifest = read_manifest("$repo_root/packages-manifest.conf");
my $common = join('', map { "$_=$manifest{common}{$_}\n" }
sort keys %{ $manifest{common} // {} });
write_binary(
"$root/packages-manifest.conf",
"[$target]\n" . rpm_package_name(capture_command('uname', '-m')) . "=*\n",
"[$target]\n" . rpm_package_name(capture_command('uname', '-m'))
. "=*\n\n[common]\n$common",
);
}
@@ -297,17 +304,11 @@ SH
sub run_apt_consumer {
my (%args) = @_;
my @dists = @{ $args{dists} // \@APT_SUITES };
my @build_mode = $args{build} ? ('--dry-run') : ('--skip-build');
my $manifest = $args{manifest};
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) . "\n" . $shared);
write_apt_manifest($manifest);
}
return run_capture(
$args{log},
@@ -318,13 +319,29 @@ sub run_apt_consumer {
'--manifest', $manifest,
'--dists', join(' ', @dists),
'--arch', 'amd64',
'--skip-build', '--skip-genesis', '--skip-tarball',
@build_mode, '--skip-genesis', '--skip-tarball',
'--publish', '--expect-arch', 'amd64 ppc64el',
($args{verify} ? () : ('--no-verify-repo')),
@{ $args{extra} // [] },
);
}
sub write_apt_manifest {
my ($path, $mutate) = @_;
my %shipped = read_manifest("$repo_root/debs-manifest.conf");
BAIL_OUT('debs-manifest.conf has no [shared] section')
unless exists $shipped{shared};
my %shared = %{ $shipped{shared} };
$mutate->(\%shared) if $mutate;
make_path((File::Basename::dirname($path)));
write_binary(
$path,
join('', map { "[$_-amd64]\nxcat-genesis-base=*\n" } @APT_SUITES)
. "\n[shared]\n"
. join('', map { "$_=$shared{$_}\n" } sort keys %shared),
);
}
# Stage one legacy genesis-base deb for the noble suite only -- enough for the runs that publish
# no Genesis release.
sub stage_legacy_deb {
@@ -397,7 +414,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/,
like(read_binary($log),
qr/\[verify-repo\] shared pool complete: 8 packages present/,
'the shared pool is gated against the manifest\'s [shared] section');
my @suite_packages;
for my $codename (@APT_SUITES) {
@@ -524,6 +542,103 @@ sub test_deb_consumer {
'pooled package still matches the verified release');
}
sub test_version_1_deb_consumer {
my @release_architectures = grep { $_ ne 's390x' } architectures();
my $release_root = make_package_release(
"$tmp/deb-version-1", 'deb', @release_architectures,
);
write_release_manifest(
$release_root, $version, $release, $revision, $epoch,
join(',', @release_architectures), 'deb', 1,
);
write_checksums($release_root);
my $apt_root = "$tmp/apt-version-1";
my $output = "$tmp/deb-version-1-output";
stage_apt_suites($output, "$tmp/deb-version-1-legacy");
my $log = "$tmp/deb-version-1.log";
my $status = run_apt_consumer(
log => $log, output => $output, apt_dir => $apt_root,
extra => [ '--genesis-release', $release_root ],
);
my $pool = "$apt_root/pool/main/xcat-genesis-openembedded";
isnt($status, 0, 'APT refuses a version 1 release for the current repository');
like(read_binary($log),
qr/Genesis release version 1 omits currently supported architectures: s390x/,
'the version 1 refusal identifies the missing architecture');
ok(!-d $pool, 'a version 1 release publishes no shared pool');
my $current_release = make_package_release(
"$tmp/deb-current-manifest", 'deb', architectures(),
);
my $missing_manifest = "$tmp/deb-version-1-missing.conf";
write_apt_manifest(
$missing_manifest,
sub { delete $_[0]->{ deb_package_name('s390x') } },
);
my $missing_apt = "$tmp/apt-version-1-missing";
my $missing_output = "$tmp/deb-version-1-missing-output";
stage_apt_suites($missing_output, "$tmp/deb-version-1-missing-legacy");
my $missing_log = "$tmp/deb-version-1-missing.log";
my $missing_status = run_apt_consumer(
log => $missing_log, output => $missing_output, apt_dir => $missing_apt,
manifest => $missing_manifest,
build => 1,
extra => [ '--genesis-release', $current_release ],
);
isnt($missing_status, 0,
'a current release does not hide an incomplete shared manifest');
like(read_binary($missing_log), qr/\[shared\] is missing supported packages: .*s390x/,
'the shared manifest failure identifies the missing current package');
unlike(read_binary($missing_log), qr/Ensure sbuild chroots/,
'an incomplete shared manifest is rejected before building');
my $unknown_manifest = "$tmp/deb-version-1-unknown.conf";
write_apt_manifest(
$unknown_manifest,
sub { $_[0]->{'xcat-genesis-openembedded-unknown'} = '2.*' },
);
my $unknown_apt = "$tmp/apt-version-1-unknown";
my $unknown_output = "$tmp/deb-version-1-unknown-output";
stage_apt_suites($unknown_output, "$tmp/deb-version-1-unknown-legacy");
my $unknown_log = "$tmp/deb-version-1-unknown.log";
my $unknown_status = run_apt_consumer(
log => $unknown_log, output => $unknown_output, apt_dir => $unknown_apt,
manifest => $unknown_manifest,
extra => [ '--genesis-release', $current_release ],
);
isnt($unknown_status, 0, 'an unknown shared manifest package is refused');
like(read_binary($unknown_log),
qr/\[shared\] has unsupported packages: xcat-genesis-openembedded-unknown/,
'the shared manifest failure identifies the unknown package');
ok(!-d "$unknown_apt/pool/main/xcat-genesis-openembedded",
'an unknown shared manifest package publishes nothing');
my $non_genesis_manifest = "$tmp/deb-non-genesis-missing.conf";
write_apt_manifest(
$non_genesis_manifest,
sub { $_[0]->{'xcat-release'} = '2.*' },
);
my $non_genesis_apt = "$tmp/apt-non-genesis-missing";
my $non_genesis_output = "$tmp/deb-non-genesis-missing-output";
stage_apt_suites($non_genesis_output, "$tmp/deb-non-genesis-missing-legacy");
my $non_genesis_log = "$tmp/deb-non-genesis-missing.log";
my $non_genesis_status = run_apt_consumer(
log => $non_genesis_log,
output => $non_genesis_output,
apt_dir => $non_genesis_apt,
manifest => $non_genesis_manifest,
extra => [ '--genesis-release', $current_release ],
);
isnt($non_genesis_status, 0, 'every shared manifest package is verified');
like(read_binary($non_genesis_log), qr/MISSING xcat-release/,
'the shared gate identifies a missing non-Genesis package');
ok(!-d "$non_genesis_apt/pool/main/xcat-genesis-openembedded",
'a missing non-Genesis package prevents APT publication');
}
sub test_signed_common_rpm_repository {
my $release_root = make_package_release("$tmp/rpm-signed", 'rpm');
my $package = "xCAT-genesis-openembedded-x86_64-$version-$release.noarch.rpm";
@@ -679,7 +794,7 @@ sub test_partial_rpm_release {
);
isnt($status, 0, 'RPM repository rejects a partial Genesis release');
like(read_binary($log), qr/Genesis release is missing supported architectures/,
like(read_binary($log), qr/Genesis release version 2 omits currently supported architectures/,
'RPM partial-release failure names the missing architectures');
ok(-f $existing, 'partial release does not remove the deployed package');
}
@@ -701,7 +816,7 @@ sub test_partial_deb_release {
);
isnt($status, 0, 'APT repository rejects a partial Genesis release');
like(read_binary($log), qr/Genesis release is missing supported architectures/,
like(read_binary($log), qr/Genesis release version 2 omits currently supported architectures/,
'APT partial-release failure names the missing architectures');
ok(-f $existing, 'partial DEB release does not remove the published package');
}
@@ -995,6 +1110,18 @@ SH
is($status, 0, 'the activation helper accepts a local-TFTP node');
is(read_binary($log), "x86_64\n", 'the activation helper runs mknb for one architecture');
write_binary($log, '');
$status = run_capture($output, $driver, 's390x');
is($status, 0, 'the activation helper accepts s390x');
is(read_binary($log), "s390x\n", 'the activation helper runs mknb for s390x');
write_binary($log, '');
$status = run_capture($output, $driver, 'unsupported');
is($status, 0, 'an unsupported architecture does not fail the package transaction');
is(read_binary($log), '', 'an unsupported architecture does not run mknb');
like(read_binary($output), qr/Invalid Genesis architecture: unsupported/,
'the activation helper reports an unsupported architecture');
write_binary($log, '');
local $ENV{XCAT_TEST_SERVICE_NODE} = 1;
local $ENV{XCAT_TEST_SHAREDTFTP} = 1;
@@ -1011,50 +1138,16 @@ SH
sub make_package_release {
my ($root, $format, @requested_architectures) = @_;
@requested_architectures = architectures() unless @requested_architectures;
my $release_root = "$root/release";
make_path($release_root);
for my $architecture (@requested_architectures) {
my $export = make_export("$root/exports/$architecture", $architecture);
my $packages = "$root/packages/$architecture";
die "Cannot package test release for $architecture\n"
if run_capture(
"$root/package-$architecture.log",
$packager,
'--architecture', $architecture,
'--export-dir', $export,
'--output-dir', $packages,
'--version', $version,
'--release', $release,
'--revision', $revision,
'--source-date-epoch', $epoch,
'--format', $format,
);
if ($format eq 'rpm') {
my $name = rpm_package_name($architecture);
make_path("$release_root/rpm", "$release_root/srpm");
copy(
"$packages/rpm/$name-$version-$release.noarch.rpm",
"$release_root/rpm/$name-$version-$release.noarch.rpm",
) or die $!;
copy(
"$packages/srpm/$name-$version-$release.src.rpm",
"$release_root/srpm/$name-$version-$release.src.rpm",
) or die $!;
} else {
my $name = deb_package_name($architecture);
make_path("$release_root/deb");
copy(
"$packages/deb/${name}_${version}-${release}_all.deb",
"$release_root/deb/${name}_${version}-${release}_all.deb",
) or die $!;
}
}
write_release_manifest(
$release_root, $version, $release, $revision, $epoch,
join(',', @requested_architectures), $format,
return build_package_release(
root => $root,
format => $format,
architectures => \@requested_architectures,
packager => $packager,
version => $version,
release => $release,
revision => $revision,
epoch => $epoch,
);
write_checksums($release_root);
return $release_root;
}
sub genesis_rpm_names {
+188 -18
View File
@@ -11,6 +11,7 @@ use Test::More;
use lib "$FindBin::Bin/../lib";
use lib "$FindBin::Bin/lib";
use XCAT::BuildUtils qw(
capture_command
command_exists
digest_file
read_binary
@@ -19,6 +20,7 @@ use XCAT::BuildUtils qw(
use XCAT::GenesisRelease qw(
architectures
deb_package_name
minimum_release_version
rpm_package_name
validated_release_checksums
validate_architecture
@@ -53,13 +55,19 @@ if ($ENV{XCAT_GENESIS_CI}) {
is_deeply(
[ architectures() ],
[ qw(x86 x86_64 ppc64 ppc64le armv7hf aarch64 riscv64) ],
[ qw(x86 x86_64 ppc64 ppc64le armv7hf aarch64 riscv64 s390x) ],
'supported architectures keep their exact xCAT names',
);
is(rpm_package_name('ppc64le'), 'xCAT-genesis-openembedded-ppc64le',
'RPM package keeps ppc64le distinct');
is(deb_package_name('x86_64'), 'xcat-genesis-openembedded-x86-64',
'DEB package uses a legal spelling of x86_64');
is(minimum_release_version('x86_64'), 1,
'legacy architectures use release format version 1');
is(minimum_release_version('x86_64', 's390x'), 2,
's390x requires release format version 2');
dies_like(sub { minimum_release_version() }, qr/requires a Genesis architecture/,
'release format selection requires an architecture');
dies_like(sub { validate_architecture('ppc') }, qr/Unsupported Genesis architecture/,
'legacy ppc alias is rejected');
@@ -181,7 +189,7 @@ like(
);
dies_like(
sub { validate_complete_release($release_dir) },
qr/Genesis release is missing supported architectures/,
qr/Genesis release version 2 omits currently supported architectures/,
'partial release cannot be published',
);
@@ -201,6 +209,49 @@ write_release_manifest(
write_checksums($complete_release);
ok(validate_complete_release($complete_release), 'complete release can be published');
my $legacy_release = "$tmp/legacy-release";
copy_tree($complete_release, $legacy_release);
for my $directory (qw(rpm srpm deb)) {
my @s390x_packages = glob("$legacy_release/$directory/*s390x*");
unlink(@s390x_packages) == @s390x_packages
or die "Cannot remove the s390x package fixture: $!\n";
}
write_release_manifest(
$legacy_release, $version, $release, $revision, $epoch,
'x86,x86_64,ppc64,ppc64le,armv7hf,aarch64,riscv64', 'deb,rpm', 1,
);
write_checksums($legacy_release);
ok(validate_release($legacy_release), 'version 1 releases remain readable');
dies_like(
sub { validate_complete_release($legacy_release) },
qr/Genesis release version 1 omits currently supported architectures: s390x/,
'version 1 releases cannot replace the current repository',
);
my $invalid_legacy_release = "$tmp/invalid-legacy-release";
copy_tree($complete_release, $invalid_legacy_release);
write_release_manifest(
$invalid_legacy_release, $version, $release, $revision, $epoch,
join(',', architectures()), 'deb,rpm', 1,
);
write_checksums($invalid_legacy_release);
dies_like(
sub { validate_release($invalid_legacy_release) },
qr/Genesis architecture s390x is not valid in release version 1/,
'version 1 rejects the version 2 architecture vocabulary',
);
my $unknown_release_version = "$tmp/unknown-release-version";
copy_tree($complete_release, $unknown_release_version);
write_release_manifest(
$unknown_release_version, $version, $release, $revision, $epoch,
join(',', architectures()), 'deb,rpm', 3,
);
write_checksums($unknown_release_version);
dies_like(sub { validate_release($unknown_release_version) },
qr/Unsupported Genesis package release version/,
'unknown release manifest versions fail');
my $deb_only_release = "$tmp/deb-only-release";
make_path("$deb_only_release/deb");
for my $architecture (architectures()) {
@@ -233,11 +284,25 @@ dies_like(sub { validate_release($missing_release) }, qr/Genesis release is miss
'incomplete architecture set fails');
SKIP: {
skip 'git is not installed', 2 unless command_exists('git');
skip 'git is not installed', 13 unless command_exists('git');
my $source = "$tmp/dirty-xcat-core";
make_path("$source/xCAT-genesis-builder/oe");
my $oe = "$source/xCAT-genesis-builder/oe";
my $capability_marker = "$tmp/capability-query-ran";
make_path($oe);
write_binary("$source/Version", "$version\n");
write_binary("$source/xCAT-genesis-builder/oe/build", "#!/bin/sh\nexit 99\n");
write_binary(
"$source/xCAT-genesis-builder/oe/build",
"#!/bin/sh\n"
. "if [ \"\${1-}\" = --list-architectures ]; then\n"
. " [ -z \"\${XCAT_TEST_CAPABILITY_MARKER-}\" ] || : >\"\$XCAT_TEST_CAPABILITY_MARKER\"\n"
. " mkdir -p \"\${XCAT_GENESIS_WORK_DIR:?}\"\n"
. " printf '%s\\n' x86_64\n"
. " exit 0\n"
. "fi\n"
. "exit 99\n",
);
chmod(0755, "$source/xCAT-genesis-builder/oe/build")
or die "Cannot make fixture build executable: $!";
write_binary("$source/xCAT-genesis-builder/oe/export", "#!/bin/sh\nexit 99\n");
for my $command (
[ 'git', '-C', $source, 'init', '-q' ],
@@ -248,22 +313,111 @@ SKIP: {
die "Cannot prepare test repository\n"
if run_capture("$tmp/git-fixture.log", @{$command});
}
my $previous_revision = capture_command('git', '-C', $source, 'rev-parse', 'HEAD');
my $commit_source = sub {
my ($path, $message) = @_;
for my $command (
[ 'git', '-C', $source, 'add', $path ],
[ 'git', '-C', $source, '-c', 'user.name=xCAT test',
'-c', 'user.email=xcat-test@example.invalid', 'commit', '-qm', $message ],
) {
die "Cannot update test repository\n"
if run_capture("$tmp/git-fixture.log", @{$command});
}
};
write_binary("$source/untracked", "not part of the commit\n");
my $log = "$tmp/dirty-source.log";
isnt(
run_capture(
$log, $builder, '--xcat-source', $source,
'--output-dir', "$tmp/dirty-output",
),
0,
'release builder rejects untracked source files',
);
{
local $ENV{XCAT_TEST_CAPABILITY_MARKER} = $capability_marker;
isnt(
run_capture(
$log, $builder, '--xcat-source', $source,
'--architecture', 's390x',
'--output-dir', "$tmp/dirty-output",
),
0,
'release builder rejects untracked source files',
);
}
like(read_binary($log), qr/xcat-core checkout is not clean/,
'dirty checkout failure is explicit');
ok(!-e $capability_marker,
'dirty source is rejected before its architecture helper runs');
unlink("$source/untracked") or die "Cannot clean the source fixture: $!\n";
write_binary("$source/revision-marker", "new revision\n");
$commit_source->('revision-marker', 'advance source revision');
my $ref_log = "$tmp/ref-mismatch.log";
{
local $ENV{XCAT_TEST_CAPABILITY_MARKER} = $capability_marker;
isnt(
run_capture(
$ref_log, $builder, '--xcat-source', $source,
'--xcat-ref', $previous_revision,
'--architecture', 's390x',
'--output-dir', "$tmp/ref-mismatch-output",
),
0,
'release builder rejects a mismatched xcat-core revision',
);
}
like(read_binary($ref_log), qr/xcat-core HEAD .* does not match \Q$previous_revision\E/,
'revision mismatch failure is explicit');
ok(!-e $capability_marker,
'revision mismatch is rejected before the architecture helper runs');
my $target_log = "$tmp/missing-target.log";
{
local $ENV{XCAT_TEST_CAPABILITY_MARKER} = $capability_marker;
isnt(
run_capture(
$target_log, $builder, '--xcat-source', $source,
'--architecture', 's390x',
'--output-dir', "$tmp/missing-target-output",
),
0,
'release builder rejects an unsupported xcat-core target',
);
}
like(read_binary($target_log), qr/does not support Genesis architecture s390x/,
'missing target failure identifies the required xcat-core support');
ok(-e $capability_marker, 'the architecture helper records a successful query');
write_binary("$oe/build", "#!/bin/sh\nexit 23\n");
chmod(0755, "$oe/build") or die "Cannot update fixture build executable: $!";
$commit_source->('xCAT-genesis-builder/oe/build', 'fail capability query');
my $failed_log = "$tmp/failed-query.log";
isnt(
run_capture(
$failed_log, $builder, '--xcat-source', $source,
'--architecture', 's390x',
'--output-dir', "$tmp/failed-query-output",
),
0,
'release builder rejects a failed architecture query',
);
like(read_binary($failed_log), qr/does not report supported Genesis architectures/,
'failed architecture queries are reported');
write_binary("$oe/build", "#!/bin/sh\nexit 0\n");
chmod(0755, "$oe/build") or die "Cannot update fixture build executable: $!";
$commit_source->('xCAT-genesis-builder/oe/build', 'empty capability query');
my $empty_log = "$tmp/empty-query.log";
isnt(
run_capture(
$empty_log, $builder, '--xcat-source', $source,
'--architecture', 's390x',
'--output-dir', "$tmp/empty-query-output",
),
0,
'release builder rejects an empty architecture query',
);
like(read_binary($empty_log), qr/reported no supported Genesis architectures/,
'empty architecture queries are reported');
}
SKIP: {
skip 'rpmbuild and rpm are not installed', 18
skip 'rpmbuild and rpm are not installed', 19
unless command_exists('rpmbuild') && command_exists('rpm');
exercise_packager('rpm');
}
@@ -276,12 +430,12 @@ SKIP: {
}
SKIP: {
skip 'dpkg-deb is not installed', 12 unless command_exists('dpkg-deb');
skip 'dpkg-deb is not installed', 14 unless command_exists('dpkg-deb');
exercise_packager('deb');
}
SKIP: {
skip 'git and dpkg-deb are not installed', 6
skip 'git and dpkg-deb are not installed', 9
unless command_exists('git') && command_exists('dpkg-deb');
exercise_builder_tmpdir();
}
@@ -465,6 +619,10 @@ sub exercise_builder_tmpdir {
<<'BUILD',
#!/bin/sh
set -eu
if [ "${1-}" = --list-architectures ]; then
printf '%s\n' x86_64 s390x
exit 0
fi
expected=$XCAT_GENESIS_WORK_DIR/build/tmp
[ "${TMPDIR:-}" = "$expected" ] || exit 41
mkdir -p "$TMPDIR/deploy"
@@ -516,7 +674,7 @@ EXPORT
$status = run_capture(
$log, $builder, '--xcat-source', $source,
'--output-dir', $output, '--work-dir', $persistent_work,
'--format', 'deb',
'--format', 'deb', '--architecture', 's390x',
);
}
is($status, 0, 'release builder isolates the OpenEmbedded tmpdir');
@@ -527,6 +685,18 @@ EXPORT
is((stat($output))[2] & oct('07777'), oct('0755'),
'release directory is readable by other users');
my $built = validate_release($output);
is($built->{architectures}, 'x86_64', 'isolated build keeps the target architecture');
is($built->{architectures}, 's390x', 'isolated build keeps the target architecture');
is($built->{formats}, 'deb', 'isolated build keeps the requested format');
is($built->{version}, 2, 'a release containing s390x uses format version 2');
my $legacy_output = "$tmp/tmpdir-legacy-release";
my $legacy_log = "$tmp/tmpdir-legacy-builder.log";
my $legacy_status = run_capture(
$legacy_log, $builder, '--xcat-source', $source,
'--output-dir', $legacy_output, '--work-dir', $persistent_work,
'--format', 'deb', '--architecture', 'x86_64',
);
is($legacy_status, 0, 'release builder keeps legacy targets buildable');
my $legacy = validate_release($legacy_output);
is($legacy->{version}, 1, 'a release without s390x uses format version 1');
}
+57 -2
View File
@@ -13,8 +13,10 @@ use XCAT::BuildUtils qw(
relative_files
write_binary
);
use XCAT::GenesisRelease qw(deb_package_name rpm_package_name);
our @EXPORT_OK = qw(
build_package_release
copy_tree
dies_like
make_export
@@ -24,6 +26,58 @@ our @EXPORT_OK = qw(
write_release_manifest
);
sub build_package_release {
my (%args) = @_;
my $root = $args{root};
my $format = $args{format};
my @architectures = @{ $args{architectures} };
my $release_root = "$root/release";
make_path($release_root);
for my $architecture (@architectures) {
my $export = make_export("$root/exports/$architecture", $architecture);
my $packages = "$root/packages/$architecture";
die "Cannot package test release for $architecture\n"
if run_capture(
"$root/package-$architecture.log",
$args{packager},
'--architecture', $architecture,
'--export-dir', $export,
'--output-dir', $packages,
'--version', $args{version},
'--release', $args{release},
'--revision', $args{revision},
'--source-date-epoch', $args{epoch},
'--format', $format,
);
if ($format eq 'rpm') {
my $name = rpm_package_name($architecture);
make_path("$release_root/rpm", "$release_root/srpm");
copy(
"$packages/rpm/$name-$args{version}-$args{release}.noarch.rpm",
"$release_root/rpm/$name-$args{version}-$args{release}.noarch.rpm",
) or die $!;
copy(
"$packages/srpm/$name-$args{version}-$args{release}.src.rpm",
"$release_root/srpm/$name-$args{version}-$args{release}.src.rpm",
) or die $!;
} else {
my $name = deb_package_name($architecture);
make_path("$release_root/deb");
copy(
"$packages/deb/${name}_$args{version}-$args{release}_all.deb",
"$release_root/deb/${name}_$args{version}-$args{release}_all.deb",
) or die $!;
}
}
write_release_manifest(
$release_root, $args{version}, $args{release}, $args{revision},
$args{epoch}, join(',', @architectures), $format,
);
write_checksums($release_root);
return $release_root;
}
sub make_export {
my ($directory, $architecture) = @_;
make_path($directory);
@@ -45,11 +99,12 @@ sub make_export {
sub write_release_manifest {
my ($directory, $xcat_version, $xcat_release, $revision, $epoch,
$architectures, $formats) = @_;
$architectures, $formats, $manifest_version) = @_;
$manifest_version //= 2;
write_binary(
"$directory/release.manifest",
"format=xcat-genesis-packages\n"
. "version=1\n"
. "version=$manifest_version\n"
. "xcat_version=$xcat_version\n"
. "xcat_release=$xcat_release\n"
. "xcat_revision=$revision\n"