2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-09 22:46:45 +00:00

Build SUSE 16 images with imgutil

SuseHandler refused anything but 15.x. What 16 needed beyond widening it:

- its repo urls are written in terms of ${releasever}, which zypper
  resolves from the target root's os-release, a file that does not exist
  yet when the first packages go in
- its repos name a zypper service backed by a package-provided directory
  the target root does not have, so zypper discarded every one of them
  as an orphan
- there is no mkinitrd to work out which kernel to build for, and bare
  dracut would build for the build host's running kernel
- the efi payloads moved out of /usr/lib64/efi, arping out of /usr/sbin,
  nsswitch.conf and protocols under /usr/etc, and the presets enable
  sshd already
- the module list predated virtio, so an image built for a KVM guest had
  no network at all, and dm-crypt could not allocate a transform for the
  encrypted image without the aes-xts modules
- urlmount still links libpthread, an empty stub since glibc 2.34 that
  nothing else in the initramfs pulls in
This commit is contained in:
Markus Hilger
2026-09-04 22:01:01 +02:00
parent 398211a6ed
commit 5645fb5cfd
5 changed files with 135 additions and 12 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ mkdir -p opt/confluent/lib/imgutil
mkdir -p opt/confluent/bin
mv imgutil opt/confluent/bin/
chmod a+x opt/confluent/bin/imgutil
mv ubuntu* suse15 el7 el9 el10 el8 opt/confluent/lib/imgutil/
mv ubuntu* suse15 suse16 el7 el9 el10 el8 opt/confluent/lib/imgutil/
mkdir -p opt/confluent/share/licenses/confluent_imgutil
cp LICENSE opt/confluent/share/licenses/confluent_imgutil
+55 -11
View File
@@ -588,9 +588,10 @@ class SuseHandler(OsHandler):
def __init__(self, name, version, arch, args):
if not isinstance(version, str):
version = version.decode('utf8')
if not version.startswith('15.'):
major = version.split('.', 1)[0]
if major not in ('15', '16'):
raise Exception('Unsupported Suse version {}'.format(version))
self.oscategory = 'suse15'
self.oscategory = 'suse{0}'.format(major)
super().__init__(name, version, arch, args)
self.zyppargs = []
self.sources = []
@@ -610,6 +611,15 @@ class SuseHandler(OsHandler):
if enterprise:
self.sources.append('file://' + os.path.join(sourcepath, 'Product-HPC'))
def zypper_cmd(self):
# 16's stock repo urls are written in terms of ${releasever}, which
# zypper resolves from the target root's os-release. That file does not
# exist yet when the first packages are installed, so say it outright.
cmd = ['zypper', '-R', self.targpath]
if self.version:
cmd.extend(['--releasever', self.version])
return cmd
def prep_root(self, args):
gpgkeys = []
mkdirp(self.targpath)
@@ -617,8 +627,19 @@ class SuseHandler(OsHandler):
gpgkeys = glob.glob('/usr/lib/rpm/gnupg/keys/*.asc')
targzypp = os.path.join(self.targpath, 'etc/zypp')
mkdirp(targzypp)
shutil.copytree(
'/etc/zypp/repos.d/', os.path.join(targzypp, 'repos.d'))
# 16 declares its repositories through a zypper service backed by
# a package-provided directory the target root does not have. Drop
# the service line so each definition stands on its own, otherwise
# zypper discards every one of them as an orphan.
targrepos = os.path.join(targzypp, 'repos.d')
mkdirp(targrepos)
for repofile in glob.glob('/etc/zypp/repos.d/*'):
with open(repofile) as repoin:
cfg = [line for line in repoin.read().splitlines()
if not line.startswith('service=')]
with open(os.path.join(
targrepos, os.path.basename(repofile)), 'w') as repoout:
repoout.write('\n'.join(cfg) + '\n')
idx = 1
for source in self.sources:
if not source:
@@ -626,7 +647,7 @@ class SuseHandler(OsHandler):
if source.startswith('file://'):
gpgpath = source.replace('file://', '')
gpgkeys.extend(glob.glob(os.path.join(gpgpath, '*/gpg-pubkey*.asc')))
subprocess.check_call(['zypper', '-R', self.targpath, 'ar', source, 'source-{}'.format(idx)])
subprocess.check_call(self.zypper_cmd() + ['ar', source, 'source-{}'.format(idx)])
idx += 1
if gpgkeys:
addkeycmd = ['rpm', '--root', self.targpath, '--import'] + gpgkeys
@@ -638,7 +659,7 @@ class SuseHandler(OsHandler):
if not source.startswith('/') and os.path.exists(os.path.abspath(source)):
source = os.path.abspath(source)
source = 'file://' + source
subprocess.check_call(['zypper', '-R', self.targpath, 'ar', source, 'source-{}'.format(idx)])
subprocess.check_call(self.zypper_cmd() + ['ar', source, 'source-{}'.format(idx)])
idx += 1
mydir = get_mydir(self.oscategory)
mkdirp(os.path.join(self.targpath, 'usr/lib/dracut/modules.d'))
@@ -652,10 +673,15 @@ class SuseHandler(OsHandler):
cmd.extend(glob.glob(os.path.join(targdir, '*')))
subprocess.check_call(cmd)
if self._interactive:
subprocess.check_call(['zypper', '-R', self.targpath, 'install'] + self.zyppargs)
subprocess.check_call(self.zypper_cmd() + ['install'] + self.zyppargs)
else:
subprocess.check_call(['zypper', '-n', '-R', self.targpath, 'install'] + self.zyppargs)
os.symlink('/usr/lib/systemd/system/sshd.service', os.path.join(self.targpath, 'etc/systemd/system/multi-user.target.wants/sshd.service'))
subprocess.check_call(self.zypper_cmd() + ['-n', 'install'] + self.zyppargs)
# 16's presets enable sshd already, so the link is often there
sshdwant = os.path.join(
self.targpath, 'etc/systemd/system/multi-user.target.wants/sshd.service')
mkdirp(os.path.dirname(sshdwant))
if not os.path.lexists(sshdwant):
os.symlink('/usr/lib/systemd/system/sshd.service', sshdwant)
with open(os.path.join(self.targpath, 'etc/permissions.local'), 'a') as permout:
permout.write(
'/usr/lib/ssh/ssh-keysign root:ssh_keys 2711\n'
@@ -675,7 +701,12 @@ class SuseHandler(OsHandler):
if os.path.exists(os.path.join(self.targpath, 'sbin/mkinitrd')):
args.cmd = ['mkinitrd']
else:
args.cmd = ['dracut', '-f']
# dracut alone would build for the running kernel, which is the
# build host's, not the one just installed into the image. 16 has
# no mkinitrd to work that out, so name the kernel and the output.
kver = sorted(os.listdir(os.path.join(self.targpath, 'lib/modules')),
key=versionize_string)[-1]
args.cmd = ['dracut', '-f', '/boot/initrd-{0}'.format(kver), kver]
run_constrainedx(fancy_chroot, (args, self.targpath))
@@ -1297,7 +1328,7 @@ def fingerprint_source_suse(files, sourcepath, args):
if ': ' not in line:
continue
key, val = line.split(': ')
if key == 'category' and val == 'suse15':
if key == 'category' and val in ('suse15', 'suse16'):
issuse = True
if key == 'name':
osname, ver, arch = val.split('-')
@@ -1739,6 +1770,15 @@ def gather_bootloader(outdir, rootpath='/'):
shimlocation = os.path.join(rootpath, 'usr/lib/shim/shimx64.efi.signed')
if not os.path.exists(shimlocation):
shimlocation = os.path.join(rootpath, 'usr/lib/shim/shimaa64.efi.signed.latest')
if not os.path.exists(shimlocation):
# SUSE 16 moved the efi payloads out of /usr/lib64/efi
shimlocation = os.path.join(rootpath, 'usr/share/efi/x86_64/shim.efi')
shimdestfilename = 'BOOTX64.EFI'
if not os.path.exists(shimlocation):
aa64shim = os.path.join(rootpath, 'usr/share/efi/aarch64/shim.efi')
if os.path.exists(aa64shim):
shimlocation = aa64shim
shimdestfilename = 'BOOTAA64.EFI'
mkdirp(os.path.join(outdir, 'boot/efi/boot'))
shutil.copyfile(shimlocation, os.path.join(outdir, 'boot/efi/boot/{0}'.format(shimdestfilename)))
for maybemokmanager in glob.glob(os.path.join(rootpath, 'boot/efi/EFI/*/mmx64.efi')):
@@ -1747,6 +1787,8 @@ def gather_bootloader(outdir, rootpath='/'):
else:
if os.path.exists(os.path.join(rootpath, 'usr/lib/shim/mmx64.efi')):
shutil.copyfile(os.path.join(rootpath, 'usr/lib/shim/mmx64.efi'), os.path.join(outdir, 'boot/efi/boot/mmx64.efi'))
elif os.path.exists(os.path.join(rootpath, 'usr/share/efi/x86_64/MokManager.efi')):
shutil.copyfile(os.path.join(rootpath, 'usr/share/efi/x86_64/MokManager.efi'), os.path.join(outdir, 'boot/efi/boot/mmx64.efi'))
for maybemokmanager in glob.glob(os.path.join(rootpath, 'boot/efi/EFI/*/mmaa64.efi')):
shutil.copyfile(maybemokmanager, os.path.join(outdir, 'boot/efi/boot/mmaa64.efi'))
break
@@ -1769,6 +1811,8 @@ def gather_bootloader(outdir, rootpath='/'):
grubbin = os.path.join(rootpath, 'usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed')
if not os.path.exists(grubbin):
grubbin = os.path.join(rootpath, 'usr/lib/grub/arm64-efi/monolithic/grubaa64.efi')
if not os.path.exists(grubbin):
grubbin = os.path.join(rootpath, 'usr/share/efi/x86_64/grub.efi')
if not os.path.exists(grubbin):
grubs = os.path.join(rootpath, 'boot/efi/EFI/*/grubx64.efi')
grubs = glob.glob(grubs)
+39
View File
@@ -0,0 +1,39 @@
dracut_install mktemp
dracut_install /usr/lib64/libtss2-tcti-device.so.*
dracut_install tpm2_create tpm2_pcrread tpm2_createpolicy tpm2_createprimary
dracut_install tpm2_load tpm2_unseal tpm2_getcap tpm2_evictcontrol
dracut_install tpm2_pcrextend tpm2_policypcr tpm2_flushcontext tpm2_startauthsession
dracut_install curl openssl tar cpio gzip lsmod ethtool xz lsmod ethtool
dracut_install modprobe touch echo cut wc bash uniq grep ip hostname
dracut_install awk egrep dirname expr sort
dracut_install ssh sshd reboot parted mkfs mkfs.ext4 mkfs.xfs xfs_db mkswap
dracut_install efibootmgr uuidgen
dracut_install du df ssh-keygen scp clear dhclient
dracut_install /lib64/libnss_dns.so.2 # glibc dropped the versioned name
dracut_install /lib64/libnss_compat*
dracut_install /usr/lib64/libnl-3.so.200
# 16 ships the stock config defaults under /usr/etc; only services stayed put
dracut_install /usr/etc/nsswitch.conf /etc/services /usr/etc/protocols
dracut_install chmod whoami head tail basename tr
dracut_install arping logger hostnamectl # 16 moved arping to /usr/bin
inst /bin/bash /bin/sh
dracut_install /lib64/libfuse.so.2 # symlink, dracut brings the target along
# urlmount still links libpthread, which since glibc 2.34 is an empty stub that
# nothing else in the initramfs pulls in
dracut_install /lib64/libpthread.so.0
dracut_install chown chroot dd expr kill parted rsync sort blockdev findfs insmod lvm
dracut_install /usr/lib/udev/rules.d/10-dm.rules /usr/sbin/dmsetup /usr/lib/udev/rules.d/95-dm-notify.rules
dracut_install /usr/lib/systemd/network/99-default.link
dracut_install losetup # multipart support
#this would be nfs with lock, but not needed, go nolock
#dracut_install mount.nfs rpcbind rpc.statd /etc/netconfig sm-notify
#dracut_install mount.nfs /etc/netconfig
inst /usr/lib/dracut/modules.d/40network/net-lib.sh /lib/net-lib.sh
# network mount, and disk imaging helpers can come from a second stage
# this is narrowly focused on getting network up and fetching images
# and those images may opt to do something with cloning or whatever
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
instmods nfsv3 nfs_acl nfsv4 dns_resolver lockd fscache sunrpc
instmods e1000 e1000e igb sfc mlx5_ib mlx5_core mlx4_en cxgb3 cxgb4 tg3 bnx2 bnx2x bna ixgb ixgbe qlge mptsas mpt2sas mpt3sas megaraid_sas ahci xhci-hcd sd_mod pmcraid be2net vfat ext3 ext4 usb_storage scsi_wait_scan ipmi_si ipmi_devintf qlcnic xfs
instmods nvme
instmods cdc_ether r8152
instmods r8169
instmods vmxnet3 virtio_net
instmods virtio_scsi vmw_pvscsi
instmods xen-netfront xen-blkfront xen-pcifront
instmods mptctl
instmods mlx4_ib mlx5_ub ib_umad ib_ipoib
instmods ice i40e hfi1 bnxt_en qed qede
instmods dm-mod dm-log raid0 raid1 raid10 raid456 dm-raid dm-thin-pool dm-crypt dm-snapshot linear dm-era
# imgutil packs encrypted by default and dm-crypt cannot allocate the transform
# without these; the stock dracut crypt module is not in the diskless image
instmods aes_generic aesni_intel xts crypto_simd
# nfs root and optionally gocryptfs
instmods fuse overlay squashfs loop zram
+22
View File
@@ -0,0 +1,22 @@
hostname
curl
irqbalance
less
sudo
tuned
xfsprogs
e2fsprogs
chrony net-tools rsync tar util-linux python3 tar dracut ethtool parted openssl bash rpm iputils lvm2 efibootmgr attr
dhcp-client
zypper
grub2-x86_64-efi
libfuse2
openssh
shim
kernel-default
tpm2.0-tools
# tpm2.0-tools only requires tctildr, and the diskless dracut module
# installs the device tcti explicitly
libtss2-tcti-device0
NetworkManager # 16 dropped wicked; confignet writes nmconnections
vim