From 0809fae379239687ed1170e04311dc2880ef0aba Mon Sep 17 00:00:00 2001 From: tv Date: Wed, 3 Feb 2016 19:23:51 +0100 Subject: cac -> cac-api --- cac-api | 493 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 493 insertions(+) create mode 100755 cac-api (limited to 'cac-api') diff --git a/cac-api b/cac-api new file mode 100755 index 0000000..b653323 --- /dev/null +++ b/cac-api @@ -0,0 +1,493 @@ +#! /bin/sh +# +#? cac-api - CloudAtCost API command line interface +#? +#? Usage: +#? +set -euf + +#PATH=$PWD/bin:$PATH +#export PATH + +urlencode() { +#! /bin/sh +sed ' + s/%/%25/g + s/ /%20/g + s/!/%21/g + s/"/%22/g + s/#/%23/g + s/\$/%24/g + s/\&/%26/g + s/'\''/%27/g + s/(/%28/g + s/)/%29/g + s/\*/%2a/g + s/+/%2b/g + s/,/%2c/g + s/-/%2d/g + s/\./%2e/g + s/\//%2f/g + s/:/%3a/g + s/;/%3b/g + s//%3e/g + s/?/%3f/g + s/@/%40/g + s/\[/%5b/g + s/\\/%5c/g + s/\]/%5d/g + s/\^/%5e/g + s/_/%5f/g + s/`/%60/g + s/{/%7b/g + s/|/%7c/g + s/}/%7d/g + s/~/%7e/g +' +} +netmask_to_prefix() {( +#! /bin/sh +set -euf + +netmask=$1 + +binaryNetmask=$(echo $1 | sed 's/^/obase=2;/;s/\./;/g' | bc | tr -d \\n) +binaryPrefix=$(echo $binaryNetmask | sed -n 's/^\(1*\)0*$/\1/p') +if ! echo $binaryPrefix | grep -q .; then + echo $0: bad netmask: $netmask >&2 + exit 4 +fi +printf %s $binaryPrefix | tr -d 0 | wc -c +)} +# + +cac_resources_cache=${cac_resources_cache-$HOME/tmp/cac_resources_cache.json} +cac_servers_cache=${cac_servers_cache-$HOME/tmp/cac_servers_cache.json} +cac_tasks_cache=${cac_tasks_cache-$HOME/tmp/cac_tasks_cache.json} +cac_templates_cache=${cac_templates_cache-$HOME/tmp/cac_templates_cache.json} + +cac_secrets=${cac_secrets-$HOME/.secrets/cac-api} + + +. "$cac_secrets" >/dev/null 2>&1 || : + + +cac_api() { + __cac_api_cli__command=${1-help} + shift || : + __cac_api_cli__"$__cac_api_cli__command" "$@" +} + +#? cac-api help [REGEX] +#? Show help message. If a regex is specified, then show usage of matching +#? commands. +#? +__cac_api_cli__help() {( + regex=${1-} + + # test -t expects GNU coreutils + if test -t 0 >/dev/null 2>&1; then + filter() { + help=$(cat) + echo "$help" | + if test $(echo "$help" | wc -l) -gt $(tput lines); then + $PAGER "$@" + else + cat "$@" + fi + } + else + filter() { + cat "$@" + } + fi + if test -z "$regex"; then + sed -n ' + s/^#?\( \(.*\)\)\?/\2/p + ' + else + __cac_api_cli__help | sed -n ' + /^cac-api '"$regex"'/,/^$/p + ' + fi < "$0" | filter +)} + +#? cac-api console SERVERSPEC +#? Print console URL. +#? +__cac_api_cli__console() {( + server=$(__cac_api_cli__getserver "$1") + sid=$(echo $server | jq -r .sid) + # TODO check reply status == ok + _cac_post_api_v1 console sid="$sid" | jq -r .console +)} + +#? cac-api servers +#? Print cached servers JSON. +#? +__cac_api_cli__servers() { + jq -r . $cac_servers_cache +} + +#? cac-api tasks +#? Print cached tasks JSON. +#? +__cac_api_cli__tasks() { + jq -r . $cac_tasks_cache +} + +#? cac-api templates +#? Print cached templates JSON. +#? +__cac_api_cli__templates() { + jq -r . $cac_templates_cache +} + +#? cac-api resources +#? Print CloudPRO resources JSON. +#? +__cac_api_cli__resources() { + jq -r . $cac_resources_cache +} + +#? cac-api update +#? Fetch and cache state JSON. +#? +__cac_api_cli__update() {( + umask 0077 + for x in \ + resources \ + servers \ + tasks \ + templates \ + # This line intentionally left blank. + do + { + json=$(_cac_fetch_$x) + eval file=\$cac_${x}_cache + echo $json | jq . > "$file".tmp + mv "$file".tmp "$file" + } & + done + wait +)} + +#? cac-api getserver SERVERSPEC +#? Print cached server JSON. +#? +__cac_api_cli__getserver() {( + + case $1 in + *:*) + k=${1%%:*} + v=${1#*:} + ;; + *) + k=label + v=${1#*:} + ;; + esac + + if result=$(jq \ + -e \ + --arg k "$k" \ + --arg v "$v" \ + ' + map(select(.[$k]==$v)) | + if (. | length) == 1 then + .[0] + else + null + end + ' \ + $cac_servers_cache); then + echo $result | jq -r . + else + echo "$0 getserver $k:$v => not unique server found" >&2 + exit 23 + fi +)} + +#? cac-api generatenetworking SERVERSPEC +#? Generate NixOS module with networking configuration. +#? +__cac_api_cli__generatenetworking() {( + server=$(__cac_api_cli__getserver "$1") + + address=$(echo $server | jq -r .ip) + gateway=$(echo $server | jq -r .gateway) + nameserver=8.8.8.8 + netmask=$(echo $server | jq -r .netmask) + prefix=$(netmask_to_prefix $netmask) + + printf '_:\n' + printf '\n' + printf '{\n' + printf ' networking.interfaces.enp2s1.ip4 = [\n' + printf ' {\n' + printf ' address = "%s";\n' $address + printf ' prefixLength = %d;\n' $prefix + printf ' }\n' + printf ' ];\n' + printf ' networking.defaultGateway = "%s";\n' $gateway + printf ' networking.nameservers = [\n' + printf ' "%s"\n' $nameserver + printf ' ];\n' + printf '}\n' +)} + +#? cac-api powerop SERVERSPEC (poweron|poweroff|reset) +#? Activate server power operations. +#? +__cac_api_cli__powerop() {( + server=$(__cac_api_cli__getserver "$1") + action=$2 + + sid=$(echo $server | jq -r .sid) + + reply=$(_cac_post_api_v1 powerop sid="$sid" action="$action") + + _cac_handle_reply 'cac-api powerop' "$reply" +)} + +#? cac-api setlabel SERVERSPEC LABEL +#? +__cac_api_cli__setlabel() {( + server=$(__cac_api_cli__getserver "$1") + label=$2 + + sid=$(echo $server | jq -r .sid) + + reply=$(_cac_post_api_v1 renameserver sid="$sid" name="$label") + + _cac_handle_reply 'cac-api setlabel' "$reply" +)} + +#? cac-api setmode SERVERSPEC (normal|safe) +#? +__cac_api_cli__setmode() {( + server=$(__cac_api_cli__getserver "$1") + mode=$2 + + sid=$(echo $server | jq -r .sid) + + reply=$(_cac_post_api_v1 runmode sid="$sid" mode="$mode") + + _cac_handle_reply 'cac-api setmode' "$reply" +)} + +#? cac-api ssh SERVERSPEC +#? +__cac_api_cli__ssh() {( + server=$(__cac_api_cli__getserver "$1") + shift + + address=$(echo $server | jq -r .ip) + target=root@$address + + SSHPASS=$(echo $server | jq -r .rootpass) + export SSHPASS + + exec sshpass -e ssh \ + -S none \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + $target \ + "$@" +)} + + +#? cac-api waitstatus SERVERSPEC ("Powered On"|...) +#? Blocks until server has specfied state. +#? +__cac_api_cli__waitstatus() { + server=$(__cac_api_cli__getserver "$1") + status=$(echo $server | jq -r .status) + + case $status in + $2) + return + ;; + esac + + echo "$(date -Is) Waiting for status: $2; current status: $status ..." >&2 + + __cac_api_cli__waitforcacheupdate __cac_api_cli__waitstatus "$@" +} + + +# XXX for __cac_api_cli__waitforcacheupdate and __cac_api_cli__poll cache means $cac_servers_cache + +#? cac-api waitforcacheupdate COMMAND [ARGS...] +#? Blocks until cache has been updated then executes "$@". +#? +__cac_api_cli__waitforcacheupdate() { + case $(inotifywait --format %f -q -e moved_to $(dirname $cac_servers_cache)) in + $(basename $cac_servers_cache)) "$@";; + *) __cac_api_cli__waitforcacheupdate "$@";; + esac +} + +#? cac-api poll [TIMESPEC=1m] +#? Continuously update cache, sleeping at least $1 between updates. +#? +__cac_api_cli__poll() { + __cac_api_cli__update + t=${1-1m} + echo "$(date -Is) cache updated; sleeping $t ..." >&2 + sleep "$t" + __cac_api_cli__poll "$@" +} + +#? cac-api build cpu=.. ram=.. storage=.. os=.. +#? Build a server from available resources. +#? cpu = 1/2/3/4/5/6/7/8 limit: 16 +#? ram = 1024 (must be multiple of 4. ex. 1024 / 2048 / 3096) limit: 32768 +#? storage = 10/20/30/40/50 ... etc limit: 1000 +#? os = 75 (must be an #id from `cac-api templates`) +#? +__cac_api_cli__build() {( + reply=$(export "$@"; _cac_post_api_v1 cloudpro/build \ + cpu="$cpu" \ + ram="$ram" \ + storage="$storage" \ + os="$os" \ + ) + + _cac_handle_reply 'cac-api build' "$reply" +)} + +#? cac-api delete SERVERSPEC +#? Delete / terminate server to add resources. +#? +__cac_api_cli__delete() {( + server=$(__cac_api_cli__getserver "$1") + sid=$(echo $server | jq -r .sid) + + reply=$(_cac_post_api_v1 cloudpro/delete sid="$sid") + + _cac_handle_reply 'cac-api delete' "$reply" +)} + + +#? +#? SERVERSPEC is a query like "mode:Safe", "sdate:08/04/2015", etc. +#? See `cac-api servers` to get an inspiration. +#? +#? See sleep(1) for TIMESPEC. +#? + +_cac_fetch_servers() {( + res=$(_cac_get_api_v1 listservers) + status=$(echo $res | jq -r .status) + + if [ "$status" = ok ]; then + echo "$res" | jq -r .data + else + echo "cac_fetch_servers: bad status: $status" >&2 + exit 1 + fi +)} + +_cac_fetch_tasks() {( + res=$(_cac_get_api_v1 listtasks) + status=$(echo $res | jq -r .status) + + if [ "$status" = ok ]; then + echo "$res" | jq -r .data + else + echo "cac_fetch_tasks: bad status: $status" >&2 + exit 1 + fi +)} + +_cac_fetch_templates() {( + res=$(_cac_get_api_v1 listtemplates) + status=$(echo $res | jq -r .status) + + if [ "$status" = ok ]; then + echo "$res" | jq -r .data + else + echo "cac_fetch_templates: bad status: $status" >&2 + exit 1 + fi +)} + +_cac_fetch_resources() {( + res=$(_cac_get_api_v1 cloudpro/resources) + status=$(echo $res | jq -r .status) + + if [ "$status" = ok ]; then + echo "$res" | jq -r .data + else + echo "cac_resources: bad cloudpro/resources status: $status" >&2 + exit 1 + fi +)} + + +# rsyncfiles : lines filename |> local-dir x rsync-target -> ? |> ? +rsyncfiles() {( + set -x + rsync \ + --rsync-path="mkdir -p \"$2\" && rsync" \ + -vzrlptD \ + --files-from=- \ + "$1"/ \ + "$2" +)} + + + + +_cac_handle_reply() {( + label=$1 + reply=$2 + + case $(echo $reply | jq -r .status) in + ok) + echo $reply | jq -r . >&2 + __cac_api_cli__update + ;; + *) + echo $label: bad reply: >&2 + echo $reply | jq -r . >&2 + exit 23 + ;; + esac +)} + + +_cac_get_api_v1() { + _cac_curl_api_v1 -G "$@" +} + +_cac_post_api_v1() { + _cac_curl_api_v1 -XPOST "$@" +} + +_cac_curl_api_v1() { + _cac_exec curl -sS "$1" "https://panel.cloudatcost.com/api/v1/$2.php" $( + shift 2 + set -- "$@" login="$cac_login" key="$cac_key" + for arg; do + echo -d $(printf '%s' "$arg" | urlencode) + done + ) +} + +_cac_exec() { + if test -z "${cac_via-}"; then + env -- "$@" + else + ssh -q "$cac_via" -t "$@" + fi +} + + + + + +case ${run-true} in + true) cac_api "$@";; +esac -- cgit v1.2.3