Browse Source

feat: add support for remote images

pull/102/head
Giovanni Torres 6 months ago
parent
commit
275d264b26
  1. 118
      kvm-install-vm
  2. 111
      tests/check_remote_images.bats

118
kvm-install-vm

@ -51,7 +51,7 @@ function usage_subcommand() {
printf " -f CPU Model / Feature (default: host)\n"
printf " -g Graphics type (default: vnc)\n"
printf " -h Display help\n"
printf " -i Custom QCOW2 Image\n"
printf " -i Custom image (local path or HTTPS URL)\n"
printf " -k SSH Public Key (default: %s/.ssh/id_rsa.pub)\n" "$HOME"
printf " -l Location of Images (default: %s/virt/images)\n" "$HOME"
printf " -L Location of VMs (default: %s/virt/vms)\n" "$HOME"
@ -86,6 +86,12 @@ function usage_subcommand() {
printf " %s create -T UTC foo\n" "$prog"
printf " Create a default VM with UTC timezone.\n"
printf "\n"
printf " %s create -i /path/to/custom-image.qcow2 foo\n" "$prog"
printf " Create a VM from a local custom image file.\n"
printf "\n"
printf " %s create -i https://example.com/image.qcow2 foo\n" "$prog"
printf " Create a VM from a remote image (HTTPS download).\n"
printf "\n"
;;
remove)
printf "NAME\n"
@ -256,6 +262,44 @@ function delete_vm() {
fi
}
function download_image() {
local url="$1"
local target_dir="$2"
local filename="$3"
# Create target directory if it doesn't exist
mkdir -p "${target_dir}"
local target_path="${target_dir}/${filename}"
local temp_path="${target_path}.part"
# Skip download if file already exists
if [ -f "${target_path}" ]; then
output "Image already exists: ${target_path}"
return 0
fi
set_wget
local continue_flag=""
if [ -f "${temp_path}" ]; then
continue_flag="--continue"
output "Partial image found. Resuming download"
else
output "Downloading image from ${url}"
fi
${WGET} \
${continue_flag} \
--directory-prefix="${target_dir}" \
--output-document="${temp_path}" \
"${url}" ||
die "Could not download image from ${url}."
mv "${temp_path}" "${target_path}"
output "Downloaded: ${target_path}"
}
function fetch_images() {
# Create image directory if it doesn't already exist
mkdir -p ${IMAGEDIR}
@ -283,22 +327,7 @@ function fetch_images() {
IMAGE=${IMAGEDIR}/${QCOW}
if [ ! -f ${IMAGEDIR}/${QCOW} ]; then
set_wget
if [ -f ${IMAGEDIR}/${QCOW}.part ]; then
CONTINUE="--continue"
output "Partial cloud image found. Resuming download"
else
CONTINUE=""
output "Cloud image not found. Downloading"
fi
${WGET} \
${CONTINUE} \
--directory-prefix ${IMAGEDIR} \
--output-document=${IMAGEDIR}/${QCOW}.part \
${IMAGE_URL}/${QCOW} ||
die "Could not download image."
mv ${IMAGEDIR}/${QCOW}.part ${IMAGEDIR}/${QCOW}
download_image "${vm_url}" "${IMAGEDIR}" "${QCOW}"
fi
}
@ -387,6 +416,18 @@ function detect_disk_format() {
esac
}
function is_url() {
local input="$1"
case "$input" in
https://*)
return 0
;;
*)
return 1
;;
esac
}
function set_boot_flag() {
local share_dir=""
@ -849,12 +890,43 @@ function create() {
check_ssh_key
if [ ! -z "${IMAGE+x}" ]; then
# Convert relative path to absolute path
IMAGE=$(realpath "${IMAGE}")
DISK_FORMAT=$(detect_disk_format "${IMAGE}")
output "Using custom image: ${IMAGE} (format: ${DISK_FORMAT})."
OS_INFO="linux2024"
LOGIN_USER="<use the default account in your custom image>"
if is_url "${IMAGE}"; then
# Handle remote URL
QCOW="${IMAGE##*/}" # Extract filename from URL
DISK_FORMAT=$(detect_disk_format "${QCOW}")
# Validate that it's a supported image format
case "${DISK_FORMAT}" in
qcow2|raw|vpc)
output "Using remote image: ${IMAGE} (format: ${DISK_FORMAT})"
;;
*)
die "Unsupported remote image format. Only .qcow2, .raw, and .vhd files are supported."
;;
esac
# Download the image
download_image "${IMAGE}" "${IMAGEDIR}" "${QCOW}"
IMAGE="${IMAGEDIR}/${QCOW}"
OS_INFO="linux2024"
LOGIN_USER="<use the default account in your custom image>"
else
# Handle local file path
if [[ "${IMAGE}" != /* ]]; then
# Convert relative path to absolute path
IMAGE=$(realpath "${IMAGE}")
fi
# Verify file exists
if [ ! -f "${IMAGE}" ]; then
die "Custom image file not found: ${IMAGE}"
fi
DISK_FORMAT=$(detect_disk_format "${IMAGE}")
output "Using custom image: ${IMAGE} (format: ${DISK_FORMAT})"
OS_INFO="linux2024"
LOGIN_USER="<use the default account in your custom image>"
fi
else
fetch_images
fi

111
tests/check_remote_images.bats

@ -0,0 +1,111 @@
#!/usr/bin/env bats
VMPREFIX=batstestvm
TESTDIR=~/virt/.tests
setup() {
# Create test directory
mkdir -p "${TESTDIR}"
}
teardown() {
# Clean up any created VMs
./kvm-install-vm remove "${VMPREFIX}"-remote-rocky 2>/dev/null || true
./kvm-install-vm remove "${VMPREFIX}"-remote-fresh 2>/dev/null || true
./kvm-install-vm remove "${VMPREFIX}"-remote-invalid 2>/dev/null || true
# Clean up downloaded images from tests
rm -f ~/virt/images/Rocky-9-GenericCloud.latest.x86_64.qcow2 2>/dev/null || true
rm -f ~/virt/images/invalid-file.txt 2>/dev/null || true
}
@test "Remote Rocky Linux image download and VM creation" {
# Use the real Rocky Linux 9 image URL for testing
ROCKY_URL="https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2"
# Test download detection and VM creation (use -n to assume no, preventing actual VM creation)
run timeout 30 ./kvm-install-vm create -n -i "${ROCKY_URL}" "${VMPREFIX}"-remote-rocky
# Check that it recognizes it as a remote image
[[ "${output}" =~ "Using remote image" ]]
[[ "${output}" =~ "format: qcow2" ]]
# Should either download or use existing image
[[ "${output}" =~ "Downloading image from" ]] || [[ "${output}" =~ "Image already exists" ]]
}
@test "Remote image fresh download" {
# Use a specific test URL to ensure fresh download
ROCKY_URL="https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2"
IMAGE_FILE="~/virt/images/Rocky-9-GenericCloud.latest.x86_64.qcow2"
# Clean up any existing image to force fresh download
rm -f "${IMAGE_FILE}" 2>/dev/null || true
rm -f "${IMAGE_FILE}.part" 2>/dev/null || true
# Test actual download (use -n to prevent VM creation)
run timeout 60 ./kvm-install-vm create -n -i "${ROCKY_URL}" "${VMPREFIX}"-remote-fresh
# Should show download activity
[[ "${output}" =~ "Using remote image" ]]
[[ "${output}" =~ "Downloading image from" ]]
}
@test "Remote image URL validation - unsupported format" {
# Test with an unsupported file extension
INVALID_URL="https://example.com/invalid-file.txt"
run timeout 5 ./kvm-install-vm create -n -i "${INVALID_URL}" "${VMPREFIX}"-remote-invalid
# Should fail with unsupported format error
[ "$status" -eq 2 ]
[[ "${output}" =~ "Unsupported remote image format" ]]
}
@test "Remote image format detection - qcow2" {
QCOW2_URL="https://example.com/test.qcow2"
# This should pass format validation but fail on download (which is expected)
run timeout 5 ./kvm-install-vm create -n -i "${QCOW2_URL}" "${VMPREFIX}"-format-test
# Should show qcow2 format detection before failing on download
[[ "${output}" =~ "format: qcow2" ]] || [[ "${output}" =~ "Could not download" ]]
}
@test "Remote image format detection - raw" {
RAW_URL="https://example.com/test.raw"
run timeout 5 ./kvm-install-vm create -n -i "${RAW_URL}" "${VMPREFIX}"-format-test
# Should show raw format detection
[[ "${output}" =~ "format: raw" ]] || [[ "${output}" =~ "Could not download" ]]
}
@test "Remote image format detection - vhd" {
VHD_URL="https://example.com/test.vhd"
run timeout 5 ./kvm-install-vm create -n -i "${VHD_URL}" "${VMPREFIX}"-format-test
# Should show vpc format detection (vhd maps to vpc)
[[ "${output}" =~ "format: vpc" ]] || [[ "${output}" =~ "Could not download" ]]
}
@test "URL detection function" {
# Test HTTPS URL detection
HTTPS_URL="https://example.com/test.qcow2"
run timeout 5 ./kvm-install-vm create -n -i "${HTTPS_URL}" "${VMPREFIX}"-url-test
# Should be detected as remote image
[[ "${output}" =~ "Using remote image" ]]
}
@test "Non-URL path handling" {
# Test that local paths still work as before
LOCAL_PATH="/nonexistent/local/file.qcow2"
run timeout 5 ./kvm-install-vm create -n -i "${LOCAL_PATH}" "${VMPREFIX}"-local-test
# Should show local file handling and fail because file doesn't exist
[[ "${output}" =~ "Custom image file not found" ]]
}
Loading…
Cancel
Save