#!/bin/bash
#
# License: GPLv2

# enable debug output
SSH_EXTRA_ARGS=""
if [ -n "${L3VM_DEBUG}" ]; then
	set -x
    SSH_EXTRA_ARGS="${SSH_EXTRA_ARGS} -vvvv"
    SSH_CMD_TRACE=1
fi

if [ -f /etc/l3vm_maintenance_lock ]; then
    echo "Maintenance of the l3vm server. Not operating"
    echo "My head hurts, please leave."
    exit 0
fi

MYSELF="${0##*/}"
# lets source all the work already done
source /usr/lib/l3vm/l3vm_lib.sh

usage_handler() {
	FUNC="$1"
	shift
	args=" $* "
	help_regex=' (--help|-h|help) '
	if [[ $args =~ $help_regex ]]; then
		# where I'm called from
		#echo "${FUNCNAME[@]}"
		case "${FUNC}" in
			main)
				echo "$MYSELF    < list | ls | clone | destroy | start | stop | suspend | resume | hibernate | address | console | rename | detach | describe | info | log | reserve | disk | register | certificate | help | --help | -h >"
				echo
				echo "   list, ls           print machines names or status"
				echo "   clone              clone template"
				echo "   destroy            destroy cloned machine"
				echo "   start              start machine (cloned or template)"
				echo "   stop               stop machine (cloned or template)"
				echo "   suspend            suspend machine (cloned or template)"
				echo "   resume             resume machine (cloned or template)"
				echo "   hibernate          save current state of VM to disk and stop it (cloned or template)"
				echo "   address            print last DNS name or IP address of machine"
				echo "   rename             rename virtual machine"
				echo "   console            attach serial console of the machine"
				echo "   detach             detach clone machine from template"
				echo "   describe           show or set VM description"
				echo "   info               print detailed information about machine"
				echo "   log                show domain log file"
				echo "   reserve            get or set machine reservation"
				echo "   disk               manipulation with disk devices"
				echo "   register           register machine using preconfigured keys"
				echo "   certificate        create and upload server certificate"
				echo "   help, --help, -h   show this menu";;
			list)
				echo "$MYSELF    < list | ls > < --templates | --clones | --all-clones | --all | --long>"
				echo
				echo "   --templates, -t	print templates names"
				echo "   --clones, -c   	print your machines you have cloned from templates"
				echo "   --all-clones   	print all machines cloned from templates"
				echo "   --all, -a      	print all machines"
				echo "   --long, -l      	print details about machines";;
			start)
				echo "$MYSELF    start < machine > [machine ...]"
				echo
				echo "Starts already defined machine (can be both template or clone)."
				echo
				echo "   --owner, -o      	override the VM ownership checks";;
			stop)
				echo "$MYSELF    stop [-f] [-o] < machine > [machine ...]"
				echo
				echo "Stop running machine (can be both template or clone) by sending ACPI power button event."
				echo "When forced, stop machine immediately."
				echo
				echo "   --force, -f       	forcefully shutdown machine"
				echo "   --owner, -o    	override the VM ownership checks";;
			suspend)
				echo "$MYSELF    suspend [-o] < machine > [machine ...]"
				echo
				echo "Suspend running machine (can be both template or clone)."
				echo
				echo "   --owner, -o    	override the VM ownership checks";;
			resume)
				echo "$MYSELF    resume [-o] < machine > [machine ...]"
				echo
				echo "Resume running machine (can be both template or clone)."
				echo
				echo "   --owner, -o    	override the VM ownership checks";;
			hibernate)
				echo "$MYSELF    hibernate [-o] < machine > [machine ...]"
				echo
				echo "Save state of running machine to disk and stop running (can be both template or clone)."
				echo
				echo "   --owner, -o    	override the VM ownership checks";;
			clone)
				echo "$MYSELF    clone [-D] [-f] [-o] < template > [ new_machine_name ] [ description ]"
				echo
				echo "Clones template and start it. If parameter \`-D' is passed, new machine won't be using"
				echo "LVM snapshot but real LVM volume and copying takes some time."
				echo
				echo "If cloned VM is not a official template you can enforce clonning using -f (--force) option."
				echo "LVM is unable to create snapshot from snapshot - so you cannot create clones from your clone."
				echo "For such cases you have to use -D to copy them."
				echo
				echo "   template               virtual machine template to be used as original"
				echo "   new_machine_name	you can specify name of the machine here. Name must start with user's login and dash (for example \"jcejka-my-vm\")."
				echo "   descripton      	optional VM description (only if new_machine_name is present)"
				echo
				echo "If new_machine_name ommited, template name will be used with -cloneX suffix."
				echo
				echo "   --derive, -D      	make a deep copy of disks instead of creating snapshots"
				echo "   --empty, -e            create new standalone disk (not a snapshot) and leave it empty."
				echo "                          This is intended as preparation of new VM for fresh installation."
				echo "                          Size of new disk will be the same as the one in template."
				echo "                          VM will not be started after clone and clone hooks will be skipped."
				echo "   --force, -f       	force to clone a machine that's not a official clone"
				echo "   --owner, -o    	override the VM ownership checks so new_machine_name can have any name";;
			destroy)
				echo "$MYSELF    destroy [-f] [-o]  < machine > [machine ...]"
				echo
				echo "Undefines machine and remove it's virtual disk from LVM"
				echo
				echo "   --force, -f    	do not ask for logical volume remove"
				echo "   --owner, -o    	override the VM ownership checks";;
			console)
				echo "$MYSELF    console [-o] < machine >"
				echo
				echo "Attach to virtual serial port so it can be used as console."
				echo
				echo "   --owner, -o    	override the VM ownership checks";;
			rename)
				echo "$MYSELF    rename [-o] < old_name > < new_name >"
				echo
				echo "Rename virtual machine from old_name to new_name."
				echo "It also renames LVM volumes used for this machine that contain old_name in their names."
				echo
				echo "   --owner, -o    	override the VM ownership checks";;
			address)
				echo "$MYSELF    address < machine >"
				echo
				echo "Return last DNS name or IP address of specified machine."
				echo
				echo "   --short, -s    	write only the address";;
			detach)
				echo "$MYSELF    detach [-o] < vm_name >"
				echo
				echo "Detach VM clone from it's template."
				echo "Convert all disks attached to virtual machine from LVM snapshots to real LVM volumes with the same content."
				echo
				echo "   --owner, -o    	override the VM ownership checks";;
			describe)
				echo "$MYSELF    describe [-o] [-e] < vm_name > [new_description]"
				echo
				echo "Get or set VM description."
				echo "Function returns current description or set new one if \"new_description\" argument is set."
				echo
				echo "   --edit,  -e    	Open description in editor instead of setting \"new_description\"."
				echo "   --owner, -o    	override the VM ownership checks";;
			info)
				echo "$MYSELF    info < vm_name > [machine ...]"
				echo
				echo "Print detailed information about virtual machine(s).";;
			log)
				echo "$MYSELF    log [ -F ] < vm_name >"
				echo
				echo "Display VM log file."
				echo
				echo "   --follow,  -F    	output appended data as the file grows (as in tail -F)";;
			reserve)
				echo "$MYSELF    reserve [-o] < vm_name > [reservation date]"
				echo
				echo "Get or set date until which the VM will be reserved."
				echo "If [reservation date] is set in format acceptable by command"
				echo "       date --date=\"reservation date\""
				echo "new date will be set. Otherwise the current value will be returned."
				echo
				echo "Examples:"
				echo "    reserve ${USER:-root}-sles15-sp4 20200711"
				echo "    reserve ${USER:-root}-sles15-sp4 \"2 Months\""
				echo
				echo "   --owner, -o    	override the VM ownership checks";;
			disk)
				echo "$MYSELF    disk [ command ] < options >"
				echo
				echo "Manage disks for virtual machines."
				echo
				echo "Disks can be allocated in following pools:"

				if [ -n "${L3VM_DATA_VG}" ]; then
					echo "  system disks: $L3VM_SYSTEM_VG"
					echo "  data disks: $L3VM_DATA_VG"
				else
					echo "  pool: $L3VM_SYSTEM_VG"
				fi
				echo ""
				echo "   attach [ options ] < vm > < disk >"
				echo "                                Attach disk to virtual machine."
				echo "                                It will use first unused device (vda, vdb,...)"
				echo "          < disk >              Disk device path or pool/name format"
				echo "                                (like /dev/$(get_default_data_pool)/my-disk01/ or $(get_default_data_pool)/my-disk01)"
				echo "          --owner, -o           override the ownership checks"
				echo "   create [ options ] < size > < name >"
				echo "                                Create new disk."
				echo "          < size >              Specify disk size, for example \"127M\" or \"10G\""
				echo "          < name >              name must start with owner's login and dash, optionaly with specified pool"
				echo "                                (for example \"jcejka-raid01\" or \"$(get_default_data_pool)/jcejka-raid01\")."
				echo "          --owner, -o           override the ownership checks"
				echo "          --pool, -p < pool >   Another way to select disk pool (default is $(get_default_data_pool))"
				echo "   delete [ options ] < disk >"
				echo "                                Delete disk device. Disk must not be attached to any VM."
				echo "          < disk >              Disk device path or pool/name format"
				echo "          --owner, -o           override the ownership checks"
				echo "   detach [ options ] < vm > < device | disk >"
				echo "                                Detach disk device from VM (but don't delete it)"
				echo "          < device | disk >     Specify disk to detach either as device in VM (vda, sda, ...),"
				echo "                                using device path (/dev/$(get_default_data_pool)/my-disk01/)"
				echo "                                or by pool/name format ($(get_default_data_pool)/my-disk01/)"
				echo "          --owner, -o           override the ownership checks"
				echo "   list"
				echo "                                List disks owner by current user."
				echo "          --all, -a             Print all disks";;
			register)
				echo "$MYSELF    register [-o] < vm_name > < template >"
				echo
				echo "Clear current registration of machine < vm_name >"
				echo "and register it again using predefined keys for < template >."
				echo "Old registration is not de-registered but only removed"
				echo "to prevent issues with shared registrations cloned machines."
				echo
				echo "   --owner, -o    	override the VM ownership checks"
				;;
			certificate)
				echo "$MYSELF    certificate [-o] < vm_name > "
				echo
				echo "Generate server key and certificate for < vm_name >."
				echo "Upload them to VM together with CA certificate."
				echo
				echo "   --owner, -o    	override the VM ownership checks"
				;;
		esac
		echo
		exit 0

	fi
}


# Read command line options and save the values to OPT_... variables
# $1 shortopts allowed short options
# $2 longopts allowed long options
# return nonzero on error and NON_OPTS variable will be filled with the non-option arguments
parse_opts() {

	SHORT_OPTS="$1"
	LONG_OPTS="$2"
	shift
	shift
	TEMP="$(getopt -o "$SHORT_OPTS" --long "$LONG_OPTS" -n "$PROG_NAME" -- "$@")"
	RET=$?

	eval set -- "$TEMP"

	# clear variables
	unset OPT_ALL OPT_CLONES OPT_ALL_CLONES OPT_TEMPLATES OPT_FORCE OPT_VERBOSE OPT_DERIVE OPT_OWNER OPT_EDIT OPT_LONG OPT_FOLLOW OPT_EMPTY OPT_POOL OPT_SHORT
	export OPT_ALL OPT_CLONES OPT_ALL_CLONES OPT_TEMPLATES OPT_FORCE OPT_VERBOSE OPT_DERIVE OPT_OWNER OPT_EDIT OPT_LONG OPT_FOLLOW OPT_EMPTY OPT_POOL OPT_SHORT

	while [ "$1" ]; do
		case "$1" in
			"-a"|"--all")
				OPT_ALL=1
				;;
			"-c"|"--clones")
				OPT_CLONES=1
				;;
			"--all-clones")
				OPT_ALL_CLONES=1
				;;
			"-f"|"--force")
				OPT_FORCE=1
				;;
			"-t"|"--templates")
				OPT_TEMPLATES=1
				;;
			"-v"|"--verbose")
				# try to increase verbosity
				OPT_VERBOSE=$((${OPT_VERBOSE:-0} + 1))
				set -x
				;;
			"-D"|"--derive")
				OPT_DERIVE=1
				;;
			"-o"|"--owner")
				OPT_OWNER=1
				;;
			"-p"|"--pool")
				shift
				OPT_POOL="$1"
				;;
			"-e"|"--edit"|"--empty")
				OPT_EDIT=1
				OPT_EMPTY=1
				;;
			"-l"|"--long")
				OPT_LONG=1
				;;
			"-s"|"--short")
				OPT_SHORT=1
				;;
			"-F"|"--follow")
				OPT_FOLLOW=1
				;;
			--)
				shift
				break ;;
			*)
				echo "Unknown parameter '$1' found. Exiting..."
				exit 1
		esac
		shift
	done

	# save non-option parameters after '--'
	NON_OPTS=$@

	if [ "${RET}" != "0" ]; then
		error "Invalid arguments. Exiting..."
		exit ${RET:-1}
	fi

	return 0
}


# list all machines belonging to current user.
# list everything in ~/.l3vm and all VM's
# with name starting with ${USER}-.
# Merge them and make the list items unique.
list_my_clones() {
	MY_CLONES=( $( { [ -f ~/.l3vm ] && cat ~/.l3vm ; list_machines | grep "^${USER}-" ; } | sort -u ) )
}

list() {

	if [ "${OPT_ALL}" = "1" ]; then
		list_machines > /dev/null
		SET=( "${MACHINES[@]}" )
	elif [ "${OPT_ALL_CLONES}" = "1" ]; then
		list_clones > /dev/null
		SET=( "${CLONES[@]}" )
	elif [ "${OPT_TEMPLATES}" = "1" ]; then
		list_templates > /dev/null
		SET=( "${L3VM_TEMPLATES[@]}" )
	else
		list_my_clones > /dev/null
		SET=( "${MY_CLONES[@]}" )
	fi

	if [ "${#SET[@]}" -eq 0 ]; then
		echo 'No machines.'
		exit 0
	fi
	vm_stat
}

RET=0
case "$1" in
	--help|-h|help)
		usage_handler main "$@";;
	"")
		usage_handler main "help";;
	initdb)
		# internal fce, do not advertise it
		if ! [ -f "${L3VM_DB}" ]; then
			lifespan_init_db
		else
			lifespan_update_db
		fi
		list_clones > /dev/null
		init_value=$(date --date="${L3VM_LIFESPAN_EXTEND}" +%s)
		for VM in  "${CLONES[@]}"; do
			reservation=$(lifespan_get "${VM}")
			if [ -z "${reservation}" ]; then
				lifespan_set "${VM}" "${init_value}"
			fi
		done
		;;
	ls|list)
		usage_handler list "$@"
		shift;
		parse_opts "actvl" "all,all-clones,templates,clones,verbose,long" "$@"
		list ${NON_OPTS}
		RET=$?;;
	start)
		usage_handler start "$@"
		shift;
		parse_opts "ov" "owner,verbose" "$@"
		start ${NON_OPTS}
		RET=$?;;
	stop)
		usage_handler stop "$@"
		shift;
		parse_opts "fov" "force,owner,verbose" "$@"
		stop ${NON_OPTS}
		RET=$?;;
	clone)
		usage_handler clone "$@"
		shift;
		parse_opts "fDeov" "force,derive,empty,owner,verbose" "$@"
		clone ${NON_OPTS}
		RET=$?;;
	destroy)
		usage_handler destroy "$@"
		shift;
		parse_opts "fov" "force,owner,verbose" "$@"
		destroy ${NON_OPTS}
		RET=$?;;
	console)
		usage_handler console "$@"
		shift;
		parse_opts "ov" "owner,verbose" "$@"
		get_console "${NON_OPTS}"
		RET=$?;;
	suspend)
		usage_handler suspend "$@"
		shift;
		parse_opts "ov" "owner,verbose" "$@"
		suspend_ ${NON_OPTS}
		RET=$?;;
	resume)
		usage_handler resume "$@"
		shift;
		parse_opts "ov" "owner,verbose" "$@"
		resume ${NON_OPTS}
		RET=$?;;
	hibernate)
		usage_handler hibernate "$@"
		shift;
		parse_opts "ov" "owner,verbose" "$@"
		hibernate ${NON_OPTS}
		RET=$?;;
	rename)
		usage_handler rename "$@"
		shift;
		parse_opts "ov" "owner,verbose" "$@"
		rename ${NON_OPTS}
		RET=$?;;
	address)
		usage_handler address "$@"
		shift;
		parse_opts "sv" "short,verbose" "$@"
		get_address ${NON_OPTS}
		RET=$?;;
	detach)
		usage_handler detach "$@"
		shift
		parse_opts "ov" "owner,verbose" "$@"
		detach_clone ${NON_OPTS}
		RET=$?;;
	describe)
		usage_handler describe "$@"
		shift
		parse_opts "eov" "edit,owner,verbose" "$@"
		describe ${NON_OPTS}
		RET=$?;;
	info)
		usage_handler info "$@"
		shift
		parse_opts "v" "verbose" "$@"
		machine_info ${NON_OPTS}
		RET=$?;;
	log)
		usage_handler log "$@"
		shift
		parse_opts "Fv" "follow,verbose" "$@"
		print_logfile ${NON_OPTS}
		RET=$?;;
	reserve)
		usage_handler reserve "$@"
		shift
		parse_opts "ov" "owner,verbose" "$@"
		reserve ${NON_OPTS}
		RET=$?;;
	disk)
		usage_handler disk "$@"
		shift

		# save disk command
		cmd=$1
		shift

		parse_opts "aop:v" "all,owner,pool:,verbose" "$@"
		case $cmd in
			attach)
				disk_attach ${NON_OPTS}
				RET=$?
				;;
			create)
				disk_create ${NON_OPTS}
				RET=$?
				;;
			delete)
				disk_delete ${NON_OPTS}
				RET=$?
				;;
			detach)
				disk_detach ${NON_OPTS}
				RET=$?
				;;
			list)
				disk_list ${NON_OPTS}
				RET=$?
				;;
			*)
				error "Unknown disk subcommand" "$command"
				usage_handler disk "--help"
				RET=1
				;;
		esac
		;;
	register)
		usage_handler register "$@"
		shift
		parse_opts "ov" "owner,verbose" "$@"
		register_machine ${NON_OPTS}
		RET=$?;;
	certificate)
		usage_handler certificate "$@"
		shift
		parse_opts "o" "verbose" "$@"
		certificate ${NON_OPTS}
		RET=$?;;
	*)
		error "Unknown command" "$1"
		RET=1
		;;
esac

exit $RET
