Add script to customize Ubuntu guest images.
The script is intended to be used from any openstack deployment system
as TripleO, Devstack, etc. to prepare ubuntu guest images before being
uploaded to OpenStack image service.
It has not been possible to virt-customize because of below Ubuntu bugs:
- https://bugs.launchpad.net/ubuntu/+source/libguestfs/+bug/1632405
- https://bugs.launchpad.net/ubuntu/+source/isc-dhcp/+bug/1650740
It has therefore been adopted a more low level strategy performing below
steps:
- mount guest image to a temporary folder
- set up an environment suitable for executing chroot
- execute customize_image function inside chroot environment
- cleanup chroot environment
Packages required by the script:
- libguestfs-tools
Packages installed on customized guest images:
- socat (for advance network feature testing, like multicast)
This change has been created because for testing multicast
socat is required, but it was designed with the idea
of being used to allow further guest images customizations.
Change-Id: Ia60991d9170495e9d41f583765b24d6cdf442639
diff --git a/tools/customize_ubuntu_image b/tools/customize_ubuntu_image
new file mode 100755
index 0000000..9c3fd07
--- /dev/null
+++ b/tools/customize_ubuntu_image
@@ -0,0 +1,172 @@
+#!/bin/bash
+
+# IMPLEMENTATION NOTE: It was not possible to implement this script using
+# virt-customize because of below ubuntu bugs:
+# - https://bugs.launchpad.net/ubuntu/+source/libguestfs/+bug/1632405
+# - https://bugs.launchpad.net/ubuntu/+source/isc-dhcp/+bug/1650740
+#
+# It has therefore been adopted a more low level strategy performing below
+# steps:
+# - mount guest image to a temporary folder
+# - set up an environment suitable for executing chroot
+# - execute customize_image function inside chroot environment
+# - cleanup chroot environment
+
+# Array of packages to be installed of guest image
+INSTALL_GUEST_PACKAGES=(
+ socat # used to replace nc for testing advanced network features like
+ # multicast
+)
+
+# Function to be executed once after chroot on guest image
+# Add more customization steps here
+function customize_image {
+ # dhclient-script requires to read /etc/fstab for setting up network
+ touch /etc/fstab
+ chmod ugo+r /etc/fstab
+
+ # Ubuntu guest image _apt user could require access to below folders
+ local apt_user_folders=( /var/lib/apt/lists/partial )
+ mkdir -p "${apt_user_folders[@]}"
+ chown _apt.root -fR "${apt_user_folders[@]}"
+
+ # Install desired packages to Ubuntu guest image
+ apt-get update -y
+ apt-get install -y "${INSTALL_GUEST_PACKAGES[@]}"
+}
+
+function main {
+ set -eux
+ trap cleanup EXIT
+ "${ENTRY_POINT:-chroot_image}" "$@"
+}
+
+# Chroot to guest image then executes customize_image function inside it
+function chroot_image {
+ local image_file=$1
+ local temp_dir=${TEMP_DIR:-$(make_temp -d)}
+
+ # Mount guest image into a temporary directory
+ local mount_dir=${temp_dir}/mount
+ mkdir -p "${mount_dir}"
+ mount_image "${mount_dir}" "${temp_dir}/pid"
+
+ # Mount system directories
+ bind_dir "/dev" "${mount_dir}/dev"
+ bind_dir "/dev/pts" "${mount_dir}/dev/pts"
+ bind_dir "/proc" "${mount_dir}/proc"
+ bind_dir "/sys" "${mount_dir}/sys"
+
+ # Mount to keep temporary files out of guest image
+ mkdir -p "${temp_dir}/apt" "${temp_dir}/cache" "${temp_dir}/tmp"
+ bind_dir "${temp_dir}/cache" "${mount_dir}/var/cache"
+ bind_dir "${temp_dir}/tmp" "${mount_dir}/tmp"
+ bind_dir "${temp_dir}/tmp" "${mount_dir}/var/tmp"
+ bind_dir "${temp_dir}/apt" "${mount_dir}/var/lib/apt"
+
+ # Replace /etc/resolv.conf symlink to use the same DNS as this host
+ sudo rm -f "${mount_dir}/etc/resolv.conf"
+ sudo cp /etc/resolv.conf "${mount_dir}/etc/resolv.conf"
+
+ # Makesure /etc/fstab exists and it is readable because it is required by
+ # /sbin/dhclient-script
+ sudo touch /etc/fstab
+ sudo chmod 644 /etc/fstab
+
+ # Copy this script to mount dir
+ local script_name=$(basename "$0")
+ local script_file=${mount_dir}/${script_name}
+ sudo cp "$0" "${script_file}"
+ sudo chmod 500 "${script_file}"
+ add_cleanup sudo rm -f "'${script_file}'"
+
+ # Execute customize_image inside chroot environment
+ local command_line=( ${CHROOT_COMMAND:-customize_image} )
+ local entry_point=${command_line[0]}
+ unset command_line[0]
+ sudo -E "ENTRY_POINT=${entry_point}" \
+ chroot "${mount_dir}" "/${script_name}" "${command_line[@]:-}"
+}
+
+# Mounts guest image to $1 directory writing pid to $1 pid file
+# Then registers umount of such directory for final cleanup
+function mount_image {
+ local mount_dir=$1
+ local pid_file=$2
+
+ # export libguest settings
+ export LIBGUESTFS_BACKEND=${LIBGUESTFS_BACKEND:-direct}
+ export LIBGUESTFS_BACKEND_SETTINGS=${LIBGUESTFS_BACKEND_SETTINGS:-force_tcg}
+
+ # Mount guest image
+ sudo -E guestmount -i \
+ --add "${image_file}" \
+ --pid-file "${pid_file}" \
+ "${mount_dir}"
+
+ add_cleanup \
+ 'ENTRY_POINT=umount_image' \
+ "'$0'" "'${mount_dir}'" "'${pid_file}'"
+}
+
+# Unmounts guest image directory
+function umount_image {
+ local mount_dir=$1
+ local pid_file=$2
+ local timeout=10
+
+ # Take PID just before unmounting
+ local pid=$(cat ${pid_file} || true)
+ sudo -E guestunmount "${mount_dir}"
+
+ if [ "${pid:-}" != "" ]; then
+ # Make sure guestmount process is not running before using image
+ # file again
+ local count=${timeout}
+ while sudo kill -0 "${pid}" 2> /dev/null && (( count-- > 0 )); do
+ sleep 1
+ done
+ if [ ${count} == 0 ]; then
+ # It is not safe to use image file at this point
+ echo "Wait for guestmount to exit failed after ${timeout} seconds"
+ fi
+ fi
+}
+
+# Creates a temporary file or directory and register removal for final cleanup
+function make_temp {
+ local temporary=$(mktemp "$@")
+ add_cleanup sudo rm -fR "'${temporary}'"
+ echo "${temporary}"
+}
+
+# Bind directory $1 to directory $2 and register umount for final cleanup
+function bind_dir {
+ local source_dir=$1
+ local target_dir=$2
+ sudo mount --bind "${source_dir}" "${target_dir}"
+ add_cleanup sudo umount "'${target_dir}'"
+}
+
+# Registers a command line to be executed for final cleanup
+function add_cleanup {
+ CLEANUP_FILE=${CLEANUP_FILE:-$(mktemp)}
+
+ echo -e "$*" >> ${CLEANUP_FILE}
+}
+
+# Execute command lines for final cleanup in reversed order
+function cleanup {
+ error=$?
+
+ local cleanup_file=${CLEANUP_FILE:-}
+ if [ -r "${cleanup_file}" ]; then
+ tac "${cleanup_file}" | bash +e -x
+ CLEANUP_FILE=
+ rm -fR "${cleanup_file}"
+ fi
+
+ exit ${error}
+}
+
+main "$@"