#!/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
)