mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-10-22 15:48:14 +00:00
d1faba077c
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>
495 lines
17 KiB
Perl
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;
|