diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7b43e4f --- /dev/null +++ b/.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 diff --git a/README.md b/README.md index 0da5213..9395cfc 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,7 @@ Tests are powered by [Bats](https://github.com/bats-core/bats-core).\ Run: ```bash -./test.sh +bash tests/run ``` ## 🤝 Contributing diff --git a/kvm-install-vm b/kvm-install-vm index 064ca6d..5964be9 100755 --- a/kvm-install-vm +++ b/kvm-install-vm @@ -4,8 +4,7 @@ set -euo pipefail # Set program name variable - basename without subshell prog=${0##*/} -function usage () -{ +function usage() { cat << EOF NAME kvm-install-vm - Install virtual guests using cloud-init on a local KVM @@ -28,11 +27,10 @@ COMMANDS remove - delete a guest domain EOF -exit 0 + exit 0 } -function usage_subcommand () -{ +function usage_subcommand() { case "$1" in create) printf "NAME\n" @@ -72,7 +70,7 @@ function usage_subcommand () printf "\n" printf "DISTRIBUTIONS\n" list_available_vms - printf "\n" + printf "\n" printf "EXAMPLES\n" printf " %s create foo\n" "$prog" 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 -red() { echo -e "\e[31m$@\e[0m" ; } -green() { echo -e "\e[32m$@\e[0m" ; } -yellow() { echo -e "\e[33m$@\e[0m" ; } +red() { echo -e "\e[31m$@\e[0m"; } +green() { echo -e "\e[32m$@\e[0m"; } +yellow() { echo -e "\e[33m$@\e[0m"; } -die() { red "ERR: $@" >&2 ; exit 2 ; } -silent() { "$@" > /dev/null 2>&1 ; } -output() { echo -e "- $@" ; } -outputn() { echo -en "- $@ ... " ; } -ok() { green "${@:-OK}" ; } +die() { + red "ERR: $@" >&2 + exit 2 +} +silent() { "$@" > /dev/null 2>&1; } +output() { echo -e "- $@"; } +outputn() { echo -en "- $@ ... "; } +ok() { green "${@:-OK}"; } -pushd() { command pushd "$@" >/dev/null ; } -popd() { command popd "$@" >/dev/null ; } +pushd() { command pushd "$@" > /dev/null; } +popd() { command popd "$@" > /dev/null; } # Join zero or more strings into a delimited string. -function join () -{ +function join() { local sep="$1" if [ $# -eq 0 ]; then return @@ -175,8 +175,7 @@ function join () # Print an optional name=value[,value,..] parameter. # Prints nothing if no values are given. -function param () -{ +function param() { if [ $# -lt 2 ]; then return # skip empty value fi @@ -187,8 +186,7 @@ function param () } # Output a command, one argument per line. -function output_command () -{ +function output_command() { local line_cont=$' \\ \n ' local command_lines=$(join "$line_cont" "$@") printf " %s\n" "$command_lines" @@ -196,38 +194,32 @@ function output_command () # Command wrapper to output the command to be run in verbose # mode and redirect stdout and stderr to the vm log file. -function run () -{ +function run() { local msg="$1" shift - if [ "${VERBOSE}" -eq 1 ] - then + if [ "${VERBOSE}" -eq 1 ]; then output "$msg with the following command" output_command "$@" else outputn "$msg" fi - ( "$@" &>> ${VMNAME}.log && ok ) + ("$@" &>> ${VMNAME}.log && ok) } # Detect OS and set wget parameters -function set_wget () -{ - if [ -f /etc/fedora-release ] - then +function set_wget() { + if [ -f /etc/fedora-release ]; then WGET="wget --quiet --show-progress" else WGET="wget" fi } -function check_vmname_set () -{ +function check_vmname_set() { [ -n "${VMNAME}" ] || die "VMNAME not set." } -function delete_vm () -{ +function delete_vm() { # Check if domain exists and set DOMAIN_EXISTS variable. domain_exists "${VMNAME}" @@ -236,29 +228,27 @@ function delete_vm () check_vmname_set - if [ "${DOMAIN_EXISTS}" -eq 1 ] - then + if [ "${DOMAIN_EXISTS}" -eq 1 ]; then outputn "Destroying ${VMNAME} domain" - virsh destroy --graceful ${VMNAME} > /dev/null 2>&1 \ - && ok \ - || yellow "(Domain is not running.)" + virsh destroy --graceful ${VMNAME} > /dev/null 2>&1 && + ok || + yellow "(Domain is not running.)" outputn "Undefining ${VMNAME} domain" - virsh undefine --managed-save --snapshots-metadata --nvram ${VMNAME} > /dev/null 2>&1 \ - && ok \ - || die "Could not undefine domain." + virsh undefine --managed-save --snapshots-metadata --nvram ${VMNAME} > /dev/null 2>&1 && + ok || + die "Could not undefine domain." else output "Domain ${VMNAME} does not exist" fi [[ -d ${VMDIR}/${VMNAME} ]] && DISKDIR=${VMDIR}/${VMNAME} || DISKDIR=${IMAGEDIR}/${VMNAME} - [ -d $DISKDIR ] \ - && outputn "Deleting ${VMNAME} files" \ - && rm -rf $DISKDIR \ - && ok + [ -d $DISKDIR ] && + outputn "Deleting ${VMNAME} files" && + rm -rf $DISKDIR && + ok - if [ "${STORPOOL_EXISTS}" -eq 1 ] - then + if [ "${STORPOOL_EXISTS}" -eq 1 ]; then outputn "Destroying ${VMNAME} storage pool" virsh pool-destroy ${VMNAME} > /dev/null 2>&1 && ok else @@ -266,8 +256,7 @@ function delete_vm () fi } -function fetch_images () -{ +function fetch_images() { # Create image directory if it doesn't already exist mkdir -p ${IMAGEDIR} @@ -277,9 +266,9 @@ function fetch_images () for vm in "${BUILTIN_VMS[@]}"; do IFS=\| read -r vm_distro vm_desc vm_arch vm_url vm_login_user <<< "$vm" if [[ "$vm_distro" == "$DISTRO" && "$vm_arch" == "$ARCH" ]]; then - 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 - IMAGE_URL="${vm_url%/*}" # Grab everything but the filename and the slash + 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 + IMAGE_URL="${vm_url%/*}" # Grab everything but the filename and the slash DISK_FORMAT="qcow2" LOGIN_USER="$vm_login_user" found="true" @@ -293,11 +282,9 @@ function fetch_images () IMAGE=${IMAGEDIR}/${QCOW} - if [ ! -f ${IMAGEDIR}/${QCOW} ] - then + if [ ! -f ${IMAGEDIR}/${QCOW} ]; then set_wget - if [ -f ${IMAGEDIR}/${QCOW}.part ] - then + if [ -f ${IMAGEDIR}/${QCOW}.part ]; then CONTINUE="--continue" output "Partial cloud image found. Resuming download" else @@ -308,7 +295,7 @@ function fetch_images () ${CONTINUE} \ --directory-prefix ${IMAGEDIR} \ --output-document=${IMAGEDIR}/${QCOW}.part \ - ${IMAGE_URL}/${QCOW} || \ + ${IMAGE_URL}/${QCOW} || die "Could not download image." mv ${IMAGEDIR}/${QCOW}.part ${IMAGEDIR}/${QCOW} @@ -316,8 +303,7 @@ function fetch_images () } -function check_ssh_key () -{ +function check_ssh_key() { local key if [ -z "${PUBKEY}" ]; then # Try to find a suitable key file. @@ -329,47 +315,42 @@ function check_ssh_key () done fi - if [ ! -f "${PUBKEY}" ] - then + if [ ! -f "${PUBKEY}" ]; then # Check for existence of a pubkey, or else exit with message die "Please generate an SSH keypair using 'ssh-keygen -t rsa' or \ specify one with the "-k" flag." else # Place contents of $PUBKEY into $KEY - KEY=$(<${PUBKEY}) + KEY=$(< ${PUBKEY}) fi } -function check_os_variant () -{ +function check_os_variant() { if [[ ${OS_INFO} != auto ]]; then - osinfo-query os short-id=${OS_INFO} >/dev/null \ - || die "Unknown OS variant '${OS_INFO}'. Please update your osinfo-db. "\ - "See https://libosinfo.org/download for more information." + osinfo-query os short-id=${OS_INFO} > /dev/null || + die "Unknown OS variant '${OS_INFO}'. Please update your osinfo-db. " \ + "See https://libosinfo.org/download for more information." fi } -function domain_exists () -{ - virsh dominfo "${1}" > /dev/null 2>&1 \ - && DOMAIN_EXISTS=1 \ - || DOMAIN_EXISTS=0 +function domain_exists() { + virsh dominfo "${1}" > /dev/null 2>&1 && + DOMAIN_EXISTS=1 || + DOMAIN_EXISTS=0 } -function storpool_exists () -{ - virsh pool-info "${1}" > /dev/null 2>&1 \ - && STORPOOL_EXISTS=1 \ - || STORPOOL_EXISTS=0 +function storpool_exists() { + virsh pool-info "${1}" > /dev/null 2>&1 && + STORPOOL_EXISTS=1 || + STORPOOL_EXISTS=0 } -function set_sudo_group () -{ +function set_sudo_group() { case "${DISTRO}" in - almalinux*|centos*|fedora*|rocky*|*-atomic|amazon*|opensuse* ) + almalinux* | centos* | fedora* | rocky* | *-atomic | amazon* | opensuse*) SUDOGROUP="wheel" ;; - ubuntu*|debian* ) + ubuntu* | debian*) SUDOGROUP="sudo" ;; *) @@ -378,46 +359,44 @@ function set_sudo_group () esac } -function check_delete_known_host () -{ +function check_delete_known_host() { output "Checking for ${IP} in known_hosts file" - grep -q ${IP} ${HOME}/.ssh/known_hosts \ - && outputn "Found entry for ${IP}. Removing" \ - && (sed --in-place "/^${IP}/d" ~/.ssh/known_hosts && ok ) \ - || output "No entries found for ${IP}" + grep -q ${IP} ${HOME}/.ssh/known_hosts && + outputn "Found entry for ${IP}. Removing" && + (sed --in-place "/^${IP}/d" ~/.ssh/known_hosts && ok) || + output "No entries found for ${IP}" } function set_boot_flag() { - local share_dir="" - - if command -v rpm >/dev/null 2>&1 && rpm -q edk2-ovmf >/dev/null 2>&1; then - share_dir="/usr/share/edk2/ovmf" - elif command -v dpkg >/dev/null 2>&1 && dpkg -s edk2-ovmf >/dev/null 2>&1; then - share_dir="/usr/share/OVMF" - else - BOOTFLAG="" - return - fi - - local machine - case "$ARCH" in - x86_64) machine="--machine q35" ;; - aarch64) machine="--machine virt" ;; - *) machine="" ;; - esac - - local suffix="" - if (( SECUREBOOT )); then - suffix=".secboot" - fi - local code_fd="${share_dir}/OVMF_CODE${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" + local share_dir="" + + if command -v rpm > /dev/null 2>&1 && rpm -q edk2-ovmf > /dev/null 2>&1; then + share_dir="/usr/share/edk2/ovmf" + elif command -v dpkg > /dev/null 2>&1 && dpkg -s edk2-ovmf > /dev/null 2>&1; then + share_dir="/usr/share/OVMF" + else + BOOTFLAG="" + return + fi + + local machine + case "$ARCH" in + x86_64) machine="--machine q35" ;; + aarch64) machine="--machine virt" ;; + *) machine="" ;; + esac + + local suffix="" + if ((SECUREBOOT)); then + suffix=".secboot" + fi + local code_fd="${share_dir}/OVMF_CODE${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" } -function create_vm () -{ +function create_vm() { # Create image directory if it doesn't already exist mkdir -p ${VMDIR} @@ -468,8 +447,7 @@ ssh_authorized_keys: timezone: ${TIMEZONE} _EOF_ - if [ ! -z "${SCRIPTNAME+x}" ] - then + if [ ! -z "${SCRIPTNAME+x}" ]; then SCRIPT=$(< $SCRIPTNAME) cat >> $USER_DATA << _EOF_ @@ -480,36 +458,38 @@ ${SCRIPT} --==BOUNDARY==-- _EOF_ else - cat >> $USER_DATA << _EOF_ + cat >> $USER_DATA << _EOF_ --==BOUNDARY==-- _EOF_ 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}))" DISK=${VMNAME}.qcow2 qemu-img create -q -f qcow2 -F qcow2 -b $IMAGE $DISK && ok - if $RESIZE_DISK - then + if $RESIZE_DISK; then outputn "Resizing the disk to $DISK_SIZE" # Workaround to prevent virt-resize from renumbering partitions and breaking grub # See https://bugzilla.redhat.com/show_bug.cgi?id=1472039 # Ubuntu will automatically grow the partition to the new size on its first boot case "$DISTRO" in - ubuntu*|amazon2) - qemu-img resize $DISK $DISK_SIZE &>> ${VMNAME}.log \ - && ok \ - || die "Could not resize disk." - ;; - *) - qemu-img create -f qcow2 \ - -o preallocation=metadata $DISK.new $DISK_SIZE &>> ${VMNAME}.log \ - && virt-resize --quiet --expand /dev/sda1 $DISK $DISK.new &>> ${VMNAME}.log \ - && (mv $DISK.new $DISK && ok) \ - || die "Could not resize disk." - ;; + ubuntu* | amazon2) + qemu-img resize $DISK $DISK_SIZE &>> ${VMNAME}.log && + ok || + die "Could not resize disk." + ;; + *) + qemu-img create -f qcow2 \ + -o preallocation=metadata $DISK.new $DISK_SIZE &>> ${VMNAME}.log && + virt-resize --quiet --expand /dev/sda1 $DISK $DISK.new &>> ${VMNAME}.log && + (mv $DISK.new $DISK && ok) || + die "Could not resize disk." + ;; esac fi @@ -518,8 +498,8 @@ _EOF_ virsh pool-create-as \ --name=${VMNAME} \ --type=dir \ - --target=${VMDIR}/${VMNAME} \ - || die "Could not create storage pool. VM may already exist. Try removing first." + --target=${VMDIR}/${VMNAME} || + die "Could not create storage pool. VM may already exist. Try removing first." # Add custom MAC Address if specified NETWORK_PARAMS="$(join ',' \ @@ -536,8 +516,7 @@ _EOF_ ${DISK_EXTRA})" # Omit the --graphics option to auto-detect. - if [ "${GRAPHICS}" = 'auto' ] - then + if [ "${GRAPHICS}" = 'auto' ]; then GRAPHICS_PARAMS="" else GRAPHICS_PARAMS="$(join ',' \ @@ -568,19 +547,18 @@ _EOF_ --noautoconsole \ ${GRAPHICS_OPTION} \ ${BOOTFLAG} \ - ${VIRT_INSTALL_EXTRA} \ - || die "Could not create domain with virt-install." + ${VIRT_INSTALL_EXTRA} || + die "Could not create domain with virt-install." virsh dominfo ${VMNAME} &>> ${VMNAME}.log # Enable autostart if true - if $AUTOSTART - then + if $AUTOSTART; then outputn "Enabling autostart" virsh autostart \ - --domain ${VMNAME} > /dev/null 2>&1 \ - && ok \ - || die "Could not enable autostart." + --domain ${VMNAME} > /dev/null 2>&1 && + ok || + die "Could not enable autostart." fi # Remove the unnecessary cloud init files @@ -592,15 +570,15 @@ _EOF_ status_file="/var/lib/libvirt/dnsmasq/${BRIDGE}.status" IP="" - timeout=60 # seconds + timeout=60 # seconds if [[ -f "$status_file" ]]; then outputn "Waiting for domain to get an IP address " - for (( i=0; i> ${DISKDIR}/${VMNAME}.log && ok ) && \ - - outputn "Attaching ${DISKNAME} to domain ${VMNAME}" + ${DISKDIR}/${DISKNAME} &>> ${DISKDIR}/${VMNAME}.log && ok) && + outputn "Attaching ${DISKNAME} to domain ${VMNAME}" (virsh attach-disk ${VMNAME} \ --source $DISKDIR/${DISKNAME} \ --target ${TARGET} \ --subdriver ${FORMAT} \ --cache none \ - --persistent &>> ${DISKDIR}/${VMNAME}.log && ok ) \ - || die "Could not attach disk." + --persistent &>> ${DISKDIR}/${VMNAME}.log && ok) || + die "Could not attach disk." else die "Target ${TARGET} is already created or in use." fi @@ -969,7 +929,7 @@ case "${subcommand}" in virsh list --all exit 0 ;; - create|remove|attach-disk|remove-disk) + create | remove | attach-disk | remove-disk) set_defaults set_custom_defaults diff --git a/tests/check_distributions.bats b/tests/check_distributions.bats index daa21fc..7a458a2 100644 --- a/tests/check_distributions.bats +++ b/tests/check_distributions.bats @@ -2,15 +2,13 @@ VMNAME=batstestvm -function create_test_vm () -{ +function create_test_vm() { local -r var="$1" run ./kvm-install-vm create -t ${var} ${VMNAME}-${var} [ "$status" -eq 0 ] } -function remove_test_vm () -{ +function remove_test_vm() { local -r var="$1" run ./kvm-install-vm remove ${VMNAME}-${var} [ "$status" -eq 0 ] diff --git a/test.sh b/tests/run old mode 100755 new mode 100644 similarity index 90% rename from test.sh rename to tests/run index a5803d9..a63bb9f --- a/test.sh +++ b/tests/run @@ -1,7 +1,7 @@ #!/bin/bash if ! command -v bats >/dev/null 2>&1; then - cat <&2 + cat <&2 Error: The 'bats' testing framework is required but was not found. Please install it and try again: @@ -12,7 +12,7 @@ Please install it and try again: sudo dnf install bats EOF - exit 1 + exit 1 fi $(which bats) tests/ diff --git a/tests/vmdir.bash b/tests/vmdir.bash index 48ef888..410897d 100644 --- a/tests/vmdir.bash +++ b/tests/vmdir.bash @@ -1,6 +1,5 @@ VMDIR=${HOME}/virt/vms -if [[ -f .kivrc ]] -then +if [[ -f .kivrc ]]; then source .kivrc fi diff --git a/tests/vmname.bash b/tests/vmname.bash index 7144a49..e45e7f9 100644 --- a/tests/vmname.bash +++ b/tests/vmname.bash @@ -1,5 +1,4 @@ -if [ -z "${VMNAME}" ] -then +if [ -z "${VMNAME}" ]; then TIMESTAMP=$(date '+%Y%m%d%H%M%S') export VMNAME="batstestvm-${TIMESTAMP}" fi