mirror of
https://github.com/xcat2/xcat-dep.git
synced 2026-09-30 23:05:17 +00:00
3f3289bc95
Exercise mixed repository finalization and changed publisher inputs on x86_64. Check native manifest selection and use File::Slurper in the goconserver fixture.
390 lines
24 KiB
Perl
390 lines
24 KiB
Perl
use strict;
|
|
use warnings;
|
|
|
|
use Cwd qw(abs_path);
|
|
use File::Copy qw(copy);
|
|
use File::Path qw(make_path);
|
|
use File::Temp qw(tempdir);
|
|
use FindBin qw($RealBin);
|
|
use JSON::PP;
|
|
use Test::More;
|
|
|
|
use lib "$RealBin/..", "$RealBin/../lib", "$RealBin/../t/lib";
|
|
use MockBuildUtils qw(read_manifest sign_and_index_repo);
|
|
use XCAT::BuildUtils qw(capture_command command_exists digest_file digest_manifest relative_files read_binary write_binary);
|
|
use XCAT::GenesisReleaseTest qw(run_capture dies_like);
|
|
use XCAT::NativeInputs qw(load_inputs stage_inputs publisher_trust verify_input validate_outputs);
|
|
|
|
plan skip_all => 'Native Linux RPM tools are required' unless $^O eq 'linux'
|
|
&& !grep { !command_exists($_) } qw(rpm rpmkeys rpmbuild rpmsign gpg gpgconf createrepo_c unshare python3);
|
|
my $build_user = $ENV{XCAT_TEST_BUILD_USER} // '';
|
|
plan skip_all => 'Set XCAT_TEST_BUILD_USER to an unprivileged fixture builder' if $> == 0 && !$build_user;
|
|
my $build_uid = $> == 0 ? getpwnam($build_user) : $>;
|
|
plan skip_all => 'The fixture builder must be unprivileged' unless defined($build_uid) && $build_uid != 0;
|
|
my @rpm_user = $> == 0 ? ('runuser', '-u', $build_user, '--') : ();
|
|
my $parent_pid = $$;
|
|
my $tmp = tempdir(CLEANUP => !$ENV{XCAT_TEST_KEEP});
|
|
diag("native input fixtures: $tmp");
|
|
my $repo = abs_path("$RealBin/..");
|
|
my $owner = $ENV{XCAT_TEST_COLLECTOR} // "$repo/mockbuild-all.pl";
|
|
my $target = 'openeuler-24.03-ppc64le';
|
|
my $json = JSON::PP->new->canonical->pretty;
|
|
my $epoch = 1788718796;
|
|
my $host_arch = capture_command('uname', '-m');
|
|
my %manifest = read_manifest("$repo/packages-manifest.conf");
|
|
my $production_plan = eval { load_inputs($repo, $manifest{$target}); };
|
|
ok($production_plan, 'the shipped full POWER manifest has an executable native input plan') or die($@);
|
|
is($production_plan->{nodes}{'xCAT-genesis-base'}{build_uid}, 0, 'the shipped Genesis owner declares its root assembly exception');
|
|
my %homes;
|
|
my %keys;
|
|
make_path("$tmp/bin", "$tmp/rpmbuild/SPECS");
|
|
chmod 0755, $tmp;
|
|
chown $build_uid, -1, "$tmp/rpmbuild", "$tmp/rpmbuild/SPECS" if $> == 0;
|
|
for my $key (qw(publisher build foreign)) {
|
|
my $home = "$tmp/key-$key";
|
|
$homes{$key} = $home;
|
|
make_path($home);
|
|
chmod 0700, $home;
|
|
is(run_capture("$tmp/key-$key.log", 'gpg', '--homedir', $home, '--batch', '--pinentry-mode', 'loopback',
|
|
'--passphrase', '', '--quick-generate-key', "$key\@example.invalid", 'rsa2048', 'sign', '0'), 0,
|
|
"create private $key key") or die(read_binary("$tmp/key-$key.log"));
|
|
my $listing = capture_command('gpg', '--homedir', $home, '--with-colons', '--list-keys');
|
|
($keys{$key}) = $listing =~ /^fpr:::::::::([0-9A-F]+):/m;
|
|
write_binary("$home/public.asc", capture_command('gpg', '--homedir', $home, '--armor', '--export', $keys{$key}));
|
|
}
|
|
END {
|
|
local $?;
|
|
for my $home (values %homes) {
|
|
run_capture("$home/cleanup.log", 'gpgconf', '--homedir', $home, '--kill', 'gpg-agent') if defined($parent_pid) && $$ == $parent_pid && -d $home;
|
|
}
|
|
}
|
|
|
|
my %rpm;
|
|
for my $name (qw(native-leaf native-child publisher-package publisher-elf publisher-arch)) {
|
|
my $arch = $name eq 'publisher-arch' ? $host_arch : 'noarch';
|
|
my $payload = $name eq 'publisher-elf' ? q{printf '\177ELFfixture\n'} : q{printf 'fixture\n'};
|
|
my $spec = <<'SPEC';
|
|
Name: NAME
|
|
Version: 1
|
|
Release: 1.oe2403
|
|
Summary: Native input contract fixture
|
|
License: MIT
|
|
BuildArch: ARCH
|
|
%description
|
|
Native input contract fixture.
|
|
%install
|
|
mkdir -p %{buildroot}/usr/share/native-inputs
|
|
PAYLOAD > %{buildroot}/usr/share/native-inputs/%{name}
|
|
%check
|
|
test "$(id -u)" -ne 0
|
|
%files
|
|
/usr/share/native-inputs/%{name}
|
|
SPEC
|
|
$spec =~ s/NAME/$name/;
|
|
$spec =~ s/ARCH/$arch/;
|
|
$spec =~ s/PAYLOAD/$payload/;
|
|
write_binary("$tmp/rpmbuild/SPECS/$name.spec", $spec);
|
|
is(run_capture("$tmp/fixture-$name.log", @rpm_user, 'rpmbuild', '-ba', '--define', "_topdir $tmp/rpmbuild",
|
|
"$tmp/rpmbuild/SPECS/$name.spec"), 0, "build real $name fixture with nonroot check")
|
|
or die(read_binary("$tmp/fixture-$name.log"));
|
|
$rpm{$name} = "$tmp/rpmbuild/RPMS/$arch/$name-1-1.oe2403.$arch.rpm";
|
|
$rpm{"$name-src"} = "$tmp/rpmbuild/SRPMS/$name-1-1.oe2403.src.rpm";
|
|
}
|
|
|
|
sub signed_copy {
|
|
my ($source, $name, $key) = @_;
|
|
my $dest = "$tmp/$name.rpm";
|
|
copy($source, $dest) or die $!;
|
|
local $ENV{GNUPGHOME} = $homes{$key};
|
|
is(run_capture("$tmp/sign-$name.log", 'rpmsign', '--define', "_gpg_name $keys{$key}",
|
|
'--define', '__gpg /usr/bin/gpg', '--addsign', $dest), 0, "sign $name with $key key")
|
|
or die(read_binary("$tmp/sign-$name.log"));
|
|
return $dest;
|
|
}
|
|
my %signed;
|
|
for my $name (qw(native-leaf-src native-child-src publisher-package publisher-elf publisher-arch)) {
|
|
$signed{$name} = signed_copy($rpm{$name}, "signed-$name", 'publisher');
|
|
}
|
|
my $foreign = signed_copy($rpm{'native-leaf-src'}, 'foreign-source', 'foreign');
|
|
|
|
copy("$RealBin/fixtures/power-wget.pl", "$tmp/bin/wget") or die $!;
|
|
copy("$RealBin/fixtures/power-mock.py", "$tmp/bin/mock") or die $!;
|
|
chmod 0755, "$tmp/bin/wget", "$tmp/bin/mock";
|
|
local $ENV{PATH} = "$tmp/bin:$ENV{PATH}";
|
|
local $ENV{NATIVE_DOWNLOADS} = "$tmp/downloads.json";
|
|
local $ENV{NATIVE_OUTPUTS} = "$tmp/outputs.json";
|
|
local $ENV{NATIVE_CALLS} = "$tmp/calls.jsonl";
|
|
write_binary($ENV{NATIVE_OUTPUTS}, $json->encode({map { $_ => [$rpm{$_}, $rpm{"$_-src"}] } qw(native-leaf native-child)}));
|
|
|
|
sub catalog {
|
|
return {version => 1, target => $target, publisher_key => {
|
|
path => 'openeuler/publisher.asc', sha256 => digest_file("$homes{publisher}/public.asc"),
|
|
fingerprint => $keys{publisher}}, build_inputs => [], inputs => [
|
|
{name => 'native-leaf', type => 'srpm', build_uid => 1000, needs => [], outputs => ['native-leaf'],
|
|
url => 'https://repo.openeuler.org/openEuler-24.03-LTS/source/Packages/native-leaf-1-1.oe2403.src.rpm',
|
|
sha256 => digest_file($signed{'native-leaf-src'})},
|
|
{name => 'native-child', type => 'srpm', build_uid => 1000, needs => ['native-leaf'], outputs => ['native-child'],
|
|
url => 'https://repo.openeuler.org/openEuler-24.03-LTS/source/Packages/native-child-1-1.oe2403.src.rpm',
|
|
sha256 => digest_file($signed{'native-child-src'})},
|
|
{name => 'publisher-package', type => 'publisher', needs => [], outputs => ['publisher-package'],
|
|
url => 'https://repo.openeuler.org/openEuler-24.03-LTS/Everything/x86_64/Packages/publisher-package-1-1.oe2403.noarch.rpm',
|
|
sha256 => digest_file($signed{'publisher-package'})}]};
|
|
}
|
|
|
|
sub prepare {
|
|
my ($name, $mutate) = @_;
|
|
my $root = "$tmp/$name source";
|
|
make_path("$root/openeuler", "$root/mock-configs/templates");
|
|
my $data = catalog();
|
|
$mutate->($data) if $mutate;
|
|
copy("$homes{publisher}/public.asc", "$root/openeuler/publisher.asc") or die $!;
|
|
write_binary("$root/openeuler/24.03-ppc64le.inputs.json", $json->encode($data));
|
|
write_binary("$root/packages-manifest.conf", "[$target]\nnative-child=1\npublisher-package=1\n");
|
|
write_binary("$root/Gitepoch", "$epoch\n");
|
|
write_binary("$root/Gitinfo", ('a' x 40) . "\n");
|
|
write_binary("$root/mock-configs/$target.cfg", "config_opts['root'] = 'native-contract'\nconfig_opts['dnf.conf'] = ''\n");
|
|
my %downloads = map { $_->{url} => $signed{$_->{name} . ($_->{type} eq 'srpm' ? '-src' : '')} } @{$data->{inputs}};
|
|
write_binary($ENV{NATIVE_DOWNLOADS}, $json->encode(\%downloads));
|
|
unlink $ENV{NATIVE_CALLS};
|
|
return ($root, $data);
|
|
}
|
|
|
|
my ($valid) = prepare('valid');
|
|
my $plan = load_inputs($valid, {'native-child' => '1', 'publisher-package' => '1'});
|
|
is_deeply($plan->{order}, [qw(native-leaf native-child publisher-package)], 'public plan orders prerequisites before consumers');
|
|
stage_inputs($plan, "$tmp/valid-stage");
|
|
is(digest_file($plan->{nodes}{'publisher-package'}{staged}), digest_file($signed{'publisher-package'}), 'publisher admission preserves signed bytes');
|
|
is(digest_file($plan->{nodes}{'native-leaf'}{staged}), digest_file($signed{'native-leaf-src'}), 'source admission preserves signed bytes');
|
|
ok(-f "$tmp/valid-stage/inputs.json", 'admission records input identity and catalog digest');
|
|
validate_outputs($plan->{nodes}{'native-leaf'}, [$rpm{'native-leaf'}], 1);
|
|
pass('declared real native output passes ownership validation');
|
|
dies_like(sub { validate_outputs($plan->{nodes}{'native-leaf'}, [$rpm{'native-child'}], 1) }, qr/Unexpected output/, 'wrong owner output fails');
|
|
dies_like(sub { validate_outputs($plan->{nodes}{'native-leaf'}, [$rpm{'native-leaf'}, $rpm{'native-leaf'}], 1) }, qr/Duplicate output/, 'duplicate output fails');
|
|
dies_like(sub { validate_outputs($plan->{nodes}{'native-leaf'}, [], 1) }, qr/Missing output/, 'empty successful build fails');
|
|
|
|
my @bad = (
|
|
['cycle', sub { $_[0]{inputs}[0]{needs} = ['native-child'] }, qr/Cyclic native dependency/],
|
|
['missing', sub { $_[0]{inputs}[0]{needs} = ['absent'] }, qr/Missing native dependency/],
|
|
['conflict', sub { $_[0]{inputs}[1]{outputs} = ['native-leaf'] }, qr/Conflicting output ownership/],
|
|
['uid', sub { $_[0]{inputs}[0]{build_uid} = 0 }, qr/Invalid native build UID/],
|
|
['foreign-release', sub { $_[0]{inputs}[2]{url} =~ s/LTS\//LTS-SP3\// }, qr/Publisher binary must be exact GA/],
|
|
['unsafe-define', sub { $_[0]{inputs}[0]{defines} = ['llvmjit 0; touch injected'] }, qr/Invalid native spec definition/],
|
|
['missing-patch', sub { $_[0]{inputs}[0]{patches} = [{path => 'absent.patch', sha256 => 'a' x 64}] }, qr/Missing input/],
|
|
['source-needs-owner', sub {
|
|
push @{$_[0]{inputs}}, {name => 'goconserver', type => 'owner', build_uid => 1000,
|
|
outputs => ['goconserver'], needs => []};
|
|
$_[0]{inputs}[0]{needs} = ['goconserver'];
|
|
}, qr/Unsupported native execution edge/],
|
|
['owner-needs-owner', sub {
|
|
push @{$_[0]{inputs}}, {name => 'goconserver', type => 'owner', build_uid => 1000,
|
|
outputs => ['goconserver'], needs => ['ipmitool-xcat']},
|
|
{name => 'ipmitool-xcat', type => 'owner', build_uid => 1000, outputs => ['ipmitool-xcat'], needs => []};
|
|
}, qr/Unsupported native execution edge/],
|
|
);
|
|
for my $case (@bad) {
|
|
my ($root) = prepare($case->[0], $case->[1]);
|
|
dies_like(sub { load_inputs($root, {'native-child' => '1'}) }, $case->[2], "$case->[0] fails before input acquisition");
|
|
ok(!-f $ENV{NATIVE_CALLS}, "$case->[0] executes no downloader or builder");
|
|
}
|
|
|
|
for my $case (
|
|
['bad-hash', $signed{'native-leaf-src'}, 'b' x 64, qr/SHA256 mismatch/],
|
|
['unsigned', $rpm{'native-leaf-src'}, digest_file($rpm{'native-leaf-src'}), qr/Publisher signature missing/],
|
|
['wrong-key', $foreign, digest_file($foreign), qr/Command failed/],
|
|
) {
|
|
my %node = %{$plan->{nodes}{'native-leaf'}};
|
|
$node{sha256} = $case->[2];
|
|
dies_like(sub { verify_input($plan, \%node, $case->[1], $plan->{trust_db}) }, $case->[3], "$case->[0] cannot enter a native root");
|
|
}
|
|
for my $case (['publisher-elf', qr/ELF payload/], ['publisher-arch', qr/not a noarch binary/]) {
|
|
my %node = (%{$plan->{nodes}{'publisher-package'}}, name => $case->[0], sha256 => digest_file($signed{$case->[0]}));
|
|
dies_like(sub { verify_input($plan, \%node, $signed{$case->[0]}, $plan->{trust_db}) }, $case->[1], "$case->[0] is rejected using the real RPM payload/header");
|
|
}
|
|
|
|
{
|
|
my ($root) = prepare('standalone-signers');
|
|
my $dest = "$tmp/standalone-repo";
|
|
make_path($dest);
|
|
my $generated = signed_copy($rpm{'native-child'}, 'generated-build-signer', 'build');
|
|
my $publisher = "$dest/publisher-package-1-1.oe2403.noarch.rpm";
|
|
my $child = "$dest/native-child-1-1.oe2403.noarch.rpm";
|
|
copy($signed{'publisher-package'}, $publisher) or die $!;
|
|
copy($generated, $child) or die $!;
|
|
is(run_capture("$tmp/standalone-createrepo.log", 'createrepo_c', $dest), 0,
|
|
'create metadata for the mixed-signer repository');
|
|
is(run_capture("$tmp/standalone-sign.log", 'gpg', '--homedir', $homes{build}, '--batch', '--yes',
|
|
'--armor', '--detach-sign', '--default-key', $keys{build}, "$dest/repodata/repomd.xml"), 0,
|
|
'sign repository metadata with the build key');
|
|
my @verify = ($^X, $owner, '--repo-root', $root, '--target', $target,
|
|
'--verify-repo', $dest, '--gpg-home', $homes{build}, '--gpg-key-name', $keys{build});
|
|
is(run_capture("$tmp/standalone-valid.log", @verify), 0,
|
|
'standalone native verification accepts each declared signing authority')
|
|
or diag(read_binary("$tmp/standalone-valid.log"));
|
|
my $resigned = signed_copy($rpm{'publisher-package'}, 'publisher-build-signer', 'build');
|
|
copy($resigned, $publisher) or die $!;
|
|
isnt(run_capture("$tmp/standalone-resigned.log", @verify), 0,
|
|
'standalone verification rejects a publisher package signed by the build key');
|
|
like(read_binary("$tmp/standalone-resigned.log"), qr/SHA256 mismatch/,
|
|
'the publisher failure identifies the changed pinned bytes');
|
|
copy($signed{'publisher-package'}, $publisher) or die $!;
|
|
my $wrong_generated = signed_copy($rpm{'native-child'}, 'generated-publisher-signer', 'publisher');
|
|
copy($wrong_generated, $child) or die $!;
|
|
isnt(run_capture("$tmp/standalone-wrong-generated.log", @verify), 0,
|
|
'standalone verification rejects the publisher key for generated output');
|
|
like(read_binary("$tmp/standalone-wrong-generated.log"), qr/NOKEY|WRONGKEY|checksig/i,
|
|
'the generated output failure identifies the unexpected signer');
|
|
copy($generated, $child) or die $!;
|
|
is(run_capture("$tmp/standalone-restored.log", @verify), 0,
|
|
'restoring both original package signatures restores standalone acceptance');
|
|
ok(!-f $ENV{NATIVE_CALLS}, 'standalone verification runs no downloader or builder');
|
|
}
|
|
|
|
{
|
|
my $dest = "$tmp/signing-repo";
|
|
make_path($dest);
|
|
my $publisher = "$dest/publisher-package-1-1.oe2403.noarch.rpm";
|
|
my $child = "$dest/native-child-1-1.oe2403.noarch.rpm";
|
|
copy($signed{'publisher-package'}, $publisher) or die $!;
|
|
copy($rpm{'native-child'}, $child) or die $!;
|
|
my @commands;
|
|
my $sequence = 0;
|
|
my %options = (
|
|
gpg_sign => 1, gpg_home => $homes{build}, gpg_key_name => $keys{build},
|
|
gpg_program => '/usr/bin/gpg', source_date_epoch => $epoch,
|
|
run => sub {
|
|
my ($command) = @_;
|
|
push @commands, $command;
|
|
my $log = "$tmp/signing-command-" . ++$sequence . '.log';
|
|
die read_binary($log) if run_capture($log, '/bin/sh', '-c', $command);
|
|
},
|
|
);
|
|
my $ok = eval { sign_and_index_repo($dest, $plan, %options); 1 };
|
|
ok($ok, 'repository signing accepts unchanged publisher input') or die($@);
|
|
is(digest_file($publisher), digest_file($signed{'publisher-package'}),
|
|
'repository signing preserves the original publisher bytes');
|
|
is(run_capture("$tmp/signing-publisher.log", 'rpmkeys', '--dbpath', $plan->{trust_db},
|
|
'--checksig', '--verbose', $publisher), 0,
|
|
'publisher RPM retains its original trusted signature');
|
|
my $build_trust = "$tmp/signing-build-trust";
|
|
make_path($build_trust);
|
|
is(run_capture("$tmp/signing-build-import.log", 'rpmkeys', '--dbpath', $build_trust,
|
|
'--import', "$homes{build}/public.asc"), 0, 'trust the build key in an isolated RPM database');
|
|
is(run_capture("$tmp/signing-child.log", 'rpmkeys', '--dbpath', $build_trust,
|
|
'--checksig', '--verbose', $child), 0, 'generated RPM verifies with the build key');
|
|
like(read_binary("$tmp/signing-child.log"), qr/Signature.*\bOK\b/i,
|
|
'generated RPM has a verified signature');
|
|
is(run_capture("$tmp/signing-metadata.log", 'gpg', '--homedir', $homes{build},
|
|
'--verify', "$dest/repodata/repomd.xml.asc", "$dest/repodata/repomd.xml"), 0,
|
|
'repository metadata has a valid build signature');
|
|
|
|
my $changed = signed_copy($rpm{'publisher-package'}, 'changed-before-signing', 'build');
|
|
copy($changed, $publisher) or die $!;
|
|
copy($rpm{'native-child'}, $child) or die $!;
|
|
isnt(digest_file($publisher), $plan->{nodes}{'publisher-package'}{sha256},
|
|
'the changed publisher RPM differs from its pinned input');
|
|
my $before = digest_manifest($dest, 'sha256', relative_files($dest));
|
|
@commands = ();
|
|
dies_like(sub { sign_and_index_repo($dest, $plan, %options) },
|
|
qr/\APublisher input changed before signing: \Q$publisher\E\n\z/,
|
|
'changed publisher bytes stop repository signing');
|
|
is_deeply(\@commands, [], 'changed publisher bytes stop before signing or indexing commands');
|
|
is(digest_manifest($dest, 'sha256', relative_files($dest)), $before,
|
|
'rejection preserves generated RPMs, publisher RPMs and repository metadata');
|
|
}
|
|
|
|
my @namespace = ('unshare', ($> == 0 ? () : ('--user', '--map-root-user')), '--mount', '--propagation', 'private');
|
|
my $can_owner = $host_arch eq 'ppc64le'
|
|
&& run_capture("$tmp/mock-loader.log", 'python3', '-c', 'from mockbuild.util import load_config') == 0
|
|
&& run_capture("$tmp/namespace.log", @namespace, 'true') == 0;
|
|
SKIP: {
|
|
skip 'Whole native owner requires POWER, native Mock and a private mount namespace', 52 unless $can_owner;
|
|
for my $case (@bad[0..2, 7, 8]) {
|
|
my ($root) = prepare("owner-$case->[0]", $case->[1]);
|
|
my $rc = run_capture("$tmp/owner-$case->[0].log", @namespace,
|
|
$^X, $owner, '--repo-root', $root, '--target', $target, '--xcat-source', $root,
|
|
'--output', "$tmp/owner-$case->[0]-output", '--skip-genesis', '--skip-tarball', '--gpg-sign',
|
|
'--gpg-home', $homes{build}, '--gpg-key-name', $keys{build}, '--scrub-all-chroots');
|
|
isnt($rc, 0, "$case->[0] fails the whole owner");
|
|
like(read_binary("$tmp/owner-$case->[0].log"), $case->[2], "$case->[0] reports its graph error at the owner boundary");
|
|
ok(!-f $ENV{NATIVE_CALLS}, "$case->[0] precedes even mock scrub");
|
|
}
|
|
for my $scenario ('success', 'failed-child', 'empty-child', 'patch-path') {
|
|
my ($root, $data) = prepare("owner-$scenario");
|
|
if ($scenario eq 'patch-path') {
|
|
write_binary("$root/native.patch", "--- a/native-leaf.spec\n+++ b/native-leaf.spec\n@@ -4 +4 @@\n-Summary: Native input contract fixture\n+Summary: Patched native input contract fixture\n");
|
|
$data->{inputs}[0]{patches} = [{path => 'native.patch', sha256 => digest_file("$root/native.patch")}];
|
|
$data->{inputs}[0]{defines} = ['llvmjit 0', 'runselftest 1'];
|
|
write_binary("$root/openeuler/24.03-ppc64le.inputs.json", $json->encode($data));
|
|
}
|
|
my $out = "$tmp/owner-$scenario-output";
|
|
my $dest = "$out/xcat-dep/openeuler24.03/ppc64le";
|
|
make_path($dest, "$root/etc-mock");
|
|
copy("$root/mock-configs/$target.cfg", "$root/etc-mock/$target.cfg") or die $!;
|
|
write_binary("$dest/sentinel", 'old repository');
|
|
local $ENV{NATIVE_FAIL} = $scenario eq 'failed-child' ? 'native-child' : '';
|
|
local $ENV{NATIVE_EMPTY} = $scenario eq 'empty-child' ? 'native-child' : '';
|
|
local $ENV{MOCKBUILD_ALL_MOUNTNS} = 1;
|
|
my @cmd = ($^X, $owner, '--repo-root', $root, '--target', $target, '--xcat-source', $root,
|
|
'--output', $out, '--run-id', 'contract', '--skip-genesis', '--skip-tarball', '--gpg-sign',
|
|
'--gpg-home', $homes{build}, '--gpg-key-name', $keys{build}, '--max-parallel', 1);
|
|
my $rc = run_capture("$tmp/owner-$scenario.log", @namespace,
|
|
'sh', '-c', 'mount --bind "$1" /etc/mock && shift && exec "$@"', 'native-test', "$root/etc-mock", @cmd);
|
|
my @calls = -f $ENV{NATIVE_CALLS} ? map { JSON::PP->new->decode($_) } split /\n/, read_binary($ENV{NATIVE_CALLS}) : ();
|
|
my @built = map { $_->{name} } grep { exists $_->{name} } @calls;
|
|
is_deeply(\@built, ['native-leaf', 'native-child'], "$scenario executes the prerequisite then its dependent through the owner");
|
|
is_deeply([map { $_->{config_rc} } grep { exists $_->{config_rc} } @calls],
|
|
[map { 0 } 1 .. ($scenario eq 'patch-path' ? 3 : 2)],
|
|
"$scenario loads generated configurations through the installed native Mock");
|
|
my $publisher = "$dest/publisher-package-1-1.oe2403.noarch.rpm";
|
|
if ($scenario eq 'success' || $scenario eq 'patch-path') {
|
|
is($rc, 0, 'whole owner signs and collects the completed native chain') or diag(read_binary("$tmp/owner-$scenario.log"));
|
|
is(-f $publisher ? digest_file($publisher) : '', digest_file($signed{'publisher-package'}), 'final publisher package remains byte-identical');
|
|
ok(-f "$dest/native-child-1-1.oe2403.noarch.rpm", 'dependent native output reaches the repository');
|
|
ok(!-f "$dest/native-leaf-1-1.oe2403.noarch.rpm", 'build-only native prerequisite stays private');
|
|
if ($scenario eq 'success' && $rc == 0) {
|
|
my @verify = ($^X, $owner, '--repo-root', $root, '--target', $target,
|
|
'--verify-repo', $dest, '--gpg-home', $homes{build}, '--gpg-key-name', $keys{build});
|
|
is(run_capture("$tmp/final-verify.log", @verify), 0, 'standalone gate accepts the declared publisher and build signers');
|
|
copy($publisher, "$tmp/publisher-preserved.rpm") or die $!;
|
|
{
|
|
local $ENV{GNUPGHOME} = $homes{build};
|
|
is(run_capture("$tmp/publisher-resign.log", 'rpmsign', '--define', "_gpg_name $keys{build}",
|
|
'--define', '__gpg /usr/bin/gpg', '--resign', $publisher), 0, 'negative control re-signs a publisher copy with the build key');
|
|
}
|
|
isnt(run_capture("$tmp/final-resigned-publisher.log", @verify), 0, 'collector rejects a re-signed publisher input even with an otherwise allowed key');
|
|
like(read_binary("$tmp/final-resigned-publisher.log"), qr/SHA256 mismatch/, 'the publisher failure identifies lost byte identity');
|
|
copy("$tmp/publisher-preserved.rpm", $publisher) or die $!;
|
|
is(digest_file($publisher), digest_file($signed{'publisher-package'}), 'restore the original publisher bytes after the negative control');
|
|
my $generated = "$dest/native-child-1-1.oe2403.noarch.rpm";
|
|
copy($generated, "$tmp/generated-preserved.rpm") or die $!;
|
|
{
|
|
local $ENV{GNUPGHOME} = $homes{publisher};
|
|
is(run_capture("$tmp/generated-resign.log", 'rpmsign', '--define', "_gpg_name $keys{publisher}",
|
|
'--define', '__gpg /usr/bin/gpg', '--resign', $generated), 0, 'negative control signs generated output with the publisher key');
|
|
}
|
|
isnt(run_capture("$tmp/final-wrong-generated-key.log", @verify), 0, 'collector rejects the publisher key for generated outputs');
|
|
like(read_binary("$tmp/final-wrong-generated-key.log"), qr/NOKEY|WRONGKEY|checksig/i, 'the generated output failure reports its signer mismatch');
|
|
copy("$tmp/generated-preserved.rpm", $generated) or die $!;
|
|
}
|
|
if ($scenario eq 'patch-path') {
|
|
my @prepared = grep { exists $_->{spec} } @calls;
|
|
is(scalar @prepared, 1, 'tracked patch uses the existing native buildsrpm path once');
|
|
like($prepared[0]{spec} // '', qr/^Summary: Patched native input contract fixture$/m,
|
|
'the real patch modifies the extracted source spec');
|
|
my @args = @{$prepared[0]{mock} // []};
|
|
ok(grep($_ eq 'llvmjit 0', @args), 'vendor disable option remains one quoted argument');
|
|
ok(grep($_ eq 'runselftest 1', @args), 'vendor test option remains enabled');
|
|
my $original = "$out/mockbuild-all/$target-contract/native-inputs/native-leaf/native-leaf-1-1.oe2403.src.rpm";
|
|
is(digest_file($original), digest_file($signed{'native-leaf-src'}), 'patch preparation preserves the original signed source');
|
|
}
|
|
} else {
|
|
isnt($rc, 0, "$scenario fails collection");
|
|
is(read_binary("$dest/sentinel"), 'old repository', "$scenario preserves the old repository");
|
|
ok(!-f $publisher, "$scenario does not publish partial publisher inputs");
|
|
ok(!-f "$dest/native-child-1-1.oe2403.noarch.rpm", "$scenario does not publish partial native outputs");
|
|
}
|
|
}
|
|
}
|
|
|
|
done_testing();
|