diff --git a/.github/workflows/genesis-openembedded.yml b/.github/workflows/genesis-openembedded.yml index d10452f..9d47f94 100644 --- a/.github/workflows/genesis-openembedded.yml +++ b/.github/workflows/genesis-openembedded.yml @@ -58,6 +58,7 @@ jobs: prove -v t/build_utils.t prove -v t/build_timeout.t prove -v t/sbuild-all.t + prove -v t/mockbuild-all.t prove -v -It/lib t/genesis_openembedded_release.t sudo -E prove -v -It/lib t/genesis_openembedded_consumer.t prove -v t/riscv64_perl_cell.t diff --git a/BUILD.md b/BUILD.md index f0c380a..b532328 100644 --- a/BUILD.md +++ b/BUILD.md @@ -106,6 +106,11 @@ Use these flags to skip specific operations: - Skips non-perl xcat-dep package builders (`elilo`, `grub2-xcat`, `ipmitool-xcat`, `syslinux-xcat`, `goconserver`, `conserver-xcat`, `xnba-undi`). - `--skip-perl` - Skips `/mockbuild-perl-packages.pl`. + - With any of the three flags above, the run repository, the tarball and the deployed cell keep + every rpm the skipped builder published, so the deploy gate still checks the whole manifest. + The run stops if that cell holds an rpm the signing key did not sign, or whose digests do + not verify when no key is configured, an rpm of another architecture, or a package at two + versions. The SRPM tarball holds only what the run built. - `--skip-build` - Skips all build steps; only runs collection/repo/tarball stages from existing artifact roots. - `--skip-createrepo` diff --git a/MockBuildUtils.pm b/MockBuildUtils.pm index a03c2f7..ff62ed0 100644 --- a/MockBuildUtils.pm +++ b/MockBuildUtils.pm @@ -8,6 +8,7 @@ use warnings; use Exporter 'import'; use File::Basename qw(basename); use File::Copy qw(copy); +use File::Glob qw(bsd_glob); use File::Find; use Sys::Hostname; use Digest::MD5 qw(md5_hex); @@ -15,12 +16,14 @@ use Digest::MD5 qw(md5_hex); our @EXPORT_OK = qw( install_deps_packages install_deps_command missing_perl_modules sh_quote print_step - version_matches required_pkgs have_rpm read_manifest + version_matches required_pkgs skipped_builder carry_over_rpms rpm_name rpm_arch rpm_source_rpm + source_package rpm_digests_ok + have_rpm read_manifest verify_repo_packages verify_repo_signature verify_rpm_signatures parse_evr evr_cmp evr_constraint_ok parse_pin rpmkeys_checksig_problem rpm_version rpm_release rpm_sigmd5 rpm_is_signed restamp_release_line cross_copy_genesis finalize_xcat_dep bump_dep_release_suffix - build_mock_uniqueext rpm_arch rpm_in_cell + build_mock_uniqueext rpm_in_cell ); # install_deps_packages($os_id): the host packages mockbuild-all.pl needs to run at all, for the @@ -106,6 +109,107 @@ sub required_pkgs { } @$pkgs; } +# rpm_name: %{name} from the header of one rpm file, undef when rpm cannot read it. +sub rpm_name { + my ($rpm) = @_; + my $name = `rpm -qp --qf '%{name}' ${\ sh_quote($rpm)} 2>/dev/null`; + return (defined $name && $name ne '') ? $name : undef; +} + +# rpm_digests_ok: whether the header and payload digests of one rpm file verify, signatures aside. +sub rpm_digests_ok { + my ($rpm) = @_; + my $out = `rpmkeys --checksig --nosignature -v ${\ sh_quote($rpm)} 2>&1`; + return ($? == 0 && $out !~ /NOT OK/i) ? 1 : 0; +} + +# rpm_source_rpm: the %{sourcerpm} header of one rpm file, undef when rpm cannot read it. Every +# subpackage of one build shares it. +sub rpm_source_rpm { + my ($rpm) = @_; + my $srpm = `rpm -qp --qf '%{sourcerpm}' ${\ sh_quote($rpm)} 2>/dev/null`; + return (defined $srpm && $srpm =~ /\.src\.rpm$/) ? $srpm : undef; +} + +# source_package: the source package name of a source rpm file name. +sub source_package { + my ($srpm) = @_; + return (defined $srpm && $srpm =~ /^(.+)-[^-]+-[^-]+\.src\.rpm$/) ? $1 : undef; +} + +# skipped_builder: whether the builder that produces a source package was skipped by the flags. +# The classes are those of required_pkgs: xCAT-genesis-base, perl-*, and everything else the +# xcat-dep builders make. The OpenEmbedded Genesis is published from a release, not built here. +sub skipped_builder { + my ($source, $skipped) = @_; + return 0 if $source =~ /^xCAT-genesis-openembedded(?:-|$)/; + return $skipped->{genesis} ? 1 : 0 if $source =~ /^xCAT-genesis-base(?:-|$)/; + return $skipped->{perl} ? 1 : 0 if $source =~ /^perl-/; + return $skipped->{dep} ? 1 : 0; +} + +# carry_over_rpms: copy the binary rpms a skipped builder published in $from into the run repository +# $to, for the builds whose source package the target manifest still names and of which the run +# carries no member yet. %$skipped holds the +# genesis, perl and dep flags; @$manifest_names is the target's manifest section; $name_of and +# $source_of map an rpm path to its package name and its source rpm (rpm_name and rpm_source_rpm +# in production, injectable for tests), and $trusted says whether an rpm may be re-signed at all +# (signed by the configured key in production), $arch is the cell's architecture and $arch_of +# reads an rpm's. A build is selected when one of its rpms is a manifest package, and is then +# carried whole, subpackages included, so a package the manifest dropped is not republished. The +# carry-over dies instead of publishing a partial or doubtful set: an unreadable rpm in the cell +# outside the OpenEmbedded Genesis family, a +# member of a selected build the key did not sign or of another architecture than the cell's or +# noarch, or a package name published more than once. Returns the copied basenames. +sub carry_over_rpms { + my ($from, $to, $skipped, $manifest_names, $name_of, $source_of, $trusted, $arch, $arch_of) = @_; + my %named = map { $_ => 1 } @$manifest_names; + my %staged = map { my $n = $name_of->($_); defined $n ? ($n => 1) : () } + grep { !/\.src\.rpm$/ } bsd_glob("$to/*.rpm"); + my (%members, %selected, @problems); + for my $rpm (sort(bsd_glob("$from/*.rpm"))) { + next if $rpm =~ /\.src\.rpm$/; + # The OpenEmbedded Genesis is published from a release and pruned by name, so a stale file + # of that family is not read at all. + next if basename($rpm) =~ /^xCAT-genesis-openembedded-/; + my $srpm = $source_of->($rpm); + my $name = $name_of->($rpm); + my $source = source_package($srpm); + if (!defined $source || !defined $name) { + push @problems, basename($rpm) . ": header unreadable"; + next; + } + next unless skipped_builder($source, $skipped); + push @{ $members{$srpm} }, { rpm => $rpm, name => $name }; + $selected{$srpm} = 1 + if $named{$name} || ($named{'xCAT-genesis-base'} && $name =~ /^xCAT-genesis-base-/); + } + my %seen; + for my $srpm (sort keys %selected) { + for my $m (@{ $members{$srpm} }) { + push @problems, basename($m->{rpm}) . ": not signed by the configured key" unless $trusted->($m->{rpm}); + my $rpm_arch = $arch_of->($m->{rpm}); + push @problems, basename($m->{rpm}) . ": architecture " . ($rpm_arch // 'unreadable') . " is not $arch or noarch" + unless defined $rpm_arch && ($rpm_arch eq $arch || $rpm_arch eq 'noarch'); + push @problems, "$m->{name}: published more than once" if $seen{ $m->{name} }++ == 1; + } + } + die "FATAL: the published cell $from cannot be carried over:\n " . join("\n ", @problems) . "\n" + if @problems; + my @copied; + for my $srpm (sort keys %selected) { + # A build the run already carries in part is the run's, whole; mixing generations of one + # source package is never right. + next if grep { $staged{ $_->{name} } } @{ $members{$srpm} }; + for my $m (@{ $members{$srpm} }) { + my $base = basename($m->{rpm}); + copy($m->{rpm}, "$to/$base") or die "Failed to carry $m->{rpm} -> $to: $!\n"; + push @copied, $base; + } + } + return sort @copied; +} + # verify_repo_packages: the PURE completeness-decision layer of the repo gate. Given the manifest's # %expected { pkg => version-pin } and the %present { pkg => version-found-or-undef } actually in a # built repo, return a list of human-readable problem strings (empty list = every package present at diff --git a/mockbuild-all.pl b/mockbuild-all.pl index d46e207..3f3efad 100755 --- a/mockbuild-all.pl +++ b/mockbuild-all.pl @@ -16,6 +16,7 @@ use POSIX qw(strftime); use FindBin qw($RealBin); use lib $RealBin, "$RealBin/lib"; use MockBuildUtils qw(sh_quote print_step version_matches required_pkgs rpm_in_cell + carry_over_rpms rpm_name rpm_arch rpm_source_rpm rpm_digests_ok install_deps_packages install_deps_command missing_perl_modules read_manifest verify_repo_packages verify_repo_signature verify_rpm_signatures rpm_version rpm_release rpm_sigmd5 restamp_release_line @@ -867,7 +868,7 @@ print "skipped $skipped_foreign rpm(s) of another architecture\n" if $skipped_fo # let a run whose builders all failed reach createrepo and the deployable tree, and fail # much later in the repo gate (verify_target_repo), naming missing packages instead of the # failed builds. -if (!$dry_run && $copied == 0) { +if (!$dry_run && $copied == 0 && @collect_roots) { die "No binary RPMs were collected. Check build logs and collection roots.\n"; } @@ -883,6 +884,31 @@ if (!$skip_genesis && !$dry_run) { } } +# A skipped builder built nothing this run, so everything it published in the cell joins the run +# repository here, ahead of the bump check, createrepo, the tarballs and the deploy gate. +if (!$dry_run && ($skip_genesis || $skip_perl || $skip_xcat_dep)) { + my $published = "$repo_dep/rh$rel/$arch"; + if (-d $published) { + my %skipped = (genesis => $skip_genesis, perl => $skip_perl, dep => $skip_xcat_dep); + # Only an rpm the configured key signed, by signer id and by rpmkeys --checksig, may be + # re-signed and republished; an unsigned run still requires the digests to verify. + my $trusted = \&rpm_digests_ok; + if ($gpg_sign || $gpg_home ne '') { + my ($dbopt, $problem) = rpmkeys_keyring($gpg_key_name, $gpg_home); + die "FATAL: $problem\n" if $problem; + my $accept = gpg_key_ids($gpg_key_name, $gpg_home); + $trusted = sub { + my $id = rpm_signer_keyid($_[0]); + return (defined $id && $accept->{$id} && !rpm_checksig_problem($_[0], $dbopt)) ? 1 : 0; + }; + } + for my $base (carry_over_rpms($published, $repo_dir, \%skipped, [sort keys %req], + \&rpm_name, \&rpm_source_rpm, $trusted, $arch, \&rpm_arch)) { + print "[collect] $base kept from the published cell $published\n"; + } + } +} + # Repo completeness -- every required package present at its pinned version (a '*' pin accepts any), # missing packages included -- is now gated ONCE, centrally, in deploy_target via verify_target_repo # (the single consolidated gate; it also runs under --skip-build and validates the deployed repo). @@ -1771,22 +1797,34 @@ sub verify_rpms_checksig { my ($dir, $keyname, $home) = @_; my @rpms = grep { !/\.src\.rpm$/ } glob("$dir/*.rpm"); return () unless @rpms; + my ($dbopt, $problem) = rpmkeys_keyring($keyname, $home); + return ($problem) if $problem; + return map { rpm_checksig_problem($_, $dbopt) } @rpms; +} + +# rpmkeys_keyring: an isolated rpm keyring holding only the signing key, as the --dbpath option for +# rpmkeys. Returns ($dbopt, undef), or (undef, $problem) when the key cannot be exported or imported. +sub rpmkeys_keyring { + my ($keyname, $home) = @_; require_command('rpmkeys'); require_command('gpg'); my $tmpdb = tempdir('rpmkeys-XXXXXXXX', TMPDIR => 1, CLEANUP => 1); my $h = ($home ne '') ? ' --homedir ' . sh_quote($home) : ''; my $keyfile = "$tmpdb/pubkey.asc"; system("gpg$h --batch --yes -a --export " . sh_quote($keyname) . ' > ' . sh_quote($keyfile) . ' 2>/dev/null'); - return ("SIGKEY: cannot export public key '$keyname' for rpmkeys --checksig") if !-s $keyfile; + return (undef, "SIGKEY: cannot export public key '$keyname' for rpmkeys --checksig") if !-s $keyfile; my $dbopt = '--dbpath ' . sh_quote($tmpdb); system("rpmkeys $dbopt --import " . sh_quote($keyfile) . ' >/dev/null 2>&1') == 0 - or return ("SIGKEY: rpmkeys --import of '$keyname' into the temp keyring failed"); - my @problems; - for my $rpm (@rpms) { - my $out = `rpmkeys $dbopt --checksig -v ${\ sh_quote($rpm)} 2>&1`; - push @problems, rpmkeys_checksig_problem(basename($rpm), $? >> 8, $out); - } - return @problems; + or return (undef, "SIGKEY: rpmkeys --import of '$keyname' into the temp keyring failed"); + return ($dbopt, undef); +} + +# rpm_checksig_problem: `rpmkeys --checksig` of one rpm against the keyring, as a problem string or +# an empty list when its digests and signature verify with the signing key. +sub rpm_checksig_problem { + my ($rpm, $dbopt) = @_; + my $out = `rpmkeys $dbopt --checksig -v ${\ sh_quote($rpm)} 2>&1`; + return rpmkeys_checksig_problem(basename($rpm), $? >> 8, $out); } # repomd_observed_signer: run gpg --verify on the detached repomd signature and extract the identity diff --git a/t/mockbuild-all.t b/t/mockbuild-all.t index 6adbe95..c4b8b2b 100644 --- a/t/mockbuild-all.t +++ b/t/mockbuild-all.t @@ -13,6 +13,7 @@ use File::Basename qw(basename); use MockBuildUtils qw(install_deps_packages install_deps_command missing_perl_modules required_pkgs version_matches rpm_sigmd5 rpm_version rpm_release rpm_is_signed rpm_arch rpm_in_cell + skipped_builder carry_over_rpms source_package restamp_release_line cross_copy_genesis finalize_xcat_dep read_manifest verify_repo_packages verify_repo_signature verify_rpm_signatures parse_evr evr_constraint_ok parse_pin rpmkeys_checksig_problem @@ -61,6 +62,174 @@ is_deeply([required_pkgs(\@all, 1, 1, 1)], [], like($unfiltered[0], qr/^MISSING xCAT-genesis-base\b/, 'gate: the flag names the absent genesis'); } +# ---- skipped_builder: which builder a source package belongs to, by the required_pkgs classes -- +{ + my %perl = (genesis => 0, perl => 1, dep => 0); + my %dep = (genesis => 0, perl => 0, dep => 1); + my %gen = (genesis => 1, perl => 0, dep => 0); + ok( skipped_builder('perl-IO-Stty', \%perl), '--skip-perl skips a perl source package'); + ok(!skipped_builder('perl-IO-Stty', \%dep), '... and --skip-xcat-dep does not'); + ok( skipped_builder('syslinux', \%dep), '--skip-xcat-dep skips a dep source package'); + ok(!skipped_builder('syslinux', \%perl), '... and --skip-perl does not'); + ok( skipped_builder('xCAT-genesis-base-x86_64', \%gen), '--skip-genesis skips the per-arch genesis source package'); + ok( skipped_builder('xCAT-genesis-base', \%gen), '... and the bare genesis name'); + ok(!skipped_builder('xCAT-genesis-base-x86_64', \%dep), 'genesis is not a dep builder package'); + ok(!skipped_builder('perl-IO-Stty', { genesis => 0, perl => 0, dep => 0 }), 'no skips -> nothing is skipped'); + ok(!skipped_builder('xCAT-genesis-openembedded-riscv64', { genesis => 1, perl => 1, dep => 1 }), + 'the OpenEmbedded Genesis belongs to no builder here, whatever is skipped'); +} + +# ---- source_package: the source package name of a source rpm file name ------------------------ +is(source_package('syslinux-6.03-1.el10.src.rpm'), 'syslinux', 'source_package strips version and release'); +is(source_package('xCAT-genesis-base-x86_64-2.19.0-snap1.noarch.src.rpm'), 'xCAT-genesis-base-x86_64', + 'source_package keeps the per-arch genesis name'); +is(source_package('garbage'), undef, 'source_package is undef for a non source rpm name'); + +# ---- carry_over_rpms: a skipped builder's published rpms stay, whatever the manifest names ------ +# Fixtures stand in for rpm headers: --..rpm, source rpm from a map. +my $name_of = sub { my $b = basename($_[0]); $b =~ s/-[^-]+-[^-]+\.[^.]+\.rpm\z//; $b }; +my %srpm_of = ( + 'syslinux-xcat' => 'syslinux-6.03-1.el10.src.rpm', + 'syslinux-extlinux' => 'syslinux-6.03-1.el10.src.rpm', + 'syslinux-debuginfo' => 'syslinux-6.03-1.el10.src.rpm', + 'elilo-xcat' => 'elilo-xcat-3.14-4.el10.src.rpm', + 'perl-IO-Stty' => 'perl-IO-Stty-0.04-5.el10.src.rpm', + 'perl-Sys-Virt' => 'perl-Sys-Virt-11.10.0-1.el10.src.rpm', + 'perl-Net-Telnet' => 'perl-Net-Telnet-3.04-1.el10.src.rpm', + 'xCAT-genesis-base-x86_64' => 'xCAT-genesis-base-x86_64-2.19.0-snap1.src.rpm', + 'xCAT-genesis-openembedded-x86_64' => 'xCAT-genesis-openembedded-x86_64-2.19.0-1.src.rpm', +); +my $source_of = sub { $srpm_of{ $name_of->($_[0]) } }; +my $all_trusted = sub { 1 }; +my $arch_of = sub { my $b = basename($_[0]); $b =~ /\.([^.]+)\.rpm\z/ ? $1 : undef }; +# the target's manifest: syslinux-xcat stands for the whole syslinux build, perl-Net-Telnet was dropped +my @manifest = qw(elilo-xcat syslinux-xcat perl-IO-Stty perl-Sys-Virt xCAT-genesis-base); +my $put = sub { + my ($dir, $base, $content) = @_; + open my $fh, '>', "$dir/$base" or die "$dir/$base: $!"; + print $fh $content; + close $fh; +}; +my $slurp = sub { local $/; open my $fh, '<', $_[0] or return undef; my $c = <$fh>; $c }; +my $cell_with = sub { + my ($tmp, @extra) = @_; + my $cell = "$tmp/cell"; + make_path($cell); + $put->($cell, 'syslinux-xcat-6.03-1.el10.x86_64.rpm', 'sysl-xcat'); + $put->($cell, 'syslinux-extlinux-6.03-1.el10.x86_64.rpm', 'extlinux'); + $put->($cell, 'syslinux-debuginfo-6.03-1.el10.x86_64.rpm', 'sysl-dbg'); + $put->($cell, 'syslinux-6.03-1.el10.src.rpm', 'sysl-src'); + $put->($cell, 'elilo-xcat-3.14-4.el10.noarch.rpm', 'elilo'); + $put->($cell, 'perl-IO-Stty-0.04-5.el10.noarch.rpm', 'stty'); + $put->($cell, 'perl-Net-Telnet-3.04-1.el10.noarch.rpm', 'telnet-dropped'); + $put->($cell, 'perl-Sys-Virt-11.10.0-1.el10.x86_64.rpm', 'virt-old'); + $put->($cell, 'xCAT-genesis-base-x86_64-2.19.0-snap1.noarch.rpm', 'genesis'); + $put->($cell, 'xCAT-genesis-openembedded-x86_64-2.19.0-1.noarch.rpm', 'oe-genesis'); + $put->($cell, 'xCAT-genesis-openembedded-stale.noarch.rpm', 'stale, not an rpm'); + $put->($cell, @$_) for @extra; + return $cell; +}; + +{ + my $tmp = tempdir(CLEANUP => 1); + my $cell = $cell_with->($tmp); + my $stage = "$tmp/stage"; + make_path($stage); + $put->($stage, 'perl-Sys-Virt-11.10.0-2.el10.x86_64.rpm', 'virt-built-now'); + + my @dep = carry_over_rpms($cell, $stage, { genesis => 0, perl => 0, dep => 1 }, \@manifest, $name_of, $source_of, $all_trusted, 'x86_64', $arch_of); + is_deeply(\@dep, + [qw(elilo-xcat-3.14-4.el10.noarch.rpm syslinux-debuginfo-6.03-1.el10.x86_64.rpm + syslinux-extlinux-6.03-1.el10.x86_64.rpm syslinux-xcat-6.03-1.el10.x86_64.rpm)], + '--skip-xcat-dep keeps every binary rpm of the dep builds, subpackages included'); + is($slurp->("$stage/syslinux-extlinux-6.03-1.el10.x86_64.rpm"), 'extlinux', 'the kept rpm is the published file'); + ok(!-e "$stage/syslinux-6.03-1.el10.src.rpm", 'source rpms are not carried'); + ok(!-e "$stage/perl-IO-Stty-0.04-5.el10.noarch.rpm", 'a builder that ran contributes nothing from the published cell'); + ok(!-e "$stage/xCAT-genesis-openembedded-x86_64-2.19.0-1.noarch.rpm", + 'an OpenEmbedded Genesis left in the published cell is not carried under --skip-xcat-dep'); + + my @perl = carry_over_rpms($cell, $stage, { genesis => 0, perl => 1, dep => 0 }, \@manifest, $name_of, $source_of, $all_trusted, 'x86_64', $arch_of); + is_deeply(\@perl, [qw(perl-IO-Stty-0.04-5.el10.noarch.rpm)], '--skip-perl keeps the published perl rpms the run lacks'); + ok(!-e "$stage/perl-Sys-Virt-11.10.0-1.el10.x86_64.rpm", 'a package the run already carries by name is not duplicated'); + is($slurp->("$stage/perl-Sys-Virt-11.10.0-2.el10.x86_64.rpm"), 'virt-built-now', 'the staged build is untouched'); + ok(!-e "$stage/perl-Net-Telnet-3.04-1.el10.noarch.rpm", 'a published package the target manifest no longer names is not republished'); + + my @gen = carry_over_rpms($cell, $stage, { genesis => 1, perl => 0, dep => 0 }, \@manifest, $name_of, $source_of, $all_trusted, 'x86_64', $arch_of); + is_deeply(\@gen, [qw(xCAT-genesis-base-x86_64-2.19.0-snap1.noarch.rpm)], '--skip-genesis keeps the published per-arch genesis rpm'); + + my @again = carry_over_rpms($cell, $stage, { genesis => 1, perl => 1, dep => 1 }, \@manifest, $name_of, $source_of, $all_trusted, 'x86_64', $arch_of); + is_deeply(\@again, [], 'a second carry-over finds everything staged already, and never the OpenEmbedded Genesis'); +} + +# ---- carry_over_rpms: a build the run already carries in part is never mixed with the old one ---- +{ + my $tmp = tempdir(CLEANUP => 1); + my $cell = $cell_with->($tmp); + my $stage = "$tmp/stage"; + make_path($stage); + $put->($stage, 'syslinux-xcat-6.04-1.el10.x86_64.rpm', 'sysl-xcat-new'); + my @dep = carry_over_rpms($cell, $stage, { genesis => 0, perl => 0, dep => 1 }, \@manifest, $name_of, $source_of, $all_trusted, 'x86_64', $arch_of); + is_deeply(\@dep, [qw(elilo-xcat-3.14-4.el10.noarch.rpm)], + 'a staged member of a build keeps every published member of that build out'); + ok(!-e "$stage/syslinux-extlinux-6.03-1.el10.x86_64.rpm", '... so old subpackages never join a newer main package'); +} + +# ---- carry_over_rpms fails closed: a selected build is carried whole or the run stops ----------- +{ + my $tmp = tempdir(CLEANUP => 1); + my $cell = $cell_with->($tmp); + my $stage = "$tmp/stage"; + make_path($stage); + my $no_debuginfo = sub { basename($_[0]) !~ /debuginfo/ }; + eval { carry_over_rpms($cell, $stage, { genesis => 0, perl => 0, dep => 1 }, \@manifest, $name_of, $source_of, $no_debuginfo, 'x86_64', $arch_of) }; + like($@, qr/syslinux-debuginfo-6\.03-1\.el10\.x86_64\.rpm: not signed by the configured key/, + 'a member of a selected build the key did not sign stops the carry-over and is named'); + ok(!-e "$stage/syslinux-xcat-6.03-1.el10.x86_64.rpm", '... and nothing of that run was copied'); + my @perl = carry_over_rpms($cell, $stage, { genesis => 0, perl => 1, dep => 0 }, \@manifest, $name_of, $source_of, $no_debuginfo, 'x86_64', $arch_of); + is_deeply(\@perl, [qw(perl-IO-Stty-0.04-5.el10.noarch.rpm perl-Sys-Virt-11.10.0-1.el10.x86_64.rpm)], + 'an unsigned rpm of a build that is not selected does not matter'); +} +{ + my $tmp = tempdir(CLEANUP => 1); + my $cell = $cell_with->($tmp, [ 'perl-IO-Stty-0.04-6.el10.noarch.rpm', 'stty-6' ]); + my $stage = "$tmp/stage"; + make_path($stage); + my $source_two = sub { my $n = $name_of->($_[0]); $n eq 'perl-IO-Stty' && $_[0] =~ /-6\.el10/ ? 'perl-IO-Stty-0.04-6.el10.src.rpm' : $srpm_of{$n} }; + eval { carry_over_rpms($cell, $stage, { genesis => 0, perl => 1, dep => 0 }, \@manifest, $name_of, $source_two, $all_trusted, 'x86_64', $arch_of) }; + like($@, qr/perl-IO-Stty: published more than once/, 'a package published at two versions stops the carry-over'); + ok(!-e "$stage/perl-IO-Stty-0.04-5.el10.noarch.rpm" && !-e "$stage/perl-IO-Stty-0.04-6.el10.noarch.rpm", '... and neither version was copied'); +} +{ + my $tmp = tempdir(CLEANUP => 1); + my $cell = $cell_with->($tmp, [ 'perl-Sys-Virt-11.10.0-1.el10.ppc64le.rpm', 'virt-ppc' ]); + my $stage = "$tmp/stage"; + make_path($stage); + my $source_ppc = sub { my $n = $name_of->($_[0]); $n eq 'perl-Sys-Virt' && $_[0] =~ /ppc64le/ ? 'perl-Sys-Virt-11.10.0-1.el10.src.rpm' : $srpm_of{$n} }; + eval { carry_over_rpms($cell, $stage, { genesis => 0, perl => 1, dep => 0 }, \@manifest, $name_of, $source_ppc, $all_trusted, 'x86_64', $arch_of) }; + like($@, qr/perl-Sys-Virt-11\.10\.0-1\.el10\.ppc64le\.rpm: architecture ppc64le is not x86_64 or noarch/, + 'a member of a selected build built for another architecture stops the carry-over'); +} +{ + my $tmp = tempdir(CLEANUP => 1); + my $cell = $cell_with->($tmp, [ 'broken-1-1.x86_64.rpm', 'not an rpm' ]); + my $stage = "$tmp/stage"; + make_path($stage); + eval { carry_over_rpms($cell, $stage, { genesis => 0, perl => 1, dep => 0 }, \@manifest, $name_of, $source_of, $all_trusted, 'x86_64', $arch_of) }; + like($@, qr/broken-1-1\.x86_64\.rpm: header unreadable/, 'an rpm whose header cannot be read stops the carry-over'); + unlike($@, qr/openembedded-stale/, '... while a stale OpenEmbedded file is never read'); +} + +# ---- carry_over_rpms: --output and --repo-dep paths may contain spaces -------------------------- +{ + my $tmp = tempdir(CLEANUP => 1); + my ($cell, $stage) = ("$tmp/published cell", "$tmp/run repo"); + make_path($cell, $stage); + $put->($cell, 'perl-IO-Stty-0.04-5.el10.noarch.rpm', 'stty'); + my @copied = carry_over_rpms($cell, $stage, { genesis => 0, perl => 1, dep => 0 }, ['perl-IO-Stty'], $name_of, $source_of, $all_trusted, 'x86_64', $arch_of); + is_deeply(\@copied, [qw(perl-IO-Stty-0.04-5.el10.noarch.rpm)], 'a published cell path with a space is read'); + ok(-f "$stage/perl-IO-Stty-0.04-5.el10.noarch.rpm", 'a run repository path with a space is written'); +} + # ---- version_matches: exact + shell-glob pins ------------------------------------------------ ok( version_matches('2.19.0', '2.*'), '2.* matches 2.19.0'); ok( version_matches('2.18.2', '2.*'), '2.* matches 2.18.2 (walks with xcat-core)');