#!/bin/bash
# cobbler-ubuntu-import

VERBOSITY=0
TEMP_D=""
ISO_DIR="/var/lib/cobbler/isos"
DEFAULT_MIRROR="http://archive.ubuntu.com/ubuntu"

error() { echo "$@" 1>&2; }
errorp() { printf "$@" 1>&2; }
fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
failp() { [ $# -eq 0 ] || errorp "$@"; exit 1; }
debug() { [ $VERBOSITY -gt 0 ] && echo "[DEBUG] $@" 1>&2; }

Usage() {
	cat <<EOF
Usage: ${0##*/} [ options ] [ release-arch [ release-arch [ ... ] ] ]

   import distro and profile for Ubuntu release-arch

   options:
      -D | --delete-iso   delete the mini iso after downloading.  Normally
                          they are kept in $ISO_DIR
      -m | --mirror M     use mirror rather than default for downloading iso
                          default: ${DEFAULT_MIRROR}
      -c | --update-check check saved ISOs against what is published on the
                          mirror.  exits 0 if an update is needed or iso does
                          not exist in $ISO_DIR
      -r | --remove       remove cobbler profile/distro and cached ISO
      -u | --update	  update iso, import, replace outdated distro and profile
      -p | --proxy	  <host[:port> http proxy to use
      -v | --verbose	  increase verbosity
   Note, arch can be i386, x86_64, or amd64.  However,
   the registered distro and profile will use 'x86_64' rather than 'amd64'.
   This is to keep consistent with cobbler naming.

   Example:
    - ${0##*/} natty-i386
EOF
}

bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; }
cleanup() {
	[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}

loop_mount() {
	# Create more loop nodes, if necessary
	local mounts=$(grep -c /dev/loop /proc/mounts) || mounts=0
	local loops=$(ls /dev/loop* | wc -l) || loops=0
	if [ $mounts -ge $loops ]; then
		mknod -m 660 /dev/loop$loops b 7 $loops
		chown root:disk /dev/loop$loops
	fi
	# Do the loop mount
	debug "Creating loopback mount from $1 at $2"
	mount -o loop "$1" "$2"
}

get_iso() {
	# first check release's update pocket for an iso.
	# grab from release pocket if that does not exist.
	if wget -O "$TEMP_D/$ISO" "$U_UPDATE"; then
		debug "Downloaded ISO from updates pocket: $U_UPDATE"
	elif wget -O "$TEMP_D/$ISO" "$U"; then
		debug "Updating from release pocket: $U"
	else
		fail "failed download for $REL-$ARCH"
	fi
	{ $delete || mv "$TEMP_D/$ISO" "$ISO_DIR/$ISO"; } ||
		fail "failed to move iso $ISO_DIR/$ISO"
}

import_iso() {
	# import iso into cobbler as $DISTRO
	local mounted
	local distro="$1"
	if cobbler distro list | grep -qs " $distro"; then
		fail "distro '$distro' already exists. cannot import."
	fi
	{ [ -f "$TEMP_D/$ISO" ] || ln -sf "$ISO_DIR/$ISO" "$TEMP_D/$ISO"; } ||
		fail "failed to create symlink to existing $ISO_DIR/$ISO"
	mounted=0 && loop_mount "$TEMP_D/$ISO" "$TEMP_D/mnt" && mounted=1 &&
		cobbler import --name="$distro" --path="$TEMP_D/mnt" \
			--breed=ubuntu --os-version="$REL" --arch="$ARCH" &&
		umount "${TEMP_D}/mnt" && mounted=0 || {
				[ $mounted -eq 0 ] || umount "$TEMP_D/mnt";
				fail "failed to import $REL-$ARCH";
			}
	echo "imported $distro"
}

reassign_distro() {
	# expected to be called after get_iso() has imported a more up to date iso under
	# a temporary name. this will reassign all profiles associated with $old_distro
	# with $new_distro.  after all profiles have been reassigned, $old_distro is
	# removed.
	local new_distro="$1"
	local old_distro="$2"
	descendants=`cobbler profile find --distro="$old_distro"`
	debug "Reassigning $old_distro to $new_distro"
	if ! cobbler distro list | grep -qs " $old_distro"; then
		fail "distro matching profile '$old_distro' does not exist"
	fi
	debug "Renaming old distro '$old_distro' to 'last-$old_distro'"
	cobbler distro rename --name="$old_distro" --newname="last-$old_distro" ||
		fail "could not rename distro '$old_distro' to 'last-$old_distro'"
	for profile in $descendants ; do
		debug "Assigning profile $profile to distro $new_distro"
		cobbler profile edit --name="$profile" --distro="$new_distro" ||
			fail "could not assign profile '$profile' to distro '$new_distro'"
	done
	debug "Removing distro last-$old_distro"
	cobbler distro remove --name="last-$old_distro" ||
		fail "could not remove stale distro 'last-$old_distro'"
	debug "Renaming distro $new_distro to $old_distro"
	cobbler distro rename --name="$new_distro" --newname="$old_distro" ||
		fail "could not rename distro '$new_distro' to '$old_distro'"
	debug "Cleaning up temporary profile '$new_distro'"
	cobbler profile remove --name="$new_distro" ||
		fail "could not remove temp profile '$new_distro'"
}

function update_available() {
	# check MD5SUM first from updates pocket if it exists, then release pocket.
	# compare netboot mini iso with what is local.
	local md5sum_local
	local md5sum_remote
	[ ! -f "$ISO_DIR/$ISO" ] && return 0
	md5sum_local=$(md5sum "$ISO_DIR/$ISO") && md5sum_local=${md5sum_local%% *} &&
		[ -n "$md5sum_local" ] || fail "failed to checksum $ISO_DIR/$ISO: $md5sum_local"
	debug "local md5sum: $md5sum_local ($ISO_DIR/$ISO)"
	wget -qO "$TEMP_D/MD5SUMS" "$MD5_UPDATE" || wget -qO "$TEMP_D/MD5SUMS" "$MD5" ||
		fail "failed to download MD5SUMS for $REL-$ARCH."
	md5sum_remote=$(awk '$2 == "./netboot/mini.iso" { print $1 }' $TEMP_D/MD5SUMS) &&
		[ -n "$md5sum_remote" ] || fail "failed to parse checksum from $TEMP_D/MD5SUMS: $md5sum_remote"
	debug "remote md5sum: $md5sum_remote"
	[ "$md5sum_local" != "$md5sum_remote" ]
}

short_opts="Dhm:o:p:vcru"
long_opts="delete-iso,help,mirror:,proxy:,remove,update,update-check,remove,verbose"
getopt_out=$(getopt --name "${0##*/}" \
	--options "${short_opts}" --long "${long_opts}" -- "$@") &&
	eval set -- "${getopt_out}" ||
	bad_Usage

delete=false
check_update=false
remove=false
mirror=${DEFAULT_MIRROR}
do_update=false

while [ $# -ne 0 ]; do
	cur=${1}; next=${2};
	case "$cur" in
		-h|--help) Usage ; exit 0;;
		-m|--mirror) mirror=${2}; shift;;
		-D|--delete-iso) delete=true;;
		-p|--proxy) proxy=${2}; shift;;
		-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
		-c|--update-check) check_update=true;;
		-r|--remove) remove=true;;
		-u|--update) do_update=true;;
		--) shift; break;;
	esac
	shift;
done

## check arguments here
[ $# -ne 0 ] || bad_Usage "must provide arguments"

TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
	fail "failed to make tempdir"
trap cleanup EXIT

if [ -n "$proxy" ] ; then
	debug "Using proxy: $proxy"
	export http_proxy="$proxy"
fi

# program starts here
if ! $delete; then
	{ [ -d "$ISO_DIR" ] || mkdir -p "$ISO_DIR"; } || fail "failed to create $ISO_DIR"
fi
mkdir "$TEMP_D/mnt" || fail "failed to make tempdir/mnt"

for tok in "$@"; do
	REL=${tok%-*}; ARCH=${tok#*-}
	[ "$ARCH" = "amd64" ] && 
		{ error "Warning: using x86_64 for arch rather than amd64"; arch="x86_64"; }
	ubuntu_arch=$ARCH; [ "$ARCH" = "x86_64" ] && ubuntu_arch=amd64
	ISO=$REL-$ARCH-mini.iso
	U=$mirror/dists/$REL/main/installer-$ubuntu_arch/current/images/netboot/mini.iso
	MD5=$mirror/dists/$REL/main/installer-$ubuntu_arch/current/images/MD5SUMS
	U_UPDATE=$mirror/dists/$REL-updates/main/installer-$ubuntu_arch/current/images/netboot/mini.iso
	MD5_UPDATE=$mirror/dists/$REL-updates/main/installer-$ubuntu_arch/current/images/MD5SUMS
	if $remove; then
		if [ -f "$ISO_DIR/$ISO" ]; then
			debug "Removing $ISO_DIR/$ISO"
			rm -rf "$ISO_DIR/$ISO" ||
				fail "Could not delete $ISO_DIR/$ISO"
		fi
		if cobbler distro list | grep -qs " $REL-$ARCH"; then
			debug "Removing cobbler distro '$REL-$ARCH'"
			cobbler distro remove --name="$REL-$ARCH" ||
				fail "Could not remove cobbler profile for $REL-$ARCH"
		fi
		exit 0
	fi
	if $check_update; then
		update_available && echo "Update for $REL-$ARCH available" && exit 0
		echo "$ISO_DIR/$ISO up to date." && exit 3
	fi
	if $do_update; then
		update_available || fail "No update available"
		# get iso from either pocket and import it with a tmp name.
		get_iso && import_iso "tmp-$REL-$ARCH"
		reassign_distro "tmp-$REL-$ARCH" "$REL-$ARCH"
		exit 0
	fi
	[[ ! -f "$ISO_DIR/$ISO" ]] && get_iso
	import_iso "$REL-$ARCH" && echo "imported $REL-$ARCH"
done

# vi: ts=4 noexpandtab
