diff --git a/lib/common.sh b/lib/common.sh index 9fbab23..c382d9f 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -51,9 +51,30 @@ usage() { } check_command() { + OPTIND=1 + _hard=1 + _warn=0 + while getopts "sw-" _opt; do + case "$_opt" in + s) # Soft check, return an error code instead of exiting + _hard=0;; + w) # Print a warning when soft checking + _warn=1;; + -) # End of options, everything after is the command + break;; + ?) + error "$_opt is an unrecognised option";; + esac + done + shift $((OPTIND-1)) trace "Checking $1 is an accessible command" if ! command -v "$1" >/dev/null 2>&1; then - error "Command not found: $1" + if is_true "$_hard"; then + error "Command not found: $1" + elif is_true "$_warn"; then + warn "Command not found: $1" + fi + return 1 fi } @@ -70,11 +91,6 @@ get_env() ( fi ) -run_krunvm() { - debug "Running krunvm $*" - buildah unshare krunvm "$@" -} - tac() { awk '{ buffer[NR] = $0; } END { for(i=NR; i>0; i--) { print buffer[i] } }' } diff --git a/lib/microvm.sh b/lib/microvm.sh new file mode 100644 index 0000000..5262210 --- /dev/null +++ b/lib/microvm.sh @@ -0,0 +1,269 @@ +#!/bin/sh + +# This implements an API to manage microVMs using krunvm and podman. + + +# Runtime to use for microVMs: podman+krun, krunvm. When empty, it will be +# automatically selected. +: "${KRUNVM_RUNNER_RUNTIME:=""}" + +# Run krunvm with the provided arguments, behind a buildah unshare. +run_krunvm() { + debug "Running krunvm $*" + buildah unshare krunvm "$@" +} + + +# Automatically select a microVM runtime based on the available commands. Set +# the KRUNVM_RUNNER_RUNTIME variable. +_microvm_runtime_auto() { + if check_command -s -- krun; then + check_command podman + KRUNVM_RUNNER_RUNTIME="podman+krun" + elif check_command -s -- krunvm; then + check_command buildah + KRUNVM_RUNNER_RUNTIME="krunvm" + fi + info "Automatically selected $KRUNVM_RUNNER_RUNTIME to handle microVMs" +} + + +# Set the microVM runtime to use. When no argument is provided, it will try to +# automatically detect it based on the available commands. +# shellcheck disable=SC2120 +microvm_runtime() { + # Pick runtime provided as an argument, when available. + [ "$#" -gt 0 ] && KRUNVM_RUNNER_RUNTIME="$1" + + # When no runtime is provided, try to auto-detect it. + [ -z "${KRUNVM_RUNNER_RUNTIME:-""}" ] && _microvm_runtime_auto + + # Enforce podman+krun as soon as anything starting podman is provided. + [ "${KRUNVM_RUNNER_RUNTIME#podman}" != "$KRUNVM_RUNNER_RUNTIME" ] && KRUNVM_RUNNER_RUNTIME="podman+krun" + + # Check if the runtime is valid. + case "$KRUNVM_RUNNER_RUNTIME" in + podman*) + check_command podman + check_command krun + ;; + krunvm) + check_command krunvm + check_command buildah + ;; + *) + error "Unknown microVM runtime: $KRUNVM_RUNNER_RUNTIME" + ;; + esac +} + + +# List all microVMs. +microvm_list() { + [ -z "$KRUNVM_RUNNER_RUNTIME" ] && microvm_runtime + case "$KRUNVM_RUNNER_RUNTIME" in + podman*) + podman ps -a --format "{{.Names}}" + ;; + krunvm) + run_krunvm list + ;; + esac +} + + +_krunvm_create() { + KRUNVM_RUNNER_IMAGE=$1 + verbose "Creating microVM '${KRUNVM_RUNNER_NAME}', $KRUNVM_RUNNER_CPUS vCPUs, ${KRUNVM_RUNNER_MEM}M memory" + # Note: reset arguments! + set -- \ + --cpus "$KRUNVM_RUNNER_CPUS" \ + --mem "$KRUNVM_RUNNER_MEM" \ + --dns "$KRUNVM_RUNNER_DNS" \ + --name "$KRUNVM_RUNNER_NAME" + if [ -n "$KRUNVM_RUNNER_VOLS" ]; then + while IFS= read -r mount || [ -n "$mount" ]; do + if [ -n "$mount" ]; then + set -- "$@" --volume "$mount" + fi + done < "${RUNNER_ENVIRONMENT}/${RUNNER_ID}.trm" - elif [ -n "$RUNNER_PID" ]; then - kill_tree "$RUNNER_PID" - fi - if [ "$RUNNER_PID" ]; then - # shellcheck disable=SC2046 # We want to wait for all children - waitpid $(ps_tree "$RUNNER_PID"|tac) - else - warning "No PID to wait for" - fi - elif [ -n "$RUNNER_PID" ]; then - kill_tree "$RUNNER_PID" - # shellcheck disable=SC2046 # We want to wait for all children - waitpid $(ps_tree "$RUNNER_PID"|tac) - fi - elif [ -n "$RUNNER_PID" ]; then - kill_tree "$RUNNER_PID" - # shellcheck disable=SC2046 # We want to wait for all children - waitpid $(ps_tree "$RUNNER_PID"|tac) + # Request for termination through .trm file, whenever possible. Otherwise, + # just stop the VM. + if [ -n "$RUNNER_ENVIRONMENT" ] \ + && [ -f "${RUNNER_ENVIRONMENT}/${1}.tkn" ] \ + && [ -n "${RUNNER_SECRET:-}" ]; then + verbose "Requesting termination via ${RUNNER_ENVIRONMENT}/${1}.trm" + printf %s\\n "$RUNNER_SECRET" > "${RUNNER_ENVIRONMENT}/${1}.trm" + microvm_wait "${RUNNER_PREFIX}-$1" + else + microvm_stop "${RUNNER_PREFIX}-$1" fi } + cleanup() { trap '' EXIT - if [ -n "${RUNNER_PID:-}" ]; then - vm_terminate - fi + if [ -n "${RUNNER_ID:-}" ]; then + vm_terminate "$RUNNER_ID" vm_delete "$RUNNER_ID" fi } @@ -339,9 +332,19 @@ trap cleanup EXIT iteration=0 while true; do + # Prefetch, since this might take time and we want to be ready to count away + # download time from the termination setting. + microvm_pull "$RUNNER_IMAGE" + + # Terminate in xx seconds. This is mostly used for demo purposes, but might + # help keeping the machines "warm" and actualised (as per the pull above). + if [ -n "$RUNNER_TERMINATE" ]; then + verbose "Terminating runner in $RUNNER_TERMINATE seconds" + sleep "$RUNNER_TERMINATE" && cleanup & + fi + RUNNER_ID="${loop}-$(random_string)" - vm_create "${RUNNER_ID}" - vm_start "${RUNNER_ID}" + vm_run "${RUNNER_ID}" vm_delete "${RUNNER_ID}" RUNNER_ID=