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:
@@ -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
@@ -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,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,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',)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user