Browse code

Refactor cloud images build code

Change-Id: I33d4fe65b44a1479a40cc88ea2fff30e77e2b271
Reviewed-on: http://photon-jenkins.eng.vmware.com:8082/1659
Reviewed-by: Alexey Makhalov <amakhalov@vmware.com>
Tested-by: gerrit-photon <photon-checkins@vmware.com>

suezzelur authored on 2016/11/10 22:45:54
Showing 22 changed files
... ...
@@ -61,9 +61,10 @@ endif
61 61
 TOOLS_BIN := $(SRCROOT)/tools/bin
62 62
 CONTAIN := $(TOOLS_BIN)/contain
63 63
 VIXDISKUTIL := $(TOOLS_BIN)/vixdiskutil
64
+IMGCONVERTER := $(TOOLS_BIN)/imgconverter
64 65
 
65 66
 .PHONY : all iso clean photon-build-machine photon-vagrant-build photon-vagrant-local cloud-image \
66
-check-tools check-docker check-bison check-g++ check-gawk check-createrepo check-vagrant check-packer check-packer-ovf-plugin check-sanity \
67
+check-tools check-docker check-bison check-g++ check-gawk check-createrepo check-kpartx check-vagrant check-packer check-packer-ovf-plugin check-sanity \
67 68
 clean-install clean-chroot build-updated-packages check
68 69
 
69 70
 THREADS?=1
... ...
@@ -450,13 +451,13 @@ photon-vagrant-local: check-packer check-vagrant
450 450
 		echo "Unable to find $(PHOTON_STAGE)/photon-$(PHOTON_RELEASE_VERSION)-$(PHOTON_BUILD_NUMBER).iso ... aborting build"; \
451 451
 	fi
452 452
 
453
-cloud-image: $(PHOTON_STAGE) $(VIXDISKUTIL) iso
453
+cloud-image: check-kpartx $(PHOTON_STAGE) $(VIXDISKUTIL) $(IMGCONVERTER) iso
454 454
 	@echo "Building cloud image $(IMG_NAME)..."
455 455
 	@cd $(PHOTON_CLOUD_IMAGE_BUILDER_DIR)
456 456
 	$(PHOTON_CLOUD_IMAGE_BUILDER) $(PHOTON_CLOUD_IMAGE_BUILDER_DIR) $(IMG_NAME) $(SRCROOT) $(PHOTON_GENERATED_DATA_DIR) $(PHOTON_STAGE)/photon-$(PHOTON_RELEASE_VERSION)-$(PHOTON_BUILD_NUMBER).iso $(ADDITIONAL_RPMS_PATH)
457 457
 
458 458
 
459
-cloud-image-all: $(PHOTON_STAGE) $(VIXDISKUTIL) iso
459
+cloud-image-all: check-kpartx $(PHOTON_STAGE) $(VIXDISKUTIL) $(IMGCONVERTER) iso
460 460
 	@echo "Building cloud images - gce, ami, azure and ova..."
461 461
 	@cd $(PHOTON_CLOUD_IMAGE_BUILDER_DIR)
462 462
 	$(PHOTON_CLOUD_IMAGE_BUILDER) $(PHOTON_CLOUD_IMAGE_BUILDER_DIR) gce $(SRCROOT) $(PHOTON_GENERATED_DATA_DIR) $(PHOTON_STAGE)/photon-$(PHOTON_RELEASE_VERSION)-$(PHOTON_BUILD_NUMBER).iso $(ADDITIONAL_RPMS_PATH)
... ...
@@ -485,6 +486,9 @@ check-gawk:
485 485
 check-createrepo:
486 486
 	@command -v createrepo >/dev/null 2>&1 || { echo "Package createrepo not installed. Aborting." >&2; exit 1; }
487 487
 
488
+check-kpartx:
489
+	@command -v kpartx >/dev/null 2>&1 || { echo "Package kpartx not installed. Aborting." >&2; exit 1; }
490
+
488 491
 check-vagrant: check-packer
489 492
 	@command -v $(VAGRANT) >/dev/null 2>&1 || { echo "Vagrant not installed or wrong path, expecting $(VAGRANT). Aborting" >&2; exit 1; }
490 493
 
... ...
@@ -558,3 +562,6 @@ $(VIXDISKUTIL): $(TOOLS_BIN)
558 558
 	@cd $(SRCROOT)/tools/src/vixDiskUtil && \
559 559
 	make
560 560
 
561
+$(IMGCONVERTER): $(TOOLS_BIN)
562
+	@cd $(SRCROOT)/tools/src/imgconverter && \
563
+	make
... ...
@@ -8,8 +8,11 @@ cd /
8 8
 echo "127.0.0.1 localhost" >> /etc/hosts
9 9
 
10 10
 # Update /etc/resolv.conf
11
-rm /etc/resolv.conf
12
-echo "nameserver 172.31.0.2" >> /etc/resolv.conf
11
+if [ -f /etc/resolv.conf ]
12
+	then
13
+		rm /etc/resolv.conf
14
+fi
15
+echo "nameserver 169.254.169.253" >> /etc/resolv.conf
13 16
 echo "search ec2.internal" >> /etc/resolv.conf
14 17
 
15 18
 
... ...
@@ -60,5 +63,7 @@ sed -i '/.*linux.*vmlinuz/ s/$/ console=ttyS0/' /boot/grub/grub.cfg
60 60
 echo 1 > /proc/sys/kernel/modules_disabled
61 61
 
62 62
 # Remove kernel symbols
63
-rm /boot/system.map*
64
-
63
+if [ -f /boot/system.map* ]
64
+	then
65
+		rm /boot/system.map*
66
+fi
65 67
\ No newline at end of file
... ...
@@ -1,12 +1,18 @@
1 1
 {
2
-	"hostname": "photon-machine",
3
-	"password": 
4
-		{
5
-			"crypted": false,
6
-			"text": "PASSWORD"
7
-		},
8
-	"type": "ami",
9
-    "size": {"root": "8", "swap": "0"},
10
-    "public_key":"<ssh-key-here>"
2
+    "hostname": "photon-machine",
3
+    "password": 
4
+        {
5
+            "crypted": false,
6
+            "text": "PASSWORD"
7
+        },
8
+    "type": "ami",
9
+    "size": {"root": "16", "swap": "0"},
10
+    "public_key":"<ssh-key-here>",
11
+    "postinstallscripts": [ "ami-patch.sh", "../password-expiry.sh" ],
12
+    "additionalfiles": [
13
+                            {"cloud-photon.cfg": "/etc/cloud/cloud.cfg"}
14
+                       ],
15
+    "artifacttype": "tgz",
16
+    "keeprawdisk": "false"
11 17
 }
12 18
 
... ...
@@ -1,12 +1,18 @@
1 1
 {
2
-	"hostname": "photon-machine",
3
-	"password": 
4
-		{
5
-			"crypted": false,
6
-			"text": "PASSWORD"
7
-		},
8
-	"type": "azure",
9
-    "size": {"root": "8", "swap": "0"},
10
-    "public_key":"<ssh-key-here>"
2
+    "hostname": "photon-machine",
3
+    "password": 
4
+        {
5
+            "crypted": false,
6
+            "text": "PASSWORD"
7
+        },
8
+    "type": "azure",
9
+    "size": {"root": "16", "swap": "0"},
10
+    "public_key":"<ssh-key-here>",
11
+    "postinstallscripts": [ "azure-patch.sh", "../password-expiry.sh" ],
12
+    "additionalfiles": [
13
+                            {"cloud-photon.cfg": "/etc/cloud/cloud.cfg"}
14
+                       ],
15
+    "artifacttype": "vhd",
16
+    "keeprawdisk": "false"
11 17
 }
12 18
 
... ...
@@ -18,22 +18,25 @@ GENERATED_DATA_PATH=$4
18 18
 PHOTON_ISO_PATH=$5
19 19
 PHOTON_STAGE_PATH=${PHOTON_ISO_PATH%/*}
20 20
 ADDITIONAL_RPMS_PATH=$6
21
-INSTALLER_PATH=$PHOTON_STAGE_PATH/$IMG_NAME
22
-ISO_MOUNT_FOLDER=$PHOTON_STAGE_PATH/iso_mount
21
+WORKING_DIR=$PHOTON_STAGE_PATH/$IMG_NAME
23 22
 
24 23
 PHOTON_IMG_OUTPUT_PATH=$PHOTON_STAGE_PATH/$IMG_NAME
25 24
 VMDK_CONFIG_FILE=${BUILD_SCRIPTS_PATH}/$IMG_NAME/vmdk_$IMG_NAME.json
26 25
 VMDK_CONFIG_SAFE_FILE=${BUILD_SCRIPTS_PATH}/$IMG_NAME/vmdk_safe_$IMG_NAME.json
27 26
 
28
-mkdir -p $INSTALLER_PATH/installer
29
-cp -R $SRC_ROOT/installer $INSTALLER_PATH/
27
+rm -rf $WORKING_DIR
28
+mkdir -p $WORKING_DIR/installer
29
+cp -R $SRC_ROOT/installer $WORKING_DIR/
30 30
 
31
-cd $INSTALLER_PATH/installer
31
+cd $WORKING_DIR/installer
32 32
 cp $VMDK_CONFIG_FILE $VMDK_CONFIG_SAFE_FILE
33 33
 cp ${BUILD_SCRIPTS_PATH}/mk-setup-vmdk.sh .
34 34
 cp ${BUILD_SCRIPTS_PATH}/mk-clean-vmdk.sh .
35 35
 
36
-
36
+if [[ $IMG_NAME == ova* ]]
37
+  then
38
+    command -v ovftool >/dev/null 2>&1 || { echo "Ovftool not installed. Aborting." >&2; exit 1; }
39
+fi
37 40
 if [[ $IMG_NAME != ova* ]]
38 41
   then
39 42
     cp ${BUILD_SCRIPTS_PATH}/mk-setup-grub.sh .
... ...
@@ -49,105 +52,26 @@ sed -i "s/PASSWORD/$PASSWORD/" $VMDK_CONFIG_SAFE_FILE
49 49
 
50 50
 if [ -n "$ADDITIONAL_RPMS_PATH" ]
51 51
   then
52
-    mkdir $PHOTON_STAGE_PATH/RPMS/additonal
53
-    cp -f $ADDITIONAL_RPMS_PATH/* $PHOTON_STAGE_PATH/RPMS/additonal/
52
+    mkdir $PHOTON_STAGE_PATH/RPMS/additional
53
+    cp -f $ADDITIONAL_RPMS_PATH/* $PHOTON_STAGE_PATH/RPMS/additional/
54 54
 fi
55 55
 
56
-./photonInstaller.py -p $GENERATED_DATA_PATH/build_install_options_$IMG_NAME.json -r $PHOTON_STAGE_PATH/RPMS -v $INSTALLER_PATH/photon-${IMG_NAME} -o $GENERATED_DATA_PATH -f $VMDK_CONFIG_SAFE_FILE
56
+./photonInstaller.py -p $GENERATED_DATA_PATH/build_install_options_$IMG_NAME.json -r $PHOTON_STAGE_PATH/RPMS -v $WORKING_DIR/photon-${IMG_NAME} -o $GENERATED_DATA_PATH -f $VMDK_CONFIG_SAFE_FILE
57 57
 cat $VMDK_CONFIG_SAFE_FILE
58 58
 rm $VMDK_CONFIG_SAFE_FILE
59 59
 
60 60
 cd $BUILD_SCRIPTS_PATH
61 61
 
62
-DISK_DEVICE=`losetup --show -f ${PHOTON_IMG_OUTPUT_PATH}/photon-${IMG_NAME}.raw`
63
-
64
-echo "Mapping device partition to loop device"
65
-kpartx -av $DISK_DEVICE
66
-
67
-DEVICE_NAME=`echo $DISK_DEVICE|cut -c6- `
68
-
69
-echo "DISK_DEVICE=$DISK_DEVICE"
70
-echo "ROOT_PARTITION=/dev/mapper/${DEVICE_NAME}p2"
71
-
72
-rm -rf $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}
73
-mkdir $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}
74
-
75
-UUID_VALUE=$(blkid -s UUID -o value /dev/mapper/${DEVICE_NAME}p2)
76
-PARTUUID_VALUE=$(blkid -s PARTUUID -o value /dev/mapper/${DEVICE_NAME}p2)
77
-if [ -z "$PARTUUID_VALUE" ] ; then
78
-  PARTUUID_VALUE=$(sgdisk -i 2 $DISK_DEVICE | grep "Partition unique GUID" | cut -d ' ' -f 4)
79
-fi
80
-
81
-mkdir -p $ISO_MOUNT_FOLDER
82
-mount -o loop $PHOTON_ISO_PATH $ISO_MOUNT_FOLDER
83
-mount -v -t ext4 /dev/mapper/${DEVICE_NAME}p2 $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}
84
-rm -rf $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/installer
85
-rm -rf $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/LOGS
86
-cp $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/shadow $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/shadow.bak
87
-sed -e "s/^\(root:\)[^:]*:/\1*:/" $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/shadow.bak > $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/shadow
88
-rm -f $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/shadow.bak
89
-rm -f $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/shadow-
90
-rm -f $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/machine-id
91
-touch $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/machine-id
92
-rm -f $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/fstab
93
-echo "UUID=$UUID_VALUE    /    ext4    defaults 1 1" >> $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/fstab
94
-sed -i "s/rootpartition=PARTUUID=$/rootpartition=PARTUUID=$PARTUUID_VALUE/" $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/boot/grub/grub.cfg
95
-
96
-mount -o bind /proc $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/proc
97
-mount -o bind /dev $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/dev
98
-mount -o bind /dev/pts $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/dev/pts
99
-mount -o bind /sys $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/sys
100
-if [ -n "$ADDITIONAL_RPMS_PATH" ]
101
-  then
102
-    mkdir $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/additional_rpms
103
-    mkdir $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/var/run
104
-    cp -f $PHOTON_STAGE_PATH/RPMS/additonal/* $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/additional_rpms/
105
-    chroot $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME} /bin/bash -c "rpm -i /additional_rpms/*"
106
-    rm -rf $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/additional_rpms/
107
-fi
108
-
109
-if [ $IMG_NAME != "ova" ] && [ $IMG_NAME != "ova_uefi" ] && [ $IMG_NAME != "ova_ovs" ]
110
-  then
111
-    cd $BUILD_SCRIPTS_PATH
112
-    if [ $IMG_NAME = "gce" ]
113
-      then
114
-        cp ntpd.service $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/lib/systemd/system/
115
-        cp eth0.service $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/lib/systemd/system/
116
-    fi
117
-    if [ $IMG_NAME != "ova_generic" ]
118
-      then
119
-        if [ -e $IMG_NAME/cloud-photon.cfg ]
120
-          then
121
-            cp -f $IMG_NAME/cloud-photon.cfg $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/cloud/cloud.cfg
122
-        fi
123
-        cp $IMG_NAME/$IMG_NAME-patch.sh $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/
124
-        cp /etc/resolv.conf $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/
125
-        echo "chrooting and running patch inside the chroot"
126
-        chroot $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME} /bin/bash -c "/$IMG_NAME-patch.sh"
127
-        rm -f $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/$IMG_NAME-patch.sh
128
-        # Change the max password days to 99999
129
-        chroot $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME} /bin/bash -c "cat /etc/shadow | cut -d: -f1 | xargs -I {} chage -I -1 -m 0 -M 99999 -E -1 -W 7 {}"
130
-        sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS   99999/' $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/etc/login.defs
131
-    fi
132
-fi
133
-umount $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/sys
134
-umount $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/dev/pts
135
-umount $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/dev
136
-umount $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}/proc
137
-umount $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME}
138
-umount $ISO_MOUNT_FOLDER
139
-rm -rf $ISO_MOUNT_FOLDER
140
-
141
-echo "Deleting device map partition"
142
-kpartx -d $DISK_DEVICE
143
-
144
-rm -rf photon-${IMG_NAME}
145
-
146
-echo "Detaching loop device from raw disk"
147
-losetup -d $DISK_DEVICE
148
-
149
-
150
-cd $IMG_NAME
151
-./mk-$IMG_NAME-image.sh $PHOTON_STAGE_PATH/$IMG_NAME $SRC_ROOT
62
+./customize_cloud_image.py \
63
+ -r ${PHOTON_IMG_OUTPUT_PATH}/photon-${IMG_NAME}.raw \
64
+ -c $VMDK_CONFIG_FILE \
65
+ -w $WORKING_DIR \
66
+ -m $PHOTON_IMG_OUTPUT_PATH/photon-${IMG_NAME} \
67
+ -a $PHOTON_STAGE_PATH/RPMS/additional \
68
+ -i $IMG_NAME \
69
+ -t $SRC_ROOT/tools/bin/ \
70
+ -b $BUILD_SCRIPTS_PATH
71
+
72
+rm -rf $WORKING_DIR/installer
152 73
 
153 74
 exit 0
154 75
new file mode 100755
... ...
@@ -0,0 +1,244 @@
0
+#!/usr/bin/python2
1
+
2
+import os
3
+import re
4
+import shutil
5
+import tarfile
6
+import fileinput
7
+from optparse import OptionParser
8
+from utils import Utils
9
+
10
+def create_ova_image(raw_image_name, tools_path, build_scripts_path, config):
11
+    output_path = os.path.dirname(os.path.realpath(raw_image_name))
12
+    utils = Utils()
13
+    # Remove older artifacts
14
+    files = os.listdir(output_path)    
15
+    for file in files:
16
+        if file.endswith(".vmdk") or file.endswith(".ova"):
17
+            os.remove(os.path.join(output_path, file))
18
+
19
+    vmx_path = output_path + '/photon-ova.vmx'
20
+    utils.replaceandsaveasnewfile(build_scripts_path + '/vmx-template', vmx_path, 'VMDK_IMAGE', output_path + '/photon-ova.vmdk')
21
+    vixdiskutil_path = tools_path + 'vixdiskutil'
22
+    vmdk_path = output_path + '/photon-ova.vmdk'
23
+    ovf_path = output_path + '/photon-ova.ovf'
24
+    mf_path = output_path + '/photon-ova.mf'
25
+    utils.runshellcommand("{} -convert {} -cap 16000 {}".format(vixdiskutil_path, raw_image_name, vmdk_path))
26
+    utils.runshellcommand("{} -wmeta toolsVersion 2147483647 {}".format(vixdiskutil_path, vmdk_path))
27
+
28
+    utils.runshellcommand("ovftool {} {}".format(vmx_path, ovf_path))
29
+    utils.replaceinfile(ovf_path, 'otherGuest', 'other3xLinux64Guest')
30
+
31
+    #Add product info
32
+    for line in fileinput.input(ovf_path, inplace=True):
33
+        if line.strip() == '</VirtualSystem>':
34
+            print ' \t<ProductSection> \n \t\t<Info>Information about the installed software</Info> \n \t\t<Product>Photon</Product> \n \t\t<Vendor>VMware Inc.</Vendor> \n \t\t<Version>1.0</Version> \n \t\t<FullVersion>1.0</FullVersion> \n \t</ProductSection> '
35
+        print line,
36
+
37
+    if os.path.exists(mf_path):
38
+        os.remove(mf_path)
39
+
40
+    cwd = os.getcwd()
41
+    os.chdir(output_path)
42
+    out = utils.runshellcommand("openssl sha1 photon-ova-disk1.vmdk photon-ova.ovf")
43
+    with open(mf_path, "w") as source:
44
+        source.write(out)
45
+    rawsplit = os.path.splitext(raw_image_name)
46
+    ova_name = rawsplit[0] + '.ova'
47
+
48
+    ovatar = tarfile.open(ova_name, "w:gz")
49
+    for name in ["photon-ova.ovf", "photon-ova.mf", "photon-ova-disk1.vmdk"]:
50
+        ovatar.add(name, arcname=os.path.basename(name))
51
+    ovatar.close()
52
+    
53
+    os.remove(vmx_path)    
54
+    os.remove(mf_path)
55
+
56
+    if 'additionalhwversion' in config:
57
+        for addlversion in config['additionalhwversion']:
58
+            new_ovf_path = output_path + "photon-ova-hw{}.ovf".format(addlversion)
59
+            mf_path = output_path + "photon-ova-hw{}.mf".format(addlversion)
60
+            utils.replaceandsaveasnewfile(ovf_path, new_ovf_path, "vmx-.*<", "vmx-{}<".format(addlversion)) 
61
+            out = utils.runshellcommand("openssl sha1 photon-ova-disk1.vmdk {}".format(new_ovf_path))
62
+            with open(mf_path, "w") as source:
63
+                source.write(out)
64
+            temp_name_list = os.path.basename(ova_name).split('-')
65
+            temp_name_list = temp_name_list[:2] + ["hw{}".format(addlversion)] + temp_name_list[2:]
66
+            new_ova_name = '-'.join(temp_name_list)
67
+            new_ova_path = output_path + new_ova_name
68
+            ovatar = tarfile.open(new_ova_path, "w:gz")
69
+            for name in [new_ovf_path, mf_path, "photon-ova-disk1.vmdk"]:
70
+                ovatar.add(name, arcname=os.path.basename(name))
71
+            ovatar.close()
72
+
73
+            os.remove(new_ovf_path)
74
+            os.remove(mf_path)
75
+    os.chdir(cwd)    
76
+    os.remove(ovf_path)
77
+    os.remove(vmdk_path)
78
+
79
+
80
+if __name__ == '__main__':
81
+    usage = "Usage: %prog [options]"
82
+    parser = OptionParser(usage)
83
+
84
+    parser.add_option("-r", "--raw-image-path",  dest="raw_image_path")
85
+    parser.add_option("-c", "--vmdk-config-path", dest="vmdk_config_path")
86
+    parser.add_option("-w",  "--working-directory",  dest="working_directory")
87
+    parser.add_option("-m",  "--mount-path",  dest="mount_path")
88
+    parser.add_option("-a",  "--additional-rpms-path",  dest="additional_rpms_path")
89
+    parser.add_option("-i",  "--image-name",  dest="image_name")
90
+    parser.add_option("-t",  "--tools-bin-path",  dest="tools_bin_path")
91
+    parser.add_option("-b",  "--build-scripts-path",  dest="build_scripts_path")
92
+
93
+    (options,  args) = parser.parse_args()
94
+    utils = Utils()
95
+    config = utils.jsonread(options.vmdk_config_path)
96
+    print options
97
+
98
+    disk_device = (utils.runshellcommand("losetup --show -f {}".format(options.raw_image_path))).rstrip('\n')
99
+    disk_partitions = utils.runshellcommand("kpartx -as {}".format(disk_device))
100
+    device_name = disk_device.split('/')[2]
101
+
102
+    if not os.path.exists(options.mount_path):
103
+        os.mkdir(options.mount_path)
104
+    loop_device_path =  "/dev/mapper/{}p2".format(device_name)
105
+
106
+    try:
107
+        print "Generating PARTUUID for the loop device ..."
108
+        partuuidval = (utils.runshellcommand("blkid -s PARTUUID -o value {}".format(loop_device_path))).rstrip('\n')
109
+        if (partuuidval == ''):
110
+            sgdiskout = utils.runshellcommand("sgdisk -i 2 {} ".format(disk_device))
111
+            partuuidval = (re.findall(r'Partition unique GUID.*', sgdiskout))[0].split(':')[1].strip(' ')
112
+
113
+        if (partuuidval == ''):
114
+            raise RuntimeError("Cannot generate partuuid")
115
+
116
+        # Mount the loop device
117
+        print "Mounting the loop device for customization ..."
118
+        utils.runshellcommand("mount -t ext4 {} {}".format(loop_device_path, options.mount_path))
119
+        shutil.rmtree(options.mount_path + "/installer", ignore_errors=True)
120
+        shutil.rmtree(options.mount_path + "/LOGS", ignore_errors=True)
121
+        # Clear the root password if not set explicitly from the config file
122
+        if (config['password']['text'] != 'PASSWORD'):
123
+            utils.replaceinfile(options.mount_path + "/etc/shadow",'root:.*?:','root:*:')
124
+        # Clear machine-id so it gets regenerated on boot
125
+        open(options.mount_path + "/etc/machine-id", "w").close()
126
+        os.remove(options.mount_path + "/etc/fstab")
127
+
128
+        f = open(options.mount_path + "/etc/fstab", "w")
129
+        f.write("PARTUUID={}    /    ext4    defaults 1 1\n".format(partuuidval))
130
+        f.close()
131
+        utils.replaceinfile(options.mount_path + "/boot/grub/grub.cfg", "rootpartition=PARTUUID=.*$", "rootpartition=PARTUUID={}".format(partuuidval))
132
+
133
+        if os.path.exists(options.additional_rpms_path):
134
+            print "Installing additional rpms"
135
+            os.mkdir(options.mount_path + "/additional_rpms")
136
+            os.mkdir(options.mount_path + "/var/run")
137
+            utils.copyallfiles(additional_rpms_path, options.mount_path + "/additional_rpms")
138
+            utils.runshellcommand("chroot {} /bin/bash -c 'rpm -i /additional_rpms/*'".format(options.mount_path))
139
+            shutil.rmtree(options.mount_path + "/additional_rpms", ignore_errors=True)
140
+            shutil.rmtree(additional_rpms_path, ignore_errors=True)
141
+
142
+        utils.runshellcommand("mount -o bind /proc {}".format(options.mount_path + "/proc"))
143
+        utils.runshellcommand("mount -o bind /dev {}".format(options.mount_path + "/dev"))
144
+        utils.runshellcommand("mount -o bind /dev/pts {}".format(options.mount_path + "/dev/pts"))
145
+        utils.runshellcommand("mount -o bind /sys {}".format(options.mount_path + "/sys"))
146
+
147
+        if 'additionalfiles' in config:
148
+            for filetuples in config['additionalfiles']:
149
+                for src,dest in filetuples.iteritems():
150
+                    shutil.copyfile(options.build_scripts_path + '/' + options.image_name + '/' + src, options.mount_path + '/' + dest)
151
+
152
+
153
+        if 'postinstallscripts' in config:
154
+            print "Running post install scripts ..."
155
+            if not os.path.exists(options.mount_path + "/tempscripts"):
156
+                os.mkdir(options.mount_path + "/tempscripts")
157
+            for script in config['postinstallscripts']:
158
+                shutil.copy(options.build_scripts_path + '/' + options.image_name + '/' + script, options.mount_path + "/tempscripts")
159
+            for script in os.listdir(options.mount_path + "/tempscripts"):
160
+                print "     ...running script {}".format(script)
161
+                utils.runshellcommand("chroot {} /bin/bash -c '/tempscripts/{}'".format(options.mount_path, script))
162
+            shutil.rmtree(options.mount_path + "/tempscripts", ignore_errors=True)
163
+
164
+        utils.runshellcommand("umount -l {}".format(options.mount_path + "/sys"))
165
+        utils.runshellcommand("umount -l {}".format(options.mount_path + "/dev/pts"))
166
+        utils.runshellcommand("umount -l {}".format(options.mount_path + "/dev"))
167
+        utils.runshellcommand("umount -l {}".format(options.mount_path + "/proc"))
168
+        utils.runshellcommand("umount -l {}".format(options.mount_path))
169
+
170
+    finally:
171
+        utils.runshellcommand("kpartx -d {}".format(disk_device))
172
+        utils.runshellcommand("losetup -d {}".format(disk_device))
173
+
174
+        shutil.rmtree(options.mount_path)
175
+
176
+        photon_release_ver = os.environ['PHOTON_RELEASE_VER']
177
+        photon_build_num = os.environ['PHOTON_BUILD_NUM']
178
+        raw_image = options.raw_image_path
179
+        new_name = ""
180
+        img_path = os.path.dirname(os.path.realpath(raw_image))
181
+        # Rename gce image to disk.raw
182
+        if options.image_name == "gce":
183
+            print "Renaming the raw file to disk.raw ..."
184
+            new_name = img_path + '/disk.raw'
185
+
186
+        else:
187
+            new_name = img_path + '/photon-' + options.image_name + '-' + photon_release_ver + '-' + photon_build_num + '.raw'
188
+
189
+        shutil.move(raw_image, new_name)
190
+        raw_image = new_name
191
+
192
+        if config['artifacttype'] == 'tgz':
193
+            print "Generating the tar.gz artifact ..."
194
+            tarname = img_path + '/photon-' + options.image_name + '-' + photon_release_ver + '-' + photon_build_num + '.tar.gz'
195
+            tgzout = tarfile.open(tarname, "w:gz")
196
+            tgzout.add(raw_image, arcname=os.path.basename(raw_image))
197
+            tgzout.close()
198
+        elif config['artifacttype'] == 'vhd':
199
+            imgconverter = tools_bin_path + '/imgconverter'
200
+            vhdname = img_path + '/photon-' + options.image_name + '-' + photon_release_ver + '-' + photon_build_num + '.vhd'
201
+            utils.runshellcommand("{} -i {} -v vhd -o {}".format(imgconverter, raw_image, vhdname))
202
+        elif config['artifacttype'] == 'ova':
203
+            create_ova_image(raw_image, options.tools_bin_path, options.build_scripts_path + '/' + options.image_name, config)
204
+            if 'customartifacts' in config:
205
+                if 'postinstallscripts' in config['customartifacts']:
206
+                    custom_path = img_path + '/photon-custom'
207
+                    if not os.path.exists(custom_path):
208
+                        os.mkdir(custom_path)
209
+                    index = 1
210
+                    for script in config['customartifacts']['postinstallscripts']:
211
+                        print "Creating custom ova {}...".format(index)
212
+                        if index > 1:
213
+                            raw_image_custom = img_path + "/photon-custom-{}".format(index) + photon_release_ver + '-' + photon_build_num + '.raw'
214
+                        else:
215
+                            raw_image_custom = img_path + "/photon-custom-" + photon_release_ver + '-' + photon_build_num + '.raw'
216
+                        shutil.move(raw_image, raw_image_custom)
217
+                        disk_device = (utils.runshellcommand("losetup --show -f {}".format(raw_image_custom))).rstrip('\n')
218
+                        disk_partitions = utils.runshellcommand("kpartx -as {}".format(disk_device))
219
+                        device_name = disk_device.split('/')[2]
220
+                        loop_device_path =  "/dev/mapper/{}p2".format(device_name)
221
+
222
+                        print "Mounting the loop device for ova customization ..."
223
+                        utils.runshellcommand("mount -t ext4 {} {}".format(loop_device_path, custom_path))
224
+                        if not os.path.exists(custom_path + "/tempscripts"):
225
+                            os.mkdir(custom_path + "/tempscripts")
226
+                        shutil.copy(options.build_scripts_path + '/' + options.image_name + '/' + script, custom_path + "/tempscripts")
227
+                        print "Running custom ova script {}".format(script)
228
+                        utils.runshellcommand("chroot {} /bin/bash -c '/tempscripts/{}'".format(custom_path, script))
229
+                        shutil.rmtree(custom_path + "/tempscripts", ignore_errors=True)
230
+                        utils.runshellcommand("umount -l {}".format(custom_path))
231
+                        utils.runshellcommand("kpartx -d {}".format(disk_device))
232
+                        utils.runshellcommand("losetup -d {}".format(disk_device))
233
+                        create_ova_image(raw_image_custom, options.tools_bin_path, options.build_scripts_path + '/' + options.image_name, config)
234
+                        raw_image = raw_image_custom
235
+                        index = index + 1
236
+
237
+                    shutil.rmtree(custom_path)                 
238
+
239
+        else:
240
+            raise ValueError("Unknown output format")
241
+
242
+        if config['keeprawdisk'] == 'false':
243
+            os.remove(raw_image)
0 244
\ No newline at end of file
1 245
deleted file mode 100644
... ...
@@ -1,11 +0,0 @@
1
-[Unit]
2
-Description=Network interface initialization
3
-After=local-fs.target network-online.target network.target
4
-Wants=local-fs.target network-online.target network.target
5
-
6
-[Service]
7
-ExecStart=/usr/sbin/ifconfig eth0 mtu 1460 up
8
-Type=oneshot
9
-
10
-[Install]
11
-WantedBy=multi-user.target
12 1
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+[Unit]
1
+Description=Network interface initialization
2
+After=local-fs.target network-online.target network.target
3
+Wants=local-fs.target network-online.target network.target
4
+
5
+[Service]
6
+ExecStart=/usr/sbin/ifconfig eth0 mtu 1460 up
7
+Type=oneshot
8
+
9
+[Install]
10
+WantedBy=multi-user.target
... ...
@@ -115,6 +115,7 @@ else
115 115
 fi
116 116
 
117 117
 export menuentry_id_option
118
+load_env -f "$BOOT_DIRECTORY"photon.cfg
118 119
 
119 120
 if [ "${prev_saved_entry}" ]; then
120 121
   set saved_entry="${prev_saved_entry}"
... ...
@@ -164,11 +165,11 @@ menuentry 'GNU/Linux' --class gnu-linux --class gnu --class os $menuentry_id_opt
164 164
     else
165 165
       search --no-floppy --fs-uuid --set=root UUID_PLACEHOLDER
166 166
     fi
167
-    echo    'Loading Linux 4.4.8 ...'
168
-    linux   /boot/vmlinuz-4.4.8 root=/dev/sda2 ro console=ttyS0,38400n8 
167
+    echo    'Loading Linux $photon_linux ...'
168
+    linux   "$BOOT_DIRECTORY"\$photon_linux root=UUID_PLACEHOLDER ro console=ttyS0,38400n8 
169 169
 }
170 170
 submenu 'Advanced options for GNU/Linux' $menuentry_id_option 'gnulinux-advanced-UUID_PLACEHOLDER' {
171
-    menuentry 'GNU/Linux, with Linux 4.4.8' --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-4.4.8-advanced-UUID_PLACEHOLDER' {
171
+    menuentry 'GNU/Linux, with Linux $photon_linux' --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-$photon_linux-advanced-UUID_PLACEHOLDER' {
172 172
         load_video
173 173
         set gfxpayload=keep
174 174
         insmod gzio
... ...
@@ -180,10 +181,10 @@ submenu 'Advanced options for GNU/Linux' $menuentry_id_option 'gnulinux-advanced
180 180
         else
181 181
           search --no-floppy --fs-uuid --set=root UUID_PLACEHOLDER
182 182
         fi
183
-        echo    'Loading Linux 4.4.8 ...'
184
-        linux   /boot/vmlinuz-4.4.8 root=/dev/sda2 ro console=ttyS0,38400n8 
183
+        echo    'Loading Linux $photon_linux ...'
184
+        linux   "$BOOT_DIRECTORY"\$photon_linux root=UUID_PLACEHOLDER ro console=ttyS0,38400n8 
185 185
     }
186
-    menuentry 'GNU/Linux, with Linux 4.4.8 (recovery mode)' --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-4.4.8-recovery-UUID_PLACEHOLDER' {
186
+    menuentry 'GNU/Linux, with Linux $photon_linux (recovery mode)' --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-$photon_linux-recovery-UUID_PLACEHOLDER' {
187 187
         load_video
188 188
         set gfxpayload=keep
189 189
         insmod gzio
... ...
@@ -195,8 +196,8 @@ submenu 'Advanced options for GNU/Linux' $menuentry_id_option 'gnulinux-advanced
195 195
         else
196 196
           search --no-floppy --fs-uuid --set=root UUID_PLACEHOLDER
197 197
         fi
198
-        echo    'Loading Linux 4.4.8 ...'
199
-        linux   /boot/vmlinuz-4.4.8 root=/dev/sda2 ro single console=ttyS0,38400n8
198
+        echo    'Loading Linux $photon_linux ...'
199
+        linux   "$BOOT_DIRECTORY"\$photon_linux root=/UUID_PLACEHOLDER ro single console=ttyS0,38400n8
200 200
     }
201 201
 }
202 202
 
203 203
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+[Unit]
1
+Description=Network Time Service
2
+After=network.target nss-lookup.target
3
+
4
+[Service]
5
+Type=forking
6
+PrivateTmp=true
7
+ExecStart=/usr/bin/ntpd -g -u ntp:ntp
8
+Restart=always
9
+
10
+[Install]
11
+WantedBy=multi-user.target
... ...
@@ -1,12 +1,20 @@
1 1
 {
2
-	"hostname": "photon-machine",
3
-	"password": 
4
-		{
5
-			"crypted": false,
6
-			"text": "PASSWORD"
7
-		},
8
-	"type": "gce",
9
-    "size": {"root": "8", "swap": "0"},
10
-    "public_key":"<ssh-key-here>"
2
+    "hostname": "photon-machine",
3
+    "password": 
4
+        {
5
+            "crypted": false,
6
+            "text": "PASSWORD"
7
+        },
8
+    "type": "gce",
9
+    "size": {"root": "16", "swap": "0"},
10
+    "public_key":"<ssh-key-here>",
11
+    "postinstallscripts": [ "gce-patch.sh", "../password-expiry.sh" ],
12
+    "additionalfiles": [
13
+                            {"cloud-photon.cfg": "/etc/cloud/cloud.cfg"},
14
+                            {"ntpd.service": "/usr/lib/systemd/system/ntpd.service"},
15
+                            {"eth0.service": "/usr/lib/systemd/system/eth0.service"}
16
+                       ],
17
+    "artifacttype": "tgz",
18
+    "keeprawdisk": "false"
11 19
 }
12 20
 
... ...
@@ -96,6 +96,7 @@ set default=0
96 96
 set timeout=5
97 97
 set root=(hd0,2)
98 98
 loadfont /boot/grub2/unifont.pf2
99
+load_env -f "$BOOT_DIRECTORY"$photon.cfg
99 100
 
100 101
 insmod gfxterm
101 102
 insmod vbe
... ...
@@ -112,7 +113,7 @@ set theme=/boot/grub2/themes/photon/theme.txt
112 112
 menuentry "Photon" {
113 113
 	insmod ext2
114 114
     insmod part_gpt
115
-	linux /boot/vmlinuz-4.4.8 init=/lib/systemd/systemd root=PARTUUID=UUID_PLACEHOLDER loglevel=3 ro
115
+	linux "$BOOT_DIRECTORY"\$photon_linux init=/lib/systemd/systemd root=PARTUUID=UUID_PLACEHOLDER loglevel=3 ro
116 116
 	initrd /boot/initrd.img-no-kmods
117 117
 }
118 118
 # End /boot/grub2/grub.cfg
... ...
@@ -89,7 +89,7 @@ fi
89 89
 sgdisk -t1:ef02 $DISK_DEVICE >> $LOGFILE
90 90
 
91 91
 echo "Mapping device partition to loop device"
92
-kpartx -av $DISK_DEVICE >> $LOGFILE
92
+kpartx -avs $DISK_DEVICE >> $LOGFILE
93 93
 
94 94
 DEVICE_NAME=`echo $DISK_DEVICE|cut -c6- `
95 95
 
96 96
deleted file mode 100644
... ...
@@ -1,12 +0,0 @@
1
-[Unit]
2
-Description=Network Time Service
3
-After=network.target nss-lookup.target
4
-
5
-[Service]
6
-Type=forking
7
-PrivateTmp=true
8
-ExecStart=/usr/bin/ntpd -g -u ntp:ntp
9
-Restart=always
10
-
11
-[Install]
12
-WantedBy=multi-user.target
13 1
new file mode 100755
... ...
@@ -0,0 +1,3 @@
0
+#!/bin/bash
1
+echo -e "changeme\nchangeme" | passwd root
2
+chage -d 0 root
0 3
\ No newline at end of file
... ...
@@ -1,12 +1,16 @@
1 1
 {
2
-	"hostname": "photon-machine",
3
-	"password": 
4
-		{
5
-			"crypted": false,
6
-			"text": "PASSWORD"
7
-		},
8
-	"type": "minimal",
9
-    "size": {"root": "8", "swap": "0"},
10
-    "public_key":"<ssh-key-here>"
2
+    "hostname": "photon-machine",
3
+    "password": 
4
+        {
5
+            "crypted": false,
6
+            "text": "PASSWORD"
7
+        },
8
+    "type": "minimal",
9
+    "size": { "root": "16", "swap": "0" },
10
+    "public_key":"<ssh-key-here>",
11
+    "customartifacts": { "postinstallscripts": ["ova-custom-patch.sh"] },
12
+    "additionalhwversion": [ "10" ],
13
+    "artifacttype": "ova",
14
+    "keeprawdisk": "false"
11 15
 }
12 16
 
13 17
new file mode 100755
... ...
@@ -0,0 +1,3 @@
0
+#!/bin/bash
1
+echo -e "changeme\nchangeme" | passwd root
2
+chage -d 0 root
0 3
\ No newline at end of file
... ...
@@ -6,7 +6,10 @@
6 6
 			"text": "PASSWORD"
7 7
 		},
8 8
 	"type": "minimal",
9
-    "size": {"root": "8", "swap": "0"},
10
-    "public_key":"<ssh-key-here>"
9
+    "size": {"root": "16", "swap": "0"},
10
+    "public_key":"<ssh-key-here>",
11
+    "customartifacts": { "postinstallscripts": ["ova_generic-custom-patch.sh"] },
12
+    "artifacttype": "ova",
13
+    "keeprawdisk": "false"
11 14
 }
12 15
 
... ...
@@ -6,6 +6,8 @@
6 6
         "text": "ovs"
7 7
     },
8 8
     "type": "ovs",
9
-    "size": {"root": "8", "swap": "0"},
10
-    "public_key":"<ssh-key-here>"
9
+    "size": {"root": "16", "swap": "0"},
10
+    "public_key":"<ssh-key-here>",
11
+    "artifacttype": "ova",
12
+    "keeprawdisk": "false"
11 13
 }
... ...
@@ -6,8 +6,10 @@
6 6
 			"text": "PASSWORD"
7 7
 		},
8 8
 	"type": "uefi",
9
-    "size": {"root": "8", "swap": "0"},
9
+    "size": {"root": "16", "swap": "0"},
10 10
     "boot":"efi",
11
-    "public_key":"<ssh-key-here>"
11
+    "public_key":"<ssh-key-here>",
12
+    "artifacttype": "ova",
13
+    "keeprawdisk": "false"
12 14
 }
13 15
 
14 16
new file mode 100755
... ...
@@ -0,0 +1,4 @@
0
+#!/bin/bash
1
+# Change the max password days to 99999
2
+cat /etc/shadow | cut -d: -f1 | xargs -I {} chage -I -1 -m 0 -M 99999 -E -1 -W 7 {}
3
+sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS   99999/' /etc/login.defs
0 4
new file mode 100644
... ...
@@ -0,0 +1,76 @@
0
+#!/usr/bin/python2
1
+
2
+import os
3
+import ctypes
4
+import ctypes.util
5
+import json
6
+import collections
7
+import subprocess
8
+import fileinput
9
+import re
10
+
11
+class Utils(object):
12
+    def __init__(self):
13
+        self.filesystems = []
14
+        with open('/proc/filesystems') as fs:
15
+            for line in fs:
16
+                self.filesystems.append(line.rstrip('\n').split('\t')[1])
17
+
18
+        self.libcloader = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
19
+
20
+    def mount(self, source, destination, filesystem, flags):
21
+        if not os.access(source, os.R_OK):
22
+            raise Exception("Could not find path " + source)
23
+        if not os.access(destination, os.F_OK):
24
+            os.mkdir(destination)
25
+        if not os.access(destination, os.W_OK):
26
+            raise Exception("Could not write to path " + destination)
27
+        if filesystem not in self.filesystems:
28
+            raise ValueError("Filesystem unknown")
29
+        ret = self.libcloader.mount(ctypes.c_char_p(source),
30
+                                    ctypes.c_char_p(destination),
31
+                                    ctypes.c_char_p(filesystem),
32
+                                    ctypes.c_char_p(flags),
33
+                                    0)
34
+        if ret != 0:
35
+            raise RuntimeError("Cannot mount {} : {}".format(source, os.strerror(ctypes.get_errno())))
36
+
37
+    def umount(self, destination):
38
+        ret = self.libcloader.umount(ctypes.c_char_p(destination))
39
+        if ret != 0:
40
+            raise RuntimeError("Cannot umount {} : {}".format(destination, os.strerror(ctypes.get_errno())))
41
+    
42
+    def jsonread(self, filename):
43
+        json_data = open(filename)
44
+        data = json.load(json_data, object_pairs_hook=collections.OrderedDict)
45
+        json_data.close()
46
+        return data
47
+
48
+    def runshellcommand(self, cmd, ignore_errors=False):
49
+        command=cmd.split()
50
+        p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
51
+        output, err = p.communicate()
52
+        rc = p.returncode
53
+        if not ignore_errors:
54
+            if rc != 0:
55
+                print err
56
+                raise RuntimeError("Cannot run command {}".format(cmd))
57
+        return output
58
+
59
+    def replaceinfile(self, filename, pattern, sub):
60
+        for line in fileinput.input(filename, inplace=True):
61
+            line = re.sub(pattern, sub, line)
62
+            print line,
63
+
64
+    def replaceandsaveasnewfile(self, old_file, new_file, pattern, sub):
65
+        with open(old_file, "r") as old, open(new_file, "w") as new:
66
+            for line in old:
67
+                line = re.sub(pattern, sub, line)
68
+                new.write(line)
69
+
70
+    def copyallfiles(self, src, target):
71
+        files = os.listdir(src)
72
+        for file in files:
73
+            filename = os.path.join(src, file)
74
+            if (os.path.isfile(filename)):
75
+                shutil.copy(filename, target)