Browse Source

style: add editorconfig and run shfmt (#95)

pull/96/head 1.2.12
Giovanni Torres 8 months ago committed by GitHub
parent
commit
87e5d65d67
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 22
      .editorconfig
  2. 2
      README.md
  3. 454
      kvm-install-vm
  4. 6
      tests/check_distributions.bats
  5. 4
      tests/run
  6. 3
      tests/vmdir.bash
  7. 3
      tests/vmname.bash

22
.editorconfig

@ -0,0 +1,22 @@
# EditorConfig is awesome: http://EditorConfig.org
root = true
[*]
charset = utf-8
indent_size = 4
end_of_line = lf
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.{md,rst}]
trim_trailing_whitespace = false
[*.sh,test/run]
shell_variant = bash
space_redirects = true # -sr
switch_case_indent = true # -ci
[*.bats]
shell_variant = bats

2
README.md

@ -213,7 +213,7 @@ Tests are powered by [Bats](https://github.com/bats-core/bats-core).\
Run: Run:
```bash ```bash
./test.sh bash tests/run
``` ```
## 🤝 Contributing ## 🤝 Contributing

454
kvm-install-vm

@ -4,8 +4,7 @@ set -euo pipefail
# Set program name variable - basename without subshell # Set program name variable - basename without subshell
prog=${0##*/} prog=${0##*/}
function usage () function usage() {
{
cat << EOF cat << EOF
NAME NAME
kvm-install-vm - Install virtual guests using cloud-init on a local KVM kvm-install-vm - Install virtual guests using cloud-init on a local KVM
@ -28,11 +27,10 @@ COMMANDS
remove - delete a guest domain remove - delete a guest domain
EOF EOF
exit 0 exit 0
} }
function usage_subcommand () function usage_subcommand() {
{
case "$1" in case "$1" in
create) create)
printf "NAME\n" printf "NAME\n"
@ -72,7 +70,7 @@ function usage_subcommand ()
printf "\n" printf "\n"
printf "DISTRIBUTIONS\n" printf "DISTRIBUTIONS\n"
list_available_vms list_available_vms
printf "\n" printf "\n"
printf "EXAMPLES\n" printf "EXAMPLES\n"
printf " %s create foo\n" "$prog" printf " %s create foo\n" "$prog"
printf " Create VM with the default parameters: CentOS 8, 1 vCPU, 1GB RAM, 10GB\n" printf " Create VM with the default parameters: CentOS 8, 1 vCPU, 1GB RAM, 10GB\n"
@ -145,22 +143,24 @@ function usage_subcommand ()
} }
# Console output colors # Console output colors
red() { echo -e "\e[31m$@\e[0m" ; } red() { echo -e "\e[31m$@\e[0m"; }
green() { echo -e "\e[32m$@\e[0m" ; } green() { echo -e "\e[32m$@\e[0m"; }
yellow() { echo -e "\e[33m$@\e[0m" ; } yellow() { echo -e "\e[33m$@\e[0m"; }
die() { red "ERR: $@" >&2 ; exit 2 ; } die() {
silent() { "$@" > /dev/null 2>&1 ; } red "ERR: $@" >&2
output() { echo -e "- $@" ; } exit 2
outputn() { echo -en "- $@ ... " ; } }
ok() { green "${@:-OK}" ; } silent() { "$@" > /dev/null 2>&1; }
output() { echo -e "- $@"; }
outputn() { echo -en "- $@ ... "; }
ok() { green "${@:-OK}"; }
pushd() { command pushd "$@" >/dev/null ; } pushd() { command pushd "$@" > /dev/null; }
popd() { command popd "$@" >/dev/null ; } popd() { command popd "$@" > /dev/null; }
# Join zero or more strings into a delimited string. # Join zero or more strings into a delimited string.
function join () function join() {
{
local sep="$1" local sep="$1"
if [ $# -eq 0 ]; then if [ $# -eq 0 ]; then
return return
@ -175,8 +175,7 @@ function join ()
# Print an optional name=value[,value,..] parameter. # Print an optional name=value[,value,..] parameter.
# Prints nothing if no values are given. # Prints nothing if no values are given.
function param () function param() {
{
if [ $# -lt 2 ]; then if [ $# -lt 2 ]; then
return # skip empty value return # skip empty value
fi fi
@ -187,8 +186,7 @@ function param ()
} }
# Output a command, one argument per line. # Output a command, one argument per line.
function output_command () function output_command() {
{
local line_cont=$' \\ \n ' local line_cont=$' \\ \n '
local command_lines=$(join "$line_cont" "$@") local command_lines=$(join "$line_cont" "$@")
printf " %s\n" "$command_lines" printf " %s\n" "$command_lines"
@ -196,38 +194,32 @@ function output_command ()
# Command wrapper to output the command to be run in verbose # Command wrapper to output the command to be run in verbose
# mode and redirect stdout and stderr to the vm log file. # mode and redirect stdout and stderr to the vm log file.
function run () function run() {
{
local msg="$1" local msg="$1"
shift shift
if [ "${VERBOSE}" -eq 1 ] if [ "${VERBOSE}" -eq 1 ]; then
then
output "$msg with the following command" output "$msg with the following command"
output_command "$@" output_command "$@"
else else
outputn "$msg" outputn "$msg"
fi fi
( "$@" &>> ${VMNAME}.log && ok ) ("$@" &>> ${VMNAME}.log && ok)
} }
# Detect OS and set wget parameters # Detect OS and set wget parameters
function set_wget () function set_wget() {
{ if [ -f /etc/fedora-release ]; then
if [ -f /etc/fedora-release ]
then
WGET="wget --quiet --show-progress" WGET="wget --quiet --show-progress"
else else
WGET="wget" WGET="wget"
fi fi
} }
function check_vmname_set () function check_vmname_set() {
{
[ -n "${VMNAME}" ] || die "VMNAME not set." [ -n "${VMNAME}" ] || die "VMNAME not set."
} }
function delete_vm () function delete_vm() {
{
# Check if domain exists and set DOMAIN_EXISTS variable. # Check if domain exists and set DOMAIN_EXISTS variable.
domain_exists "${VMNAME}" domain_exists "${VMNAME}"
@ -236,29 +228,27 @@ function delete_vm ()
check_vmname_set check_vmname_set
if [ "${DOMAIN_EXISTS}" -eq 1 ] if [ "${DOMAIN_EXISTS}" -eq 1 ]; then
then
outputn "Destroying ${VMNAME} domain" outputn "Destroying ${VMNAME} domain"
virsh destroy --graceful ${VMNAME} > /dev/null 2>&1 \ virsh destroy --graceful ${VMNAME} > /dev/null 2>&1 &&
&& ok \ ok ||
|| yellow "(Domain is not running.)" yellow "(Domain is not running.)"
outputn "Undefining ${VMNAME} domain" outputn "Undefining ${VMNAME} domain"
virsh undefine --managed-save --snapshots-metadata --nvram ${VMNAME} > /dev/null 2>&1 \ virsh undefine --managed-save --snapshots-metadata --nvram ${VMNAME} > /dev/null 2>&1 &&
&& ok \ ok ||
|| die "Could not undefine domain." die "Could not undefine domain."
else else
output "Domain ${VMNAME} does not exist" output "Domain ${VMNAME} does not exist"
fi fi
[[ -d ${VMDIR}/${VMNAME} ]] && DISKDIR=${VMDIR}/${VMNAME} || DISKDIR=${IMAGEDIR}/${VMNAME} [[ -d ${VMDIR}/${VMNAME} ]] && DISKDIR=${VMDIR}/${VMNAME} || DISKDIR=${IMAGEDIR}/${VMNAME}
[ -d $DISKDIR ] \ [ -d $DISKDIR ] &&
&& outputn "Deleting ${VMNAME} files" \ outputn "Deleting ${VMNAME} files" &&
&& rm -rf $DISKDIR \ rm -rf $DISKDIR &&
&& ok ok
if [ "${STORPOOL_EXISTS}" -eq 1 ] if [ "${STORPOOL_EXISTS}" -eq 1 ]; then
then
outputn "Destroying ${VMNAME} storage pool" outputn "Destroying ${VMNAME} storage pool"
virsh pool-destroy ${VMNAME} > /dev/null 2>&1 && ok virsh pool-destroy ${VMNAME} > /dev/null 2>&1 && ok
else else
@ -266,8 +256,7 @@ function delete_vm ()
fi fi
} }
function fetch_images () function fetch_images() {
{
# Create image directory if it doesn't already exist # Create image directory if it doesn't already exist
mkdir -p ${IMAGEDIR} mkdir -p ${IMAGEDIR}
@ -277,9 +266,9 @@ function fetch_images ()
for vm in "${BUILTIN_VMS[@]}"; do for vm in "${BUILTIN_VMS[@]}"; do
IFS=\| read -r vm_distro vm_desc vm_arch vm_url vm_login_user <<< "$vm" IFS=\| read -r vm_distro vm_desc vm_arch vm_url vm_login_user <<< "$vm"
if [[ "$vm_distro" == "$DISTRO" && "$vm_arch" == "$ARCH" ]]; then if [[ "$vm_distro" == "$DISTRO" && "$vm_arch" == "$ARCH" ]]; then
QCOW="${vm_url##*/}" # Grab just the file from the URL to pass into virt-install QCOW="${vm_url##*/}" # Grab just the file from the URL to pass into virt-install
OS_INFO="$vm_distro" # Distro name should come from osinfo OS_INFO="$vm_distro" # Distro name should come from osinfo
IMAGE_URL="${vm_url%/*}" # Grab everything but the filename and the slash IMAGE_URL="${vm_url%/*}" # Grab everything but the filename and the slash
DISK_FORMAT="qcow2" DISK_FORMAT="qcow2"
LOGIN_USER="$vm_login_user" LOGIN_USER="$vm_login_user"
found="true" found="true"
@ -293,11 +282,9 @@ function fetch_images ()
IMAGE=${IMAGEDIR}/${QCOW} IMAGE=${IMAGEDIR}/${QCOW}
if [ ! -f ${IMAGEDIR}/${QCOW} ] if [ ! -f ${IMAGEDIR}/${QCOW} ]; then
then
set_wget set_wget
if [ -f ${IMAGEDIR}/${QCOW}.part ] if [ -f ${IMAGEDIR}/${QCOW}.part ]; then
then
CONTINUE="--continue" CONTINUE="--continue"
output "Partial cloud image found. Resuming download" output "Partial cloud image found. Resuming download"
else else
@ -308,7 +295,7 @@ function fetch_images ()
${CONTINUE} \ ${CONTINUE} \
--directory-prefix ${IMAGEDIR} \ --directory-prefix ${IMAGEDIR} \
--output-document=${IMAGEDIR}/${QCOW}.part \ --output-document=${IMAGEDIR}/${QCOW}.part \
${IMAGE_URL}/${QCOW} || \ ${IMAGE_URL}/${QCOW} ||
die "Could not download image." die "Could not download image."
mv ${IMAGEDIR}/${QCOW}.part ${IMAGEDIR}/${QCOW} mv ${IMAGEDIR}/${QCOW}.part ${IMAGEDIR}/${QCOW}
@ -316,8 +303,7 @@ function fetch_images ()
} }
function check_ssh_key () function check_ssh_key() {
{
local key local key
if [ -z "${PUBKEY}" ]; then if [ -z "${PUBKEY}" ]; then
# Try to find a suitable key file. # Try to find a suitable key file.
@ -329,47 +315,42 @@ function check_ssh_key ()
done done
fi fi
if [ ! -f "${PUBKEY}" ] if [ ! -f "${PUBKEY}" ]; then
then
# Check for existence of a pubkey, or else exit with message # Check for existence of a pubkey, or else exit with message
die "Please generate an SSH keypair using 'ssh-keygen -t rsa' or \ die "Please generate an SSH keypair using 'ssh-keygen -t rsa' or \
specify one with the "-k" flag." specify one with the "-k" flag."
else else
# Place contents of $PUBKEY into $KEY # Place contents of $PUBKEY into $KEY
KEY=$(<${PUBKEY}) KEY=$(< ${PUBKEY})
fi fi
} }
function check_os_variant () function check_os_variant() {
{
if [[ ${OS_INFO} != auto ]]; then if [[ ${OS_INFO} != auto ]]; then
osinfo-query os short-id=${OS_INFO} >/dev/null \ osinfo-query os short-id=${OS_INFO} > /dev/null ||
|| die "Unknown OS variant '${OS_INFO}'. Please update your osinfo-db. "\ die "Unknown OS variant '${OS_INFO}'. Please update your osinfo-db. " \
"See https://libosinfo.org/download for more information." "See https://libosinfo.org/download for more information."
fi fi
} }
function domain_exists () function domain_exists() {
{ virsh dominfo "${1}" > /dev/null 2>&1 &&
virsh dominfo "${1}" > /dev/null 2>&1 \ DOMAIN_EXISTS=1 ||
&& DOMAIN_EXISTS=1 \ DOMAIN_EXISTS=0
|| DOMAIN_EXISTS=0
} }
function storpool_exists () function storpool_exists() {
{ virsh pool-info "${1}" > /dev/null 2>&1 &&
virsh pool-info "${1}" > /dev/null 2>&1 \ STORPOOL_EXISTS=1 ||
&& STORPOOL_EXISTS=1 \ STORPOOL_EXISTS=0
|| STORPOOL_EXISTS=0
} }
function set_sudo_group () function set_sudo_group() {
{
case "${DISTRO}" in case "${DISTRO}" in
almalinux*|centos*|fedora*|rocky*|*-atomic|amazon*|opensuse* ) almalinux* | centos* | fedora* | rocky* | *-atomic | amazon* | opensuse*)
SUDOGROUP="wheel" SUDOGROUP="wheel"
;; ;;
ubuntu*|debian* ) ubuntu* | debian*)
SUDOGROUP="sudo" SUDOGROUP="sudo"
;; ;;
*) *)
@ -378,46 +359,44 @@ function set_sudo_group ()
esac esac
} }
function check_delete_known_host () function check_delete_known_host() {
{
output "Checking for ${IP} in known_hosts file" output "Checking for ${IP} in known_hosts file"
grep -q ${IP} ${HOME}/.ssh/known_hosts \ grep -q ${IP} ${HOME}/.ssh/known_hosts &&
&& outputn "Found entry for ${IP}. Removing" \ outputn "Found entry for ${IP}. Removing" &&
&& (sed --in-place "/^${IP}/d" ~/.ssh/known_hosts && ok ) \ (sed --in-place "/^${IP}/d" ~/.ssh/known_hosts && ok) ||
|| output "No entries found for ${IP}" output "No entries found for ${IP}"
} }
function set_boot_flag() { function set_boot_flag() {
local share_dir="" local share_dir=""
if command -v rpm >/dev/null 2>&1 && rpm -q edk2-ovmf >/dev/null 2>&1; then if command -v rpm > /dev/null 2>&1 && rpm -q edk2-ovmf > /dev/null 2>&1; then
share_dir="/usr/share/edk2/ovmf" share_dir="/usr/share/edk2/ovmf"
elif command -v dpkg >/dev/null 2>&1 && dpkg -s edk2-ovmf >/dev/null 2>&1; then elif command -v dpkg > /dev/null 2>&1 && dpkg -s edk2-ovmf > /dev/null 2>&1; then
share_dir="/usr/share/OVMF" share_dir="/usr/share/OVMF"
else else
BOOTFLAG="" BOOTFLAG=""
return return
fi fi
local machine local machine
case "$ARCH" in case "$ARCH" in
x86_64) machine="--machine q35" ;; x86_64) machine="--machine q35" ;;
aarch64) machine="--machine virt" ;; aarch64) machine="--machine virt" ;;
*) machine="" ;; *) machine="" ;;
esac esac
local suffix="" local suffix=""
if (( SECUREBOOT )); then if ((SECUREBOOT)); then
suffix=".secboot" suffix=".secboot"
fi fi
local code_fd="${share_dir}/OVMF_CODE${suffix}.fd" local code_fd="${share_dir}/OVMF_CODE${suffix}.fd"
local vars_fd="${share_dir}/OVMF_VARS${suffix}.fd" local vars_fd="${share_dir}/OVMF_VARS${suffix}.fd"
BOOTFLAG="--boot uefi,loader=${code_fd},loader.readonly=yes,loader.type=pflash,nvram_template=$vars_fd,nvram=/var/tmp/$(basename "$vars_fd"),loader.secure=$( (( SECUREBOOT )) && echo yes || echo no ) --features smm=on $machine" BOOTFLAG="--boot uefi,loader=${code_fd},loader.readonly=yes,loader.type=pflash,nvram_template=$vars_fd,nvram=/var/tmp/$(basename "$vars_fd"),loader.secure=$( ((SECUREBOOT)) && echo yes || echo no) --features smm=on $machine"
} }
function create_vm () function create_vm() {
{
# Create image directory if it doesn't already exist # Create image directory if it doesn't already exist
mkdir -p ${VMDIR} mkdir -p ${VMDIR}
@ -468,8 +447,7 @@ ssh_authorized_keys:
timezone: ${TIMEZONE} timezone: ${TIMEZONE}
_EOF_ _EOF_
if [ ! -z "${SCRIPTNAME+x}" ] if [ ! -z "${SCRIPTNAME+x}" ]; then
then
SCRIPT=$(< $SCRIPTNAME) SCRIPT=$(< $SCRIPTNAME)
cat >> $USER_DATA << _EOF_ cat >> $USER_DATA << _EOF_
@ -480,36 +458,38 @@ ${SCRIPT}
--==BOUNDARY==-- --==BOUNDARY==--
_EOF_ _EOF_
else else
cat >> $USER_DATA << _EOF_ cat >> $USER_DATA << _EOF_
--==BOUNDARY==-- --==BOUNDARY==--
_EOF_ _EOF_
fi fi
{ echo "instance-id: ${VMNAME}"; echo "local-hostname: ${VMNAME}"; } > $META_DATA {
echo "instance-id: ${VMNAME}"
echo "local-hostname: ${VMNAME}"
} > $META_DATA
outputn "Copying cloud image ($(basename ${IMAGE}))" outputn "Copying cloud image ($(basename ${IMAGE}))"
DISK=${VMNAME}.qcow2 DISK=${VMNAME}.qcow2
qemu-img create -q -f qcow2 -F qcow2 -b $IMAGE $DISK && ok qemu-img create -q -f qcow2 -F qcow2 -b $IMAGE $DISK && ok
if $RESIZE_DISK if $RESIZE_DISK; then
then
outputn "Resizing the disk to $DISK_SIZE" outputn "Resizing the disk to $DISK_SIZE"
# Workaround to prevent virt-resize from renumbering partitions and breaking grub # Workaround to prevent virt-resize from renumbering partitions and breaking grub
# See https://bugzilla.redhat.com/show_bug.cgi?id=1472039 # See https://bugzilla.redhat.com/show_bug.cgi?id=1472039
# Ubuntu will automatically grow the partition to the new size on its first boot # Ubuntu will automatically grow the partition to the new size on its first boot
case "$DISTRO" in case "$DISTRO" in
ubuntu*|amazon2) ubuntu* | amazon2)
qemu-img resize $DISK $DISK_SIZE &>> ${VMNAME}.log \ qemu-img resize $DISK $DISK_SIZE &>> ${VMNAME}.log &&
&& ok \ ok ||
|| die "Could not resize disk." die "Could not resize disk."
;; ;;
*) *)
qemu-img create -f qcow2 \ qemu-img create -f qcow2 \
-o preallocation=metadata $DISK.new $DISK_SIZE &>> ${VMNAME}.log \ -o preallocation=metadata $DISK.new $DISK_SIZE &>> ${VMNAME}.log &&
&& virt-resize --quiet --expand /dev/sda1 $DISK $DISK.new &>> ${VMNAME}.log \ virt-resize --quiet --expand /dev/sda1 $DISK $DISK.new &>> ${VMNAME}.log &&
&& (mv $DISK.new $DISK && ok) \ (mv $DISK.new $DISK && ok) ||
|| die "Could not resize disk." die "Could not resize disk."
;; ;;
esac esac
fi fi
@ -518,8 +498,8 @@ _EOF_
virsh pool-create-as \ virsh pool-create-as \
--name=${VMNAME} \ --name=${VMNAME} \
--type=dir \ --type=dir \
--target=${VMDIR}/${VMNAME} \ --target=${VMDIR}/${VMNAME} ||
|| die "Could not create storage pool. VM may already exist. Try removing first." die "Could not create storage pool. VM may already exist. Try removing first."
# Add custom MAC Address if specified # Add custom MAC Address if specified
NETWORK_PARAMS="$(join ',' \ NETWORK_PARAMS="$(join ',' \
@ -536,8 +516,7 @@ _EOF_
${DISK_EXTRA})" ${DISK_EXTRA})"
# Omit the --graphics option to auto-detect. # Omit the --graphics option to auto-detect.
if [ "${GRAPHICS}" = 'auto' ] if [ "${GRAPHICS}" = 'auto' ]; then
then
GRAPHICS_PARAMS="" GRAPHICS_PARAMS=""
else else
GRAPHICS_PARAMS="$(join ',' \ GRAPHICS_PARAMS="$(join ',' \
@ -568,19 +547,18 @@ _EOF_
--noautoconsole \ --noautoconsole \
${GRAPHICS_OPTION} \ ${GRAPHICS_OPTION} \
${BOOTFLAG} \ ${BOOTFLAG} \
${VIRT_INSTALL_EXTRA} \ ${VIRT_INSTALL_EXTRA} ||
|| die "Could not create domain with virt-install." die "Could not create domain with virt-install."
virsh dominfo ${VMNAME} &>> ${VMNAME}.log virsh dominfo ${VMNAME} &>> ${VMNAME}.log
# Enable autostart if true # Enable autostart if true
if $AUTOSTART if $AUTOSTART; then
then
outputn "Enabling autostart" outputn "Enabling autostart"
virsh autostart \ virsh autostart \
--domain ${VMNAME} > /dev/null 2>&1 \ --domain ${VMNAME} > /dev/null 2>&1 &&
&& ok \ ok ||
|| die "Could not enable autostart." die "Could not enable autostart."
fi fi
# Remove the unnecessary cloud init files # Remove the unnecessary cloud init files
@ -592,15 +570,15 @@ _EOF_
status_file="/var/lib/libvirt/dnsmasq/${BRIDGE}.status" status_file="/var/lib/libvirt/dnsmasq/${BRIDGE}.status"
IP="" IP=""
timeout=60 # seconds timeout=60 # seconds
if [[ -f "$status_file" ]]; then if [[ -f "$status_file" ]]; then
outputn "Waiting for domain to get an IP address " outputn "Waiting for domain to get an IP address "
for (( i=0; i<timeout; i++ )); do for ((i = 0; i < timeout; i++)); do
IP=$( IP=$(
{ grep -B1 -m1 "\"mac-address\": \"$MAC\"" "$status_file" || true; } | { grep -B1 -m1 "\"mac-address\": \"$MAC\"" "$status_file" || true; } |
awk -F'"' '/ip-address/ {print $4; exit}' awk -F'"' '/ip-address/ {print $4; exit}'
) )
if [[ -n "$IP" ]]; then if [[ -n "$IP" ]]; then
@ -626,8 +604,7 @@ _EOF_
output " ssh ${LOGIN_USER}@${VMNAME}" output " ssh ${LOGIN_USER}@${VMNAME}"
CONSOLE=$(virsh domdisplay ${VMNAME}) CONSOLE=$(virsh domdisplay ${VMNAME})
# Workaround because VNC port number shown by virsh domdisplay is offset from 5900 # Workaround because VNC port number shown by virsh domdisplay is offset from 5900
if [ "${GRAPHICS}" = 'vnc' ] if [ "${GRAPHICS}" = 'vnc' ]; then
then
CONSOLE_NO_PORT=$(echo $CONSOLE | cut -d ':' -f 1,2 -) CONSOLE_NO_PORT=$(echo $CONSOLE | cut -d ':' -f 1,2 -)
CONSOLE_PORT=$(expr 5900 + $(echo $CONSOLE | cut -d ':' -f 3 -)) CONSOLE_PORT=$(expr 5900 + $(echo $CONSOLE | cut -d ':' -f 3 -))
output "Console at ${CONSOLE_NO_PORT}:${CONSOLE_PORT}" output "Console at ${CONSOLE_NO_PORT}:${CONSOLE_PORT}"
@ -640,24 +617,21 @@ _EOF_
} }
# Delete VM # Delete VM
function remove () function remove() {
{
# Parse command line arguments # Parse command line arguments
while getopts ":l:L:hv" opt while getopts ":l:L:hv" opt; do
do
case "$opt" in case "$opt" in
l ) IMAGEDIR="${OPTARG}" ;; l) IMAGEDIR="${OPTARG}" ;;
L ) VMDIR="${OPTARG}" ;; L) VMDIR="${OPTARG}" ;;
v ) VERBOSE=1 ;; v) VERBOSE=1 ;;
h ) usage ;; h) usage ;;
* ) die "Unsupported option. Run 'kvm-install-vm help remove'." ;; *) die "Unsupported option. Run 'kvm-install-vm help remove'." ;;
esac esac
done done
shift $((OPTIND - 1)) shift $((OPTIND - 1))
if [ "$#" != 1 ] if [ "$#" != 1 ]; then
then
printf "Please specify a single host to remove.\n" printf "Please specify a single host to remove.\n"
printf "Run 'kvm-install-vm help remove' for usage.\n" printf "Run 'kvm-install-vm help remove' for usage.\n"
exit 1 exit 1
@ -668,32 +642,31 @@ function remove ()
delete_vm delete_vm
} }
function set_defaults () function set_defaults() {
{
# Defaults are set here. Override using command line arguments. # Defaults are set here. Override using command line arguments.
AUTOSTART=false # Automatically start VM at boot time AUTOSTART=false # Automatically start VM at boot time
ARCH=$(uname -m) # Architecture (autodetected) ARCH=$(uname -m) # Architecture (autodetected)
CPUS=1 # Number of virtual CPUs CPUS=1 # Number of virtual CPUs
FEATURE=host-model # Use host cpu features to the guest FEATURE=host-model # Use host cpu features to the guest
MEMORY=1536 # Amount of RAM in MB MEMORY=1536 # Amount of RAM in MB
DISK_SIZE="" # Disk Size in GB DISK_SIZE="" # Disk Size in GB
DNSDOMAIN=example.local # DNS domain DNSDOMAIN=example.local # DNS domain
GRAPHICS=spice # Graphics type or "auto" GRAPHICS=spice # Graphics type or "auto"
RESIZE_DISK=false # Resize disk (boolean) RESIZE_DISK=false # Resize disk (boolean)
IMAGEDIR=${HOME}/virt/images # Directory to store images IMAGEDIR=${HOME}/virt/images # Directory to store images
VMDIR=${HOME}/virt/vms # Directory to store virtual machines VMDIR=${HOME}/virt/vms # Directory to store virtual machines
BRIDGE=virbr0 # Hypervisor bridge BRIDGE=virbr0 # Hypervisor bridge
PUBKEY="" # SSH public key PUBKEY="" # SSH public key
DISTRO=rocky9 # Distribution DISTRO=rocky9 # Distribution
MACADDRESS="" # MAC Address MACADDRESS="" # MAC Address
PORT=-1 # Console port PORT=-1 # Console port
TIMEZONE=US/Eastern # Timezone TIMEZONE=US/Eastern # Timezone
ADDITIONAL_USER=${USER} # User ADDITIONAL_USER=${USER} # User
ASSUME_YES=0 # Assume yes to prompts ASSUME_YES=0 # Assume yes to prompts
ASSUME_NO=0 # Assume no to prompts ASSUME_NO=0 # Assume no to prompts
VERBOSE=0 # Verbosity VERBOSE=0 # Verbosity
VIRTTYPE=kvm # Virt type (kvm, xen, qemu) VIRTTYPE=kvm # Virt type (kvm, xen, qemu)
SECUREBOOT=0 # Enable UEFI with SecureBoot SECUREBOOT=0 # Enable UEFI with SecureBoot
# Reset OPTIND # Reset OPTIND
OPTIND=1 OPTIND=1
@ -726,8 +699,7 @@ function set_defaults ()
BUILTIN_VMS=("${DEFAULT_BUILTIN_VMS[@]}") BUILTIN_VMS=("${DEFAULT_BUILTIN_VMS[@]}")
} }
function set_custom_defaults () function set_custom_defaults() {
{
# Source custom defaults: first local .kivrc, then fallback to ~/.kivrc # Source custom defaults: first local .kivrc, then fallback to ~/.kivrc
if [ -f "./.kivrc" ]; then if [ -f "./.kivrc" ]; then
source "./.kivrc" source "./.kivrc"
@ -750,7 +722,6 @@ function set_custom_defaults ()
done done
} }
function list_available_vms() { function list_available_vms() {
for key in "${!BUILTIN_VM_DESCRIPTIONS[@]}"; do for key in "${!BUILTIN_VM_DESCRIPTIONS[@]}"; do
local arch="${BUILTIN_VM_ARCHS[$key]}" local arch="${BUILTIN_VM_ARCHS[$key]}"
@ -775,59 +746,54 @@ function list_available_vms() {
done | sort done | sort
} }
function create () function create() {
{
# Parse command line arguments # Parse command line arguments
while getopts ":b:c:d:D:f:g:i:k:l:L:m:M:p:s:t:T:u:V:ahynSv" opt while getopts ":b:c:d:D:f:g:i:k:l:L:m:M:p:s:t:T:u:V:ahynSv" opt; do
do
case "$opt" in case "$opt" in
a ) AUTOSTART="${OPTARG}" ;; a) AUTOSTART="${OPTARG}" ;;
b ) BRIDGE="${OPTARG}" ;; b) BRIDGE="${OPTARG}" ;;
c ) CPUS="${OPTARG}" ;; c) CPUS="${OPTARG}" ;;
d ) DISK_SIZE="${OPTARG}" ;; d) DISK_SIZE="${OPTARG}" ;;
D ) DNSDOMAIN="${OPTARG}" ;; D) DNSDOMAIN="${OPTARG}" ;;
f ) FEATURE="${OPTARG}" ;; f) FEATURE="${OPTARG}" ;;
g ) GRAPHICS="${OPTARG}" ;; g) GRAPHICS="${OPTARG}" ;;
i ) IMAGE="${OPTARG}" ;; i) IMAGE="${OPTARG}" ;;
k ) PUBKEY="${OPTARG}" ;; k) PUBKEY="${OPTARG}" ;;
l ) IMAGEDIR="${OPTARG}" ;; l) IMAGEDIR="${OPTARG}" ;;
L ) VMDIR="${OPTARG}" ;; L) VMDIR="${OPTARG}" ;;
m ) MEMORY="${OPTARG}" ;; m) MEMORY="${OPTARG}" ;;
M ) MACADDRESS="${OPTARG}" ;; M) MACADDRESS="${OPTARG}" ;;
p ) PORT="${OPTARG}" ;; p) PORT="${OPTARG}" ;;
s ) SCRIPTNAME="${OPTARG}" ;; s) SCRIPTNAME="${OPTARG}" ;;
t ) DISTRO="${OPTARG}" ;; t) DISTRO="${OPTARG}" ;;
T ) TIMEZONE="${OPTARG}" ;; T) TIMEZONE="${OPTARG}" ;;
u ) ADDITIONAL_USER="${OPTARG}" ;; u) ADDITIONAL_USER="${OPTARG}" ;;
V ) VIRTTYPE="${OPTARG}" ;; V) VIRTTYPE="${OPTARG}" ;;
y ) ASSUME_YES=1 ;; y) ASSUME_YES=1 ;;
n ) ASSUME_NO=1 ;; n) ASSUME_NO=1 ;;
S ) SECUREBOOT=1 ;; S) SECUREBOOT=1 ;;
v ) VERBOSE=1 ;; v) VERBOSE=1 ;;
h ) usage ;; h) usage ;;
* ) die "Unsupported option. Run 'kvm-install-vm help create'." ;; *) die "Unsupported option. Run 'kvm-install-vm help create'." ;;
esac esac
done done
shift $((OPTIND - 1)) shift $((OPTIND - 1))
# Resize disk if you specify a disk size either via cmdline option or .kivrc # Resize disk if you specify a disk size either via cmdline option or .kivrc
if [ -n "${DISK_SIZE}" ] if [ -n "${DISK_SIZE}" ]; then
then
RESIZE_DISK=true RESIZE_DISK=true
DISK_SIZE="${DISK_SIZE}G" # Append 'G' for Gigabyte DISK_SIZE="${DISK_SIZE}G" # Append 'G' for Gigabyte
fi fi
# Yes (-y) and No (-n) are mutually exclusive. # Yes (-y) and No (-n) are mutually exclusive.
if [[ "${ASSUME_YES}" -eq 1 ]] && [[ "${ASSUME_NO}" -eq 1 ]] if [[ "${ASSUME_YES}" -eq 1 ]] && [[ "${ASSUME_NO}" -eq 1 ]]; then
then
printf "Please specify only one of -y or -n flags.\n" printf "Please specify only one of -y or -n flags.\n"
exit 1 exit 1
fi fi
# After all options are processed, make sure only one variable is left (vmname) # After all options are processed, make sure only one variable is left (vmname)
if [ "$#" != 1 ] if [ "$#" != 1 ]; then
then
printf "Please specify a single host to create.\n" printf "Please specify a single host to create.\n"
printf "Run 'kvm-install-vm create help' for usage.\n" printf "Run 'kvm-install-vm create help' for usage.\n"
exit 1 exit 1
@ -842,8 +808,7 @@ function create ()
# Check for ssh key # Check for ssh key
check_ssh_key check_ssh_key
if [ ! -z "${IMAGE+x}" ] if [ ! -z "${IMAGE+x}" ]; then
then
output "Using custom QCOW2 image: ${IMAGE}." output "Using custom QCOW2 image: ${IMAGE}."
OS_INFO="auto" OS_INFO="auto"
LOGIN_USER="<use the default account in your custom image>" LOGIN_USER="<use the default account in your custom image>"
@ -886,22 +851,20 @@ function create ()
create_vm create_vm
} }
function attach-disk () function attach-disk() {
{
# Set default variables # Set default variables
local FORMAT=qcow2 local FORMAT=qcow2
local TARGET="" local TARGET=""
local DISKSIZE="" local DISKSIZE=""
# Parse command line arguments # Parse command line arguments
while getopts ":d:f:t:h" opt while getopts ":d:f:t:h" opt; do
do
case "$opt" in case "$opt" in
d ) DISKSIZE="${OPTARG}G" ;; d) DISKSIZE="${OPTARG}G" ;;
f ) FORMAT="${OPTARG}" ;; f) FORMAT="${OPTARG}" ;;
t ) TARGET="${OPTARG}" ;; t) TARGET="${OPTARG}" ;;
h ) usage ;; h) usage ;;
* ) die "Unsupported option. Run 'kvm-install-vm help attach-disk'." ;; *) die "Unsupported option. Run 'kvm-install-vm help attach-disk'." ;;
esac esac
done done
@ -910,8 +873,7 @@ function attach-disk ()
[ ! -z ${TARGET} ] || die "You must specify a target device, for e.g. '-t vdb'" [ ! -z ${TARGET} ] || die "You must specify a target device, for e.g. '-t vdb'"
[ ! -z ${DISKSIZE} ] || die "You must specify a size (in GB) for the new device, for e.g. '-d 5'" [ ! -z ${DISKSIZE} ] || die "You must specify a size (in GB) for the new device, for e.g. '-d 5'"
if [ "$#" != 1 ] if [ "$#" != 1 ]; then
then
printf "Please specify a single host to attach a disk to.\n" printf "Please specify a single host to attach a disk to.\n"
printf "Run 'kvm-install-vm help attach-disk' for usage.\n" printf "Run 'kvm-install-vm help attach-disk' for usage.\n"
exit 1 exit 1
@ -922,20 +884,18 @@ function attach-disk ()
[[ -d ${VMDIR}/${VMNAME} ]] && DISKDIR=${VMDIR}/${VMNAME} || DISKDIR=${IMAGEDIR}/${VMNAME} [[ -d ${VMDIR}/${VMNAME} ]] && DISKDIR=${VMDIR}/${VMNAME} || DISKDIR=${IMAGEDIR}/${VMNAME}
DISKNAME=${VMNAME}-${TARGET}-${DISKSIZE}.${FORMAT} DISKNAME=${VMNAME}-${TARGET}-${DISKSIZE}.${FORMAT}
if [ ! -f "${DISKDIR}/${DISKNAME}" ] if [ ! -f "${DISKDIR}/${DISKNAME}" ]; then
then
outputn "Creating new '${TARGET}' disk image for domain ${VMNAME}" outputn "Creating new '${TARGET}' disk image for domain ${VMNAME}"
(qemu-img create -f ${FORMAT} -o size=$DISKSIZE,preallocation=metadata \ (qemu-img create -f ${FORMAT} -o size=$DISKSIZE,preallocation=metadata \
${DISKDIR}/${DISKNAME} &>> ${DISKDIR}/${VMNAME}.log && ok ) && \ ${DISKDIR}/${DISKNAME} &>> ${DISKDIR}/${VMNAME}.log && ok) &&
outputn "Attaching ${DISKNAME} to domain ${VMNAME}"
outputn "Attaching ${DISKNAME} to domain ${VMNAME}"
(virsh attach-disk ${VMNAME} \ (virsh attach-disk ${VMNAME} \
--source $DISKDIR/${DISKNAME} \ --source $DISKDIR/${DISKNAME} \
--target ${TARGET} \ --target ${TARGET} \
--subdriver ${FORMAT} \ --subdriver ${FORMAT} \
--cache none \ --cache none \
--persistent &>> ${DISKDIR}/${VMNAME}.log && ok ) \ --persistent &>> ${DISKDIR}/${VMNAME}.log && ok) ||
|| die "Could not attach disk." die "Could not attach disk."
else else
die "Target ${TARGET} is already created or in use." die "Target ${TARGET} is already created or in use."
fi fi
@ -969,7 +929,7 @@ case "${subcommand}" in
virsh list --all virsh list --all
exit 0 exit 0
;; ;;
create|remove|attach-disk|remove-disk) create | remove | attach-disk | remove-disk)
set_defaults set_defaults
set_custom_defaults set_custom_defaults

6
tests/check_distributions.bats

@ -2,15 +2,13 @@
VMNAME=batstestvm VMNAME=batstestvm
function create_test_vm () function create_test_vm() {
{
local -r var="$1" local -r var="$1"
run ./kvm-install-vm create -t ${var} ${VMNAME}-${var} run ./kvm-install-vm create -t ${var} ${VMNAME}-${var}
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
} }
function remove_test_vm () function remove_test_vm() {
{
local -r var="$1" local -r var="$1"
run ./kvm-install-vm remove ${VMNAME}-${var} run ./kvm-install-vm remove ${VMNAME}-${var}
[ "$status" -eq 0 ] [ "$status" -eq 0 ]

4
test.sh → tests/run

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
if ! command -v bats >/dev/null 2>&1; then if ! command -v bats >/dev/null 2>&1; then
cat <<EOF >&2 cat <<EOF >&2
Error: The 'bats' testing framework is required but was not found. Error: The 'bats' testing framework is required but was not found.
Please install it and try again: Please install it and try again:
@ -12,7 +12,7 @@ Please install it and try again:
sudo dnf install bats sudo dnf install bats
EOF EOF
exit 1 exit 1
fi fi
$(which bats) tests/ $(which bats) tests/

3
tests/vmdir.bash

@ -1,6 +1,5 @@
VMDIR=${HOME}/virt/vms VMDIR=${HOME}/virt/vms
if [[ -f .kivrc ]] if [[ -f .kivrc ]]; then
then
source .kivrc source .kivrc
fi fi

3
tests/vmname.bash

@ -1,5 +1,4 @@
if [ -z "${VMNAME}" ] if [ -z "${VMNAME}" ]; then
then
TIMESTAMP=$(date '+%Y%m%d%H%M%S') TIMESTAMP=$(date '+%Y%m%d%H%M%S')
export VMNAME="batstestvm-${TIMESTAMP}" export VMNAME="batstestvm-${TIMESTAMP}"
fi fi

Loading…
Cancel
Save