support/cloud-image-builder/customize_cloud_image.py
039cb7bb
 #!/usr/bin/python3
8ff5c437
 
 import os
 import re
 import shutil
 import tarfile
 import fileinput
039cb7bb
 from argparse import ArgumentParser
64ead533
 import json
039cb7bb
 from utils import Utils
8ff5c437
 
 def create_ova_image(raw_image_name, tools_path, build_scripts_path, config):
     output_path = os.path.dirname(os.path.realpath(raw_image_name))
     utils = Utils()
     # Remove older artifacts
64ead533
     files = os.listdir(output_path)
8ff5c437
     for file in files:
e7b64224
         if file.endswith(".vmdk"):
8ff5c437
             os.remove(os.path.join(output_path, file))
 
     vmx_path = output_path + '/photon-ova.vmx'
039cb7bb
     utils.replaceandsaveasnewfile(build_scripts_path + '/vmx-template',
                                   vmx_path, 'VMDK_IMAGE',
                                   output_path + '/photon-ova.vmdk')
8ff5c437
     vixdiskutil_path = tools_path + 'vixdiskutil'
     vmdk_path = output_path + '/photon-ova.vmdk'
     ovf_path = output_path + '/photon-ova.ovf'
     mf_path = output_path + '/photon-ova.mf'
3b85c978
     ovfinfo_path = build_scripts_path + '/ovfinfo.txt'
039cb7bb
     vmdk_capacity = (int(config['size']['root']) +
                      int(config['size']['swap'])) * 1024
     utils.runshellcommand(
         "{} -convert {} -cap {} {}".format(vixdiskutil_path,
                                            raw_image_name,
                                            vmdk_capacity,
                                            vmdk_path))
     utils.runshellcommand(
         "{} -wmeta toolsVersion 2147483647 {}".format(vixdiskutil_path, vmdk_path))
8ff5c437
 
     utils.runshellcommand("ovftool {} {}".format(vmx_path, ovf_path))
     utils.replaceinfile(ovf_path, 'otherGuest', 'other3xLinux64Guest')
 
     #Add product info
3b85c978
     if os.path.exists(ovfinfo_path):
         with open(ovfinfo_path) as f:
             lines = f.readlines()
             for line in fileinput.input(ovf_path, inplace=True):
40e72bb6
                 if line.strip() == '</VirtualHardwareSection>':
3b85c978
                     for ovfinfoline in lines:
039cb7bb
                         print(ovfinfoline)
40e72bb6
                 else:
039cb7bb
                     print(line)
8ff5c437
 
     if os.path.exists(mf_path):
         os.remove(mf_path)
 
     cwd = os.getcwd()
     os.chdir(output_path)
     out = utils.runshellcommand("openssl sha1 photon-ova-disk1.vmdk photon-ova.ovf")
     with open(mf_path, "w") as source:
         source.write(out)
     rawsplit = os.path.splitext(raw_image_name)
     ova_name = rawsplit[0] + '.ova'
 
039cb7bb
     ovatar = tarfile.open(ova_name, "w", format=tarfile.USTAR_FORMAT)
8ff5c437
     for name in ["photon-ova.ovf", "photon-ova.mf", "photon-ova-disk1.vmdk"]:
         ovatar.add(name, arcname=os.path.basename(name))
     ovatar.close()
64ead533
     os.remove(vmx_path)
8ff5c437
     os.remove(mf_path)
 
     if 'additionalhwversion' in config:
         for addlversion in config['additionalhwversion']:
e7b64224
             new_ovf_path = output_path + "/photon-ova-hw{}.ovf".format(addlversion)
             mf_path = output_path + "/photon-ova-hw{}.mf".format(addlversion)
039cb7bb
             utils.replaceandsaveasnewfile(
                 ovf_path, new_ovf_path, "vmx-.*<", "vmx-{}<".format(addlversion))
             out = utils.runshellcommand("openssl sha1 photon-ova-disk1.vmdk "
                                         "photon-ova-hw{}.ovf".format(addlversion))
8ff5c437
             with open(mf_path, "w") as source:
                 source.write(out)
             temp_name_list = os.path.basename(ova_name).split('-')
             temp_name_list = temp_name_list[:2] + ["hw{}".format(addlversion)] + temp_name_list[2:]
             new_ova_name = '-'.join(temp_name_list)
e7b64224
             new_ova_path = output_path + '/' + new_ova_name
039cb7bb
             ovatar = tarfile.open(new_ova_path, "w", format=tarfile.USTAR_FORMAT)
8ff5c437
             for name in [new_ovf_path, mf_path, "photon-ova-disk1.vmdk"]:
                 ovatar.add(name, arcname=os.path.basename(name))
             ovatar.close()
 
             os.remove(new_ovf_path)
             os.remove(mf_path)
64ead533
     os.chdir(cwd)
8ff5c437
     os.remove(ovf_path)
     os.remove(vmdk_path)
64ead533
     files = os.listdir(output_path)
e7b64224
     for file in files:
         if file.endswith(".vmdk"):
64ead533
             os.remove(os.path.join(output_path, file))
8ff5c437
 
 
 if __name__ == '__main__':
     usage = "Usage: %prog [options]"
039cb7bb
     parser = ArgumentParser(usage)
 
     parser.add_argument("-r", "--raw-image-path", dest="raw_image_path")
     parser.add_argument("-c", "--vmdk-config-path", dest="vmdk_config_path")
     parser.add_argument("-w", "--working-directory", dest="working_directory")
     parser.add_argument("-m", "--mount-path", dest="mount_path")
     parser.add_argument("-a", "--additional-rpms-path", dest="additional_rpms_path")
     parser.add_argument("-i", "--image-name", dest="image_name")
     parser.add_argument("-t", "--tools-bin-path", dest="tools_bin_path")
     parser.add_argument("-b", "--build-scripts-path", dest="build_scripts_path")
     parser.add_argument("-s", "--src-root", dest="src_root")
 
     options = parser.parse_args()
8ff5c437
     utils = Utils()
     config = utils.jsonread(options.vmdk_config_path)
039cb7bb
     print(options)
8ff5c437
 
039cb7bb
     disk_device = (utils.runshellcommand(
         "losetup --show -f {}".format(options.raw_image_path))).rstrip('\n')
8ff5c437
     disk_partitions = utils.runshellcommand("kpartx -as {}".format(disk_device))
     device_name = disk_device.split('/')[2]
 
     if not os.path.exists(options.mount_path):
         os.mkdir(options.mount_path)
039cb7bb
     loop_device_path = "/dev/mapper/{}p2".format(device_name)
8ff5c437
 
     try:
039cb7bb
         print("Generating PARTUUID for the loop device ...")
         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 == '':
8ff5c437
             raise RuntimeError("Cannot generate partuuid")
 
         # Mount the loop device
039cb7bb
         print("Mounting the loop device for customization ...")
         utils.runshellcommand(
             "mount -t ext4 {} {}".format(loop_device_path, options.mount_path))
8ff5c437
         shutil.rmtree(options.mount_path + "/installer", ignore_errors=True)
         shutil.rmtree(options.mount_path + "/LOGS", ignore_errors=True)
         # Clear the root password if not set explicitly from the config file
039cb7bb
         if config['password']['text'] != 'PASSWORD':
             utils.replaceinfile(options.mount_path + "/etc/shadow",
                                 'root:.*?:', 'root:*:')
8ff5c437
         # Clear machine-id so it gets regenerated on boot
         open(options.mount_path + "/etc/machine-id", "w").close()
         os.remove(options.mount_path + "/etc/fstab")
 
         f = open(options.mount_path + "/etc/fstab", "w")
039cb7bb
         if uuidval != '':
d60ce58d
             f.write("UUID={}    /    ext4    defaults 1 1\n".format(uuidval))
         else:
             f.write("PARTUUID={}    /    ext4    defaults 1 1\n".format(partuuidval))
8ff5c437
         f.close()
039cb7bb
         utils.replaceinfile(options.mount_path + "/boot/grub/grub.cfg",
                             "rootpartition=PARTUUID=.*$",
                             "rootpartition=PARTUUID={}".format(partuuidval))
8ff5c437
 
         if os.path.exists(options.additional_rpms_path):
039cb7bb
             print("Installing additional rpms")
8ff5c437
             os.mkdir(options.mount_path + "/additional_rpms")
             os.mkdir(options.mount_path + "/var/run")
039cb7bb
             utils.copyallfiles(options.additional_rpms_path,
                                options.mount_path + "/additional_rpms")
             utils.runshellcommand(
                 "chroot {} /bin/bash -c 'rpm -i /additional_rpms/*'".format(options.mount_path))
8ff5c437
             shutil.rmtree(options.mount_path + "/additional_rpms", ignore_errors=True)
039cb7bb
             shutil.rmtree(options.additional_rpms_path, ignore_errors=True)
8ff5c437
 
         utils.runshellcommand("mount -o bind /proc {}".format(options.mount_path + "/proc"))
10d18a45
         utils.runshellcommand("mount -o bind /dev {}".format(options.mount_path + "/dev"))
         utils.runshellcommand("mount -o bind /dev/pts {}".format(options.mount_path + "/dev/pts"))
8ff5c437
         utils.runshellcommand("mount -o bind /sys {}".format(options.mount_path + "/sys"))
 
         if 'additionalfiles' in config:
039cb7bb
             print("  Copying additional files into the raw image ...")
8ff5c437
             for filetuples in config['additionalfiles']:
039cb7bb
                 for src, dest in filetuples.items():
                     if (os.path.isdir(options.build_scripts_path + '/' +
                                       options.image_name + '/' + src)):
                         shutil.copytree(options.build_scripts_path + '/' +
                                         options.image_name + '/' + src,
                                         options.mount_path + dest, True)
9b9c0eac
                     else:
039cb7bb
                         shutil.copyfile(options.build_scripts_path + '/' +
                                         options.image_name + '/' + src,
                                         options.mount_path + dest)
8ff5c437
 
 
         if 'postinstallscripts' in config:
039cb7bb
             print("  Running post install scripts ...")
8ff5c437
             if not os.path.exists(options.mount_path + "/tempscripts"):
                 os.mkdir(options.mount_path + "/tempscripts")
             for script in config['postinstallscripts']:
039cb7bb
                 shutil.copy(options.build_scripts_path + '/' +
                             options.image_name + '/' + script,
                             options.mount_path + "/tempscripts")
8ff5c437
             for script in os.listdir(options.mount_path + "/tempscripts"):
039cb7bb
                 print("     ...running script {}".format(script))
                 utils.runshellcommand(
                     "chroot {} /bin/bash -c '/tempscripts/{}'".format(options.mount_path, script))
8ff5c437
             shutil.rmtree(options.mount_path + "/tempscripts", ignore_errors=True)
 
9b9c0eac
     except Exception as e:
039cb7bb
         print(e)
9b9c0eac
 
     finally:
8ff5c437
         utils.runshellcommand("umount -l {}".format(options.mount_path + "/sys"))
10d18a45
         utils.runshellcommand("umount -l {}".format(options.mount_path + "/dev/pts"))
         utils.runshellcommand("umount -l {}".format(options.mount_path + "/dev"))
8ff5c437
         utils.runshellcommand("umount -l {}".format(options.mount_path + "/proc"))
9b9c0eac
 
         utils.runshellcommand("sync")
8ff5c437
         utils.runshellcommand("umount -l {}".format(options.mount_path))
 
c061ff83
         mount_out = utils.runshellcommand("mount")
039cb7bb
         print("List of mounted devices:")
         print(mount_out)
c061ff83
 
8ff5c437
         utils.runshellcommand("kpartx -d {}".format(disk_device))
         utils.runshellcommand("losetup -d {}".format(disk_device))
 
         shutil.rmtree(options.mount_path)
 
         photon_release_ver = os.environ['PHOTON_RELEASE_VER']
         photon_build_num = os.environ['PHOTON_BUILD_NUM']
         raw_image = options.raw_image_path
         new_name = ""
         img_path = os.path.dirname(os.path.realpath(raw_image))
         # Rename gce image to disk.raw
         if options.image_name == "gce":
039cb7bb
             print("Renaming the raw file to disk.raw ...")
8ff5c437
             new_name = img_path + '/disk.raw'
 
         else:
039cb7bb
             new_name = (img_path + '/photon-' + options.image_name +
                         '-' + photon_release_ver + '-' +
                         photon_build_num + '.raw')
8ff5c437
 
         shutil.move(raw_image, new_name)
         raw_image = new_name
 
         if config['artifacttype'] == 'tgz':
039cb7bb
             print("Generating the tar.gz artifact ...")
             tarname = (img_path + '/photon-' + options.image_name +
                        '-' + photon_release_ver + '-' +
                        photon_build_num + '.tar.gz')
 
c061ff83
             tgzout = tarfile.open(tarname, "w:gz")
8ff5c437
             tgzout.add(raw_image, arcname=os.path.basename(raw_image))
             tgzout.close()
9b9c0eac
         elif config['artifacttype'] == 'xz':
039cb7bb
             print("Generating the xz artifact ...")
9b9c0eac
             utils.runshellcommand("xz -z -k {}".format(raw_image))
039cb7bb
 #            tarname = img_path + '/photon-' + options.image_name +
 #            '-' + photon_release_ver + '-' + photon_build_num +
 #            '.xz'
9b9c0eac
 #            tgzout = tarfile.open(tarname, "w:xz")
 #            tgzout.add(raw_image, arcname=os.path.basename(raw_image))
 #            tgzout.close()
8ff5c437
         elif config['artifacttype'] == 'vhd':
94577239
             relrawpath = os.path.relpath(raw_image, options.src_root)
039cb7bb
             vhdname = (os.path.dirname(relrawpath) + '/photon-' +
                        options.image_name + '-' + 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(options.src_root, '/mnt/' + relrawpath))
64ead533
             mbsize = 1024 * 1024
b47102eb
             mbroundedsize = ((int(json.loads(info_output)["virtual-size"])/mbsize + 1) * mbsize)
039cb7bb
             utils.runshellcommand(
                 "docker run -v {}:/mnt:rw anishs/qemu-img resize -f raw {} {}"
                 .format(options.src_root, '/mnt/' + relrawpath, mbroundedsize))
             utils.runshellcommand(
                 "docker run -v {}:/mnt:rw anishs/qemu-img convert {} -O "
                 "vpc -o subformat=fixed,force_size {}"
                 .format(options.src_root, '/mnt/' + relrawpath, '/mnt/' + vhdname))
8ff5c437
         elif config['artifacttype'] == 'ova':
039cb7bb
             create_ova_image(raw_image, options.tools_bin_path,
                              options.build_scripts_path + '/' + options.image_name, config)
8ff5c437
             if 'customartifacts' in config:
                 if 'postinstallscripts' in config['customartifacts']:
                     custom_path = img_path + '/photon-custom'
                     if not os.path.exists(custom_path):
                         os.mkdir(custom_path)
                     index = 1
                     for script in config['customartifacts']['postinstallscripts']:
039cb7bb
                         print("Creating custom ova {}...".format(index))
8ff5c437
                         if index > 1:
039cb7bb
                             raw_image_custom = (img_path + "/photon-custom-{}".format(index) +
                                                 photon_release_ver + '-' +
                                                 photon_build_num + '.raw')
8ff5c437
                         else:
039cb7bb
                             raw_image_custom = (img_path + "/photon-custom-" +
                                                 photon_release_ver + '-' +
                                                 photon_build_num + '.raw')
8ff5c437
                         shutil.move(raw_image, raw_image_custom)
039cb7bb
                         disk_device = (
                             utils.runshellcommand(
                                 "losetup --show -f {}".format(raw_image_custom))).rstrip('\n')
                         disk_partitions = utils.runshellcommand(
                             "kpartx -as {}".format(disk_device))
8ff5c437
                         device_name = disk_device.split('/')[2]
039cb7bb
                         loop_device_path = "/dev/mapper/{}p2".format(device_name)
8ff5c437
 
039cb7bb
                         print("Mounting the loop device for ova customization ...")
                         utils.runshellcommand(
                             "mount -t ext4 {} {}".format(loop_device_path, custom_path))
8ff5c437
                         if not os.path.exists(custom_path + "/tempscripts"):
                             os.mkdir(custom_path + "/tempscripts")
039cb7bb
                         shutil.copy(options.build_scripts_path + '/' + options.image_name +
                                     '/' + script, custom_path + "/tempscripts")
                         print("Running custom ova script {}".format(script))
                         utils.runshellcommand("chroot {} /bin/bash -c '/tempscripts/{}'"
                                               .format(custom_path, script))
8ff5c437
                         shutil.rmtree(custom_path + "/tempscripts", ignore_errors=True)
                         utils.runshellcommand("umount -l {}".format(custom_path))
c061ff83
 
                         mount_out = utils.runshellcommand("mount")
039cb7bb
                         print("List of mounted devices:")
                         print(mount_out)
c061ff83
 
8ff5c437
                         utils.runshellcommand("kpartx -d {}".format(disk_device))
                         utils.runshellcommand("losetup -d {}".format(disk_device))
039cb7bb
                         create_ova_image(raw_image_custom, options.tools_bin_path,
                                          options.build_scripts_path + '/' + options.image_name,
                                          config)
8ff5c437
                         raw_image = raw_image_custom
                         index = index + 1
 
64ead533
                     shutil.rmtree(custom_path)
8ff5c437
 
         else:
             raise ValueError("Unknown output format")
 
         if config['keeprawdisk'] == 'false':
49cc4c52
             os.remove(raw_image)