2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-22 15:48:14 +00:00
Files
xcat-core/xCAT-server/share/xcat/netboot/imgutils/imgutils.pm
T
Timothy Middelkoop d1faba077c feat(genimage): opt-in package signature checks for EL images
Add the linuximage gpgcheck attribute. When it is set to 1 or yes,
genimage passes --gpgcheck to the shared RPM image builder, which
exports the builder's trusted RPM keys exactly as it does for openEuler
and writes gpgcheck=1 with that gpgkey for the pkgdir, kerneldir and
otherpkgdir repositories. Unset keeps the existing unverified
configuration, openEuler stays always verified, and builders without
the option reject the attribute instead of ignoring it.

Collect the otherpkgs repositories in imgutils so that an otherpkgdir
holding only URLs no longer adds a file:/// repository that fails to
download metadata on EL.

Signed-off-by: Timothy Middelkoop <tmiddelkoop@internet2.edu>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:24:33 -05:00

495 lines
17 KiB
Perl

#!/usr/bin/perl -w
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
# #(C)IBM Corp
package imgutils;
use strict;
use warnings "all";
use File::Basename;
use File::Copy qw(move);
use File::Find;
use File::Path;
use Cwd qw(realpath);
use xCAT::SvrUtils;
sub el_major_version {
my $version = shift;
return if !defined($version);
my ($os_family, $os_major) = xCAT::SvrUtils::parseosver($version);
return
if !defined($os_family)
|| $os_family !~ /^(?:rhels?|rhelc|rhelhpc|centos(?:-stream)?|rocky|alma(?:linux)?|ol)$/;
return $os_major if length($os_major);
return;
}
sub rpm_installroot_command {
my ( $osver, $rootimg_dir, $non_interactive, $dnf_available ) = @_;
$non_interactive ||= "";
$dnf_available = -x "/usr/bin/dnf" unless defined($dnf_available);
my $majorrel = el_major_version($osver);
my $openeuler_release = openeuler_release_version($osver);
die "Unsupported openEuler image release: $osver\n"
if defined($osver) && $osver =~ /^openeuler/ && !defined($openeuler_release);
my $pkgmgr = "yum";
# EL8 and newer are dnf-native. Keep yum as the fallback for legacy
# systems and minimal environments that still provide only yum.
if (defined($majorrel) && $majorrel > 7 && $dnf_available) {
$pkgmgr = "dnf";
}
if (defined($openeuler_release)) {
die "openEuler image creation requires dnf\n" unless $dnf_available;
$pkgmgr = "dnf";
}
my $cmd = "$pkgmgr $non_interactive -c /tmp/genimage.$$.yum.conf --installroot=$rootimg_dir/ --disablerepo=* ";
if (defined($majorrel) && $majorrel > 7) {
$cmd .= "--releasever=" . $majorrel . " ";
$cmd .= "--setopt=module_platform_id=platform:el" . $majorrel . " ";
}
if (defined($openeuler_release)) {
$cmd .= "--releasever=$openeuler_release --setopt=strict=1 '--setopt=*.skip_if_unavailable=False' '--setopt=*.gpgcheck=1' ";
}
return $cmd;
}
sub openeuler_release_version {
my $osver = shift;
return undef unless defined($osver) && $osver =~ /^openeuler(.+)$/;
my $release = xCAT::Utils::normalize_openeuler_version($1);
return undef unless defined($release);
$release =~ s/sp([0-9]+)$/LTS_SP$1/ or $release .= 'LTS';
return $release;
}
sub rpm_trusted_keys {
open(my $rpm, '-|', 'rpm', '-qa', '--qf', "%{DESCRIPTION}\n", 'gpg-pubkey*')
or die "Cannot read trusted RPM keys: $!\n";
local $/;
my $keys = <$rpm>;
close($rpm) or die "Cannot read trusted RPM keys\n";
die "No trusted RPM signing keys are installed\n" unless defined($keys) && length($keys);
return $keys;
}
sub rpm_repository_config {
my ($osver, $id, $url, $gpgkey) = @_;
my $config = "[$id]\nname=$id\nbaseurl=$url\n";
if (defined(openeuler_release_version($osver))) {
die "openEuler repositories require trusted RPM signing keys\n"
unless defined($gpgkey) && length($gpgkey);
$config .= "gpgcheck=1\ngpgkey=$gpgkey\nskip_if_unavailable=False\n\n";
} elsif (defined($gpgkey) && length($gpgkey)) {
# Other RPM images verify signatures only when the caller opts in
# by supplying the trusted keys (linuximage.gpgcheck).
$config .= "gpgcheck=1\ngpgkey=$gpgkey\nskip_if_unavailable=True\n\n";
} else {
$config .= "gpgcheck=0\nskip_if_unavailable=True\n\n";
}
return $config;
}
sub otherpkgs_repository_urls {
my ($subdir, $local_dir, @urls) = @_;
my @repos = @urls;
# Without a local otherpkgdir there is no local repository to add; a
# file:///<subdir> entry would only produce a metadata download failure.
push @repos, "file://$local_dir/$subdir" if defined($local_dir) && length($local_dir);
return @repos;
}
sub disable_vendor_repositories {
my ($rootimg_dir) = @_;
my @internet_repo_file_list = ("oracle-linux-ol8.repo", "oracle-linux-ol9.repo", "oracle-linux-ol10.repo", "uek-ol8.repo", "uek-ol9.repo", "uek-ol10.repo", "Rocky-AppStream.repo", "Rocky-BaseOS.repo", "Rocky-Extras.repo", "rocky.repo", "rocky-extras.repo", "CentOS-Base.repo", "centos.repo", "centos-addons.repo", "almalinux-ha.repo", "almalinux-nfv.repo", "almalinux-plus.repo", "almalinux-powertools.repo", "almalinux.repo", "almalinux-resilientstorage.repo", "almalinux-rt.repo", "openEuler.repo");
foreach (@internet_repo_file_list) {
if (-e "$rootimg_dir/etc/yum.repos.d/$_") {
system("sed -i -e 's/^[[:space:]]*enabled[[:space:]]*=[[:space:]]*1/enabled=0/' $rootimg_dir/etc/yum.repos.d/$_");
}
}
}
sub publish_boot_files {
my ($native_bootdir, $destdir, $initrd, $mode) = @_;
move($initrd, "$native_bootdir/initrd-$mode.gz")
or die("Error: failed to move the initial ramdisk for $mode: $!\n");
my $old_kernel = -e "$destdir/kernel" || -l "$destdir/kernel";
if ($old_kernel) {
link("$destdir/kernel", "$native_bootdir/previous-kernel")
or die("Error: failed to preserve the previous kernel: $!\n");
}
rename("$native_bootdir/kernel", "$destdir/kernel")
or die("Error: failed to publish the kernel: $!\n");
unless (rename("$native_bootdir/initrd-$mode.gz", "$destdir/initrd-$mode.gz")) {
my $error = $!;
my $restored = $old_kernel
? rename("$native_bootdir/previous-kernel", "$destdir/kernel")
: unlink("$destdir/kernel");
unless ($restored) {
$native_bootdir->unlink_on_destroy(0);
die("Error: failed to publish the initial ramdisk: $error; kernel rollback failed: $!. Boot files retained in $native_bootdir\n");
}
die("Error: failed to publish the initial ramdisk for $mode: $error\n");
}
return 1;
}
sub varsubinline{
my $line=shift;
my $refvardict=shift;
my @varsinline= $line =~ /\$\{?(\w+)\}?/g;
my @unresolvedvars;
foreach my $var(@varsinline){
if(exists $refvardict->{$var}){
$line=~ s/\$\{$var\}/$refvardict->{$var}/g;
$line=~ s/\$$var/$refvardict->{$var}/g;
}else{
push @unresolvedvars,$var;
}
}
return $line;
}
sub _profile_lookup_osbase_list {
my $osver = shift;
# OS version on s390x can contain 'sp', e.g. sles11sp1
# If OS version contains 'sp', get the index of 'sp' instead of '.'
if ($osver =~ /sles/ && $osver =~ /sp/) {
my $dotpos = rindex($osver, "sp");
return (substr($osver, 0, $dotpos));
}
return grep { $_ ne $osver } xCAT::SvrUtils::get_os_search_list($osver);
}
sub resolver_library_paths {
my $arch = shift;
my $libdir = $arch =~ /x86_64|aarch64|riscv64/ ? 'lib64' : 'lib';
return ( "$libdir/libnss_dns.so.2", "$libdir/libresolv.so.2" );
}
sub get_profile_def_filename {
my $osver = shift;
my $profile = shift;
my $arch = shift;
my $tmp_base = shift;
my $base = realpath($tmp_base); #get the full path
if (!$base) { $base = $tmp_base; }
my $ext = shift;
my @osbase = _profile_lookup_osbase_list($osver);
my $fallbackos;
if ($osver =~ /^leap15/) {
$fallbackos = "sle15";
}
if (-r "$base/$profile.$osver.$arch.$ext") {
return "$base/$profile.$osver.$arch.$ext";
}
foreach my $osbase (@osbase) {
if (-r "$base/$profile.$osbase.$arch.$ext") {
return "$base/$profile.$osbase.$arch.$ext";
}
}
if ($fallbackos && -r "$base/$profile.$fallbackos.$arch.$ext") {
return "$base/$profile.$fallbackos.$arch.$ext";
} elsif (-r "$base/$profile.$arch.$ext") {
return "$base/$profile.$arch.$ext";
} elsif (-r "$base/$profile.$osver.$ext") {
return "$base/$profile.$osver.$ext";
}
foreach my $osbase (@osbase) {
if (-r "$base/$profile.$osbase.$ext") {
return "$base/$profile.$osbase.$ext";
}
}
if ($fallbackos && -r "$base/$profile.$fallbackos.$ext") {
return "$base/$profile.$fallbackos.$ext";
} elsif (-r "$base/$profile.$ext") {
return "$base/$profile.$ext";
}
return "";
}
sub include_file
{
my $file = shift;
my $idir = shift;
my @text = ();
$file=varsubinline($file,\%ENV);
unless ($file =~ /^\//) {
$file = $idir . "/" . $file;
}
open(INCLUDE, $file) ||
return "#INCLUDEBAD:cannot open $file#";
while (<INCLUDE>) {
chomp($_);
s/\s+$//; #remove trailing spaces
next if /^\s*$/; #-- skip empty lines
next
if (/^\s*#/
&& !/^\s*#INCLUDE:[^#^\n]+#/
&& !/^\s*#NEW_INSTALL_LIST#/
&& !/^\s*#ENV:[^#^\n]+#/); #-- skip comments
push(@text, $_);
}
close(INCLUDE);
return join(',', @text);
}
sub get_package_names {
my $plist_file_list = shift;
my %pkgnames = ();
my @plist_file_names = split ',', $plist_file_list;
foreach my $plist_file_name (@plist_file_names) {
# this variable needs to be cleaned when loop the pkglist files
my @tmp_array = ();
if ($plist_file_name && -r $plist_file_name) {
my $pkgfile;
open($pkgfile, "<", "$plist_file_name");
while (<$pkgfile>) {
chomp;
s/\s+$//; #remove trailing white spaces
next if /^\s*$/; #-- skip empty lines
next
if (/^\s*#/
&& !/^\s*#INCLUDE:[^#^\n]+#/
&& !/^\s*#NEW_INSTALL_LIST#/
&& !/^\s*#ENV:[^#^\n]+#/); #-- skip comments
push(@tmp_array, $_);
}
close($pkgfile);
if (@tmp_array > 0) {
my $pkgtext = join(',', @tmp_array);
#handle the #INLCUDE# tag recursively
my $idir = dirname($plist_file_name);
my $doneincludes = 0;
while (not $doneincludes) {
$doneincludes = 1;
if ($pkgtext =~ /#INCLUDE:[^#^\n]+#/) {
$doneincludes = 0;
$pkgtext =~ s/#INCLUDE:([^#^\n]+)#/include_file($1,$idir)/eg;
}
}
#print "\n\npkgtext=$pkgtext\n\n";
my @tmp = split(',', $pkgtext);
my $pass = 1;
foreach (@tmp) {
my $idir;
if (/^--/) {
$idir = "POST_REMOVE"; #line starts with -- means the package should be removed after otherpkgs are installed
s/^--//;
} elsif (/^-/) {
$idir = "PRE_REMOVE"; #line starts with single - means the package should be removed before otherpkgs are installed
s/^-//;
} elsif (/^#NEW_INSTALL_LIST#/) {
$pass++;
next;
} elsif (/^#ENV:([^#^\n]+)#/) {
my $pa = $pkgnames{$pass}{ENVLIST};
my $env = $1;
if (exists($pkgnames{$pass}{ENVLIST})) {
push(@$pa, $env);
} else {
$pkgnames{$pass}{ENVLIST} = [$env];
}
next;
} elsif (/^#INCLUDEBAD:([^#^\n]+)#/) {
my $pa = $pkgnames{$pass}{INCLUDEBAD};
my $file = $1;
if (exists($pkgnames{$pass}{INCLUDEBAD})) {
push(@$pa, $file);
} else {
$pkgnames{$pass}{INCLUDEBAD} = [$file];
}
next;
} elsif (/^#/) {
# ignore all other comment lines
next;
} else {
$idir = dirname($_);
}
my $fn = basename($_);
if (exists($pkgnames{$pass}{$idir})) {
my $pa = $pkgnames{$pass}{$idir};
push(@$pa, $fn);
} else {
$pkgnames{$pass}{$idir} = [$fn];
}
}
}
}
}
return %pkgnames;
}
sub default_net_drivers {
my ( $family, $arch ) = @_;
my %drivers = (
rh => {
x86 => [qw(tg3 bnx2 bnx2x e1000 e1000e igb mlx_en mlx5_core virtio_net be2net)],
x86_64 => [qw(tg3 bnx2 bnx2x e1000 e1000e igb mlx_en mlx5_core virtio_net be2net)],
aarch64 => [qw(tg3 bnx2 bnx2x e1000e igb mlx_en mlx5_core virtio_net)],
riscv64 => [qw(e1000 e1000e igb ixgbe r8169 tg3 bnx2x mlx5_core virtio_net)],
ppc64 => [qw(e1000 e1000e igb ibmveth ehea)],
s390x => [qw(qdio ccwgroup)],
},
sles => {
x86 => [qw(tg3 bnx2 bnx2x e1000 e1000e virtio_net virtio_balloon igb mlx4_en mlx5_core be2net)],
x86_64 => [qw(tg3 bnx2 bnx2x e1000 e1000e virtio_net virtio_balloon igb mlx4_en mlx5_core be2net)],
ppc64 => [qw(tg3 e1000 e1000e igb ibmveth ehea be2net)],
s390x => [qw(qdio ccwgroup qeth qeth_l2 qeth_l3)],
},
ubuntu => {
x86 => [qw(tg3 bnx2 bnx2x e1000 e1000e igb mlx_en mlx5_core virtio_net overlay)],
x86_64 => [qw(tg3 bnx2 bnx2x e1000 e1000e igb mlx_en mlx5_core virtio_net overlay)],
ppc64el => [qw(tg3 bnx2 bnx2x e1000 e1000e igb ibmveth ehea mlx_en mlx4_en mlx5_core virtio_net overlay)],
ppc64 => [qw(e1000 e1000e igb ibmveth ehea)],
riscv64 => [qw(e1000 e1000e igb ixgbe r8169 tg3 bnx2x mlx5_core virtio_net overlay)],
s390x => [qw(qdio ccwgroup)],
},
);
return () if !exists($drivers{$family}) || !exists($drivers{$family}{$arch});
return @{ $drivers{$family}{$arch} };
}
sub _kernel_module_name {
my $path = shift;
my $name = basename($path);
$name =~ s/\.ko(?:\.(?:gz|xz|zst))?$//x;
$name =~ tr/-/_/;
return $name;
}
sub target_kernel_module_availability {
my ( $rootimg_dir, $kernelver, @modules ) = @_;
my %requested = map { _kernel_module_name($_) => 1 } @modules;
my %available = map { $_ => 0 } keys %requested;
my $module_root = "$rootimg_dir/lib/modules/$kernelver";
my $modules_dep = "$module_root/modules.dep";
if (open(my $dep_fh, '<', $modules_dep)) {
while (my $line = <$dep_fh>) {
my ($path) = split /:/x, $line, 2;
my $name = _kernel_module_name($path);
if ($requested{$name} && -f "$module_root/$path") {
$available{$name} = 1;
}
}
close($dep_fh);
}
my $modules_builtin = "$module_root/modules.builtin";
if (open(my $builtin_fh, '<', $modules_builtin)) {
while (my $path = <$builtin_fh>) {
chomp($path);
my $name = _kernel_module_name($path);
$available{$name} = 1 if $requested{$name};
}
close($builtin_fh);
}
if (-d $module_root && grep { !$available{$_} } keys %requested) {
find(
{
no_chdir => 1,
wanted => sub {
return if !-f $File::Find::name;
return if $File::Find::name !~ /\.ko(?:\.(?:gz|xz|zst))?$/x;
my $name = _kernel_module_name($File::Find::name);
$available{$name} = 1 if $requested{$name};
},
},
$module_root,
);
}
return %available;
}
sub resolve_mellanox_default_net_drivers {
my ( $rootimg_dir, $kernelver, $requested_drivers, @default_drivers ) = @_;
my @drivers;
my %seen;
my $has_mellanox_defaults = grep {
_kernel_module_name($_) =~ /^mlx(?:_en|4_en|5_core)$/x
} @default_drivers;
my $has_mellanox_drivers = $has_mellanox_defaults || grep {
_kernel_module_name($_) =~ /^mlx(?:_en|4_en|5_core)$/x
} @{$requested_drivers};
if (!$has_mellanox_drivers) {
return grep { !$seen{$_}++ } (@{$requested_drivers}, @default_drivers);
}
my %available = target_kernel_module_availability(
$rootimg_dir,
$kernelver,
qw(mlx_en mlx4_en mlx5_core mlx4_ib mlx5_ib ib_ipoib),
);
# Keep unavailable explicit requests, except for the historical mlx4 alias.
foreach my $driver (@{$requested_drivers}) {
my $name = _kernel_module_name($driver);
if ($name eq 'mlx_en'
&& !$available{'mlx_en'}
&& $available{'mlx4_en'})
{
$driver =~ s/mlx_en/mlx4_en/;
}
push @drivers, $driver;
}
foreach my $driver (@default_drivers) {
my $name = _kernel_module_name($driver);
if ($name eq 'mlx_en') {
if (!$available{'mlx_en'}) {
next if !$available{'mlx4_en'};
$driver =~ s/mlx_en/mlx4_en/;
}
} elsif ($name eq 'mlx4_en' || $name eq 'mlx5_core') {
next if !$available{$name};
}
push @drivers, $driver;
}
if ($has_mellanox_defaults) {
my @mellanox_ib_drivers = grep { $available{$_} } qw(mlx4_ib mlx5_ib);
push @drivers, map { "$_.ko" } @mellanox_ib_drivers;
if (@mellanox_ib_drivers && $available{'ib_ipoib'}) {
push @drivers, 'ib_ipoib.ko';
}
}
return grep { !$seen{$_}++ } @drivers;
}
1;