Skip to content

Instantly share code, notes, and snippets.

@jirutka
Created July 10, 2017 00:14
Show Gist options
  • Save jirutka/872389ca4858f65844e02a0f4d176827 to your computer and use it in GitHub Desktop.
Save jirutka/872389ca4858f65844e02a0f4d176827 to your computer and use it in GitHub Desktop.
This is a wrapper for oneimage for uploading image from local system to OpenNebula server.
#!/bin/sh
# vim: set ts=4:
#
# Install dependencies on Alpine:
# apk add ruby ruby-io-console ruby-json ruby-nokogiri ruby-xmlrpc bind-tools
# gem install opennebula-cli
#
#---help---
# Usage: oneimage-upload [options]
#
# This is a wrapper for oneimage for uploading image from local system
# to OpenNebula server. It starts a web server that listens on port 80 and
# runs oneimage with URL of the given image served by this web server.
#
# Options:
# --name IMAGE_NAME Name of the new image (required).
# --endpoint ONE_XMLRPC URL of OpenNebula XMLRPC frontend (required).
# --password ONE_PASSWORD Password to authenticate with OpenNebula (required*).
# --path IMAGE_FILE Path of the disk image on local filesystem (required).
# --perms IMAGE_PERMS The image permissions (see oneimage chmod).
# --timeout TIMEOUT How long to wait (in seconds) until ONe server download
# the image and change its state to READY.
# Default is 300s.
# --user ONE_USER User name used to connect to OpenNebula (required*).
# ...and all options accepted by "oneimage create"
#---help---
readonly PROGNAME='oneimage-upload'
die() {
printf '\033[1;31mERROR:\033[0m %s: %s\n' $PROGNAME "$@" >&2 # bold red
exit 1
}
_oneimage() {
oneimage "$@" \
${ONE_USER:+--user "$ONE_USER"} \
${ONE_PASSWORD:+--password "$ONE_PASSWORD"} \
--endpoint "$ONE_XMLRPC"
}
# Prints IP address of interface that can reach host with the given IP.
my_ip() {
local reach_ip="$1"
ip route get "$reach_ip" 2>/dev/null \
| grep -v unreachable \
| sed -En 's/.* src ([0-9a-f.:]+) .*/\1/p' \
| grep .
}
# Parses image state from XML response of "oneimage show".
parse_image_state() {
local xml="${1:-$(cat)}"
local state=$(echo "$xml" | sed -En 's|.*<STATE>([^>]+)</STATE>.*|\1|p')
if [ -z "$state" ]; then
printf 'Invalid response:\n%s\n' "$xml" >&2
return 1
fi
# See https://docs.opennebula.org/5.0/integration/system_interfaces/api.html.
case "$state" in
0) echo INIT;;
1) echo READY;;
3) echo DISABLED;;
4) echo LOCKED;;
5) echo ERROR;;
*) echo $state;;
esac
}
# Parses host part of the given URL address.
url_parse_host() {
echo "$1" | sed -E 's|^\w+://||; s|^\w+:?\w*@||; s|/.*$||; s|:\d+$||'
}
usage() {
sed -En '/^#---help---/,/^#---help---/p' "$0" | sed -E 's/^# ?//; 1d;$d;'
exit ${1:-0}
}
cleanup() {
trap '' EXIT HUP INT TERM
test -z "${httpd_pid:-}" || kill $httpd_pid || kill -9 $httpd_pid
test -d "${temp_dir:-}" && rm -Rf "$temp_dir" || :
}
#============================ M a i n ============================#
other_opts=''
while [ $# -gt 0 ]; do
case "$1" in
--*=*) opt="${1%%=*}" val="${1#*=}" n=1;;
*) opt="$1" val="$2" n=2;;
esac
case "$opt" in
-d | --datastore) ONE_DATASTORE="$val";;
--description) IMAGE_DESCRIPTION="$val";;
--endpoint) ONE_XMLRPC="$val";;
--name) IMAGE_NAME="$val";;
--password) ONE_PASSWORD="$val";;
--path) IMAGE_FILE="$val";;
--user) ONE_USER="$val";;
--perms) IMAGE_PERMS="$val";;
--timeout) TIMEOUT="$val";;
-h | --help) usage 0;;
-V | --version) usage 1 >&2;;
*) other_opts="$other_opts $1" n=1;;
esac
shift $n
done
: ${TIMEOUT:=300}
test -n "${IMAGE_FILE:-}" || die 'Either --path or $IMAGE_FILE must be defined!'
test -r "$IMAGE_FILE" || die "File '$IMAGE_FILE' does not exist or not readable!"
test -n "${ONE_XMLRPC:-}" || die 'Either --endpoint or $ONE_XMLRPC must be defined!'
test -n "${IMAGE_NAME:-}" || die 'Either --name or $IMAGE_NAME must be defined!'
trap cleanup EXIT HUP INT TERM
temp_dir=$(mktemp -d /tmp/$PROGNAME.XXXXXX)
ln -s "$(realpath "$IMAGE_FILE")" "$temp_dir"/
one_host=$(url_parse_host "$ONE_XMLRPC")
one_ip=$(dig +short $one_host) || die 'Failed to resolve IP of ONe server!'
server_ip=$(my_ip $one_ip) || die "$one_host ($one_ip) is not reachable!"
path="http://$server_ip/${IMAGE_FILE##*/}"
echo "Serving image at $path" >&2
busybox httpd -f -h "$temp_dir" &
httpd_pid="$!"
echo "Creating image $IMAGE_NAME" >&2
out=$(_oneimage create \
${ONE_DATASTORE:+--datastore "$ONE_DATASTORE"} \
${IMAGE_DESCRIPTION:+--description "$IMAGE_DESCRIPTION"} \
--name="$IMAGE_NAME" \
--path="$path" \
$other_opts)
image_id=$(echo "$out" | sed -En 's/^ID: (\d+)/\1/p' | grep .) \
|| die "$out"
echo "Image created with ID $image_id, please wait until it's uploaded..." >&2
elapsed=0
while true; do
[ $elapsed -lt $TIMEOUT ] || die "Timeout ${TIMEOUT}s exceeded!"
state=$(_oneimage show --xml $image_id \
| parse_image_state) || exit 1
case "$state" in
READY) break;;
INIT | LOCKED) ;;
*) die "Something went wrong, image state is: $state";;
esac
sleep 2
elapsed=$(( elapsed + 2 ))
done
if [ -n "${IMAGE_PERMS:-}" ]; then
echo "Changing permissions to $IMAGE_PERMS" >&2
_oneimage chmod $image_id $IMAGE_PERMS
fi
echo 'Completed' >&2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment