Merge "Add script to customize Ubuntu guest images."

This commit is contained in:
Zuul 2018-07-11 21:33:10 +00:00 committed by Gerrit Code Review
commit baa9f2e1a4
1 changed files with 172 additions and 0 deletions

172
tools/customize_ubuntu_image Executable file
View File

@ -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 "$@"