#!/usr/bin/python3 import os import re import shutil import tarfile import lzma as xz import fileinput from argparse import ArgumentParser import json from utils import Utils import ovagenerator def prepLoopDevice(loop_device_path, mount_path): Utils.runshellcommand( "mount -t ext4 {} {}".format(loop_device_path, mount_path)) Utils.runshellcommand("mount -o bind /proc {}".format(mount_path + "/proc")) Utils.runshellcommand("mount -o bind /dev {}".format(mount_path + "/dev")) Utils.runshellcommand("mount -o bind /dev/pts {}".format(mount_path + "/dev/pts")) Utils.runshellcommand("mount -o bind /sys {}".format(mount_path + "/sys")) def cleanupMountPoints(mount_path): Utils.runshellcommand("umount -l {}".format(mount_path + "/sys")) Utils.runshellcommand("umount -l {}".format(mount_path + "/dev/pts")) Utils.runshellcommand("umount -l {}".format(mount_path + "/dev")) Utils.runshellcommand("umount -l {}".format(mount_path + "/proc")) Utils.runshellcommand("sync") Utils.runshellcommand("umount -l {}".format(mount_path)) def installAdditionalRpms(mount_path, additional_rpms_path): os.mkdir(mount_path + "/additional_rpms") Utils.copyallfiles(additional_rpms_path, mount_path + "/additional_rpms") Utils.runshellcommand( "chroot {} /bin/bash -c 'rpm -i /additional_rpms/*'".format(mount_path)) shutil.rmtree(mount_path + "/additional_rpms", ignore_errors=True) shutil.rmtree(additional_rpms_path, ignore_errors=True) def writefstabandgrub(mount_path, uuidval, partuuidval): os.remove(mount_path + "/etc/fstab") f = open(mount_path + "/etc/fstab", "w") if uuidval != '': f.write("UUID={} / ext4 defaults 1 1\n".format(uuidval)) else: f.write("PARTUUID={} / ext4 defaults 1 1\n".format(partuuidval)) f.close() Utils.replaceinfile(mount_path + "/boot/grub/grub.cfg", "rootpartition=PARTUUID=.*$", "rootpartition=PARTUUID={}".format(partuuidval)) def generateUuid(loop_device_path): partuuidval = (Utils.runshellcommand( "blkid -s PARTUUID -o value {}".format(loop_device_path))).rstrip('\n') uuidval = (Utils.runshellcommand( "blkid -s UUID -o value {}".format(loop_device_path))).rstrip('\n') if partuuidval == '': sgdiskout = Utils.runshellcommand( "sgdisk -i 2 {} ".format(disk_device)) partuuidval = (re.findall(r'Partition unique GUID.*', sgdiskout))[0].split(':')[1].strip(' ').lower() if partuuidval == '': raise RuntimeError("Cannot generate partuuid") return (uuidval, partuuidval) def customizeImage(config, mount_path): build_scripts_path = os.path.dirname(os.path.abspath(__file__)) image_name = config['image_type'] if 'additionalfiles' in config: for filetuples in config['additionalfiles']: for src, dest in filetuples.items(): if (os.path.isdir(build_scripts_path + '/' + image_name + '/' + src)): shutil.copytree(build_scripts_path + '/' + image_name + '/' + src, mount_path + dest, True) else: shutil.copyfile(build_scripts_path + '/' + image_name + '/' + src, mount_path + dest) if 'postinstallscripts' in config: if not os.path.exists(mount_path + "/tempscripts"): os.mkdir(mount_path + "/tempscripts") for script in config['postinstallscripts']: shutil.copy(build_scripts_path + '/' + image_name + '/' + script, mount_path + "/tempscripts") for script in os.listdir(mount_path + "/tempscripts"): print(" ...running script {}".format(script)) Utils.runshellcommand( "chroot {} /bin/bash -c '/tempscripts/{}'".format(mount_path, script)) shutil.rmtree(mount_path + "/tempscripts", ignore_errors=True) if 'expirepassword' in config and config['expirepassword']: # Do not run 'chroot -R' from outside. It will not find nscd socket. Utils.runshellcommand("chroot {} /bin/bash -c 'chage -d 0 root'".format(mount_path)) def createOutputArtifact(raw_image_path, config, src_root, tools_bin_path): photon_release_ver = os.environ['PHOTON_RELEASE_VER'] photon_build_num = os.environ['PHOTON_BUILD_NUM'] new_name = "" img_path = os.path.dirname(os.path.realpath(raw_image_path)) # Rename gce image to disk.raw if config['image_type'] == "gce": new_name = img_path + '/disk.raw' else: new_name = (img_path + '/photon-' + config['image_type'] + '-' + photon_release_ver + '-' + photon_build_num + '.raw') shutil.move(raw_image_path, new_name) raw_image = new_name if config['artifacttype'] == 'tgz': print("Generating the tar.gz artifact ...") outputfile = (img_path + '/photon-' + config['image_type'] + '-' + photon_release_ver + '-' + photon_build_num + '.tar.gz') generateCompressedFile(raw_image, outputfile, "w:gz") elif config['artifacttype'] == 'xz': print("Generating the xz artifact ...") outputfile = (img_path + '/photon-' + config['image_type'] + '-' + photon_release_ver + '-' + photon_build_num + '.xz') generateCompressedFile(raw_image, outputfile, "w:xz") elif 'vhd' in config['artifacttype']: relrawpath = os.path.relpath(raw_image, src_root) vhdname = (os.path.dirname(relrawpath) + '/photon-' + config['image_type'] + '-' + photon_release_ver + '-' + photon_build_num + '.vhd') print("Converting raw disk to vhd ...") info_output = Utils.runshellcommand( "docker run -v {}:/mnt:rw anishs/qemu-img info -f raw --output json {}" .format(src_root, '/mnt/' + relrawpath)) mbsize = 1024 * 1024 mbroundedsize = ((int(json.loads(info_output)["virtual-size"])/mbsize + 1) * mbsize) Utils.runshellcommand( "docker run -v {}:/mnt:rw anishs/qemu-img resize -f raw {} {}" .format(src_root, '/mnt/' + relrawpath, mbroundedsize)) Utils.runshellcommand( "docker run -v {}:/mnt:rw anishs/qemu-img convert {} -O " "vpc -o subformat=fixed,force_size {}" .format(src_root, '/mnt/' + relrawpath, '/mnt/' + vhdname)) if config['artifacttype'] == 'vhd.gz': outputfile = (img_path + '/photon-' + config['image_type'] + '-' + photon_release_ver + '-' + photon_build_num + '.vhd.tar.gz') generateCompressedFile(vhdname, outputfile, "w:gz") elif config['artifacttype'] == 'ova': ovagenerator.create_ova_image(raw_image, tools_bin_path, config) elif config['artifacttype'] == 'raw': pass else: raise ValueError("Unknown output format") if not config['keeprawdisk']: os.remove(raw_image) def generateCompressedFile(inputfile, outputfile, formatstring): if formatstring == "w:xz": in_file = open(inputfile, 'rb') in_data = in_file.read() out_file = open(inputfile+".xz", 'wb') out_file.write(xz.compress(in_data)) in_file.close() out_file.close() else: tarout = tarfile.open(outputfile, formatstring) tarout.add(inputfile, arcname=os.path.basename(inputfile)) tarout.close() def generateImage(raw_image_path, additional_rpms_path, tools_bin_path, src_root, config): working_directory = os.path.dirname(raw_image_path) mount_path = os.path.splitext(raw_image_path)[0] build_scripts_path = os.path.dirname(os.path.abspath(__file__)) if os.path.exists(mount_path) and os.path.isdir(mount_path): shutil.rmtree(mount_path) os.mkdir(mount_path) disk_device = (Utils.runshellcommand( "losetup --show -f {}".format(raw_image_path))).rstrip('\n') disk_partitions = Utils.runshellcommand("kpartx -as {}".format(disk_device)) device_name = disk_device.split('/')[2] if not device_name: raise Exception("Could not create loop device and partition") loop_device_path = "/dev/mapper/{}p2".format(device_name) print(loop_device_path) try: (uuidval, partuuidval) = generateUuid(loop_device_path) # Prep the loop device prepLoopDevice(loop_device_path, mount_path) # Clear the root password if not set explicitly from the config file if config['passwordtext'] == 'PASSWORD': Utils.replaceinfile(mount_path + "/etc/shadow", 'root:.*?:', 'root:*:') # Clear machine-id so it gets regenerated on boot open(mount_path + "/etc/machine-id", "w").close() # Write fstab writefstabandgrub(mount_path, uuidval, partuuidval) if additional_rpms_path and os.path.exists(additional_rpms_path): installAdditionalRpms(mount_path, additional_rpms_path) # Perform additional steps defined in installer config customizeImage(config, mount_path) except Exception as e: print(e) finally: cleanupMountPoints(mount_path) Utils.runshellcommand("kpartx -d {}".format(disk_device)) Utils.runshellcommand("losetup -d {}".format(disk_device)) shutil.rmtree(mount_path) createOutputArtifact(raw_image_path, config, src_root, tools_bin_path) if __name__ == '__main__': parser = ArgumentParser() parser.add_argument("-r", "--raw-image-path", dest="raw_image_path") parser.add_argument("-c", "--config-path", dest="config_path") parser.add_argument("-a", "--additional-rpms-path", dest="additional_rpms_path") parser.add_argument("-t", "--tools-bin-path", dest="tools_bin_path") parser.add_argument("-s", "--src-root", dest="src_root") options = parser.parse_args() if config_path: config = Utils.jsonread(options.config_path) else: raise Exception("No config file defined") generateImage( options.raw_image_path, options.working_directory, options.additional_rpms_path, options.tools_bin_path, options.src_root, config )