Proxmox automated conversion to template

This post is not finished, more text will be provided soon.

The following script takes a virtual machine with the purpose to act as a base machine for new machines and creates a Proxmox template from it by:

  • Creating a snapshot
  • Deleting prevously created template if it exists
  • Cloning the machine
  • Connecting to the machine using temporary ssh key
  • Using previously published oem-config-prepare script on the cloned machine (with the added removal of the temporary ssh key)
  • Converting the cloned virtual machine to a template

Most configurable options are located in the configurations section

#!/usr/bin/env bash
#
# Date: 2017-05-31
# Version: 1.0
# Author: Stellan Nordenbro <stellan@nordenbro.com>
#
# The MIT License (MIT)
#
# Copyright (c) 2017 Stellan Nordenbro
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

# Quick-check before we allow bad things to happen
if [ -z "${BASH_VERSINFO}" ]; then
  echo "ERROR: You must execute this script with BASH"
  exit 255
fi

# Go to the correct folder
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR

# The usage text
usage() {
  echo "Usage: `basename $0` [-hdqS]" 1>&2
  echo
  echo "Optional parameters:" 1>&2
  echo " -h             Displays this message" 1>&2
  echo " -d             Dry-run: no commands will be executed" 1>&2
  echo " -q             Quiet: output to stdout will be hidden" 1>&2
  echo " -S             Silent: output to both stdout and stderr will be hidden" 1>&2
  echo
  exit 1;
}

# Parse commandline
ALLARGS="$@"
set -- $(getopt hdqS -- "$@")
while true;
do
    case "$1" in
                (-h) usage;;
                (-d) DRY_RUN=yes;;
                (-q) QUIET=yes;;
                (-S) SILENT=yes;;
                (--) ;;
                (-*) echo "Error: unrecognized option $1" 1>&2; exit 1;;
                (*)  break;;
    esac
    shift
done

# send all stdout to /dev/null
if [ "${QUIET}" = "yes" ] || [ "${SILENT}" = "yes" ]; then
        exec 1> /dev/null
fi

# send all stdout and stderr to /dev/null
if [ "${SILENT}" = "yes" ]; then
        exec 2> /dev/null
fi

##############################################
# FUNCTIONS
#############################################
function GetVMOption() {
	if [ $# -ne 2 ]; then
                echo "ERROR: ${FUNCNAME} expected 2 parameters: Usage ${FUNCNAME} VM_ID OPTION"
                exit 1
	fi

	value=$(qm config $1 | grep $2 | awk '{ print $2}')
	echo "$value"
}

function SetVMOption() {
	if [ $# -ne 3 ]; then
		echo "ERROR: ${FUNCNAME} expected 2 parameters: Usage ${FUNCNAME} VM_ID OPTION VALUE"
		exit 1
	fi

	if [ "${DRY_RUN}" = "yes" ]; then
		echo "   DRY-RUN:   qm set $1 -${2} $3"
	else
		qm set $1 -${2} $3
	fi
}

#############################################
# CONFIGURATION
#############################################
HOSTNAME=$(hostname)
VM_ID=10000
VM_NAME=$(GetVMOption ${VM_ID} name)
SNAPSHOT_NAME="A$(date +"%Y%m%d_%H%M")"
SNAPSHOT_DESCRIPTION="Automated snapshot by template creation script"
TEMPLATE_ID=10001
TEMPLATE_NAME="template-${VM_NAME}"
TEMPLATE_POOL="Templates"
TEMPLATE_STORAGE="shared-templates"
TEMPLATE_DESCRIPTION="Template created $(date +"%Y-%m-%d %T")"
SSH_KEY="~/.ssh/temp_id_rsa"

echo "Creating template of base virtual machines on $HOSTNAME"
echo -e "-------------------------------------------------------nn"

#############################################
# SNAPSHOT
#############################################
echo "* Creating snapshot ${SNAPSHOT_NAME} on virtual machine ${VM_NAME} with id ${VM_ID}..."
if [ "${DRY_RUN}" = "yes" ]; then
	echo "   DRY-RUN:   qm snapshot ${VM_ID} "${SNAPSHOT_NAME}" -description "${SNAPSHOT_DESCRIPTION}" -vmstate 0"
else
	qm snapshot ${VM_ID} "${SNAPSHOT_NAME}" -description "${SNAPSHOT_DESCRIPTION}" -vmstate 0
	snapshot_count=$(qm listsnapshot ${VM_ID} | awk '{print $1}' | grep "${SNAPSHOT_NAME}" | wc -l)
	if [ $snapshot_count != 1 ]; then
		echo "ERROR: Snaphot creation failed on virtual machine ${VM_NAME} with id ${VM_ID}"
		exit 1
	fi
fi
echo -e "* Done creating snapshot ${SNAPSHOT_NAME}.nn"

#############################################
# EXISTING TEMPLATE CHECK
#############################################
echo "* Checking for existing template vms..."
if [ "${DRY_RUN}" = "yes" ]; then
	echo "   DRY-RUN:   qm list | grep -v "VMID" | grep "${TEMPLATE_NAME}" | awk '{print $1}' | grep ${TEMPLATE_ID} | wc -l"
fi
echo -e "* Done checking for existing tempalte vms.nn"

#############################################
# EXISTING TEMPLATE DELETE
#############################################
vm_count=$(qm list | grep -v "VMID" | grep "${TEMPLATE_NAME}" | awk '{print $1}' | grep ${TEMPLATE_ID} | wc -l)
vm_id_count=$(qm list | grep -v "VMID" | awk '{print $1}' | grep ${TEMPLATE_ID} | wc -l)
if [ $vm_id_count != $vm_count ]; then
	echo "ERROR: There is a virtual machine with id ${TEMPLATE_ID} but probably not then name ${TEMPLATE_NAME}, aborting." | tee /dev/stderr
	exit 1
fi

if [ $vm_count = 1 ]; then
	echo "* Deleting old template ${TEMPLATE_NAME} with id ${TEMPLATE_ID} after clearing protection"
	if [ "${DRY_RUN}" = "yes" ]; then
		SetVMOption ${TEMPLATE_ID} protection 0
		echo "   DRY-RUN:   qm destroy ${TEMPLATE_ID}"
	else
		SetVMOption ${TEMPLATE_ID} protection 0
		qm destroy ${TEMPLATE_ID}
		if [ $(qm list | grep -v "VMID" | awk '{print $1}' | grep ${TEMPLATE_ID} | wc -l) = 1 ]; then
			echo "ERROR: Unable to delete VM ID: ${TEMPLATE_ID}, aborting." | tee /dev/stderr
			exit 1
		fi
	fi
	echo -e "* Done deleting existing template.nn"
fi

#############################################
# CLONE VM TO TEMPLATE
#############################################
echo "* Creating template clone ${TEMPLATE_NAME} from snapshot ${SNAPSHOT_NAME}"
if [ "${DRY_RUN}" = "yes" ]; then
	echo "   DRY-RUN:   qm clone ${VM_ID} ${TEMPLATE_ID} -description "${TEMPLATE_DESCRIPTION}" -format qcow2 -full 1 -name "${TEMPLATE_NAME}" -pool "${TEMPLATE_POOL}" -snapname "${SNAPSHOT_NAME}" -storage "${TEMPLATE_STORAGE}""
else
	qm clone ${VM_ID} ${TEMPLATE_ID} -description "${TEMPLATE_DESCRIPTION}" -format qcow2 -full 1 -name "${TEMPLATE_NAME}" -pool "${TEMPLATE_POOL}" -snapname "${SNAPSHOT_NAME}" -storage "${TEMPLATE_STORAGE}"
	sleep 10
	if [ $(qm list | grep -v "VMID" | grep "${TEMPLATE_NAME}" | awk '{print $1}' | grep ${TEMPLATE_ID} | wc -l) != 1 ]; then
		echo "ERROR: Unable to clone VM ID: ${VM_ID} to TEMPLATE ID: ${TEMPLATE_ID}, aborting." | tee /dev/stderr
		exit 1
	fi
fi
echo -e "* Done creating template clone ${TEMPLATE_NAME}.nn"

#############################################
# TEMPLATE PREPARATION
#############################################
echo "* Preparing template ${TEMPLATE_NAME}"
if [ "${DRY_RUN}" = "yes" ]; then
	echo "   DRY-RUN:   qm start ${TEMPLATE_ID}"
	echo "   DRY_RUN:   sleep 30"
	echo "   DRY-RUN:   ip=$(qm agent ${TEMPLATE_ID} network-get-interfaces | grep ip-address | grep -o "192.168.[0-9]*.[0-9]*" | head -n 1)"
	echo "   DRY_RUN:   ssh -4 -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -i ~/.ssh/base_id_rsa root@${ip} "/sbin/oem-config-prepare -f""
else
	qm start ${TEMPLATE_ID}
	sleep 30
	ip=$(qm agent ${TEMPLATE_ID} network-get-interfaces | grep ip-address | grep -o "192.168.[0-9]*.[0-9]*" | head -n 1)
	count=0
	while [[ -z $ip ]] && [[ $count -lt 600 ]]
	do
		count=$((count+1))
		echo -n "."
		sleep 1
		ip=$(qm agent ${TEMPLATE_ID} network-get-interfaces | grep ip-address | grep -o "192.168.[0-9]*.[0-9]*" | head -n 1)
	done

	if [ -z $ip ]; then
		echo "ERROR: The machine does not seem to have an IP, maybe boot failed, aborting." | tee /dev/stderr
		exit 1
	fi

	ssh -4 -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -i ${SSH_KEY} root@${ip} "/sbin/oem-config-prepare -f"
fi
echo -e "* Done preparing template ${TEMPLATE_NAME}.nn"

#############################################
# CONVERT TEMPLATE
#############################################
echo "* Converting virtual machine ${TEMPLATE_NAME} to template"
if [ "${DRY_RUN}" = "yes" ]; then
	echo "   DRY-RUN:   qm wait ${TEMPLATE_ID}"
	echo "   DRY-RUN:   qm template ${TEMPLATE_ID}"
else
	qm wait ${TEMPLATE_ID}
	qm template ${TEMPLATE_ID}
fi
echo -e "* Done converting virtual machine ${TEMPLATE_NAME} to template.nn"

echo "Script execution done."

If anyone else have use for this script then go ahead, no guarantees of course, use it on your own risk…

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s