2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-20 22:55:40 +00:00

test(ubuntu): execute rendered Subiquity command sequences

Render Subiquity configuration with real database rows and execute the emitted commands in private namespaces. Check resulting files, failure ordering and the install-monitor handshake without rewriting shell fragments.
This commit is contained in:
Vinícius Ferrão
2026-10-09 15:02:53 -03:00
parent fdf18e24c3
commit 1d362211e7
5 changed files with 262 additions and 122 deletions
+2 -2
View File
@@ -9,7 +9,7 @@ jobs:
- name: Refresh package indexes
run: sudo apt-get update
- name: Install dependencies
run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests bats bubblewrap cpio xz-utils gzip gawk mawk rpm rsyslog build-essential fakeroot reprepro devscripts debhelper libcapture-tiny-perl libfile-slurper-perl libjson-perl libyaml-pp-perl libparallel-forkmanager-perl libsoap-lite-perl libdbi-perl libdbd-sqlite3-perl libcgi-pm-perl quilt openssh-server dpkg looptools genometools software-properties-common grub-common grub-efi-amd64-bin squashfs-tools pigz
run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests bats bubblewrap cpio xz-utils gzip gawk mawk rpm rsyslog build-essential fakeroot reprepro devscripts debhelper libcapture-tiny-perl libfile-slurper-perl libjson-perl libyaml-pp-perl libparallel-forkmanager-perl libsoap-lite-perl libdbi-perl libdbd-sqlite3-perl libcgi-pm-perl quilt openssh-server dpkg looptools genometools software-properties-common grub-common grub-efi-amd64-bin squashfs-tools pigz python3-yaml
- name: Enable postscript test namespaces
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
- name: Install lifecycle test dependencies
@@ -77,7 +77,7 @@ jobs:
run: |
log=$(mktemp)
trap 'rm -f -- "$log"' EXIT
prove -v xCAT-test/native/install_profile_riscv64.t xCAT-test/native/netboot_profile_riscv64.t xCAT-test/native/builddebs_riscv64.t | tee "$log"
prove -v xCAT-test/native/install_profile_riscv64.t xCAT-test/native/netboot_profile_riscv64.t xCAT-test/native/builddebs_riscv64.t xCAT-test/native/ubuntu_subiquity_template.t | tee "$log"
! grep -qi skipped "$log"
rpm_lifecycle_test:
runs-on: ubuntu-24.04
+41
View File
@@ -0,0 +1,41 @@
#!/bin/sh
set -eu
case "${0##*/}" in
ip) exit 0 ;;
getent)
[ "$*" = 'ahostsv4 master' ] || exit 64
[ "$XCAT_TEST_SCENARIO" != unresolved ] || exit 2
printf '127.0.0.1 STREAM master\n'
;;
wget)
printf '%s\n' "$*" >>/fixture/downloads
output=
timeout=
while [ "$#" -gt 0 ]; do
case "$1" in
-T) timeout=$2; shift 2 ;;
-O) output=$2; shift 2 ;;
http://127.0.0.1:8080/install/autoinst/*) url=$1; shift ;;
*) exit 64 ;;
esac
done
[ "$timeout" = 30 ] || exit 64
name=${url##*/}
[ -n "$output" ] || output=$name
case "$XCAT_TEST_SCENARIO:$name" in
fail-disk:getinstdisk|fail-pre:node.pre) exit 8 ;;
empty-disk:getinstdisk|empty-pre:node.pre) : >"$output"; exit 0 ;;
esac
cp "/fixture/payloads/$name" "$output"
;;
curtin)
[ "$1 $2 $3" = 'in-target --target /target' ] || exit 64
shift 3
case "$*" in
update-grub2) cp /target/etc/default/grub /fixture/grub-input ;;
/root/post.script) exec /target/root/post.script ;;
*) exit 64 ;;
esac
;;
*) exit 64 ;;
esac
+46
View File
@@ -0,0 +1,46 @@
use strict;
use warnings;
use File::Copy qw(copy);
use File::Slurper qw(read_text write_text);
use IO::Socket::INET;
use JSON::PP qw(decode_json encode_json);
use POSIX qw(_exit);
alarm 90;
my $commands = decode_json(read_text('/fixture/commands.json'));
copy('/fixture/autoinstall.yaml', '/autoinstall.yaml') or die $!;
my $listener = IO::Socket::INET->new(
LocalAddr => '127.0.0.1', LocalPort => 3002, Listen => 1, ReuseAddr => 1,
) or die "Cannot start install monitor: $!";
my $pid = fork();
die "fork: $!" unless defined $pid;
if (!$pid) {
alarm 60;
my $client = $listener->accept() or _exit(1);
$client->autoflush(1);
print {$client} "ready\n";
my $request = <$client>;
write_text('/fixture/monitor-request', $request // '');
print {$client} "done\n" if defined($request) && $request eq "next\n";
close($client);
_exit(0);
}
close($listener);
my @statuses;
my $status = 0;
for my $phase (qw(early-commands late-commands)) {
for my $command (@{$commands->{$phase}}) {
my @arguments = ref($command) eq 'ARRAY' ? @$command : ('/bin/sh', '-c', $command);
system(@arguments);
$status = $? == -1 ? 255 : $? & 127 ? 128 + ($? & 127) : $? >> 8;
push @statuses, [$phase, $status];
last if $status;
}
last if $status;
}
kill 'TERM', $pid if $status;
waitpid($pid, 0);
write_text('/fixture/monitor-status', "$?\n");
write_text('/fixture/statuses.json', encode_json(\@statuses));
copy('/autoinstall.yaml', '/fixture/final-autoinstall.yaml') or die $!;
exit $status;
@@ -0,0 +1,173 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Copy qw(copy);
use Capture::Tiny qw(capture);
use File::Path qw(make_path);
use File::Slurper qw(read_text write_text);
use FindBin;
use lib "$FindBin::Bin/../lib", "$FindBin::Bin/../../perl-xCAT", "$FindBin::Bin/../../xCAT-server/lib/perl";
use JSON::PP qw(encode_json decode_json);
use Test::More;
use XCAT::Test::File qw(repo_path);
use XCAT::Test::Sandbox qw(sandbox_root sandbox_run);
use XCAT::Test::Template qw(template_database set_row render_template);
plan skip_all => 'Subiquity command execution requires Linux' unless $^O eq 'linux';
my $database = template_database();
require xCAT::SvrUtils;
set_row('noderes', {node => 'node'}, {xcatmaster => 'master'});
write_text("$database/extra.pkglist", "htop\nwget\n");
write_text("$database/image.pkglist", "#INCLUDE:$database/extra.pkglist#\nvim\n");
sub load_yaml {
my ($file) = @_;
my ($stdout, $stderr, $status) = capture {
system('/usr/bin/python3', '-I', '-c',
'import json, sys, yaml; json.dump(yaml.safe_load(open(sys.argv[1])), sys.stdout)', $file)
};
die "Cannot parse $file: $stderr" if $status;
return decode_json($stdout);
}
sub render {
my ($version, $arch, $nic, $primary) = @_;
set_row('nodetype', {node => 'node'}, {os => "ubuntu$version", arch => $arch, provmethod => 'image'});
set_row('noderes', {node => 'node'}, {installnic => $nic, primarynic => $primary});
my $template = xCAT::SvrUtils->get_tmpl_file_name(
repo_path('xCAT-server/share/xcat/install/ubuntu'), 'compute', "ubuntu$version", $arch, 'subiquity');
my $output = "$database/autoinstall.yaml";
render_template($database, $template, $output, 'node', "$database/image.pkglist",
"/install/ubuntu$version/$arch", 'ubuntu', undef, {xcatmaster => '127.0.0.1'}, osarch => $arch);
my $text = read_text($output);
like($text, qr/\A#cloud-config\n/, 'renders a cloud-config document');
unlike($text, qr/#(?:TABLE|CRYPT|SUBIQUITY|INCLUDE|XCATVAR)[^\n]*#/, 'resolves all template inputs');
return (load_yaml($output)->{autoinstall}, $text);
}
for my $release (qw(20.04 22.04 24.04 26.04)) {
subtest "Ubuntu $release data" => sub {
my ($config) = render($release, 'x86_64', '', '');
is($config->{version}, 1, 'uses autoinstall version 1');
is_deeply($config->{identity}, {
realname => 'xCAT Admin', username => 'xcatadm', hostname => 'node', password => '*',
}, 'supplies a noninteractive identity with a locked password');
is($config->{kernel}{package}, 'linux-generic', 'selects the generic kernel');
ok($config->{ssh}{'install-server'}, 'installs the SSH server');
ok(!$config->{'user-data'}{package_update} && !$config->{'user-data'}{package_upgrade}, 'leaves automatic package updates disabled');
is($config->{'user-data'}{timezone}, 'UTC', 'uses the site timezone');
my %packages;
$packages{$_}++ for @{$config->{packages}};
is($packages{$_}, 1, "installs $_ once") for qw(openssh-server wget htop vim);
ok(!$packages{'nfs-common'}, 'does not require NFS tools from the offline media');
ok(!$config->{apt}{geoip}, 'keeps mirror geolocation disabled');
is($config->{apt}{'mirror-selection'}{primary}[0]{uri}, 'http://archive.ubuntu.com/ubuntu', 'selects the x86 archive');
if ($release eq '20.04' || $release eq '22.04') {
is($config->{apt}{sources}{'xcat-ubuntu-archive.list'}{source},
'deb http://archive.ubuntu.com/ubuntu $RELEASE main restricted universe multiverse',
'defers the legacy apt suite to the installer release');
} else {
ok(!exists $config->{apt}{sources}{'xcat-ubuntu-archive.list'}, 'does not duplicate the Deb822 primary mirror');
}
};
}
my @cases = (
['named', 'eno2', '', 'eno2', 0],
['primary', '', 'ens3', 'ens3', 0],
['mac', '', '', undef, 0],
['literal-mac', '52:54:00:12:34:56', 'ens3', undef, 0],
['unresolved', 'eno2', '', 'eno2', 0],
['fail-disk', '', '', undef, 8],
['empty-disk', '', '', undef, 1],
['disk-failure', '', '', undef, 9],
['fail-pre', '', '', undef, 8],
['empty-pre', '', '', undef, 1],
['pre-failure', '', '', undef, 7],
['no-partition', '', '', undef, 1],
['post-failure', '', '', undef, 42],
);
for my $case (@cases) {
my ($name, $nic, $primary, $rename, $expected_status) = @$case;
subtest "installer $name" => sub {
my ($config) = render('24.04', 'riscv64', $nic, $primary);
is($config->{apt}{'mirror-selection'}{primary}[0]{uri}, 'http://ports.ubuntu.com/ubuntu-ports', 'selects the RISC-V ports archive');
my $root = sandbox_root();
make_path(map { "$root/$_" } qw(payloads target/etc/default target/etc/apt/sources.list.d target/etc/apt/apt.conf.d target/root));
write_text("$root/commands.json", encode_json($config));
# Subiquity removes the cloud-config wrapper before executing early commands.
my ($rewritten, $stderr, $rewrite_status) = capture {
system('/usr/bin/python3', '-I', '-c',
'import json, sys, yaml; yaml.safe_dump(json.load(open(sys.argv[1])), sys.stdout, explicit_end=True)',
"$root/commands.json")
};
die "Cannot serialize autoinstall config: $stderr" if $rewrite_status;
write_text("$root/autoinstall.yaml", $rewritten);
write_text("$root/etc/resolv.conf", "nameserver 192.0.2.53\n");
my $hosts = "127.0.0.1 localhost\n";
$hosts .= "127.0.1.1 oldname\n" if $name eq 'named';
write_text("$root/target/etc/hosts", $hosts);
write_text("$root/target/etc/default/grub", "GRUB_TIMEOUT=5\n");
my @temporary_sources = qw(xcat-otherpkgs-0.list xcat-otherpkgs-1.sources xcat-pkgdir-0.list xcat-pkgdir-1.sources);
write_text("$root/target/etc/apt/sources.list.d/$_", "temporary\n") for @temporary_sources;
write_text("$root/target/etc/apt/sources.list.d/admin.list", "preserved\n");
write_text("$root/target/etc/apt/apt.conf.d/94curtin-config", "temporary\n");
write_text("$root/payloads/getinstdisk", <<'SH');
#!/bin/sh
[ "$XCAT_TEST_SCENARIO" != disk-failure ] || exit 9
printf /dev/vda >/tmp/xcat.install_disk
SH
write_text("$root/payloads/node.pre", <<'SH');
#!/bin/sh
[ "$(cat /tmp/xcat.install_disk 2>/dev/null)" = /dev/vda ] || exit 3
[ "$XCAT_TEST_SCENARIO" != pre-failure ] || exit 7
printf ran >/fixture/pre-ran
[ "$XCAT_TEST_SCENARIO" != no-partition ] || exit 0
printf 'storage:\n layout:\n name: direct\n' >/tmp/partitionfile
SH
write_text("$root/payloads/node.post", <<'SH');
#!/bin/sh
printf ran >/fixture/post-ran
[ "$XCAT_TEST_SCENARIO" != post-failure ] || exit 42
SH
for my $command (qw(ip getent wget curtin)) {
copy(repo_path('xCAT-test/fixtures/subiquity/command'), "$root/bin/$command") or die $!;
chmod 0755, "$root/bin/$command" or die $!;
}
copy(repo_path('xCAT-test/fixtures/subiquity/run.pl'), "$root/run.pl") or die $!;
my ($status, $output) = sandbox_run($root, {env => {XCAT_TEST_SCENARIO => $name}}, $^X, '/fixture/run.pl');
unless (is($status, $expected_status, 'the complete command sequence returns the expected status')) {
diag($output);
diag(read_text("$root/$_")) for grep { -f "$root/$_" } qw(tmp/pre-install.log target/var/log/xcat/xcat.log statuses.json);
return;
}
my $statuses = decode_json(read_text("$root/statuses.json"));
is($statuses->[-1][1], $expected_status, 'the result comes from an installer command');
if ($expected_status) {
ok(!-e "$root/monitor-request", 'does not advance the boot chain after failure');
is(read_text("$root/target/etc/apt/apt.conf.d/94curtin-config"), "temporary\n", 'does not run later cleanup after failure');
ok(!-e "$root/post-ran", 'does not run the postscript after early failure') unless $name eq 'post-failure';
return;
}
is(read_text("$root/monitor-request"), "next\n", 'advances through the real monitor handshake');
is(read_text("$root/monitor-status"), "0\n", 'completes the monitor exchange');
is(read_text("$root/pre-ran"), 'ran', 'executes the downloaded pre-script');
is(read_text("$root/post-ran"), 'ran', 'executes the downloaded postscript');
is(load_yaml("$root/final-autoinstall.yaml")->{storage}{layout}{name}, 'direct', 'injects the supplied storage layout');
is(read_text("$root/etc/resolv.conf"), ($name eq 'unresolved' ? "nameserver 192.0.2.53\n" : "nameserver 127.0.0.1\n") . "domain example.invalid\n", 'writes a numeric resolver or retains DHCP DNS');
my $network = load_yaml("$root/target/etc/netplan/00-xcat-install.yaml")->{network};
my %interface = (match => {macaddress => '52:54:00:12:34:56'}, dhcp4 => JSON::PP::true, 'dhcp4-overrides' => {'use-domains' => JSON::PP::true});
$interface{'set-name'} = $rename if defined $rename;
is_deeply($network, {version => 2, ethernets => {'xcat-install' => \%interface}}, 'writes the resolved interface and DHCP policy');
is((stat("$root/target/etc/netplan/00-xcat-install.yaml"))[2] & oct('0777'), oct('0600'), 'restricts netplan permissions');
is(read_text("$root/target/etc/hostname"), "node\n", 'writes the node hostname');
is(read_text("$root/target/etc/hosts"), "127.0.0.1 localhost\n127.0.1.1 node\n", 'replaces or appends the local hostname');
ok(-f "$root/target/etc/cloud/cloud-init.disabled", 'disables target cloud-init');
is(read_text("$root/grub-input"), "GRUB_TIMEOUT=5\nGRUB_CMDLINE_LINUX=\"console=ttyS0 quiet\"\n", 'passes the quoted kernel command line to update-grub2');
ok(!-e "$root/target/etc/apt/sources.list.d/$_", "removes $_ after the postscript") for @temporary_sources;
ok(!-e "$root/target/etc/apt/apt.conf.d/94curtin-config", 'removes installer apt settings');
is(read_text("$root/target/etc/apt/sources.list.d/admin.list"), "preserved\n", 'retains administrator sources');
};
}
done_testing();
-120
View File
@@ -1,120 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
my $tmpl_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/share/xcat/install/ubuntu/compute.subiquity.tmpl" : '';
$tmpl_path = "xCAT-server/share/xcat/install/ubuntu/compute.subiquity.tmpl"
unless -f $tmpl_path;
plan skip_all => "compute.subiquity.tmpl not found" unless -f $tmpl_path;
my $tmpl = do { local $/; open my $fh, '<', $tmpl_path or die $!; <$fh> };
like($tmpl, qr/^#cloud-config/, 'template starts with #cloud-config');
like($tmpl, qr/autoinstall:/, 'template has autoinstall: key');
like($tmpl, qr/version:\s*1/, 'template has version: 1');
like($tmpl, qr/^\s*identity:/m, 'template has an identity section so subiquity does not prompt');
like($tmpl, qr/^ - #INCLUDE_DEFAULT_PKGLIST_AUTOINSTALL#\n/m, 'the packages list carries the osimage pkglist through the autoinstall token');
like($tmpl, qr/^ - openssh-server\n/m, '... and keeps openssh-server, which xCAT needs on the node');
like($tmpl, qr/^ - wget\n/m, '... and wget, which the early and late commands use');
like($tmpl, qr/kernel:/, 'template has kernel section');
like($tmpl, qr/package:\s*linux-generic/, 'template specifies linux-generic kernel');
like($tmpl, qr/#UBUNTU_SUBIQUITY_APT_CONFIG#/, 'template renders apt section from osimage context');
unlike($tmpl, qr/^\s*apt:/m, 'template does not carry a static apt section');
unlike($tmpl, qr/WARN: no partitionfile/, 'template does not silently fall back when xCAT pre-script fails');
unlike($tmpl, qr/INSTALL_DISK=""/, 'template does not guess an install disk in early-commands');
unlike($tmpl, qr/geoip:\s*true/, 'template does not enable geoip');
like($tmpl, qr/ssh:/, 'template has ssh section');
like($tmpl, qr/install-server:\s*true/, 'template enables ssh install-server');
unlike($tmpl, qr/package_update:\s*true/, 'template does not enable package_update');
unlike($tmpl, qr/^\s*-\s+nfs-common\s*$/m, 'template does not require nfs-common from offline ISO packages');
# YAML safety: use printf with single-quoted arguments instead of shell-specific
# escape sequences. dash does not portably interpret printf \xNN.
unlike($tmpl, qr/echo.*GRUB_CMDLINE.*\\"/, 'no escaped double quotes in echo GRUB line');
unlike($tmpl, qr/\\\\x22/, 'template does not rely on non-portable printf hex escapes');
like($tmpl, qr/printf ''%s\\n'' ''GRUB_CMDLINE_LINUX="#TABLEBLANKOKAY:bootparams:\$NODE:kcmdline#"''/, 'GRUB line uses portable printf quoting');
like($tmpl, qr/\/target\/etc\/netplan\/00-xcat-install\.yaml/, 'template writes an xCAT-owned target netplan file');
# Regression: an UNSET noderes.installnic must not fatally break Subiquity xnba generation, and
# the interface must still be resolved in xCAT's order (installnic -> primarynic -> mac.mac). The
# template read #TABLE:noderes:$NODE:installnic#, and Template.pm's tabdb raises "Unable to find
# requested field <installnic> from table <noderes>" -> "Failed to generate xnba configurations"
# when the node carries no installnic, so the Ubuntu diskful install never started. The resolution
# now happens in Perl (xCAT::Template, via xCAT::NetworkUtils::gen_net_boot_params) and reaches the
# template already resolved, so no part of that order is re-derived in shell. The resolution itself
# and the netplan it produces are covered by ubuntu_subiquity_installnic.t.
unlike($tmpl, qr/noderes:\$NODE:(?:installnic|primarynic)/,
'the template does not read installnic/primarynic itself (fatal when unset, and it would have to redo the fallback)');
like($tmpl, qr/installnic="#SUBIQUITYINSTALLNIC#"/,
'the target netplan uses the interface name xCAT resolved for this node');
like($tmpl, qr/installmac="#SUBIQUITYINSTALLMAC#"/,
'the target netplan uses the MAC address xCAT resolved for this node');
like($tmpl, qr/if \[ -z "\$\{installnic\}" \]; then/,
'no resolved interface name means match by MAC alone, with no set-name rename');
unlike($tmpl, qr/tr ''A-F'' ''a-f''/,
'the shell no longer normalizes the MAC (Template.pm resolves mac.mac entries)');
unlike($tmpl, qr/cut -d''\|'' -f1/,
'the shell no longer splits mac.mac entries (Template.pm resolves them for this node)');
like($tmpl, qr/printf ''%s\\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \\"\$\{installmac\}\\"" " set-name: \$\{installnic\}" " dhcp4: true" " dhcp4-overrides:" " use-domains: true" >\/target\/etc\/netplan\/00-xcat-install\.yaml;/, 'target netplan printf stays on one shell line');
like($tmpl, qr/" macaddress: \\"\$\{installmac\}\\""/, 'target netplan matches by MAC address');
like($tmpl, qr/" set-name: \$\{installnic\}"/, 'target netplan sets the expected installnic name');
like($tmpl, qr/"\s+dhcp4: true"/, 'target netplan enables DHCPv4 on installnic');
like($tmpl, qr/"\s+use-domains: true"/, 'target netplan takes the search domain DHCP offers, so short names resolve on the node');
like($tmpl, qr/printf ''%s\\n'' ''#HOSTNAME#'' >\/target\/etc\/hostname/, 'template writes target hostname before disabling cloud-init');
like($tmpl, qr/sed -i ''s\/\^127\\\.0\\\.1\\\.1\.\*\/127\.0\.1\.1 #HOSTNAME#\/'' \/target\/etc\/hosts/, 'template updates target hosts entry for hostname');
like($tmpl, qr/touch \/target\/etc\/cloud\/cloud-init\.disabled/, 'target cloud-init is disabled after target netplan is written');
# Regression: downloaded files are required and checked with test -s before use.
like($tmpl, qr/wget -T 30 -O \/tmp\/getinstdisk http:\/\/#XCATVAR:XCATMASTER#/, 'getinstdisk download is required');
like($tmpl, qr/test -s \/tmp\/getinstdisk/, 'getinstdisk checked with -s not -x');
like($tmpl, qr/wget -T 30 -O \/tmp\/pre\.sh http:\/\/#XCATVAR:XCATMASTER#/, 'pre.sh download is required');
like($tmpl, qr/test -s \/tmp\/pre\.sh/, 'pre.sh checked with -s not -x');
like($tmpl, qr/test -s \/tmp\/partitionfile/, 'partitionfile from pre-script is required');
unlike($tmpl, qr/wget .*?\|\| true/, 'xCAT control artifact downloads are not masked');
unlike($tmpl, qr/if \[ -x \/tmp\/getinstdisk \]/, 'getinstdisk not checked with -x');
unlike($tmpl, qr/if \[ -x \/tmp\/pre\.sh \]/, 'pre.sh not checked with -x');
# Regression: apt configuration is generated by Template.pm so release-specific
# Subiquity behavior can be handled without cloning this template per release.
unlike($tmpl, qr/noble-|jammy-|focal-/, 'template avoids release-specific apt suite names');
like($tmpl, qr/#UBUNTU_SUBIQUITY_APT_CONFIG#/, 'template keeps dynamic apt renderer marker');
like($tmpl, qr{2>&1'\n(?:\s*#[^\n]*\n)*\s*- rm -f (?:/target/etc/apt/sources\.list\.d/xcat-(?:otherpkgs|pkgdir)-\*\.(?:list|sources)\s+){4}/target/etc/apt/apt\.conf\.d/94curtin-config$}m,
'the installer sources for the otherpkgs repository and the pkgdir mirrors, and the apt configuration curtin wrote, are removed from the target by a late-command of their own, after the post script block');
# the post script block: its status is the post script's, so a failed post script stops the install
{
my ($block) = $tmpl =~ /\n - '(\{\n.*?\n \} >>\/target\/var\/log\/xcat\/xcat\.log 2>&1)'\n/s;
ok( defined $block, 'the post script block is found' ) or last;
$block =~ s/''/'/g;
require File::Temp;
my $root = File::Temp->newdir();
mkdir "$root/bin" or die;
for my $tool ( 'curtin', 'wget' ) {
open( my $fh, '>', "$root/bin/$tool" ) or die;
print {$fh} $tool eq 'curtin' ? "#!/bin/sh\ncase \"\$*\" in *post.script*) exit 42;; esac\nexit 0\n" : "#!/bin/sh\nfor a; do case \"\$a\" in http*) touch \"\${a##*/}\";; esac; done\nexit 0\n";
close $fh; chmod 0755, "$root/bin/$tool";
}
( my $script = $block ) =~ s{/target}{$root/target}g;
$script =~ s{/tmp/pre-install\.log}{$root/pre-install.log}g;
for my $token ( [ '#SUBIQUITYINSTALLNIC#', '' ], [ '#SUBIQUITYINSTALLMAC#', '52:54:00:00:00:01' ], [ '#HOSTNAME#', 'cn1' ], [ '#XCATVAR:XCATMASTER#', '192.0.2.10' ],
[ '#COLONHTTPPORT#', '' ], [ '#TABLEBLANKOKAY:bootparams:$NODE:kcmdline#', '' ] ) {
$script =~ s/\Q$token->[0]\E/$token->[1]/g;
}
require File::Path;
File::Path::make_path( map { "$root/target/$_" } qw(etc/default root var/log/xcat) );
open( my $hosts, '>', "$root/target/etc/hosts" ) or die; print {$hosts} "127.0.0.1 localhost\n"; close $hosts;
open( my $pre, '>', "$root/pre-install.log" ) or die; close $pre;
my $cwd = File::Spec->rel2abs('.');
chdir $root or die;
local $ENV{PATH} = "$root/bin:$ENV{PATH}";
system( 'sh', '-c', $script );
my $status = $? >> 8;
chdir $cwd or die;
is( $status, 42, 'a failing post script fails the late-command block, so Subiquity stops the install instead of switching the node to disk boot' );
}
done_testing();