2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-02 00:41:42 +00:00

refactor(xcat-server): move the builder repository name out of the postscript

The name of the EL builder repository was decided inside servicenode, so a test of that decision
had to load the postscript. It is a table with one row per vendor and EL major version, and it
belongs in a module.

ELBuilderRepo ships in /install/postscripts and is copied to /xcatpost with the postscripts, so
it loads on a service node that has no xCAT package yet. servicenode keeps the side effects:
enable EPEL, ask the module for the ids, enable the first one dnf knows, and report what it
could not enable.

servicenode_builder_repo.t is now a table: Alma, Rocky, CentOS Stream, RHEL and Oracle Linux
across EL8, EL9 and EL10, plus the capitalised PowerTools that Rocky 8.4 and earlier shipped.
Returning crb for EL8 fails nine of its assertions.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
Daniel Hilst
2026-09-28 20:58:36 -03:00
parent d02b865e79
commit 5b932190dc
3 changed files with 148 additions and 102 deletions
+49 -35
View File
@@ -6,46 +6,60 @@ use File::Spec;
use FindBin;
use Test::More;
# xCAT-server requires perl modules that EL keeps in the distribution builder repository:
# perl-IO-Tty, perl-Crypt-CBC, perl-Crypt-Rijndael and perl(Expect). That repository is
# DISABLED on a fresh EL install, so dnf install xCATsn on a service node does not resolve and
# the node ends with no xcatd. The servicenode postscript enabled EPEL and nothing else.
# xCAT-server requires perl modules EL keeps in the distribution builder repository:
# perl-IO-Tty, perl-Crypt-CBC, perl-Crypt-Rijndael and perl(Expect). That repository is disabled
# on a fresh EL install, so dnf install xCATsn on a service node does not resolve and the node
# ends with no xcatd. The servicenode postscript enabled EPEL and nothing else.
#
# The repository has a different id on every EL vendor and release, so the decision is a sub and
# this test drives it. Enabling the repository is a side effect and stays in the postscript.
# The repository is not called crb on EL8, and each vendor names it differently, so the decision
# is a module and this is its table. Enabling the repository is a side effect and stays in the
# postscript.
my $script = File::Spec->catfile($FindBin::Bin, '..', '..', 'xCAT', 'postscripts', 'servicenode');
plan skip_all => "servicenode not found" unless -f $script;
# The postscript guards its own body with caller(), so requiring it compiles the subs and runs
# nothing. A postscript that loses that guard executes here, which is the loud failure to have.
eval { require $script; 1 } or plan skip_all => "could not load servicenode: $@";
can_ok('main', 'builder_repo_ids') or done_testing() && exit;
use lib File::Spec->catdir($FindBin::Bin, '..', '..', 'xCAT', 'postscripts');
use_ok('ELBuilderRepo') or done_testing() && exit;
sub ids { return [ main::builder_repo_ids(@_) ] }
my @table = (
# vendor major arch expected ids
[ 'almalinux', 8, 'x86_64', [ 'powertools', 'PowerTools' ] ],
[ 'almalinux', 9, 'x86_64', ['crb'] ],
[ 'almalinux', 10, 'x86_64', ['crb'] ],
[ 'rocky', 8, 'x86_64', [ 'powertools', 'PowerTools' ] ],
[ 'rocky', 9, 'ppc64le', ['crb'] ],
[ 'rocky', 10, 'x86_64', ['crb'] ],
[ 'centos', 8, 'x86_64', [ 'powertools', 'PowerTools' ] ],
[ 'centos', 9, 'x86_64', ['crb'] ],
[ 'centos', 10, 'x86_64', ['crb'] ],
[ 'rhel', 8, 'ppc64le', ['codeready-builder-for-rhel-8-ppc64le-rpms'] ],
[ 'rhel', 9, 'x86_64', ['codeready-builder-for-rhel-9-x86_64-rpms'] ],
[ 'rhel', 10, 'x86_64', ['codeready-builder-for-rhel-10-x86_64-rpms'] ],
[ 'ol', 8, 'x86_64', ['ol8_codeready_builder'] ],
[ 'ol', 9, 'x86_64', ['ol9_codeready_builder'] ],
[ 'ol', 10, 'x86_64', ['ol10_codeready_builder'] ],
);
is_deeply(ids('almalinux', '9.8', 'x86_64'), ['crb'],
'AlmaLinux 9 names crb');
is_deeply(ids('rocky', '10.0', 'x86_64'), ['crb'],
'Rocky 10 names crb');
is_deeply(ids('centos', '9', 'ppc64le'), ['crb'],
'CentOS Stream 9 names crb');
is_deeply(ids('almalinux', '8.10', 'x86_64'), [ 'powertools', 'PowerTools' ],
'AlmaLinux 8 names powertools, and the CentOS 8 spelling after it');
is_deeply(ids('rhel', '9.4', 'x86_64'), ['codeready-builder-for-rhel-9-x86_64-rpms'],
'RHEL 9 names its arch-qualified codeready-builder repository');
is_deeply(ids('rhel', '8.9', 'ppc64le'), ['codeready-builder-for-rhel-8-ppc64le-rpms'],
'RHEL 8 keeps the arch in the repository id');
is_deeply(ids('ol', '9.3', 'x86_64'), ['ol9_codeready_builder'],
'Oracle Linux 9 names its own codeready builder repository');
for my $row (@table) {
my ($vendor, $major, $arch, $want) = @$row;
my @got = ELBuilderRepo::builder_repo_ids($vendor, $major, $arch);
is_deeply(\@got, $want, "$vendor EL$major names @$want");
}
# A caller with nothing to go on must get nothing to enable, not a guess.
is_deeply(ids(undef, '9', 'x86_64'), [], 'no distribution id yields no repository');
is_deeply(ids('almalinux', undef, 'x86_64'), [], 'no version yields no repository');
is_deeply(ids('almalinux', 'rawhide', 'x86_64'), [], 'a version with no major number yields no repository');
# The EL8 name is the one that is easy to get wrong: the display name reads
# "AlmaLinux 8 - PowerTools" while the id is lowercase, and crb does not exist there at all.
for my $vendor (qw(almalinux rocky centos)) {
my @got = ELBuilderRepo::builder_repo_ids($vendor, 8, 'x86_64');
ok(!grep({ $_ eq 'crb' } @got), "$vendor EL8 does not name crb");
is($got[0], 'powertools', "$vendor EL8 tries the lowercase id first");
}
# The arch only reaches the RHEL id. A missing arch must not build a repository id with a hole
# in it, which dnf would accept as an unknown repository and silently skip.
is_deeply(ids('rhel', '9.4', undef), ['codeready-builder-for-rhel-9-x86_64-rpms'],
'a missing arch falls back to x86_64 rather than an empty field');
# Nothing to go on must yield nothing to enable, not a guess.
is_deeply([ ELBuilderRepo::builder_repo_ids(undef, 9, 'x86_64') ], [], 'no vendor yields no repository');
is_deeply([ ELBuilderRepo::builder_repo_ids('almalinux', undef, 'x86_64') ], [], 'no major version yields no repository');
is_deeply([ ELBuilderRepo::builder_repo_ids('almalinux', '9.8', 'x86_64') ], [],
'a version that is not a bare major yields no repository, so the caller must parse it');
# The arch reaches the RHEL id alone. A missing arch must not leave a hole in it, which dnf
# accepts as an unknown repository and skips without a word.
is_deeply([ ELBuilderRepo::builder_repo_ids('rhel', 9, undef) ],
['codeready-builder-for-rhel-9-x86_64-rpms'], 'a missing arch falls back to x86_64');
done_testing();
+51
View File
@@ -0,0 +1,51 @@
package ELBuilderRepo;
# The repository ids that carry the EL builder packages.
#
# xCAT-server requires perl-IO-Tty, perl-Crypt-CBC, perl-Crypt-Rijndael and perl(Expect). EL
# keeps them in the distribution builder repository, which is disabled on a fresh install, so
# dnf install xCATsn on a service node does not resolve. Every vendor names that repository
# differently and the name changed at EL9: it is not crb on EL8.
#
# This module ships in /install/postscripts and is copied to /xcatpost with the postscripts, so
# it loads on a node that has no xCAT packages yet.
use strict;
use warnings;
#-----------------------------------------------------------------------------
=head3 builder_repo_ids
Arguments:
$vendor the ID field of /etc/os-release, for example almalinux, rocky, centos, rhel, ol
$major the EL major version, 8, 9 or 10
$arch the machine architecture. Only the RHEL id carries one
Returns:
the repository ids to try, most likely first, or an empty list when the arguments name
no EL release. An empty list is the answer for "do not guess".
Covered: AlmaLinux, Rocky and CentOS Stream (crb on 9 and later, powertools on 8, with the
capitalised PowerTools that Rocky 8.4 and earlier shipped after it), RHEL
(codeready-builder-for-rhel-<major>-<arch>-rpms) and Oracle Linux
(ol<major>_codeready_builder). Another vendor takes the community spellings, which is a
guess.
=cut
#-----------------------------------------------------------------------------
sub builder_repo_ids {
my ($vendor, $major, $arch) = @_;
return () unless defined $vendor && length $vendor;
return () unless defined $major && $major =~ /^\d+$/;
$arch = 'x86_64' unless defined $arch && length $arch;
return ("codeready-builder-for-rhel-$major-$arch-rpms") if $vendor eq 'rhel';
return ("ol${major}_codeready_builder") if $vendor eq 'ol';
return ('powertools', 'PowerTools') if $major == 8;
return ('crb');
}
1;
+48 -67
View File
@@ -44,6 +44,10 @@ if ($^O =~ /^aix/i) {
}
use lib "$::XCATROOT/lib/perl";
# ELBuilderRepo ships beside this script and reaches the node in /xcatpost with it.
use File::Basename qw(dirname);
use Cwd qw(abs_path);
use lib dirname(abs_path($0));
use strict;
# MAIN
@@ -177,52 +181,14 @@ exit $rc;
#-----------------------------------------------------------------------------
=head3 builder_repo_ids
The repository ids that carry the EL builder packages, most likely first.
xCAT-server requires perl-IO-Tty, perl-Crypt-CBC, perl-Crypt-Rijndael and perl(Expect).
EL keeps them in the builder repository, which every vendor names differently and which is
disabled on a fresh install. The caller enables the first id dnf knows.
Covered: AlmaLinux, Rocky and CentOS Stream (crb on 9 and 10, powertools on 8), RHEL
(codeready-builder-for-rhel-<major>-<arch>-rpms) and Oracle Linux
(ol<major>_codeready_builder). Not covered: any other vendor, which gets crb on 9 and later
and powertools on 8 because those are the community spellings.
Arguments:
$id the ID field of /etc/os-release
$version the VERSION_ID field of /etc/os-release
$arch the machine architecture, for the RHEL id only
Returns:
the repository ids to try, in order, or an empty list when the arguments name no EL
release. An empty list is the answer for "do not guess".
=cut
#-----------------------------------------------------------------------------
sub builder_repo_ids {
my ($id, $version, $arch) = @_;
return () unless defined $id && length $id;
return () unless defined $version;
my ($major) = $version =~ /^(\d+)/;
return () unless defined $major;
$arch = 'x86_64' unless defined $arch && length $arch;
return ("codeready-builder-for-rhel-$major-$arch-rpms") if $id eq 'rhel';
return ("ol${major}_codeready_builder") if $id eq 'ol';
return ('powertools', 'PowerTools') if $major == 8;
return ('crb');
}
#-----------------------------------------------------------------------------
=head3 enable_el_dependency_repos
Enable EPEL and the builder repository on an EL service node, and say so when either is
still absent. Neither is fatal: a site can run without EPEL, and xcat-dep carries the same
perl closure. What must not happen is silence.
Enable EPEL and the distribution builder repository on an EL service node, and say so when
either is still absent. Neither is fatal: a site can run without EPEL, and xcat-dep carries
the same perl closure. What must not happen is silence.
ELBuilderRepo names the builder repository. It ships beside this script and reaches the node
in /xcatpost with it, so it loads where no xCAT package is installed yet.
=cut
@@ -231,41 +197,56 @@ sub enable_el_dependency_repos {
my ($log_label) = @_;
my $osrel = `cat /etc/os-release 2>/dev/null` || '';
my ($id) = $osrel =~ /^ID="?([^"\n]+)"?/m;
my ($vendor) = $osrel =~ /^ID="?([^"\n]+)"?/m;
my ($version) = $osrel =~ /^VERSION_ID="?([^"\n]+)"?/m;
my ($major) = defined $version ? $version =~ /^(\d+)/ : ();
my $arch = `uname -m 2>/dev/null`;
chomp $arch if defined $arch;
`logger -t $log_label -p local4.info servicenode: enabling EPEL and the builder repository for xCAT-server dependencies`;
&runcmd("dnf -y install epel-release");
unless (`dnf repolist --enabled 2>/dev/null` =~ /^epel\b/m) {
my $m = "servicenode: EPEL is not enabled on $::hname. xCAT-server perl dependencies must"
. " then come from xcat-dep, or xCATsn will not install.";
print "$m\n";
`logger -t $log_label -p local4.warning "$m"`;
&report_missing_repo($log_label, "EPEL", "");
}
my @ids = &builder_repo_ids($id, $version, $arch);
my $known = `dnf repolist --all 2>/dev/null` || '';
my $enabled = '';
foreach my $repo (@ids) {
next unless $known =~ /^\Q$repo\E\s/m;
&runcmd("dnf -y install dnf-plugins-core") unless -x "/usr/bin/dnf-3";
if (&runcmd("dnf config-manager --set-enabled $repo") == 0) {
$enabled = $repo;
last;
}
}
if ($enabled) {
`logger -t $log_label -p local4.info "servicenode: enabled the builder repository $enabled"`;
my @ids;
if (eval { require ELBuilderRepo; 1 }) {
@ids = ELBuilderRepo::builder_repo_ids($vendor, $major, $arch);
}
else {
my $m = "servicenode: no builder repository could be enabled on $::hname (tried: "
. join(', ', @ids) . "). perl-IO-Tty, perl-Crypt-CBC, perl-Crypt-Rijndael and"
. " perl(Expect) must then come from xcat-dep, or xCATsn will not install.";
print "$m\n";
`logger -t $log_label -p local4.warning "$m"`;
`logger -t $log_label -p local4.warning "servicenode: ELBuilderRepo did not load: $@"`;
}
my $known = `dnf repolist --all 2>/dev/null` || '';
foreach my $repo (@ids) {
next unless $known =~ /^\Q$repo\E\s/m;
&runcmd("dnf -y install dnf-plugins-core");
next unless &runcmd("dnf config-manager --set-enabled $repo") == 0;
`logger -t $log_label -p local4.info "servicenode: enabled the builder repository $repo"`;
return 0;
}
&report_missing_repo($log_label, "no builder repository", " (tried: " . join(', ', @ids) . ")");
return 0;
}
#-----------------------------------------------------------------------------
=head3 report_missing_repo
Print and log that a repository could not be enabled. Printing is what makes it visible: the
node copies this script's output into /var/log/xcat/xcat.log, while the EPEL step reported
only to syslog, where nothing read it.
=cut
#-----------------------------------------------------------------------------
sub report_missing_repo {
my ($log_label, $what, $detail) = @_;
my $m = "servicenode: $what could be enabled on $::hname$detail. perl-IO-Tty, perl-Crypt-CBC,"
. " perl-Crypt-Rijndael and perl(Expect) must then come from xcat-dep, or xCATsn will"
. " not install.";
print "$m\n";
`logger -t $log_label -p local4.warning "$m"`;
return 0;
}