diff --git a/src/cmd-buildextend-live b/src/cmd-buildextend-live index 8a1570179e2c7f3c6b631934977883838383cedd..02d1ebef339492b6ea53bf572a4387a922099969 100755 --- a/src/cmd-buildextend-live +++ b/src/cmd-buildextend-live @@ -15,6 +15,7 @@ import sys import tarfile import tempfile import time +import yaml sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from cosalib.builds import Builds @@ -133,26 +134,28 @@ def align_initrd_for_uncompressed_append(destf): # Return OS features table for features.json, which is read by -# nestos-installer {iso|pxe} customize +# coreos-installer {iso|pxe} customize def get_os_features(): - features = {} - - f = runcmd(['/usr/bin/ostree', 'cat', '--repo', repo, - buildmeta_commit, '/usr/libexec/nestos-installer-service'], - capture_output=True).stdout.decode() - # eventually we can assume nestos-installer >= 0.12.0, delete this check, - # and hardcode the feature flag - if '--config-file' in f: - features['installer-config'] = True - - f = runcmd(['/usr/bin/ostree', 'cat', '--repo', repo, - buildmeta_commit, - '/usr/lib/dracut/modules.d/35nestos-network/nestos-copy-firstboot-network.sh'], - capture_output=True).stdout.decode() - # eventually we can assume /etc/coreos-firstboot-network support and - # hardcode the feature flag - if 'etc_firstboot_network_dir' in f: - features['live-initrd-network'] = True + features = { + # coreos-installer >= 0.12.0 + 'installer-config': True, + # coreos/fedora-coreos-config@3edd2f28 + 'live-initrd-network': True, + } + + # coreos-installer >= 0.16.0 + try: + f = runcmd(['/usr/bin/ostree', 'cat', '--repo', repo, buildmeta_commit, + '/usr/share/nestos-installer/example-config.yaml'], + capture_output=True).stdout.decode() + features['installer-config-directives'] = { + k: True for k in yaml.safe_load(f) + } + except subprocess.CalledProcessError as e: + if e.returncode == 1: + print('nestos-installer example-config.yaml not found. Not setting feature.') + else: + raise return features @@ -216,8 +219,8 @@ def generate_iso(): initrd_img = 'initrd.img' # other files rootfs_img = 'rootfs.img' - ignition_img = 'ignition.img' kargs_file = 'kargs.json' + igninfo_file = 'igninfo.json' tmpisofile = os.path.join(tmpdir, iso_name) @@ -260,7 +263,6 @@ def generate_iso(): # Generate initramfs stamp file indicating that this is a live # initramfs. Store the build ID in it. - stamppath = os.path.join(tmpinitrd_base, 'etc/nestos-live-initramfs') os.makedirs(os.path.dirname(stamppath), exist_ok=True) with open(stamppath, 'w') as fh: @@ -275,14 +277,18 @@ def generate_iso(): fh.write(args.build + '\n') # Add placeholder for Ignition CPIO file. This allows an external tool, - # `nestos-installer iso ignition embed`, to modify an existing ISO image + # `coreos-installer iso ignition embed`, to modify an existing ISO image # to embed a user's custom Ignition config. The tool wraps the Ignition # config in a cpio.xz and write it directly into this file in the ISO # image. The cpio.xz will be read into the initramfs filesystem at # runtime and the Ignition Dracut module will ensure that the config is - # moved where Ignition will see it. - with open(os.path.join(tmpisoimages, ignition_img), 'wb') as fdst: - fdst.write(bytes(ignition_img_size)) + # moved where Ignition will see it. We only handle !s390x here since that's + # the simple case (where layered initrds are supported). The s390x case is + # handled lower down + if basearch != 's390x': + with open(os.path.join(tmpisoimages, 'ignition.img'), 'wb') as fdst: + fdst.write(bytes(ignition_img_size)) + igninfo_json = {'file': 'images/ignition.img'} # Generate JSON file that lists OS features available to # coreos-installer {iso|pxe} customize. Put it in the initramfs for @@ -295,18 +301,31 @@ def generate_iso(): with open(os.path.join(tmpisocoreos, 'features.json'), 'w') as fh: fh.write(features) + # Get PRETTY_NAME + with tempfile.TemporaryDirectory() as tmpd: + runcmd(['/usr/bin/ostree', 'checkout', '--repo', repo, '--user-mode', + '--subpath', "/usr/lib/os-release", buildmeta_commit, tmpd]) + pretty_name = subprocess.check_output(['sh', '-euc', '. ./os-release; echo -n $PRETTY_NAME'], + encoding='utf-8', cwd=tmpd) + # Add osmet files tmp_osmet = os.path.join(tmpinitrd_rootfs, img_metal_obj['path'] + '.osmet') + fast_arg = [] + if args.fast: + fast_arg = ['--fast'] print('Generating osmet file for 512b metal image') - runcmd(['/usr/lib/coreos-assembler/osmet-pack', - img_metal, '512', tmp_osmet, img_metal_checksum, - 'fast' if args.fast else 'normal']) + runcmd(['/usr/lib/coreos-assembler/runvm-coreos-installer', img_metal, + tmp_osmet, 'pack', 'osmet', '/dev/disk/by-id/virtio-coreos', + '--description', pretty_name, '--checksum', img_metal_checksum, + '--output', '/var/tmp/coreos-installer-output'] + fast_arg) if img_metal4k_obj: tmp_osmet4k = os.path.join(tmpinitrd_rootfs, img_metal4k_obj['path'] + '.osmet') print('Generating osmet file for 4k metal image') - runcmd(['/usr/lib/coreos-assembler/osmet-pack', - img_metal4k, '4096', tmp_osmet4k, img_metal4k_checksum, - 'fast' if args.fast else 'normal']) + runcmd(['/usr/lib/coreos-assembler/runvm-coreos-installer', + img_metal4k, tmp_osmet4k, 'pack', 'osmet', + '/dev/disk/by-id/virtio-coreos', '--description', pretty_name, + '--checksum', img_metal4k_checksum, '--output', + '/var/tmp/coreos-installer-output'] + fast_arg) # Generate root squashfs print(f'Compressing squashfs with {squashfs_compression}') @@ -384,6 +403,8 @@ def generate_iso(): kargs_json['files'].append({ 'path': os.path.join(dir_suffix, filename), 'offset': karg_area_start.start(), + 'pad': '#', + 'end': '\n', }) if karg_embed_area_length == 0: karg_embed_area_length = length @@ -461,10 +482,8 @@ def generate_iso(): [fp2.write(line.replace('@INITRD_LOAD_ADDRESS@', INITRD_ADDRESS)) for line in fp1] for prmfile in ['cdboot.prm', 'genericdvd.prm', 'generic.prm']: with open(os.path.join(tmpisoimages, prmfile), 'w') as fp1: - line1 = 'cio_ignore=all,!condev rd.cmdline=ask' with open(os.path.join(tmpisoroot, 'zipl.prm'), 'r') as fp2: - line1 += ' ' + ' '.join([line2.strip('\n') for line2 in fp2]) - fp1.write(line1) + fp1.write(fp2.read().strip()) # s390x's z/VM CMS files are limited to 8 char for filenames and extensions # Also it is nice to keep naming convetion with Fedora/RHEL for existing users and code @@ -472,12 +491,62 @@ def generate_iso(): shutil.move(os.path.join(tmpisoimagespxe, kernel_img), kernel_dest) kernel_img = 'kernel.img' + if args.fixture: + # truncate it to 128k so it includes the offsets to the initrd and kargs + # https://github.com/ibm-s390-linux/s390-tools/blob/032304d5034e/netboot/mk-s390image#L21-L24 + with open(kernel_dest, 'rb+') as f: + f.truncate(128 * 1024) + with open(iso_initramfs, 'rb+') as f: + f.truncate(1024) + + # On s390x, we reserve space for the Ignition config in the initrd + # image directly since the bootloader doesn't support multiple initrds. + # We do this by inflating the initramfs just for the duration of the + # `mk-s390image` call. + initramfs_size = os.stat(iso_initramfs).st_size + # sanity-check it's 4-byte aligned (see align_initrd_for_uncompressed_append) + assert initramfs_size % 4 == 0 + # combine kernel, initramfs and cmdline using the mk-s390image tool + os.truncate(iso_initramfs, initramfs_size + ignition_img_size) runcmd(['/usr/bin/mk-s390image', - kernel_dest, - os.path.join(tmpisoimages, 'cdboot.img'), - '-r', iso_initramfs, - '-p', os.path.join(tmpisoimages, 'cdboot.prm')]) + kernel_dest, + os.path.join(tmpisoimages, 'cdboot.img'), + '-r', iso_initramfs, + '-p', os.path.join(tmpisoimages, 'cdboot.prm')]) + os.truncate(iso_initramfs, initramfs_size) + + # Get the kargs and initramfs offsets in the cdboot.img. For more info, see: + # https://github.com/ibm-s390-linux/s390-tools/blob/032304d5034e/netboot/mk-s390image#L21-L23 + CDBOOT_IMG_OFFS_INITRD_START_BYTES = 66568 + CDBOOT_IMG_OFFS_KARGS_START_BYTES = 66688 + CDBOOT_IMG_OFFS_KARGS_MAX_SIZE = 896 + with open(os.path.join(tmpisoimages, 'cdboot.img'), 'rb') as f: + f.seek(CDBOOT_IMG_OFFS_INITRD_START_BYTES) + offset = struct.unpack(">Q", f.read(8))[0] + + # sanity-check we're at the right spot by comparing a few bytes + f.seek(offset) + with open(iso_initramfs, 'rb') as canonical: + if f.read(1024) != canonical.read(1024): + raise Exception(f"expected initrd at offset {offset}") + + igninfo_json = { + 'file': 'images/cdboot.img', + 'offset': offset + initramfs_size, + 'length': ignition_img_size, + } + + # kargs are part of 'images/cdboot.img' blob + kargs_json['files'].append({ + 'path': 'images/cdboot.img', + 'offset': CDBOOT_IMG_OFFS_KARGS_START_BYTES, + 'pad': '\0', + 'end': '\0', + }) + kargs_json.update( + size=CDBOOT_IMG_OFFS_KARGS_MAX_SIZE, + ) # generate .addrsize file for LPAR with open(os.path.join(tmpisoimages, 'initrd.addrsize'), 'wb') as addrsize: addrsize_data = struct.pack(">iiii", 0, int(INITRD_ADDRESS, 16), 0, @@ -521,11 +590,27 @@ def generate_iso(): buildmeta_commit, tmpimageefidir]) #Reference:https://github.com/coreos/coreos-assembler/pull/2435 - # Find name of vendor directory vendor_ids = [n for n in os.listdir(tmpimageefidir) if n != "BOOT"] if len(vendor_ids) != 1: raise Exception(f"did not find exactly one EFI vendor ID: {vendor_ids}") + vendor_id = vendor_ids[0] + + # Always replace live/EFI/{vendor} to actual live/EFI/{vendor_id} + # https://github.com/openshift/os/issues/954 + dfd = os.open(tmpisoroot, os.O_RDONLY) + grubfilepath = ensure_glob('EFI/*/grub.cfg', dir_fd=dfd) + if len(grubfilepath) != 1: + raise Exception(f'Found != 1 grub.cfg files: {grubfilepath}') + srcpath = os.path.dirname(grubfilepath[0]) + if srcpath != f'EFI/{vendor_id}': + print(f"Renaming '{srcpath}' to 'EFI/{vendor_id}'") + os.rename(srcpath, f"EFI/{vendor_id}", src_dir_fd=dfd, dst_dir_fd=dfd) + # And update kargs.json + for file in kargs_json['files']: + if file['path'] == grubfilepath[0]: + file['path'] = f'EFI/{vendor_id}/grub.cfg' + os.close(dfd) # Delete fallback and its CSV file. Its purpose is to create # EFI boot variables, which we don't want when booting from @@ -540,22 +625,22 @@ def generate_iso(): for path in ensure_glob(os.path.join(tmpimageefidir, "BOOT", "mm*.efi")): os.unlink(path) - for path in ensure_glob(os.path.join(tmpimageefidir, vendor_ids[0], "BOOT*.CSV")): + for path in ensure_glob(os.path.join(tmpimageefidir, vendor_id, "BOOT*.CSV")): os.unlink(path) # Drop vendor copies of shim; we already have it in BOOT*.EFI in # BOOT - for path in ensure_glob(os.path.join(tmpimageefidir, vendor_ids[0], "shim*.efi")): + for path in ensure_glob(os.path.join(tmpimageefidir, vendor_id, "shim*.efi")): os.unlink(path) # openEuler shim package has two fb*.efi that put in "BOOT" and "openEuler" dir, delete them all - for path in ensure_glob(os.path.join(tmpimageefidir, vendor_ids[0], "fb*.efi")): + for path in ensure_glob(os.path.join(tmpimageefidir, vendor_id, "fb*.efi")): os.unlink(path) # Consolidate remaining files into BOOT. shim needs GRUB to be # there, and the rest doesn't hurt. - for path in ensure_glob(os.path.join(tmpimageefidir, vendor_ids[0], "*")): + for path in ensure_glob(os.path.join(tmpimageefidir, vendor_id, "*")): shutil.move(path, os.path.join(tmpimageefidir, "BOOT")) - os.rmdir(os.path.join(tmpimageefidir, vendor_ids[0])) + os.rmdir(os.path.join(tmpimageefidir, vendor_id)) # Inject a stub grub.cfg pointing to the one in the main ISO image. # @@ -573,7 +658,7 @@ def generate_iso(): # pointing to efiboot.img) and needs a grub.cfg there. with open(os.path.join(tmpimageefidir, "BOOT", "grub.cfg"), "w") as fh: fh.write(f'''search --label "{volid}" --set root --no-floppy -set prefix=($root)/EFI/{vendor_ids[0]} +set prefix=($root)/EFI/{vendor_id} echo "Booting via ESP..." configfile $prefix/grub.cfg boot @@ -612,11 +697,17 @@ boot kargs_json['files'].sort(key=lambda f: f['path']) if kargs_json['files']: # Store the location of "karg embed areas" for use by - # `nestos-installer iso kargs modify` + # `coreos-installer iso kargs modify` with open(os.path.join(tmpisocoreos, kargs_file), 'w') as fh: json.dump(kargs_json, fh, indent=2, sort_keys=True) fh.write('\n') + # Write out the igninfo.json file. This is used by coreos-installer to know + # how to embed the Ignition config. + with open(os.path.join(tmpisocoreos, igninfo_file), 'w') as fh: + json.dump(igninfo_json, fh, indent=2, sort_keys=True) + fh.write('\n') + # Define inputs and outputs genisoargs_final = genisoargs + ['-o', tmpisofile, tmpisoroot] @@ -625,7 +716,7 @@ boot f.truncate(miniso_data_file_size) if args.fixture: - # Replace or delete anything irrelevant to nestos-installer + # Replace or delete anything irrelevant to coreos-installer with open(os.path.join(tmpisoimages, 'efiboot.img'), 'w') as fh: fh.write('efiboot.img\n') with open(os.path.join(tmpisoimagespxe, 'rootfs.img'), 'w') as fh: @@ -634,17 +725,19 @@ boot fh.write('initrd data\n') with open(os.path.join(tmpisoimagespxe, 'vmlinuz'), 'w') as fh: fh.write('the kernel\n') - with open(os.path.join(tmpisoisolinux, 'isolinux.bin'), 'rb+') as fh: - flen = fh.seek(0, 2) - fh.truncate(0) - fh.truncate(flen) - fh.seek(64) - # isohybrid checks for this magic - fh.write(b'\xfb\xc0\x78\x70') - for f in ensure_glob(os.path.join(tmpisoisolinux, '*.c32')): - os.unlink(f) - for f in ensure_glob(os.path.join(tmpisoisolinux, '*.msg')): - os.unlink(f) + # this directory doesn't exist on s390x + if os.path.isdir(tmpisoisolinux): + with open(os.path.join(tmpisoisolinux, 'isolinux.bin'), 'rb+') as fh: + flen = fh.seek(0, 2) + fh.truncate(0) + fh.truncate(flen) + fh.seek(64) + # isohybrid checks for this magic + fh.write(b'\xfb\xc0\x78\x70') + for f in ensure_glob(os.path.join(tmpisoisolinux, '*.c32')): + os.unlink(f) + for f in ensure_glob(os.path.join(tmpisoisolinux, '*.msg')): + os.unlink(f) runcmd(genisoargs_final) @@ -657,16 +750,17 @@ boot # The only difference with the miniso is that we drop these two files. # Keep everything else the same to maximize file matching between the # two versions so we can get the smallest delta. E.g. we keep the - # `nestos.liveiso` karg, even though the miniso doesn't need it. - # nestos-installer takes care of removing it. + # `coreos.liveiso` karg, even though the miniso doesn't need it. + # coreos-installer takes care of removing it. os.unlink(iso_rootfs) os.unlink(miniso_data) runcmd(genisoargs_minimal) if basearch == "x86_64": runcmd(['/usr/bin/isohybrid', '--uefi', f'{tmpisofile}.minimal']) # this consumes the minimal image - runcmd(['nestos-installer', 'pack', 'minimal-iso', - tmpisofile, f'{tmpisofile}.minimal', "--consume"]) + runcmd(['/usr/lib/coreos-assembler/runvm-coreos-installer', img_metal, '', + 'pack', 'minimal-iso', tmpisofile, f'{tmpisofile}.minimal', + '--consume']) buildmeta['images'].update({ 'live-iso': { diff --git a/src/osmet-pack b/src/runvm-coreos-installer similarity index 62% rename from src/osmet-pack rename to src/runvm-coreos-installer index 300d2369da8ba7de92bec74068d3d4c0a8db153c..dd331c646a06085eaee3dd9c0fced9b581dacfcc 100755 --- a/src/osmet-pack +++ b/src/runvm-coreos-installer @@ -1,17 +1,20 @@ #!/bin/bash set -euo pipefail +# This script runs the coreos-installer found in the compose itself inside a +# chroot inside a supermin VM. The first argument is the path to the metal +# image. It will be available as /dev/disk/by-id/virtio-coreos. The second +# argument is an optional path to an output file. It will be available as +# /var/tmp/coreos-installer-output. All following arguments are passed as is to +# coreos-installer. + if [ ! -f /etc/cosa-supermin ]; then dn=$(dirname "$0") # shellcheck source=src/cmdlib.sh . "${dn}"/cmdlib.sh img_src=$1; shift - sector_size=$1; shift - osmet_dest=$1; shift - checksum=$1; shift - speed=$1; shift - coreinst=${1:-${OSMET_PACK_COREOS_INSTALLER:-}} + output_path=$1; shift workdir=$(pwd) TMPDIR=$(readlink -f tmp/tmp-osmet-pack) @@ -30,14 +33,20 @@ if [ ! -f /etc/cosa-supermin ]; then fi device_opts= - if [ "$sector_size" != 512 ]; then - device_opts=",physical_block_size=${sector_size},logical_block_size=${sector_size}" + if [[ "${img_src}" == *metal4k* ]]; then + device_opts=",physical_block_size=4096,logical_block_size=4096" + fi + + virtioserial_args=() + if [ -n "${output_path}" ]; then + virtioserial_args+=(-chardev "file,id=coreosout,path=${output_path}" \ + -device "virtserialport,chardev=coreosout,name=coreosout") fi - # stamp it with "osmet" serial so we find it easily in the VM - runvm -drive "if=none,id=osmet,format=raw,readonly=on,file=${img_src}" \ - -device "virtio-blk,serial=osmet,drive=osmet${device_opts}" -- \ - /usr/lib/coreos-assembler/osmet-pack "$@" + runvm -drive "if=none,id=coreos,format=raw,readonly=on,file=${img_src}" \ + -device "virtio-blk,serial=coreos,drive=coreos${device_opts}" \ + "${virtioserial_args[@]}" -- \ + /usr/lib/coreos-assembler/runvm-coreos-installer "$@" mv "${TMPDIR}/osmet.bin" "${osmet_dest}" rm -rf "${TMPDIR}" @@ -85,3 +94,8 @@ RUST_BACKTRACE=full ${coreinst} pack osmet /dev/disk/by-id/virtio-osmet \ --output /tmp/osmet.bin $fast mv /tmp/osmet.bin "${osmet_dest}" + +# and now transfer out the output file if provided (see header) +if [ -f /var/tmp/coreos-installer-output ]; then + cp /var/tmp/coreos-installer-output /dev/virtio-ports/coreosout +fi