2
0
mirror of https://github.com/xcat2/xcat-dep.git synced 2026-10-12 08:12:34 +00:00

Merge pull request #82 from VersatusHPC/fix/openeuler-build-fixes

fix(xcat-dep): no openEuler target builds, for three independent reasons
This commit is contained in:
Daniel Hilst
2026-10-05 11:09:55 -03:00
committed by GitHub
9 changed files with 189 additions and 47 deletions
@@ -77,6 +77,9 @@ jobs:
prove -v t/riscv64_perl_cell.t
sudo -E prove -v -It/lib t/common-repo-gate.t
prove -v t/openeuler.t
prove -v t/openeuler_mock_template_include.t
prove -v -Ilib t/openeuler_native_trust_dbpath.t
prove -v -Ilib t/openeuler_native_cpio_scan.t
bats t/verify-xcatsn-install.bats
# Ubuntu's AppArmor policy blocks these unprivileged test namespaces.
+40 -28
View File
@@ -9,10 +9,12 @@ use Exporter qw(import);
use File::Basename qw(basename);
use File::Copy qw(copy);
use File::Path qw(make_path);
use File::Spec ();
use File::Temp qw(tempdir);
use JSON::PP;
our @EXPORT_OK = qw(load_inputs stage_inputs verify_input rpm_identity validate_outputs publisher_trust);
our @EXPORT_OK = qw(load_inputs stage_inputs verify_input rpm_identity validate_outputs publisher_trust
trust_dbpath scan_cpio_for_elf);
sub read_file {
my ($path) = @_;
@@ -168,35 +170,38 @@ sub read_exact {
return $data;
}
sub scan_cpio_for_elf {
my ($fh) = @_;
while (1) {
my $header = read_exact($fh, 110);
die "Invalid RPM cpio header\n" unless $header =~ /\A07070[12][0-9A-Fa-f]{104}\z/;
my @fields = map { hex($_) } $header =~ /\A.{6}(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})\z/s;
my ($size, $namesize) = @fields[6, 11];
die "Invalid RPM cpio filename\n" unless $namesize > 0 && $namesize <= 1048576;
my $name = read_exact($fh, $namesize);
die "Invalid RPM cpio filename terminator\n" unless $name =~ s/\0\z//;
read_exact($fh, (4 - (110 + $namesize) % 4) % 4);
last if $name eq 'TRAILER!!!' && $size == 0;
my $prefix = read_exact($fh, $size < 4 ? $size : 4);
die "ELF payload in publisher noarch RPM: $name\n" if $prefix eq "\x7fELF";
$size -= length($prefix);
while ($size) { my $count = $size < 65536 ? $size : 65536; read_exact($fh, $count); $size -= $count; }
read_exact($fh, (4 - $fields[6] % 4) % 4);
}
my $tail;
while (read($fh, $tail, 65536)) { die "Unexpected data after RPM cpio trailer\n" if $tail =~ /[^\0]/; }
return 1;
}
sub reject_elf_payload {
my ($path) = @_;
open my $fh, '-|', 'rpm2cpio', $path or die "Cannot read RPM payload: $!\n";
binmode $fh;
my $error;
eval {
while (1) {
my $header = read_exact($fh, 110);
die "Invalid RPM cpio header\n" unless $header =~ /\A07070[12][0-9A-Fa-f]{104}\z/;
my @fields = map { hex($_) } $header =~ /\A.{6}(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})(.{8})\z/s;
my ($size, $namesize) = @fields[6, 11];
die "Invalid RPM cpio filename\n" unless $namesize > 0 && $namesize <= 1048576;
my $name = read_exact($fh, $namesize);
die "Invalid RPM cpio filename terminator\n" unless $name =~ s/\0\z//;
read_exact($fh, (4 - (110 + $namesize) % 4) % 4);
last if $name eq 'TRAILER!!!' && $size == 0;
my $prefix = read_exact($fh, $size < 4 ? $size : 4);
die "ELF payload in publisher noarch RPM: $name\n" if $prefix eq "\x7fELF";
$size -= length($prefix);
while ($size) { my $count = $size < 65536 ? $size : 65536; read_exact($fh, $count); $size -= $count; }
read_exact($fh, (4 - $fields[6] % 4) % 4);
}
my $tail;
while (read($fh, $tail, 65536)) { die "Unexpected data after RPM cpio trailer\n" if $tail =~ /[^\0]/; }
1;
} or $error = $@;
my $closed = close($fh);
my ($scanned, $error);
eval { $scanned = scan_cpio_for_elf($fh); 1 } or $error = $@;
close $fh;
die $error if $error;
die "rpm2cpio failed: $path\n" unless $closed;
die "RPM payload did not end in a cpio trailer: $path\n" unless $scanned;
}
sub verify_input {
@@ -218,9 +223,16 @@ sub verify_input {
return $id;
}
sub trust_dbpath {
my $root = $ENV{XCAT_DEP_TRUST_TMP} || File::Spec->tmpdir();
# The keyring lives until this process exits, after the last verify_input of the run.
return tempdir('xcat-dep-trust-XXXXXX', DIR => $root, CLEANUP => 1);
}
sub publisher_trust {
my ($plan, $work) = @_;
make_path("$work/trust", "$work/gnupg");
my $trust = trust_dbpath();
make_path($trust, "$work/gnupg");
chmod 0700, "$work/gnupg";
my $listing = capture('gpg', '--homedir', "$work/gnupg", '--batch', '--with-colons', '--show-keys', $plan->{publisher_key});
my @primary;
@@ -231,8 +243,8 @@ sub publisher_trust {
if ($pub && $fields[0] eq 'fpr') { push @primary, $fields[9]; $pub = 0; }
}
die "Publisher public key fingerprint mismatch\n" unless @primary == 1 && $primary[0] eq $plan->{publisher_fingerprint};
run('rpmkeys', '--dbpath', "$work/trust", '--import', $plan->{publisher_key});
return "$work/trust";
run('rpmkeys', '--dbpath', $trust, '--import', $plan->{publisher_key});
return $trust;
}
sub stage_inputs {
+1 -1
View File
@@ -1,4 +1,4 @@
include('templates/openeuler-20.03-sp4.tpl')
include('templates/openeuler-20.03.tpl')
config_opts['root'] = 'openeuler-20.03sp4-x86_64'
config_opts['target_arch'] = 'x86_64'
config_opts['legal_host_arches'] = ('x86_64',)
+1 -1
View File
@@ -1,4 +1,4 @@
include('templates/openeuler-22.03-sp4.tpl')
include('templates/openeuler-22.03.tpl')
config_opts['root'] = 'openeuler-22.03sp4-x86_64'
config_opts['target_arch'] = 'x86_64'
config_opts['legal_host_arches'] = ('x86_64',)
+6 -10
View File
@@ -9,16 +9,12 @@ from mockbuild.config import load_config
source = Path(sys.argv[1]).resolve()
with tempfile.TemporaryDirectory() as directory:
config_path = Path(directory)
(config_path / 'templates').mkdir()
absent = []
for parent in ('openeuler-20.03-sp4.tpl', 'openeuler-22.03-sp4.tpl', 'openeuler-24.03.tpl'):
source_template = Path('/etc/mock/templates') / parent
if not source_template.is_file():
absent.append(parent)
continue
shutil.copyfile(source_template, config_path / 'templates' / parent)
shutil.copyfile(source / 'templates/openeuler-lts-xcat.tpl', config_path / 'templates/openeuler-lts-xcat.tpl')
result = {'_absent_templates': absent}
templates = config_path / 'templates'
templates.mkdir()
for installed in Path('/etc/mock/templates').glob('*.tpl'):
shutil.copyfile(installed, templates / installed.name)
shutil.copyfile(source / 'templates/openeuler-lts-xcat.tpl', templates / 'openeuler-lts-xcat.tpl')
result = {}
for wrapper in sorted(source.glob('openeuler-*.cfg')):
try:
config = load_config(str(config_path), str(wrapper))
-7
View File
@@ -21,18 +21,11 @@ open(my $pipe, '-|', 'python3', "$RealBin/fixtures/mock-configs.py", "$RealBin/.
my $json = do {local $/; <$pipe>};
close($pipe) or die "native mock config loader failed: $?";
my $configs = decode_json($json);
my $absent = delete $configs->{_absent_templates} || [];
diag("base template absent on this host, its configs are skipped: @$absent") if @$absent;
for my $cell (@cells) {
my ($version, $release, $releasever, $arch) = @$cell;
my $target = "openeuler-$version-$arch";
my $config = $configs->{$target};
if ($config->{error}) {
my ($missing) = $config->{error} =~ m{templates/([^/\s]+\.tpl)};
if (defined($missing) && grep { $_ eq $missing } @$absent) {
note("$target skipped: $missing is not installed on this host");
next;
}
fail("$target loads: $config->{error}");
next;
}
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/perl
use strict;
use warnings;
use Test::More;
use FindBin qw($RealBin);
use File::Basename qw(basename);
use File::Slurper qw(read_text);
my @cfg = sort glob("$RealBin/../mock-configs/openeuler-*.cfg");
cmp_ok(scalar @cfg, '>=', 2, 'mock-configs/ carries openEuler configurations')
or die "no mock-configs/openeuler-*.cfg under $RealBin/.. -- this test reads nothing\n";
my %shipped = map { basename($_) => 1 } glob("$RealBin/../mock-configs/templates/*.tpl");
ok($shipped{'openeuler-lts-xcat.tpl'}, 'the xCAT openEuler template is shipped here');
for my $path (@cfg) {
my $name = basename($path);
my $text = read_text($path);
my ($release) = $text =~ /openeuler_repository_release'\]\s*=\s*'([^']+)'/;
ok(defined $release, "$name: declares openeuler_repository_release") or next;
my @inc = $text =~ /include\('templates\/([^']+)'\)/g;
cmp_ok(scalar @inc, '>=', 1, "$name: includes at least one template");
my ($majmin) = $release =~ /\A(\d+\.\d+)/;
ok(defined $majmin, "$name: its release starts with a major.minor ($release)") or next;
for my $t (@inc) {
next if $shipped{$t}; # shipped beside the cfg, so it always resolves
is($t, "openeuler-$majmin.tpl",
"$name: its mock-core-configs template is openeuler-$majmin.tpl, not '$t'");
}
}
done_testing();
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/perl
use strict;
use warnings;
use Test::More;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use XCAT::NativeInputs qw(scan_cpio_for_elf);
sub member {
my ($name, $data) = @_;
my $n = length($name) + 1;
my $rec = '070701'
. join('', map { sprintf '%08X', $_ }
(1, 0100644, 0, 0, 1, 0, length($data), 0, 0, 0, 0, $n, 0))
. "$name\0";
$rec .= "\0" x ((4 - (110 + $n) % 4) % 4);
$rec .= $data;
$rec .= "\0" x ((4 - length($data) % 4) % 4);
return $rec;
}
sub trailer {
my $n = length('TRAILER!!!') + 1;
my $rec = '070701' . join('', map { sprintf '%08X', $_ } (0) x 13);
substr($rec, 6 + 11 * 8, 8) = sprintf '%08X', $n;
$rec .= "TRAILER!!!\0";
$rec .= "\0" x ((4 - (110 + $n) % 4) % 4);
return $rec;
}
sub scan {
my ($bytes) = @_;
open my $fh, '<', \$bytes or die "in-memory open: $!";
binmode $fh;
my $r = eval { scan_cpio_for_elf($fh) };
return $@ unless defined $r;
return $r ? 'OK' : "FALSY VERDICT: '$r'";
}
is(scan(member('./usr/share/perl5/CGI.pm', "package CGI;\n1;\n") . trailer()), 'OK',
'a noarch payload of plain files is accepted');
like(scan(member('./usr/bin/thing', "\x7fELF\x02\x01\x01\x00") . trailer()),
qr/ELF payload in publisher noarch RPM: \.\/usr\/bin\/thing/,
'an ELF member is refused, and the message names the file');
like(scan(substr(member('./usr/share/doc/README', 'x' x 4096) . trailer(), 0, 200)),
qr/Truncated RPM payload/, 'a stream that ends before its trailer is refused');
like(scan(member('./a', 'x') . trailer() . 'trailing junk'),
qr/Unexpected data after RPM cpio trailer/, 'data after the trailer is refused');
like(scan('N' x 200), qr/Invalid RPM cpio header/, 'a stream that is not cpio is refused');
done_testing();
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/perl
use strict;
use warnings;
use Test::More;
use File::Spec ();
use File::Temp qw(tempdir);
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use XCAT::NativeInputs qw(trust_dbpath);
my $scratch = tempdir('trust-dbpath-XXXXXX', TMPDIR => 1, CLEANUP => 1);
my $root = File::Spec->catdir($scratch, 'local-root');
my $foreign = File::Spec->catdir($scratch, 'foreign');
mkdir $root or die "mkdir $root: $!";
mkdir $foreign or die "mkdir $foreign: $!";
sub fresh_private_dir_under {
my ($db, $under, $what) = @_;
ok(defined $db && length $db, "$what: trust_dbpath answers") or return;
like($db, qr/^\Q$under\E\//, "$what: the keyring goes under the local root");
ok(-d $db && !-l $db, "$what: the keyring is a real directory, not a symlink");
is((lstat $db)[2] & 07777, 0700, "$what: the keyring directory is private");
opendir my $dh, $db or return fail("$what: cannot read $db");
is_deeply([grep { !/^\.\.?\z/ } readdir $dh], [], "$what: the keyring directory starts empty");
}
{
local $ENV{XCAT_DEP_TRUST_TMP} = $root;
my $first = trust_dbpath();
fresh_private_dir_under($first, $root, 'first call');
# Another user takes over the path the first call answered.
rmdir $first if -d $first && !-l $first;
symlink $foreign, $first or die "symlink $first: $!";
my $second = trust_dbpath();
isnt($second, $first, 'a path held by someone else is not reused');
fresh_private_dir_under($second, $root, 'second call');
}
{
my $other = File::Spec->catdir($scratch, 'other-root');
mkdir $other or die "mkdir $other: $!";
local $ENV{XCAT_DEP_TRUST_TMP} = $other;
fresh_private_dir_under(trust_dbpath(), $other, 'XCAT_DEP_TRUST_TMP override');
}
done_testing();