daisy_workflows/image_build/enterprise_linux/build_installer.py (89 lines of code) (raw):

#!/usr/bin/env python3 # Copyright 2017 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Convert EL ISO to GCE Image and prep for installation. Parameters (retrieved from instance metadata): el_release: The EL release to build. el_savelogs: true to ask Anaconda to save logs (for debugging). """ import difflib import logging import os import re import utils def main(): # Get Parameters release = utils.GetMetadataAttribute('el_release', raise_on_not_found=True) savelogs = utils.GetMetadataAttribute('el_savelogs') == 'true' logging.info('EL Release: %s' % release) logging.info('Build working directory: %s' % os.getcwd()) iso_file = '/files/installer.iso' ks_cfg = '/files/ks.cfg' utils.AptGetInstall(['rsync']) # Write the installer disk. Write GPT label, create partition, # copy installer boot files over. logging.info('Writing installer disk.') installer_disk = ('/dev/' + os.path.basename( os.readlink('/dev/disk/by-id/google-disk-installer'))) utils.Execute(['parted', installer_disk, 'mklabel', 'gpt']) utils.Execute(['sync']) utils.Execute(['parted', installer_disk, 'mkpart', 'primary', 'fat32', '1MB', '2048MB']) utils.Execute(['sync']) utils.Execute(['parted', installer_disk, 'mkpart', 'primary', 'ext2', '2048MB', '100%']) utils.Execute(['sync']) utils.Execute(['parted', installer_disk, 'set', '1', 'boot', 'on']) utils.Execute(['sync']) utils.Execute(['parted', installer_disk, 'set', '1', 'esp', 'on']) utils.Execute(['sync']) installer_disk1 = ('/dev/' + os.path.basename( os.readlink('/dev/disk/by-id/google-disk-installer-part1'))) installer_disk2 = ('/dev/' + os.path.basename( os.readlink('/dev/disk/by-id/google-disk-installer-part2'))) utils.Execute(['mkfs.vfat', '-F', '32', installer_disk1]) utils.Execute(['sync']) utils.Execute(['fatlabel', installer_disk1, 'ESP']) utils.Execute(['sync']) utils.Execute(['mkfs.ext4', '-L', 'INSTALLER', installer_disk2]) utils.Execute(['sync']) utils.Execute(['mkdir', '-vp', 'iso', 'installer', 'boot']) utils.Execute(['mount', '-o', 'ro,loop', '-t', 'iso9660', iso_file, 'iso']) utils.Execute(['mount', '-t', 'vfat', installer_disk1, 'boot']) utils.Execute(['mount', '-t', 'ext4', installer_disk2, 'installer']) utils.Execute(['cp', '-r', 'iso/EFI', 'boot/']) utils.Execute(['cp', '-r', 'iso/images', 'boot/']) utils.Execute(['cp', iso_file, 'installer/']) utils.Execute(['cp', ks_cfg, 'installer/']) # The kickstart config contains a preinstall script copying, reloading, and # triggering this rule in the install environment. This allows us to use # predictable names for block devices. It would be perferable to take a # simpler approach such as selecting the disk with an unkown partition table # but kickstart does not believe the default google nvme device names are # are deterministic and refuses to use them without user input. utils.Execute(['cp', '-L', '/usr/lib/udev/rules.d/65-gce-disk-naming.rules', 'installer/']) utils.Execute(['cp', '-L', '/usr/lib/udev/google_nvme_id', 'installer/']) # Modify boot config. with open('boot/EFI/BOOT/grub.cfg', 'r+') as f: oldcfg = f.read() cfg = re.sub(r'-l .RHEL.*', r"""-l 'ESP'""", oldcfg) cfg = re.sub(r'timeout=60', 'timeout=1', cfg) cfg = re.sub(r'set default=.*', 'set default="0"', cfg) cfg = re.sub(r'load_video\n', r'serial --speed=115200 --unit=0 --word=8 --parity=no\n' 'terminal_input serial\nterminal_output serial\n', cfg) # Change boot args. args = ' '.join([ 'inst.text', 'inst.ks=hd:LABEL=INSTALLER:/%s' % ks_cfg, 'console=ttyS0,115200', 'inst.gpt', 'inst.loglevel=debug' ]) # Tell Anaconda not to store its logs in the installed image, # unless requested to keep them for debugging. if not savelogs: args += ' inst.nosave=all' cfg = re.sub(r'inst\.stage2.*', r'\g<0> %s' % args, cfg) # Change labels to explicit partitions. cfg = re.sub(r'LABEL=[^ ]+', 'LABEL=INSTALLER', cfg) # Print out a the modifications. diff = difflib.Differ().compare( oldcfg.splitlines(1), cfg.splitlines(1)) logging.info('Modified grub.cfg:\n%s' % '\n'.join(diff)) f.seek(0) f.write(cfg) f.truncate() # Update google_nvme_id to remove xxd dependency, if necessary # The current worker uses an older version with open('installer/google_nvme_id', 'r+') as f: old = f.read() new = re.sub(r'xxd -p -seek 384 \| xxd -p -r', 'dd bs=1 skip=384 2>/dev/null', old) f.seek(0) f.write(new) f.truncate() utils.Execute(['umount', 'installer']) utils.Execute(['umount', 'iso']) utils.Execute(['umount', 'boot']) if __name__ == '__main__': try: main() logging.success('EL Installer build successful!') except Exception as e: logging.error('EL Installer build failed: %s' % str(e))