diff options
Diffstat (limited to 'cac-api')
-rwxr-xr-x | cac-api | 582 |
1 files changed, 582 insertions, 0 deletions
@@ -0,0 +1,582 @@ +#! /usr/bin/env bash +# +#? cac-api - CloudAtCost API command line interface +#? +#? Usage: +#? +set -euf + +#PATH=$PWD/bin:$PATH +#export PATH + +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} +<<<<<<< HEAD:cac +cac_secrets=${cac_secrets-$HOME/.secrets/cac} +cac_always_update=${cac_always_update-false} +cac_update_interval=${cac_update_interval-10} + +_load_secrets() { + if test ! -r "$cac_secrets" ;then + echo "unable to load secrets from cac_secrets='$cac_secrets'" >&2 + __cac_cli__help + return 1 + else + . "$cac_secrets" + fi +} + +_cac_main() { + _load_secrets + case ${run-true} in true) + __cac_cli__command=${1-help} + shift || : + __cac_cli__"$__cac_cli__command" "$@" + esac +======= + +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" "$@" +>>>>>>> 0809fae379239687ed1170e04311dc2880ef0aba:cac-api +} + +#? 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. +#? +<<<<<<< HEAD:cac +__cac_cli__servers() { + _cac_maybe_update +======= +__cac_api_cli__servers() { +>>>>>>> 0809fae379239687ed1170e04311dc2880ef0aba:cac-api + jq -r . $cac_servers_cache +} + +#? cac-api tasks +#? Print cached tasks JSON. +#? +<<<<<<< HEAD:cac +__cac_cli__tasks() { + _cac_maybe_update +======= +__cac_api_cli__tasks() { +>>>>>>> 0809fae379239687ed1170e04311dc2880ef0aba:cac-api + jq -r . $cac_tasks_cache +} + +#? cac-api templates +#? Print cached templates JSON. +#? +<<<<<<< HEAD:cac +__cac_cli__templates() { + _cac_maybe_update +======= +__cac_api_cli__templates() { +>>>>>>> 0809fae379239687ed1170e04311dc2880ef0aba:cac-api + jq -r . $cac_templates_cache +} + +#? cac-api resources +#? Print CloudPRO resources JSON. +#? +<<<<<<< HEAD:cac +__cac_cli__resources() { + _cac_maybe_update +======= +__cac_api_cli__resources() { +>>>>>>> 0809fae379239687ed1170e04311dc2880ef0aba:cac-api + jq -r . $cac_resources_cache +} + +#? cac-api update +#? Fetch and cache state JSON. +#? +__cac_api_cli__update() {( + umask 0077 + pids="" + 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" + } & + pids="$pids $!" + done + for pid in $pids; do wait $pid;done +)} + +#? cac-api getserver SERVERSPEC +#? Print cached server JSON. +#? +<<<<<<< HEAD:cac +__cac_cli__getserver() {( + _cac_maybe_update +======= +__cac_api_cli__getserver() {( + +>>>>>>> 0809fae379239687ed1170e04311dc2880ef0aba:cac-api + 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 + + # cac_always_update is not compatible with inotifywait + case $cac_always_update in + true) + echo "$(date -Is) Waiting ${cac_update_interval}s for status: $2; current status: $status ..." + sleep ${cac_update_interval} + __cac_cli__waitstatus "$@" + ;; + false) + echo "$(date -Is) Waiting for status: $2; current status: $status ..." >&2 + __cac_cli__waitforcacheupdate __cac_cli__waitstatus "$@" + ;; + esac +} + +<<<<<<< HEAD:cac +# helper to implement cac_always_update +_cac_maybe_update() { + case $cac_always_update in true) + echo "$(date -Is) updating cache file" >&2 + __cac_cli__update + esac +======= + __cac_api_cli__waitforcacheupdate __cac_api_cli__waitstatus "$@" +>>>>>>> 0809fae379239687ed1170e04311dc2880ef0aba:cac-api +} + + +# 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" +)} + +#? +#? Uses the following `environment variables`: +#? cac_resources_cache=$HOME/tmp/cac_resources_cache.json +#? cac_servers_cache=$HOME/tmp/cac_servers_cache.json +#? cac_tasks_cache=$HOME/tmp/cac_tasks_cache.json +#? cac_templates_cache=$HOME/tmp/cac_templates_cache.json +#? cac_secrets=$HOME/.secrets/cac +#? cac_always_update=false < set to true to always update when using cache files +#? cac_update_interval=10 < interval (in s) used by `cac waitstatus` +#? run=true < set to false to be able to source this file +#? +#? You can override these by setting them beforehand. +#? +#? `cac_secrets` will be sourced and may provide the following two entries: +#? cac_login=<cac-email> +#? cac_key=<cac-api-key> +#? +#? 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 +)} + + + + + + +_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 +} + +# +# imported: +urlencode() { +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() {( + 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 +)} + +# rsyncfiles : lines filename |> local-dir x rsync-target -> ? |> ? +rsyncfiles() {( + set -x + rsync \ + --rsync-path="mkdir -p \"$2\" && rsync" \ + -vzrlptD \ + --files-from=- \ + "$1"/ \ + "$2" +)} + +# + +<<<<<<< HEAD:cac +_cac_main "$@" +======= +case ${run-true} in + true) cac_api "$@";; +esac +>>>>>>> 0809fae379239687ed1170e04311dc2880ef0aba:cac-api |