gnscli/gnscli.sh

2405 lines
72 KiB
Bash
Raw Normal View History

2021-12-19 23:31:58 +11:00
#!/bin/bash
# node stop givesjq error
# connect xxx & [opens iterm (or whatever)]
# when oepning new project and caching, "open" the project
# when quitting or changing projects, "close" curproject
# warn if curprojectis closed
VER=0.1
# helper function to turn a directory of scripts into inline
## defaults at the end of this script
function scriptstoinline() {
local outfile files
echo "About to convert scripts from $SCRIPTDIR to inline format:"
files=$(ls -A "${SCRIPTDIR}")
echo "$files" | sed -e 's/^/ - /'
echo
outfile=""
while [[ -z $outfile ]]; do
getstr ":" "./out" "Enter file to place output in"
outfile="$retstr"
if [[ -e $outfile ]]; then
getyn "n" "'$outfile' already exists. Overwrite"
[[ $? -ne 0 ]] && outfile=""
fi
done
cp /dev/null $outfile;
for f in $SCRIPTDIR/*; do
echo -n "Processing ${f}..."
echo "#START_INLINE:$(basename $f)" >> $outfile
cat "$f" >> $outfile
echo "#END_INLINE:$(basename $f)" >> $outfile
echo "done"
done
echo
echo "All done. Replace inline code with the contents of: $outfile"
}
function mkscripts() {
local dir
dir="$1"
info "Creating helper scripts in ${dir}"
cat "$THISSCRIPT" | awk -v dir="$dir" -F: 'BEGIN { curfile=""; } /^#START_INLINE/ { curfile = dir "/" $2; printf(" %s...", curfile) > "/dev/stderr"; } /^#END_INLINE/ { curfile=""; printf("done\n") > "/dev/stderr"; } (curfile != "") { print > curfile; }'
}
function initauth() {
local u p doscripts=0 files
[[ ! -e $CONFDIR ]] && mkdir -p "$CONFDIR" && info "Created $CONFDIR"
[[ ! -e $TMPDIR ]] && mkdir -p "$TMPDIR" && info "Created $CACHEDIR"
[[ ! -e $CACHEDIR ]] && mkdir -p "$CACHEDIR" && info "Created $TMPDIR"
[[ ! -e $SCRIPTDIR ]] && mkdir -p "$SCRIPTDIR" && info "Created $SCRIPTDIR"
[[ ! -e $HISTFILE ]] && cp /dev/null "$HISTFILE" && info "Created $HISTFILE"
# if [[ -e $AUTHFILE ]]; then
# warn "Authentication file $AUTHFILE already exists - skipping creation."
# else
# info "Creating authentication file"
# read -p "Enter gns username: " u
# read -sp "Enter gns password: " p
# echo
# cp /dev/null "${AUTHFILE}"
# [[ $? -ne 0 ]] && error "Can't create $AUTHFILE" && return 1
# echo "$u" >"${AUTHFILE}"
# echo "$p" >>"${AUTHFILE}"
# chmod 600 "$AUTHFILE"
# info "Authentication details stored in $AUTHFILE"
# fi
[[ ! -e $RCFILE ]] && echo "# each line in this file is used as a command-line argument" > "$RCFILE" && info "Created initial $RCFILE"
[[ ! -e $SRVFILE ]] && echo "exampleserver:gns3.example.net:3080" > "$SRVFILE" && info "Created initial $SRVFILE - you need to update this!"
files=$(ls -A "${SCRIPTDIR}")
if [[ -n $files ]]; then
warn "Script directory $SCRIPTDIR already contains files:"
echo -en "$YELLOW"
echo "$files" | sed -e 's/^/ - /'
echo
echo -en "$PLAIN"
getyn n "Overwrite scripts with defaults"
[[ $? -eq 0 ]] && doscripts=1 || doscripts=0
else
doscripts=1
fi
if [[ $doscripts -eq 1 ]]; then
mkscripts "$SCRIPTDIR"
fi
echo -e "$BOLD${CYAN}Initialisation of $BOLD$CONFDIR$PLAIN complete.$PLAIN"
return 0
}
function cache_uuids() { # cache_uuids $$ [locations]
local loc epidx epname api_endpoint curlres mainpid thisfile
local where
mainpid="$1"
shift
if [[ $# -ge 1 ]]; then
where="$*"
else
where="$curlocs"
fi
for loc in $where; do
thisfile="${CACHEFILEBASE}.${loc}"
cp /dev/null "$thisfile"
for epname in ${ep_name[@]}; do
info "- Caching UUID data for $loc $epname"
epidx=$(getepidx $epname)
api_endpoint="${ep_apiendpoint[$epidx]}"
curlres=$(runcurlget $loc $api_endpoint)
[[ $? -ne 0 ]] && error "curl to $thisurl failed" && return 1
echo "$curlres" | jq -r "try (.[] | [ .${ep_idfield[$epidx]}, .name ] | @csv) catch empty" | tr -d '"' >> "$thisfile"
done
done
kill -SIGUSR1 $mainpid
}
function loadcachefile() { # loadcachefile [dc1 dc2 ...]
local f line dc added
if [[ ! -d "$CACHEDIR" ]]; then
return 1
fi
clear_cache
added=0
if [[ $# -ge 1 ]]; then
for dc in $*; do
f=${CACHEFILEBASE}.${dc}
if [[ -e ${f} ]]; then
while read -r line ; do
adduuid "${line%%,*}" "${line##*,}"
if [[ $? -eq 0 ]]; then
added=$((added + 1))
fi
done < "${f}"
fi
done
addmsgq $(info "Loaded $added UUIDs from cache files for $*" 2>&1)
else
ls ${CACHEFILEBASE}.* >/dev/null 2>&1
if [[ $? -eq 0 ]]; then
for f in ${CACHEFILEBASE}.*; do
while read -r line ; do
adduuid ${line%%,*} ${line##*,}
done < "$f"
done
addmsgq $(info "Loaded cache files for all servers" 2>&1)
fi
fi
}
function uuid_callback() {
nuuids=0
loadcachefile $curlocs
uuidend=$(($(gdate +%s%N)/1000))
uuidsecs=$(echo "scale=2; ($uuidend - $uuidstart) / 1000000;" | bc)
if [[ $nuuids -ge 1 ]]; then
addmsgq $(info "Cached $nuuids UUID(s) for $curlocs in $uuidsecs seconds." 2>&1)
else
addmsgq $(error "No UUIDs found for [$curlocs]" 2>&1)
fi
CACHING=""
trap - SIGUSR1
}
function start_recache_if_needed() {
local dc f needed toload
needed=""
toload=""
for dc in $curlocs; do
f=${CACHEFILEBASE}.${dc}
if [[ -e ${f} ]]; then
toload="$toload $dc"
else
needed="$needed $dc"
fi
done
[[ -n $toload ]] && loadcachefile $toload
[[ -n $needed ]] && start_recache $needed
}
function start_recache() {
local rv
uuidstart=$(($(gdate +%s%N)/1000))
if [[ -z $recache_pid ]]; then
# setup callback for uuid cache reload handling
trap uuid_callback SIGUSR1
info "Initiating UUID cache refresh for $curlocs..."
CACHING="background task: refreshing cache for $curlocs"
cache_uuids $$ $* &
recache_pid=$!
rv=0
else
rv=1
fi
return $rv
}
# populates global retstr
function getstr() { # getstr last_prompt_char default_answer "prompt string"
local lastchar def prompt rv
lastchar="$1"
shift
def="$1"
shift
prompt="$*"
retstr=""
echo -n -e "${PURPLE}$prompt [$BOLD$def$PLAIN$PURPLE]${lastchar}${PLAIN} "
read retstr
echo -e "$PLAIN"
[[ -z $retstr ]] && retstr="$def"
}
# populates global yn, returns 0 for yes, 1 for no
function getyn() { # getyn default_answer "prompt string"
local def prompt rv
def="$1"
shift
prompt="$*"
yn=""
while [[ $yn != "y" && $yn != "n" ]]; do
getstr "?" "$def" "$prompt"
yn="$retstr"
done
if [[ $yn == "y" ]]; then
rv=0
else
rv=1
fi
return $rv
}
function arraycontains() { # arraycontains arrayname lookfor wildcard
local arr lookfor varname wild lookfor_modified
varname="$1"
lookfor="$2"
shift 2
eval arr=( '"${'${varname}'[@]}"' )
if [[ $# -ge 1 ]]; then
lookfor_modified=" $lookfor"
else
lookfor_modified=" $lookfor "
fi
[[ " ${arr[@]} " =~ $lookfor_modified ]] && return 0
return 1
}
function getcmd() {
local prefix pstr input varname rv
varname=$1
if [[ -z $curproj ]]; then
prefix="[no_project_selected]"
else
prefix="[${curproj}]"
fi
#echo "${BOLD}$prefix rosh> ${PLAIN}"
pstr="$prefix gnscli> "
[[ ! -z $CACHING ]] && info "$CACHING"
set -f
builtin read -p "$(echo -en $BOLD)${pstr}$(echo -e "$PLAIN") " -e -r input
set +f
rv=$?
eval "$varname='$input'"
return $rv
}
function listprojects() {
local x format
format=" %-24s|%-36s${PLAIN}\n"
printf "${UNDERLINE}${BOLD}$format" "Project" "UUID"
for x in ${!proj_name[@]}; do
printf "$format" "${proj_name[$x]}" "${proj_id[$x]}"
done
}
function listservers() {
local x format
format=" %-12s|%-24s|%-34s${PLAIN}\n"
printf "${UNDERLINE}${BOLD}$format" "Location" "Host" "API URL"
for x in ${!loc_name[@]}; do
printf "$format" "${loc_name[$x]}" "${loc_engine[$x]}" "${loc_api[$x]}"
done
}
function profile() {
local fname
fname=${FUNCNAME[2]}
[[ -z $fname ]] && fname=${FUNCNAME[1]}
[[ $PROFILING -eq 1 ]] && echo -e "${YELLOW}${BOLD}${FUNCNAME[1]}(): ${PLAIN}${YELLOW}$*${PLAIN}" 1>&2
}
function debug() {
if [[ $VERBOSE -eq 1 ]]; then
echo -e "${PURPLE}${BOLD}${FUNCNAME[1]}(): ${PLAIN}${PURPLE}$*${PLAIN}" 1>&2
fi
}
function info() {
echo -e "${CYAN}${ITALIC}[$*]${PLAIN}" 1>&2
}
function error() {
[[ $innotify -eq 1 ]] && fail
echo -e "${RED}${BOLD}ERROR: ${PLAIN}${RED}$*${PLAIN}" 1>&2
}
function notify() {
echo -en "${PURPLE}${BOLD}* ${PLAIN}${PURPLE}$*...${PLAIN} " 1>&2
innotify=1
}
function notify_nodots() {
echo -e "${PURPLE}${BOLD}* ${PLAIN}${PURPLE}$*${PLAIN} " 1>&2
}
function ok() {
local msg=${*:-ok}
[[ $innotify -eq 0 ]] && return 1
echo -e "$GREEN$msg$PLAIN" 1>&2
innotify=0
}
function fail() {
local msg=${*:-failed}
[[ $innotify -eq 0 ]] && return 1
echo -e "$RED$msg$PLAIN" 1>&2
innotify=0
}
function warn() {
echo -e "${YELLOW}${BOLD}Warning: ${PLAIN}${YELLOW}$*${PLAIN}" 1>&2
}
function usage() {
echo "usage: $0 OPTIONS command"
echo
echo " -h Show this text."
echo " -i Generate initial auth file."
echo " -s server Set initial server."
echo " Valid servers: [${loc_name[@]}]"
echo " -p project Set initial project."
echo " -P Profiling mode."
echo " -v Verbose mode."
echo
}
function addcmd() {
local adm
[[ -z $ncmds ]] && ncmds=0
if [[ $1 == "-a" ]]; then
adm=1
shift
else
adm=0
fi
cmd_name[$ncmds]="$1"
cmd_desc[$ncmds]="$2"
cmd_adm[$ncmds]="$adm"
if [[ $3 == ** ]]; then
cmd_minargs[$ncmds]="${3%%+*}"
cmd_maxargs[$ncmds]="99"
cmd_needargs[$ncmds]=">= ${cmd_minargs[$ncmds]}"
elif [[ $3 == *-* ]]; then
cmd_minargs[$ncmds]="${3%%-*}"
cmd_maxargs[$ncmds]="${3##*-}"
cmd_needargs[$ncmds]="${cmd_minargs[$ncmds]}-${cmd_maxargs[$ncmds]}"
else
cmd_minargs[$ncmds]="$3"
cmd_maxargs[$ncmds]="$3"
cmd_needargs[$ncmds]="$3"
fi
shift 3
cmd_aliases[$ncmds]=" $* "
cmd_usage[$ncmds]=""
cmd_optionname[$ncmds]=""
cmd_optiondesc[$ncmds]=""
cmd_maxoptnamelen[$ncmds]=0
ncmds=$((ncmds + 1))
}
function addcmdalias() {
local defarg
[[ -z $ncmdaliases ]] && ncmdaliases=0
cmdalias_src[$ncmdaliases]="$1"
cmdalias_dst[$ncmdaliases]="$2"
defarg="$3"
cmdalias_defaultarg[$ncmdaliases]="$defarg"
ncmdaliases=$((ncmdaliases + 1))
}
function addmsgq() {
msgq[$nmsgq]="$*"
nmsgq=$((nmsgq + 1))
}
function dumpmsgq() {
local x
for x in ${!msgq[@]}; do
echo -e "${msgq[$x]}"
done
msgq=()
nmsgq=0
}
function clear_cache() {
uuid_id=()
uuid_name=()
nuuids=0
}
function adduuid() { # uuid name
# local i
# add
uuid_id[$nuuids]="${1}"
uuid_name[$nuuids]="${2}"
nuuids=$((nuuids + 1))
# i=$(uuid_to_idx "${1}")
# if [[ $? -eq 0 ]]; then
# # update
# uuid_name[$i]="${2}"
# return 1
# else
# # add
# uuid_id[$nuuids]="${1}"
# uuid_name[$nuuids]="${2}"
# nuuids=$((nuuids + 1))
# return 0
# fi
}
function uuid_to_name() {
local x
#for x in ${!uuid_id[@]}; do
# if [[ ${uuid_id[$x]} == $1 ]]; then
# echo "${uuid_name[$x]}"
# return 0
# fi
#done
# faster
for ((x=0; x<${#uuid_id[@]}; ++x)); do
if [[ "${uuid_id[$x]}" == "$1" ]]; then
echo "${uuid_name[$x]}"
return 0
fi
done
echo "$1"
return 1
}
function uuid_to_idx() {
local x
for x in ${!uuid_id[@]}; do
if [[ ${uuid_id[$x]} == $1 ]]; then
echo "${$x}"
return 0
fi
done
return 1
}
function addloc() {
[[ -z $nlocs ]] && nlocs=0
loc_name[$nlocs]="$1"
if [[ $nlocs -eq 0 ]]; then
curlocs="$1"
fi
loc_engine[$nlocs]="$2"
loc_port[$nlocs]="$3"
loc_api[$nlocs]="http://${loc_engine[$nlocs]}:${loc_port[$nlocs]}/v2"
nlocs=$((nlocs + 1))
}
function addproject() {
[[ -z $nprojects ]] && nprojects=0
proj_id[$nprojects]="$1"
proj_name[$nprojects]="$2"
nprojects=$((nprojects + 1))
}
# Don't use '#'. Use ^ for spaces.
function addepfields() { # addepfields ep_name field1 field2 field3 ...
local idx
idx=$(getepidx $1)
if [[ $? -ne 0 ]]; then
error "appepfields(): can't find endpoint named '$1'"
return 1
fi
shift
while [[ $# -ge 1 ]]; do
[[ -z ${ep_fields[$idx]} ]] && ep_fields[$idx]="${1}" || ep_fields[$idx]="${ep_fields[$idx]}#${1}"
shift
done
}
# Don't use '#'. Use ^ for spaces.
function addeptitles() { # addeptitles ep_name title1 title2 title3 ... # Don't use '#' or spaces.
local idx
idx=$(getepidx $1)
if [[ $? -ne 0 ]]; then
error "appeptitles(): can't find endpoint named '$1'"
return 1
fi
shift
while [[ $# -ge 1 ]]; do
ep_titles[$idx]="${ep_titles[$idx]}#${1}"
shift
done
}
function addendpoint() { # addendpoint ep_name ep_apiendpoint ep_jqobjectname ep_idfieldname [defaultfieldname]
[[ -z $neps ]] && neps=0
ep_name[$neps]="$1"
ep_apiendpoint[$neps]="$2"
ep_jqobj[$neps]="$3"
ep_idfield[$neps]="$4"
ep_defaultfield[$neps]="${5:-name}"
ep_alias[$neps]=""
ep_fields[$neps]=""
ep_titles[$neps]="Server"
ep_validactions[$neps]=""
neps=$((neps + 1))
}
function addepalias() { # addeptitles ep_name alias1 alias2 alias3 ...
local idx
idx=$(getepidx $1)
if [[ $? -ne 0 ]]; then
error "appepalias(): can't find endpoint named '$1'"
return 1
fi
shift
while [[ $# -ge 1 ]]; do
ep_alias[$idx]="${ep_alias[$idx]} ${1} "
shift
done
}
function addepactions() { # addepactions ep_name action_name [action_name2] ...
local idx
idx=$(getepidx $1)
if [[ $? -ne 0 ]]; then
error "appepaction(): can't find endpoint named '$1'"
return 1
fi
shift
while [[ $# -ge 1 ]]; do
ep_validactions[$idx]="${ep_validactions[$idx]} ${1} "
shift
done
}
function getcmdidx() { # getcmdidx commandname adminonly(1|0)
local x adm lookfor match
lookfor=$1
adm=$2
match=0
for x in ${!cmd_name[@]}; do
#echo "check alias '${cmd_aliases[$x]}' against '* $lookfor *'" >/dev/stderr
if [[ ${cmd_name[$x]} == $lookfor ]]; then
match=1
elif [[ ${cmd_aliases[$x]} == *\ $lookfor\ * ]]; then
match=1
else
match=0
fi
if [[ $match -eq 1 ]]; then
if [[ -z $adm || ${cmd_adm[$x]} == $adm ]]; then
echo "$x"
return 0
fi
fi
done
return 1
}
function addcmdusage() { # addcmdusage cmd_name "usage goes here" "line 2" "line 3" ...
local cname idx text
cname="$1"
shift
idx=$(getcmdidx $cname)
[[ $? -ne 0 ]] && return 1
text=""
while [[ $# -ge 1 ]]; do
[[ -z $text ]] && text="$1" || text="$text\n$1"
shift 1
done
cmd_usage[$idx]="$text"
}
function addcmdoption() { # addcmdusage cmd_name "usage goes here" "line 2" "line 3" ...
local cname idx text
cname="$1"
shift
idx=$(getcmdidx $cname)
if [[ $? -ne 0 ]]; then
error "Tried to addcmdoption() for nonexistant command '$cname'"
kill $$
fi
while [[ $# -ge 1 ]]; do
[[ -z ${cmd_optionname[$idx]} ]] && cmd_optionname[$idx]="$1" || cmd_optionname[$idx]="${cmd_optionname[$idx]}#$1"
[[ ${#1} -gt ${cmd_maxoptnamelen[$idx]} ]] && cmd_maxoptnamelen[$idx]=${#1}
shift 1
[[ -z ${cmd_optiondesc[$idx]} ]] && cmd_optiondesc[$idx]="$1" || cmd_optiondesc[$idx]="${cmd_optiondesc[$idx]}#$1"
shift 1
done
}
function showcmdhelp() {
local real_cname cname idx text ln x format optname_arr optdesc_arr len
cname="$1"
shift
idx=$(getcmdidx $cname)
if [[ $? -ne 0 ]]; then
error "Invalid command '$cname'"
return 1
fi
real_cname="${cmd_name[$idx]}"
[[ ${cmd_adm[$idx]} -eq 1 ]] && real_cname="\\${real_cname}"
# make array of options
IFS='#' read -r -a optname_arr <<< "${cmd_optionname[$idx]}"
IFS='#' read -r -a optdesc_arr <<< "${cmd_optiondesc[$idx]}"
len=${cmd_maxoptnamelen[$idx]}
echo -n "Usage: ${real_cname} "
[[ $len -gt 0 ]] && echo -n "[OPTIONS] "
# show usage if we have it
if [[ -n ${cmd_usage[$idx]} ]]; then
echo -e "${cmd_usage[$idx]}" | awk '(NR == 1) { print }'
else
echo
fi
echo " ${cmd_desc[$idx]}."
if [[ -n ${cmd_usage[$idx]} ]]; then
echo
echo -e "${cmd_usage[$idx]}" | awk '(NR > 1) { printf(" %s\n",$0); }'
fi
# show options
if [[ $len -gt 0 ]]; then
echo
format=" %-${len}s %s\n"
for x in ${!optname_arr[@]}; do
printf "$format" "${optname_arr[$x]}" "${optdesc_arr[$x]}"
done
fi
# Show aliases
if [[ -n ${cmd_aliases[$idx]// /} ]]; then
echo
echo "Aliases for '${real_cname}': ${cmd_aliases[$idx]}"
fi
echo
}
function getprojidx() { # getprojidx project_name_or_uuid
local x
for x in ${!proj_name[@]}; do
if [[ ${proj_name[$x]} == $1 || ${proj_id[$x]} == $1 ]]; then
echo "$x"
return 0
fi
done
return 1
}
function getlocidx() { # getlocidx <syd|mlb|etc>
local x
for x in ${!loc_name[@]}; do
if [[ ${loc_name[$x]} == $1 ]]; then
echo "$x"
return 0
fi
done
return 1
}
function getlocidx_byregion() { # getlocidx <au|uk|etc>
local x
for x in ${!loc_region[@]}; do
if [[ ${loc_region[$x]} == $1 ]]; then
echo "$x"
return 0
fi
done
return 1
}
function getalllocs() {
echo ${loc_name[@]}
}
function get() { # getloc <syd|mlb|etc> <name|grid|api|etc>
local idx
idx=$(getlocidx $1)
if [[ $? -ne 0 ]]; then
return 1
fi
eval "echo \${loc_${2}[$idx]}"
}
function runcurlget() { # location api_endpoint(ovname)
local thisapi thisurl curlres rv
local thisauthdom u p thisauthstr
local loc api_andpoint
loc="$1"
api_endpoint="$2"
thisapi=$(get $loc api)
[[ -z $thisapi ]] && echo "cant find api for '$loc'" && return 1
thisurl="$thisapi/$api_endpoint"
thisurl=${thisurl/_CURPROJECT_/$curprojid}
debug "url is $thisurl"
#thisauthdom=$(get $loc authdomain)
#u=$(head -1 "$AUTHFILE")
#p=$(tail -1 "$AUTHFILE")
#thisauthstr="$u@$thisauthdom:$p"
#unset u
#unset p
debug "curl -sk --header 'Accept: application/json' $thisurl"
curlres=$(curl -sk --header 'Accept: application/json' $thisurl)
rv=$?
[[ $rv -ne 0 ]] && error "curl to $thisurl failed"
echo "$curlres"
return $rv
}
function runcurlaction() { # location api_endpoint(ovname) obname ovmethod
local thisapi thisurl curlres rv
#local thisauthdom u p thisauthstr
local loc api_endpoint obname ovmethod
#local data="<action/>"
local data=""
loc="$1"
api_endpoint="$2"
obname="$3"
ovmethod="$4"
thisapi=$(get $loc api)
[[ -z $thisapi ]] && echo "cant find api for '$loc'" && return 1
#thisauthdom=$(get $loc authdomain)
thisurl="$thisapi/$api_endpoint/$obname/$ovmethod"
thisurl=${thisurl/_CURPROJECT_/$curprojid}
#u=$(head -1 "$AUTHFILE")
#p=$(tail -1 "$AUTHFILE")
#thisauthstr="$u@$thisauthdom:$p"
#unset u
#unset p
[[ $VERBOSE -eq 1 ]] && debug "curl -XPOST -sk --header 'Content-type: application/xml' --header 'Accept: application/json' -d \"$data\" $thisurl"
curlres=$(curl -XPOST -sk --header 'Content-type: application/xml' --header 'Accept: application/json' -d "$data" $thisurl)
rv=$?
[[ $rv -ne 0 ]] && error "curl to $thisurl failed"
echo "$curlres"
return $rv
}
# getarrayopt arrname f
#
# ie. if myvar=("aaa" "-z" "-ofirst_test" "123" "-osecond_test"
#
# then "getarrayopt myvar o" will output "first_test"
function getarrayopt() { # getarrayopt arrname optname
local arrname optname arrtext optval rv newarr
local localdb
localdb=0
arrname="$1"
optname="$2"
[[ $localdb -eq 1 ]] && info "look for -$optname in \$$arrname"
# fix in case array indices aren't sequential
arrtext=$(eval "echo \"\${${arrname}[@]}\"")
newarr=( $arrtext )
[[ $localdb -eq 1 ]] && info "arrtext is '$arrtext'"
if arraycontains $arrname "-$optname" WILDCARD; then
local this
# get array index of given option
idx=$(echo "${arrtext}" | awk -v lookfor="-$optname" '{ for (x=1;x<=NF;x++) { if (index($x,lookfor) == 1) print (x-1); } }')
[[ $localdb -eq 1 ]] && info " found at idx $idx"
this=${newarr[$idx]}
optval=${this##*-$optname} # strip "-x" from the start
optval=${optval%% *} # strip all other options from the end
[[ $localdb -eq 1 ]] && info " optval is $optval"
rv=0
else
[[ $localdb -eq 1 ]] && info " not found"
optval=""
rv=1
fi
echo "$optval"
return $rv
}
# convert action name into gns method name
function getgnsmethod() {
local rv=0
case "$1" in
open) echo "open";;
close) echo "close";;
start) echo "start";;
stop) echo "stop";;
#migrate) echo "migrate";;
*)
echo
rv=1;
;;
esac
return $rv
}
function runaction_loc() { # runaction_loc syd|etc api_endpoint action_name ob_uuid ob_name [options]
local thisgrid thisauthdom thisurl thisauthstr curlres loc u p
local api_endpoint jq_obj opts opts_arr deffield epidx r1 r2 rv
local actionname method trv extrainfo
rv=0
loc="$1"
epidx="$2"
actionname="$3"
ob="$4"
obname="$5"
extrainfo="$6"
thisgrid=$(get $loc grid)
api_endpoint="${ep_apiendpoint[$epidx]}"
jq_obj="${ep_jqobj[$epidx]}"
deffield="${ep_defaultfield[$epidx]}"
shift 6
opts="$*"
opts_arr=( $opts )
# special case
if [[ $actionname == "connect" ]]; then
local locidx
# todo: only a single curloc, not multiple
locidx=$(getlocidx $curlocs)
if [[ $? -ne 0 ]]; then
error "Couldn't determine index of current location '$curlocs'"
return 1
fi
tmux ls 2>&1 | egrep -q "^$curproj:"
if [[ $? -eq 0 ]]; then
debug "using existing tmux session with: tmux neww -S -n $obname telnet ${loc_engine[$locidx]} ${extrainfo}"
# create or attach to window
tmux neww -S -n $obname telnet ${loc_engine[$locidx]} ${extrainfo}
trv=$?
else
debug "making new tmux session with: tmux new -s gnscli -n $obname -d telnet ${loc_engine[$locidx]} ${extrainfo}"
# create a new session and window
tmux new -s $curproj -n $obname -d telnet ${loc_engine[$locidx]} ${extrainfo}
trv=$?
fi
if [[ $trv -eq 0 ]]; then
tmux a -t $curproj
else
error "tmux call failed"
return 1
fi
else
method=$(getgnsmethod "$actionname")
if [[ $? -ne 0 ]] ; then
error "Can't determine gns API method for command '$action'"
return 1
fi
debug "location: ${loc}"
debug "api endpoint: ${api_endpoint}"
debug "jq obj: ${jq_obj}"
debug "actionname: ${actionname} (gns method: $method)"
debug "obname: ${ob}"
debug "options: ${opts}"
curlres=$(runcurlaction $loc $api_endpoint $ob $method )
if [[ $? -ne 0 ]]; then
rv=1
fi
debug "curlres is [$curlres]"
echo "$curlres"
fi
return $rv
}
function getdata_loc() { # getdata_loc syd|etc api_endpoint [options]
local thisgrid thisauthdom thisurl thisauthstr curlres loc u p
local api_endpoint jq_obj opts opts_arr deffield epidx r1 r2 rv
rv=0
loc="$1"
epidx="$2"
thisgrid=$(get $loc grid)
api_endpoint="${ep_apiendpoint[$epidx]}"
jq_obj="${ep_jqobj[$epidx]}"
deffield="${ep_defaultfield[$epidx]}"
shift 2
opts="$*"
opts_arr=( $opts )
debug "options: ${opts}"
curlres=$(runcurlget $loc $api_endpoint)
[[ $? -ne 0 ]] && error "curl to $loc API failed" && return 1
debug "outmode is $outmode"
if [[ $outmode == "raw" ]]; then
[[ ! -z $curlres ]] && echo "$curlres" || rv=2
elif [[ $outmode == "verbose" ]]; then
#if [[ $locnum -eq 1 ]]; then
# # bold+underline the heading, and store in a temp file
# echo "$curlres" | jq -r "$jqfilter" 2>/dev/null | tr -d '"' | awk '{ if (NR == 1) { printf "\033[1m\033[4m" $0 "\033[0m\n" } }' >$HEADINGFILE
#fi
debug "jqfilter is $jqfilter"
debug "r1 pre jqfilter is $curlres"
r1=$(echo "$curlres" | jq -r "$jqfilter" 2>/dev/null | tr -d '"')
debug "r1 post jqfilter is $r1"
#if [[ ! -z $regexpfilter ]]; then
# [[ $VERBOSE -eq 1 ]] && debug "r1 pre regexpfilter is $r1"
# r1=$(echo "$r1" | awk -v filter="$regexpfilter" '(NR==1){ print } (NR !=1 && (match($0,filter))) { print }')
#fi
[[ ! -z $r1 ]] && echo "$r1" || rv=2
else # namesonly
r1=$(echo "$curlres")
echo "$r1" >/tmp/out
if [[ ! -z $jqfilter ]]; then
debug "jqfilter is $jqfilter"
debug "r1 pre jqfilter is $r1"
r1=$(echo "$r1" | jq -r "$jqfilter" 2>/dev/null)
debug "r1 post jqfilter is $r1"
fi
if [[ ! -z $jqscript ]]; then
debug "jqscript is $jqscript"
debug "r1 pre jqscript is $r1"
r1=$(echo "$r1" | jq -rf "$jqscript")
fi
# if [[ ! -z $regexpfilter ]]; then
# debug "r1 pre regexpfilter is $r1"
# r1=$(echo "$r1" | awk -v filter="$regexpfilter" '(NR==1){ print } (NR !=1 && (match($0,filter))) { print }')
# fi
if [[ ! -z $awkscript ]]; then
debug "r1 pre awkscript is $r1"
r1=$(echo "$r1" | awk -f "$awkscript")
fi
debug "semi-final r1 is [$r1]"
if [[ $cmd == "list" ]]; then
debug "Global cmd is $GLOBALCMD"
if [[ $GLOBALCMD == "action" ]]; then
# ie. we end up with each line being:
# location,object,object_uuid
#r1=$(echo "$r1" | egrep -v "^$" | sed "s/^/$loc,$thisgrid,/")
r1=$(echo "$r1" | egrep -v "^$" | sed "s/^/$loc,/")
else
#r1=$(echo "$r1" | egrep -v "^$" | sed "s/^/$(echo -e "$BOLD")\[$thisgrid\] $(echo -e "$PLAIN")/")
r1=$(echo "$r1" | egrep -v "^$")
fi
fi
debug "final r1 is [$r1]"
[[ -n $r1 ]] && echo "$r1" | sed 's/__END__://' || rv=2
fi
#echo "$curlres" | jq -r "$jqfilter" | sed "s/^/$(echo -e "$BOLD")\[$thisgrid\] $(echo -e "$PLAIN")/"
debug "about to exit with code $rv"
return $rv
}
function getlocidx() { # getlocidx locname
local x lookfor
lookfor="$1"
for x in ${!loc_name[@]}; do
if [[ ${loc_name[$x]} == $lookfor ]]; then
echo "$x"
return 0
fi
done
return 1
}
function getepidx() { # getepidx epname
local x lookfor
lookfor="$1"
for x in ${!ep_name[@]}; do
if [[ ${ep_name[$x]} == $lookfor || ${ep_alias[$x]} == *\ $lookfor\ * ]]; then
echo "$x"
return 0
elif [[ ${ep_name[$x]} == ${lookfor}s || ${ep_alias[$x]} == *\ ${lookfor}s\ * ]]; then
echo "$x"
return 0
fi
done
return 1
}
function getepjqobj() {
local x
for x in ${!ep_name[@]}; do
if [[ ${ep_name[$x]} == $1 || ${ep_alias[$x]} == *\ $1\ * ]]; then
echo "${ep_jqobj[$x]}"
return 0
fi
done
return 1
}
function getepovname() {
local x
for x in ${!ep_name[@]}; do
if [[ ${ep_name[$x]} == $1 || ${ep_alias[$x]} == *\ $1\ * ]]; then
echo "${ep_apiendpoint[$x]}"
return 0
fi
done
return 1
}
function getbasefilter() { # epidx [regexp_filter]
local bf x first epidx local refilter defaultvalue="unknown" thisdefault
local filterarg
epidx="$1"
shift
filterarg="$*"
[[ $filterarg == "*" ]] && filterarg=".*"
[[ -n "$*" ]] && refilter="select(.name|test(\"^$*$\"))" || refilter="."
IFS='#' read -r -a titles <<< "${ep_titles[$epidx]}"
IFS='#' read -r -a fields <<< "${ep_fields[$epidx]}"
bf=""
for x in ${titles[@]}; do
x=${x//^/ }
[[ -z $bf ]] && bf="([" || bf="$bf, "
bf="${bf}\"${x}\""
done
#bf="${bf}] ), (.$jq_obj[] | $refilter | [\"_LOCATION_\""
bf="${bf}] ), (.[] | $refilter | [\"_LOCATION_\""
first=1
for x in ${fields[@]}; do
thisdefault="defaultvalue"
[[ $x == ".status" ]] && thisdefault="project_closed"
x=${x//^/ }
#bf="${bf}, try(${x}) catch(\"xx\")"
bf="${bf}, ${x} // \"${thisdefault}\""
done
bf="${bf}]) | @csv"
echo "${bf}"
}
function profile_start() {
profile "start profiling"
prof_start=$(($(gdate +%s%N)/1000))
prof_last="$prof_start"
}
function profile_mark() {
local now secs
now=$(($(gdate +%s%N)/1000))
secs=$(echo "scale=2; ($now - $prof_last) / 1000000;" | bc)
profile "[+${secs} secs] $* "
prof_last="$now"
}
# Output CSV contents as a nicely formatted table.
# If a heading row is detected, it is bolded and underlines.
# The last table line is also underlined.
# A blank line is printed before each change of first column value - this is generally the data centre name.
function csv_to_table() { # csv_to_table num_data_lines_excluding_heading "csv_string_with_newlines"
local ndatalines msg
local table
local data ln
local uuidcolumn uuidcolumns thisuuid repl line
local col colidx
profile_start
ndatalines="$1"
shift 1
msg="$*"
# table=$(echo -e "$msg" | column -t -s, | awk -v ndatalines=$ndatalines '{ if (match($0, "DC|TOTAL") == 1) { hdgline=1; } else { hdgline=0 } if (!hdgline && pfw != "" && $1 != pfw) { print " " } if (NR == 1 ) { printf "\033[1m\033[4m" $0 "\033[0m\n" } else if (NR == ndatalines ) { printf "\033[4m" $0 "\033[0m\n"} else if (!hdgline || index($0,"TOTAL") == 1) { print } if (!hdgline) { pfw=$1; } }')
table=$(echo -e "$msg" | column -t -s, | awk -v ndatalines=$ndatalines 'BEGIN { max=0 } { if (length($0) > max) max=length($0); } { if (match($0, "Server|TOTAL") == 1) { hdgline=1; } else { hdgline=0 } if (!hdgline && pfw != "" && $1 != pfw) { print " " } if (NR == 1 ) { printf "\033[1m\033[4m" $0 "\033[0m\n" } else if (NR == ndatalines) { print; for (n=0;n<max;n++) printf("-"); printf("\n"); } else if (!hdgline || index($0,"TOTAL") == 1) { print } if (!hdgline) { pfw=$1; } }')
profile_mark "made initial table"
# Find out which columns need a UUID lookup
if [[ $usecache -eq 1 ]]; then
uuidcolumns=$(csv_find_uuid_cols "$msg")
table=${table//_UUID/ }
else
uuidcolumns=""
fi
profile_mark "found uuid columns"
# At this point, $table is the full table table without UUID replacement
ln=1
table2=""
while read -r line ; do
if [[ $ln -gt 1 ]]; then
# Replace UUIDS with names from cache
for uuidcolumn in $uuidcolumns; do
uuidcolumn=$((uuidcolumn - 1))
#if [[ $ln -eq $ndatalines ]]; then
# # Account for underline ANSI code
# uuidcolumn=$(( $uuidcolumn + $UNDERLINE_LENGTH))
#fi
thisuuid=${line:${uuidcolumn}:${UUIDLENGTH}}
debug "uuidcol $uuidcolumn: thisuuid is [$thisuuid]"
repl=$( printf "%-${UUIDLENGTH}s" "$(uuid_to_name $thisuuid)")
debug "repl is $repl"
[[ -n $repl ]] && line="${line/${thisuuid}/$repl}"
done
fi
table2="${table2}${line}\n"
ln=$((ln + 1))
done <<< "$table"
profile_mark "replaced uuids with names"
# save RAM in case of super big data
unset table
profile_mark "created colourisation sed script"
table2=$(echo "$table2" | gsed "$ADD_COLOURS")
echo -e "$table2"
profile_mark "added colours"
}
# Process the first line of CSV contents and try to figure out which
# columnds contain an object UUID based on the heading.
function csv_find_uuid_cols() {
echo "$*" | column -t -s, | egrep "^(Server)" | head -1 | awk 'BEGIN {start=1} { idx=1; while (idx>0) { idx=match(substr($0,start), "[a-zA-Z-]+_UUID"); if (idx>0) { uuidcols = uuidcols " " start+idx-1; start+=(idx+RLENGTH); } } } END { print uuidcols }'
}
function runaction() { # runaction <nodes|vms|etc> <actionname> targetlist options
local targetlist
local loc ob obuuid what api_endpoint jq_obj
local start end opts opts_a
local x epidx
local actionname line all
local force=0 foreground=0 f rv files n thisfile jqres goterror
local good allgoodresults errs allbadresults
local jqf='[ "_DC_", .job.id, "_OB_", .status ] | @csv'
local jqf_bad='[ "_DC_", "_OB_", .fault.detail, .fault.reason, .status ] | @csv'
local objecttype extrainfo
start=$(($(gdate +%s%N)/1000))
lastqsecs=""
lastprocsecs=""
what="$1"
shift
actionname="$1"
shift
targetlist="$1"
shift
opts="$*"
opts_a=( $* )
epidx=$(getepidx $what)
[[ $? -ne 0 ]] && error "unknown endpoint '$what'" && return 1
jq_obj="${ep_jqobj[$epidx]}"
api_endpoint="${ep_apiendpoint[$epidx]}"
[[ -z $api_endpoint ]] && error "no endpointname for endpoint '$what'" && return 1
debug "what = $what"
debug "actionname = $actionname"
debug "targetlist = $targetlist"
debug "opts is $opts"
arraycontains opts_a "-f" && force=1 || force=0
arraycontains opts_a "-F" && foreground=1 || foreground=0
if [[ $foreground -eq 1 ]]; then
# just use one line
line=$(echo "$targetlist" | head -1)
loc=$(echo "$line" | cut -d, -f1)
ob=$(echo "$line" | cut -d, -f2)
obuuid=$(echo "$line" | cut -d, -f3)
extrainfo=$(echo "$line" | cut -d, -f4)
runaction_loc $loc $epidx $actionname $obuuid $ob $extrainfo $opts > "$TMPDIR/run,$loc,$ob"
else
pids=""
while read -r line ; do
loc=$(echo "$line" | cut -d, -f1)
ob=$(echo "$line" | cut -d, -f2)
obuuid=$(echo "$line" | cut -d, -f3)
extrainfo=$(echo "$line" | cut -d, -f4)
runaction_loc $loc $epidx $actionname $obuuid $ob $extrainfo $opts > "$TMPDIR/run,$loc,$ob" &
pids="$pids $!"
done <<< "$targetlist"
wait $pids
files=( $TMPDIR/run,* )
unset ACTIONRES_LOC
unset ACTIONRES_MSG
n=0
errs=0
good=0
rv=0
# Capitalise first letter
objecttype=$(echo ${what:0:1} | tr '[a-z]' '[A-Z]')${what:1}
allgoodresults="Server,${objecttype},Job Status"
allbadresults="Server,${objecttype},Detail,Reason,Status"
for f in ${!files[@]} ; do
local thiscsv
thisfile="${files[$f]}"
debug "processing file $thisfile"
debug "contents: $(cat $thisfile)"
ACTIONRES_LOC[$n]=$(echo "$thisfile" | cut -d, -f2)
ACTIONRES_OB[$n]=$(echo "$thisfile" | cut -d, -f3)
jqres=$(cat ${thisfile} | jq . 2>/dev/null)
if [[ $? -eq 0 ]]; then
ACTIONRES_MSG[$n]="$jqres"
else
ACTIONRES_MSG[$n]=$(cat ${thisfile})
fi
if [[ ${ACTIONRES_MSG[$n]} == *detail*reason* ]]; then
goterror=1
elif [[ ${ACTIONRES_MSG[$n]} == *rror* ]]; then
goterror=1
else
goterror=0
fi
if [[ $goterror -eq 1 ]]; then
thiscsv_bad=$(echo "${ACTIONRES_MSG[$n]}" | jq -r "${jqf_bad}" | sed -e "s/_DC_/${ACTIONRES_LOC[$n]}/g" | sed -e "s/_OB_/${ACTIONRES_OB[$n]}/" | tr -d '"[]' | egrep -v "^$")
allbadresults="${allbadresults}\n${thiscsv_bad}"
errs=$((errs + 1))
else
thiscsv=$(echo "${ACTIONRES_MSG[$n]}" | jq -r "${jqf}" | sed -e "s/_DC_/${ACTIONRES_LOC[$n]}/g" | sed -e "s/_OB_/${ACTIONRES_OB[$n]}/" | tr -d '"' | egrep -v "^$")
allgoodresults="${allgoodresults}\n${thiscsv}"
good=$((good + 1))
#echo -e "$GREEN" >/dev/stderr
fi
n=$((n + 1))
done
echo
if [[ $errs -gt 0 ]]; then
local fullres_bad
echo -e "${RED}$errs x '${BOLD}$actionname${PLAIN}${RED}' actions failed.${PLAIN}"
fullres_bad=$(csv_to_table $(($errs + 1)) "$allbadresults")
echo "$fullres_bad"
echo
fi
if [[ $good -ge 1 ]]; then
local fullres
echo -e "${GREEN}$good x '${BOLD}$actionname${PLAIN}${GREEN}' actions submitted successfully.${PLAIN}"
fullres=$(csv_to_table $(($good + 1)) "$allgoodresults")
echo "$fullres"
fi
if [[ $good -ge 1 ]]; then
rv=0
else
rv=1
fi
rm -f $TMPDIR/run,*
fi
end=$(($(gdate +%s%N)/1000))
lastqsecs=$(echo "scale=2; ($end - $start) / 1000000;" | bc)
return $rv
}
function validate_action() { # nodes/vms/etv actionname
local what acion epidx
what="$1"
action="$2"
epidx=$(getepidx $what)
[[ $? -ne 0 ]] && return 1
[[ ${ep_validactions[$epidx]} != *\ $action\ * ]] && return 1
return 0
}
function getdata() { # getdata <nodes|vms|etc> <cmd> options
local loc what api_endpoint jq_obj
local start end opts opts_a parallel
local idx thisopt outfile datawithheading bf
local basefilter outmode bitswewant x epidx titles fields
local heading cols wantcols
local cmd fullres line everything uuidcols ln
local usecache refilter
local w greenwords yellowwords redwords uuidcol
local errordebug=0
local quiet=0
start=$(($(gdate +%s%N)/1000))
lastqsecs=""
lastprocsecs=""
what="$1"
shift
cmd="$1"
shift
set -f
opts="$*"
opts_a=( $* )
set +f
epidx=$(getepidx $what)
[[ $? -ne 0 ]] && error "unknown endpoint '$what'" && return 1
jq_obj="${ep_jqobj[$epidx]}"
api_endpoint="${ep_apiendpoint[$epidx]}"
[[ -z $api_endpoint ]] && error "no endpointname for endpoint '$what'" && return 1
#outmode=namesonly # global
if [[ $cmd == "show" ]]; then
outmode=namesonly # global
else
outmode=verbose # global
fi
arraycontains opts_a "-r" && outmode=raw
arraycontains opts_a "-q" && quiet=1
arraycontains opts_a "-v" && outmode=verbose
arraycontains opts_a "-s" && outmode=namesonly
arraycontains opts_a "-n" && usecache=0 || usecache=1
arraycontains opts_a "-e" && errordebug=1
debug "opts is $opts"
outfile=$(getarrayopt opts_a o)
refilter=$(getarrayopt opts_a f)
[[ $refilter == "*" ]] && refilter=".*"
[[ -n $refilter ]] && obfilter="select(.name|test(\"^$refilter$\"))" || obfilter="."
[[ -n $refilter ]] && [[ $quiet -eq 0 ]] && info "${what} filter: ^$refilter$"
# Allow standard globs rather than regexp globs
debug "refilter is $refilter"
# determine jq filter based on object name
jqfilter=""
jqscript=""
awkscript=""
if [[ $cmd == "list" ]]; then
if [[ $outmode == "verbose" ]]; then
if [[ -z ${ep_fields[$epidx]} ]]; then
error "Verbose mode not supported for the '$api_endpoint' endpoint"
return 1
else
#bitswewant=" [ .name, .address, .status, .spm.status ]"
basefilter=$(getbasefilter $epidx "$refilter")
#basefilter="([\"DC\", \"Node \",\"IP \",\"Status\",\"SPM\"] ), (.$jq_obj[] | [\"_LOCATION_\", .name, .address, .status, .spm.status]) | @csv"
fi
elif [[ $outmode == "namesonly" ]]; then
basefilter=".[] | $obfilter | .${ep_defaultfield[$epidx]}"
debug "(list) basefilter is $basefilter"
if [[ $GLOBALCMD == "action" ]]; then
basefilter="${basefilter} + \",\" + .${ep_idfield[$epidx]}"
if [[ ${ep_name[$epidx]} == "nodes" ]]; then
basefilter="${basefilter} + \",\" + (.console|tostring)"
fi
fi
fi
elif [[ $cmd == "show" ]]; then
if [[ $outmode == "verbose" ]]; then
error "Verbose mode not supported for show command yet"
return 1
else # namesonly
#basefilter=".$jq_obj[] | select(.name|test(\"${arg_array[1]}\"))"
basefilter=".[] | $obfilter"
#basefilter=".[]"
jqscript="$SCRIPTDIR/${jq_obj}.jq"
awkscript="$SCRIPTDIR/detail.awk"
debug "jqscript is $jqscript"
debug "awkscript is $awkscript"
if [[ ! -z $jqscript && ! -e $jqscript ]]; then
error "'show' not yet implemented for object type '$jq_obj'"
return 1
fi
fi
fi
locnum=1
if [[ -z $outfile ]]; then
pids=""
notify "Obtaining data from gns API"
for loc in $curlocs; do
[[ ! -z $basefilter ]] && jqfilter=${basefilter/_LOCATION_/${loc}}
[[ $VERBOSE -eq 1 ]] && debug "jqfilter is $jqfilter"
[[ $VERBOSE -eq 1 ]] && debug "jqscript is $jqscript"
[[ $VERBOSE -eq 1 ]] && debug "awkscript is $awkscript"
#echo "jqfilter is:" >/dev/stderr
#echo "$jqfilter" >/dev/stderr
debug " starting getdata $loc $epidx $opts"
getdata_loc $loc $epidx $opts > "$TMPDIR/get.$loc" 2>"$TMPDIR/err.$loc" &
pids="$pids $!"
locnum=$((locnum + 1))
done
wait $pids
debug all pids finished
all=$(cat "$TMPDIR"/get.* 2>/dev/null | tr -d : | egrep -v 'DC|Server' | sed '/^[[:space:]]*$/d')
if [[ -z $all ]]; then
fail
cat "$TMPDIR"/err.*
rm -f "$TMPDIR"/get.*
rm -f "$TMPDIR"/err.*
return 1
fi
ok
if [[ $errordebug -eq 1 ]]; then
if [[ $( ls "$TMPDIR"/err.* | wc -l | bc) -gt 0 ]]; then
cat "$TMPDIR"/err.* >/dev/stderr
fi
fi
debug "combined results: [$all]"
end=$(($(gdate +%s%N)/1000))
lastqsecs=$(echo "scale=2; ($end - $start) / 1000000;" | bc)
start=$(($(gdate +%s%N)/1000))
rescount=$(printf %d $(echo -n "$all" | wc -c))
if [[ $rescount -eq 0 ]]; then
echo -e " ${BOLD}${YELLOW}(no results)${PLAIN}"
echo
rm -f $TMPDIR/get.*
rm -f $TMPDIR/err.*
elif [[ $outmode == "verbose" && $cmd == "list" ]]; then
# totals
# get col numbers which we need to total
heading=$(cat $TMPDIR/get.* | head -1 | tr ',' ' ')
debug "heading is [$heading]"
cols=( $heading )
wantcols=""
for x in ${!cols[@]}; do
debug " checking ${cols[$x]}"
if [[ ${cols[$x]} =~ CPUs|RAM|VMs ]]; then
wantcols="$wantcols,$((x + 1))"
fi
done
debug "wantcols is [$wantcols]"
if [[ ! -z $wantcols ]]; then
wantcols="$wantcols,"
# generate totals line
cat $TMPDIR/get.* | column -t -s, | awk -vCOLS="${wantcols}" '{ gsub(" GB", "_GB"); if (index($0,"Server") == 1 && maxf == "") { maxf = NF } else { split(COLS, c, ","); for (x in c) { if (c[x]) { this = $(c[x]); rv=gsub("_GB","",this); if (rv!=0) { isgb[c[x]]=1; } tot[c[x]] += this; } } } gsub("_GB"," GB"); } END { line=""; for (i=1; i<=maxf; i++) { if (i==1) { str="TOTAL:"} else if (index(COLS, "," i ",")) { str = tot[i]; } else { str=" "} line = line sprintf("%s%s,",str, isgb[i] ? " GB" : ""); } printf("%s\n",line); }' > "$TOTFILE"
ndatalines=$(printf %d $(cat $TMPDIR/get.* | wc -l))
else
cp /dev/null "$TOTFILE"
ndatalines=-1
fi
# print combined data (with only one heading row) and totals
#everything=$(cat $TMPDIR/get.* "$TOTFILE" | column -t -s,)
everything=$(cat $TMPDIR/get.* "$TOTFILE")
notify "Processing data"
fullres=$(csv_to_table $ndatalines "$everything")
ok && echo 1>&2
echo "$fullres"
debug "fullres is: [$fullres]"
rm -f $TOTFILE
else
# ie. show
fullres=$(cat $TMPDIR/get.*)
if [[ $outmode == "raw" ]]; then
echo "$fullres"
else
local updated_result
# Replace UUIDS with names from cache
# and colourise certain words
greenwords="true up enabled"
redwords="false down paused uninitialized disabled"
yellowwords="migrating"
uuidcol="$PURPLE"
updated_result=""
while read -r line ; do
key=${line%%:*}
val=${line##*:}
val=$(echo "$val" | sed -e 's/^.* //')
if [[ $key =~ [A-Za-z]+\ UUID ]]; then
repl="$(uuid_to_name ${val})"
if [[ $? -eq 0 ]]; then
line="${line/${val}/$repl} (${uuidcol}${val}${PLAIN})"
else
line="${line/$val/${uuidcol}${val}${PLAIN}}"
fi
#if [[ -n $repl ]]; then
# #line=$(echo "${line}" | sed -e 's/[A-Za-z]\+ UUID/UUID/')
# line="${line/${val}/$repl}"
#fi
elif [[ $key =~ UUID ]]; then
# colourise the entire key
line="${line/${val}/${uuidcol}${val}$PLAIN}"
fi
updated_result="${updated_result}${line}\n"
done <<< "$fullres"
fi
#cat $TMPDIR/get.*
fi
updated_result=$(echo "$updated_result" | gsed "$ADD_COLOURS")
echo -e "$updated_result"
rm -f $TMPDIR/get.*
else
# serial
cp /dev/null "$outfile"
notify "Obtaining data from gns API into file"
for loc in $curlocs; do
jqfilter=${basefilter/_LOCATION_/${loc}}
getdata_loc $loc $epidx $opts >> "$outfile"
locnum=$((locnum + 1))
done
if [[ $errordebug -eq 1 ]]; then
if [[ $( ls "$TMPDIR"/err.* | wc -l | bc) -gt 0 ]]; then
cat "$TMPDIR"/err.* >/dev/stderr
fi
fi
ok
#if [[ -e ${HEADINGFILE} ]]; then
# datawithheading=$(cat ${HEADINGFILE} ${outfile})
# echo "${datawithheading}" > "$outfile"
# rm -f $HEADINGFILE
#fi
info $(printf "%d line(s) written to %s" $(cat "$outfile" | wc -l) "$outfile" )
fi
#end=$(($(gdate +%s%N)/1000))
#lastqsecs=$(echo "scale=2; ($end - $start) / 1000000;" | bc)
end=$(($(gdate +%s%N)/1000))
lastprocsecs=$(echo "scale=2; ($end - $start) / 1000000;" | bc)
}
function listcommands() {
local x wantadm
if [[ $1 == "ADMIN" ]]; then
wantadm=1
prefix='\'
else
wantadm=0
prefix=""
fi
for x in ${!cmd_name[@]}; do
if [[ ${cmd_adm[$x]} -eq $wantadm ]]; then
printf " %-10s %s\n" "${prefix}${cmd_name[$x]}" "${cmd_desc[$x]}"
if [[ $wantadm -eq 0 && ${cmd_name[$x]} == "help" ]]; then
# show that there is admin help available
printf " %-10s %s\n" "\\help" "List admin commands."
fi
fi
done
}
function generate_random_string() {
local segmentlen=5 x str="" thissegment
for x in {1..5}; do
thissegment=$(LC_CTYPE=C tr -dc a-z0-9 < /dev/urandom | fold -w ${segmentlen} | head -n 1)
[[ -n $str ]] && str="${str} "
str="${str}${thissegment}"
done
echo "$str"
}
function expand_cmdalias() { # expand_cmdalias "cmd" newcmd_var newargs_var "args"
local origcmd newcmd x firstword rest defarg changed=0 args
local newarg_varname newargs
local newcmd_varname
local repl_cmd repl_args
origcmd="$1"
shift
newcmd_varname="$1"
shift
newarg_varname="$1"
shift
args="$*"
newargs="$args" # default
newcmd="$origcmd" # default
for x in ${!cmdalias_src[@]} ; do
if [[ ${cmdalias_src[$x]} == $origcmd ]]; then
debug " matched"
# Matched
# cmd is FIRST WORD of _dst. rest goes before args.
repl_cmd="${cmdalias_dst[$x]%% *}"
repl_args="${cmdalias_dst[$x]#* }"
newcmd="$repl_cmd"
if [[ -n $args ]]; then
newargs="$repl_args $args"
else
debug "using default arg $defarg"
defarg=${cmdalias_defaultarg[$x]}
defarg=${defarg/_CURPROJECT_/$curproj}
debug " default arg is: [$defarg]"
newargs="$repl_args $defarg"
fi
changed=1
break
fi
done
eval "$newcmd_varname=\"$newcmd\""
eval "$newarg_varname=\"$newargs\""
if [[ $changed -eq 1 ]]; then
debug "expanded [$origcmd] to [$cmd], args is [$newargs]"
return 0
fi
return 1
}
function processcmd() {
local cmd arg newarg rv newlocs x err admin idx opts pipe gotargs
local whattolist actionname="" actionfilter=""
local showerror=0 showerroropt=""
local epidx
cmd=$1
shift
arg="$*"
shift
[[ -z ${cmd:0:1} ]] && return 0
[[ ${cmd:0:1} == "#" ]] && return 0
if [[ ${cmd:0:1} == \\ ]]; then
cmd=${cmd:1}
admin=1
else
admin=0
fi
idx=$(getcmdidx "$cmd" $admin)
rv=$?
if [[ $rv -ne 0 ]]; then
# check aliases
debug "pre replacedargs is [$args]"
expand_cmdalias "$cmd" replacedcmd replacedargs "$arg"
debug "post replacedargs is [$replacedargs]"
cmd="$replacedcmd"
arg="$replacedargs"
idx=$(getcmdidx "$cmd" $admin)
rv=$?
fi
if [[ $rv -ne 0 ]]; then
if [[ $admin -eq 1 ]]; then
error "Invalid admin command '\\$cmd'"
else
error "Invalid command '$cmd'"
fi
return 1
fi
# replace command aliases with base command name
cmd=${cmd_name[$idx]}
GLOBALCMD="$cmd"
# strip pipe suffix
if [[ $arg == *\|* ]]; then
pipe=${arg#*|}
arg=${arg%%|*}
else
pipe="cat"
fi
# strip options from arguments
declare -a opts
newarg=""
for x in $arg; do
if [[ ${x:0:1} == - ]]; then
opts+=($x)
else
[[ -z $newarg ]] && newarg="$x" || newarg="$newarg $x"
fi
done
arg="$newarg"
arg_array=( $arg )
if arraycontains opts "-h"; then
showcmdhelp "$cmd"
return 0
fi
if arraycontains opts "-e"; then
showerror=1
showerroropt="-e"
fi
gotargs=$( printf %d $(wc -w <<< "$arg"))
if [[ $gotargs -lt ${cmd_minargs[$idx]} || $gotargs -gt ${cmd_maxargs[$idx]} ]]; then
error "${cmd_name[$idx]} requires ${cmd_needargs[$idx]} arguments (got $gotargs)"
return 1
fi
obname="" # global
if [[ $cmd == "show" ]]; then
whattolist=${arg_array[0]} && unset 'arg_array[0]'
[[ ${#arg_array[@]} -ge 1 ]] && opts+=("-f${arg_array[@]}")
elif [[ $cmd == "list" ]]; then
whattolist=${arg_array[0]} && unset 'arg_array[0]'
[[ ${#arg_array[@]} -ge 1 ]] && opts+=("-f${arg_array[@]}")
elif [[ $cmd == "action" ]]; then
whattolist=${arg_array[0]} && unset 'arg_array[0]'
actionname=${arg_array[1]} && unset 'arg_array[1]'
if [[ ${#arg_array[@]} -ge 1 ]]; then
actionfilter="-f${arg_array[@]}"
obname="${arg_array[@]}"
else
error "Name of $whattolist not provided."
return 1
fi
fi
rv=1
if [[ $cmd == "help" && -n $arg ]]; then
showcmdhelp "$arg"
rv=$?
elif [[ $admin -eq 1 ]]; then
case ${cmd} in
ls)
listservers
rv=$?
;;
lp)
listprojects
rv=$?
;;
p)
setproject "${arg}"
rv=$?
start_recache_if_needed
;;
c)
start_recache
rv=$?
[[ $rv -ne 0 ]] && error "UUID re-cache is already in progress (PID $recache_pid)"
;;
sc)
if [[ -z ${arg} ]]; then
for x in ${!uuid_id[@]}; do
echo "UUID: ${uuid_id[$x]} -> ${uuid_name[$x]}"
done
else
echo " UUID ${arg} -> $(uuid_to_name ${arg})"
fi
;;
q)
DONE=1
;;
help)
listcommands ADMIN
;;
*)
error "Admin command '$cmd' not implemented"
rv=1
;;
esac
else
case ${cmd} in
list|show)
getdata ${whattolist} $cmd ${opts[@]} >"$TMPFILE"
rv=$?
[[ -e $TMPFILE ]] && cat "$TMPFILE" | $pipe
if [[ $rv -eq 0 ]]; then
local timestr
timestr=$(printf "query time: %s seconds" "$lastqsecs")
[[ -n $lastprocsecs ]] && timestr="${timestr}$(printf ", processing time: %s secs" "$lastprocsecs")"
echo
info "$timestr"
fi
rm -f "$TMPFILE"
;;
action)
# TODO: ooremove any output format opts
validate_action ${whattolist} $actionname
if [[ $? -ne 0 ]]; then
error "'$actionname' is not a valid action for ${whattolist}s"
return 1
fi
epidx=$(getepidx $whattolist)
if [[ $? -ne 0 ]]; then
error "'$whattolist' is not a valid endpoint"
return 1
fi
# When getting a list of what to operate on, we need
# to get the ID as well as the name, and the ID field
# depends on the obejct type.
#action_idfield=${ep_idfield[$epidx]}
# Get a list of objects to operate on
# ie. turn regexp into a list of dcs and obnames first
getdata ${whattolist} list $actionfilter $showerroropt -s -q >"$TMPFILE"
rv=$?
if [[ $rv -ne 0 ]]; then
error "Query for matching objects failed."
rv=1
elif [[ ! -e $TMPFILE ]]; then
error "No matching objects found."
rv=1
elif grep -q "no results" $TMPFILE; then
error "No ${whattolist}s found matching '$obname'."
rv=1
elif [[ ! -e $TMPFILE ]]; then
error "Results file does not exist."
rv=1
else
local ndcs nobs data confirm=0
ok
debug "tmpfile contents: [$(cat $TMPFILE)]"
echo
data=$(cat "$TMPFILE" | egrep -v "^$")
# how many servers?
ndcs=$( printf %d $(echo "$data" | awk -F, '{ print $1 }' | sort -u | wc -l))
[[ $ndcs -eq 1 ]] && dc_ess="" || dc_ess="s"
nobs=$( printf %d $(echo "$data" | awk -F, '{ print $2 }' | sort -u | wc -l))
[[ $nobs -eq 1 ]] && ob_ess="" || ob_ess="s"
if [[ $actionname == "connect" ]]; then
if [[ $nobs -gt 1 ]]; then
error "Can't run '$actionname' with multiple objects"
return 1
else
confirm=1
fi
else
echo -e "${PURPLE}About to run '${BOLD}$actionname${PLAIN}${PURPLE}' on ${BOLD}${nobs}${PLAIN}${PURPLE} ${whattolist}${ob_ess} on ${BOLD}${ndcs}${PLAIN}${PURPLE} server${dc_ess}:${PLAIN}"
echo "$data" | awk -F, "BEGIN {lastdc=\"\"} { if (\$1 != lastdc) { print \" ${YELLOW}- ${BOLD}\" \$1 \"${PLAIN}\"; lastdc=\$1; } print \" ${YELLOW}- \" \$2 \"${PLAIN}\"}"
echo
if [[ $ndcs -le 1 ]]; then
getyn n "Really proceed"
[[ $? -eq 0 ]] && confirm=1 || confirm=0
else
local confirmcode entered_string
confirmcode=$(generate_random_string)
notify_nodots "Confirmation code is: ${BOLD}${confirmcode}${PLAIN}"
getstr ":" "n" "Enter the above code to proceed, anything else will abort"
entered_string="$retstr"
[[ $entered_string == $confirmcode ]] && confirm=1 || confirm=0
fi
fi
if [[ $confirm -eq 1 ]]; then
actiontargets=$(echo "$data")
if [[ $actionname == "connect" ]]; then
local devname sevname srvport
devname=$(echo "$data" | awk -F, '{ print $2 }')
srvname=$(echo "$data" | awk -F, '{ print $1 }')
srvport=$(echo "$data" | awk -F, '{ print $4 }')
notify_nodots "Connecting to $devname ($srvname:$srvport)"
opts+=("-F")
else
notify "Submitting actions to gns API"
fi
rm -f "$TMPFILE"
debug "about to call runaction with:"
debug " whattolist = $whattolist"
debug " actionname = $actionname"
debug " actiontargets = $actiontargets"
debug " opts = ${opts[@]}"
debug " outputfile = ${TMPFILE}"
runaction ${whattolist} $actionname "$actiontargets" ${opts[@]} >"$TMPFILE"
rv=$?
if [[ $actionname != "connect" ]]; then
ok
[[ -e $TMPFILE ]] && cat "$TMPFILE" | $pipe
echo
info $(printf "action submission time: %s seconds" "$lastqsecs")
fi
fi
fi
rm -f "$TMPFILE"
;;
exit)
DONE=1
;;
help)
listcommands NOADMIN
;;
*)
error "Command '$cmd' not implemented"
rv=1
;;
esac
fi
return $rv
}
function setproject() { # setproject [project_name]
local newproj x err rv idx
newproj="${1}"
err=0
if [[ -z $newproj ]]; then
newproj=${proj_name[0]}
if [[ -z $newproj ]]; then
error "Could not set default project - none found on server!"
return 1
fi
fi
idx=$(getprojidx ${newproj})
if [[ $? -ne 0 ]]; then
error "invalid project '$newproj'"
err=1
fi
curprojid=${proj_id[$idx]}
if [[ $err -eq 0 ]]; then
curproj="${newproj}"
info "Project set to: $curproj ($curprojid)"
rv=0
else
rv=1
fi
return $rv
}
function checkfor() { # checkfor name [pkg_name]
local what pkgname
local os ok=0 confirm=0
what=$1
pkg_name=${2:-$what}
os=$(uname -s)
which $what >/dev/null 2>&1
if [[ $? -eq 0 ]]; then
ok=1
else
error "$what binary not found in \$PATH."
getyn n "Try to install it?"
[[ $? -eq 0 ]] && confirm=1 || confirm=0
if [[ $confirm -eq 1 ]]; then
case $os in
Darwin)
brew install $pkg_name && ok=1
;;
Linux)
if [[ -f /etc/debian_version ]]; then
apt-get install $pkg_name && ok=1
elif [[ -f /etc/redhat_release ]]; then
apt-get install $pkg_name && ok=1
fi
;;
*)
echo "Don't know how to install stuff on OS '$os'"
;;
esac
[[ $ok -eq 1 ]] && info "Successfully installed $pkg_name" || error "Failed to install $pkg_name"
fi
fi
which $what >/dev/null 2>&1
return $?
}
function killtmux() {
tmux kill-session -t "$1" >/dev/null 2>&1
}
function cleanup() {
local x
echo
notify "Killing remaining tmux sessions"
for x in ${proj_name[@]}; do
killtmux "$x"
done
ok
notify "Cleaning up temporary files"
[[ -n $TMPDIR && -d $TMPDIR ]] && rm -f "$TMPDIR/*"
ok
}
function processarg() {
case "$i" in
h)
usage;
exit 1;
;;
I)
scriptstoinline
exit $?;
;;
i)
initauth;
exit $?;
;;
p)
DEFPROJECT="${OPTARG}"
;;
P)
PROFILING=1
;;
v)
VERBOSE=1
info verbose mode
;;
*)
error "invalid argument: $i";
usage;
;;
esac
}
function loadservers() {
local sname shost sport
if [[ -e $SRVFILE ]]; then
notify "Loading servers from $SRVFILE"
while read -r f ; do
sname=$(echo "$f" | awk -F: '{ print $1 }')
shost=$(echo "$f" | awk -F: '{ print $2 }')
sport=$(echo "$f" | awk -F: '{ print $3 }')
if [[ -z $sname || -z $shost || -z $sport ]]; then
fail
error "Bad line in server file: $f"
return 1
fi
addloc $sname $shost $sport
done < <(egrep -v "(^#|^$)" $SRVFILE)
fi
ok "Got $nlocs servers"
return 0
}
BOLD="\033[1m"
PLAIN="\033[0m"
ITALIC="\033[3m"
UNDERLINE="\033[4m"
RED="\033[31m"
YELLOW="\033[33m"
GREEN="\033[32m"
BLUE="\033[34m"
PURPLE="\033[35m"
CYAN="\033[36m"
LINK="$BLUE$UNDERLINE"
UNDERLINE_PRINTED=$(echo -en "$UNDERLINE")
UNDERLINE_LENGTH=${#UNDERLINE_PRINTED}
# Generate sed script to add colours to certain words
UUIDCHAR="[0-9a-f]"
UUID_REGEXP=${UUIDCHAR}\{8\}-${UUIDCHAR}\{4\}-${UUIDCHAR}\{4\}-${UUIDCHAR}\{4\}-${UUIDCHAR}\{12\}
WORDCOLOURS=( "${GREEN}" "${RED}" "${YELLOW}" )
UUIDCOL="$PURPLE"
COLOURED_WORDS=( "true\|up\|enabled\|complete\|finished\|started\|opened" "false\|down\|paused\|uninitialized\|disabled\|failed\|stopped\|closed" "migrating\|powering_up\|project_closed\|unknown" )
ADD_COLOURS=""
for colidx in ${!WORDCOLOURS[@]}; do
col=${WORDCOLOURS[$colidx]}
ADD_COLOURS="${ADD_COLOURS}s/\b\(${COLOURED_WORDS[$colidx]}\)\b/\\${col}\\1\\${PLAIN}/g;"
done
ADD_COLOURS="${ADD_COLOURS}s/\(${UUID_REGEXP}\)/${UUIDCOL}\\1${PLAIN}/g"
nlocs=0
neps=0
# addendpoint ep_name ep_apiendpoint ep_jqobjectname idfieldname [defaultfieldname]
addendpoint projects projects project project_id name
addepalias projects p
addeptitles projects "Project " "UUID" "Status "
addepfields projects ".name" ".project_id" ".status"
addepactions projects open close
addendpoint nodes projects/_CURPROJECT_/nodes node node_id name
addepalias nodes n
addeptitles nodes "Node " "Model_UUID" "Status " "Console_Port"
addepfields nodes ".name" ".template_id" ".status" ".console"
addepactions nodes start stop connect
addendpoint links projects/_CURPROJECT_/links link link_id link_id
addepalias links l
addeptitles links "Type" "A-Host_UUID " "A-Port" "Z-Host_UUID " "Z-Port"
addepfields links ".link_type" ".nodes[0].node_id" ".nodes[0].label.text" ".nodes[1].node_id" ".nodes[1].label.text"
addendpoint models templates model template_id name
addepalias models m
addeptitles models "Name" "Category" "UUID"
addepfields models ".name" ".category" ".template_id"
addcmd list "List elements of a given type (eg. nodes, links, etc)" 1+ ls l
addcmdusage list "object_type [regexp_filter]" "object_type can be: [${ep_name[*]}]"
addcmdoption list "-n" "Don't resolve UUIDs to names"
addcmdoption list "-o filename" "Send output to 'filename'"
addcmdoption list "-r" "Raw mode - show raw JSON from gns API"
addcmdoption list "-v" "Verbose mode - show object details"
addcmd show "Show detail of a given element (eg. compute node, VM, etc)" 2 sh
addcmdusage show "object_type [regexp_filter]" "object_type can be: [${ep_jqobj[*]}]"
addcmdoption show "-n" "Don't resolve UUIDs to names"
addcmdoption show "-o filename" "Send output to 'filename'"
addcmdoption show "-r" "Raw mode - show raw JSON from gns API"
addcmd action "Perform action on given object." 3 act do
declare -a lines
for x in ${!ep_jqobj[@]}; do
if [[ -z ${ep_validactions[$x]} ]]; then
lines+=("${ep_jqobj[$x]} actions: n/a")
else
lines+=("${ep_jqobj[$x]} actions: ${ep_validactions[$x]}")
fi
done
addcmdusage action "object_type action_type regexp_filter" "object_type can be: [${ep_jqobj[*]}]" "" "${lines[@]}"
addcmdoption action "-f" "Force - don't ask for confirmation"
addcmdalias "open" "action project open" "_CURPROJECT_"
addcmdalias "close" "action project close" "_CURPROJECT_"
addcmdalias "start" "action node start" ""
addcmdalias "stop" "action node stop" ""
addcmdalias "connect" "action node connect" ""
addcmdalias "c" "action node connect" ""
addcmd help "List regular commands" 0 "?" "h"
addcmd exit "Exit from gnscli" 0 quit
addcmd -a help "List admin commands" 0 "?" "h"
addcmd -a p "Select project filter (comma or space separated list)" 1 loc
addcmdusage p "project_list" "project_list is a comma-separated list made up of [${loc_name[*]}]"
addcmd -a lp "List GNS3 projects on current server(s)" 0 listprojects
addcmd -a ls "List GNS3 servers" 0 listservers l
addcmd -a c "Re-cache UUIDs for current projects" 0 cache
addcmd -a sc "Show cached name for given UUID (or all UUIDs if none provided)." 0-1 showcache
addcmdusage sc "[uuid]" "If UUID is not provided, all UUID-to-name mappings are shown."
addcmd -a q "Exit from gnscli" 0
VERBOSE=0
CONFDIR="$HOME/.gnscli"
HISTFILE="$CONFDIR/history"
TMPDIR="$CONFDIR/tmp"
SCRIPTDIR="$CONFDIR/scripts"
#HEADINGFILE="$CONFDIR/head.tmp"
TMPFILE="$TMPDIR/temp.tmp"
TOTFILE="$TMPDIR/tot.tmp"
#AUTHFILE="$CONFDIR/auth"
CACHEDIR="$CONFDIR/uuid_cache"
CACHEFILEBASE="$CACHEDIR/mappings"
RCFILE="$CONFDIR/rc"
SRVFILE="$CONFDIR/servers"
UUIDLENGTH=36
CACHING=""
PROFILING=0
DEFPROJECT=""
curproj=""
nmsgq=0
recache_pid=""
THISSCRIPT="$0"
RCARGS=""
if [[ -e $RCFILE ]]; then
info "processing $RCFILE"
while read -r f ; do
RCARGS="$f $ALLARGS"
done < <(egrep -v "(^#|^$)" $RCFILE)
fi
VALIDARGS="hiIp:Pv"
while getopts "$VALIDARGS" i $RCARGS; do
processarg "$i"
done
OPTIND=0
while getopts "$VALIDARGS" i ; do
processarg "$i"
done
shift $((OPTIND - 1))
if [[ ! -e $TMPDIR ]]; then
error "Temporary file dir $TMPDIR doesn't exist. Use -i to create it."
exit 1
else
rm -fr ${TMPDIR}/*.tmp
rm -fr ${TMPDIR}/get.*
rm -fr ${TMPDIR}/run.*
fi
if [[ ! -d $CONFDIR ]]; then
error "Configuration directory $CONFDIR doesn't exist. Use -i to create it."
exit 1
fi
checkfor gdate coreutils || exit 1
loadservers
if [[ $nlocs -lt 1 ]]; then
error "No servers found - check $SRVFILE"
exit 1
fi
alllocs=$(getalllocs)
# prepopulate list of projects
notify "Getting initial projectlist"
oldverbose=$VERBOSE
VERBOSE=1
errfile="$TMPDIR"/err
output=$(getdata projects show -e -r 2>"$errfile")
rv=$?
VERBOSE=$oldverbose
if [[ $rv -eq 0 ]]; then
ok
#cat "$errfile"
rm -f "$errfile"
jqoutput=$(echo "$output" | jq -r '.[] | .project_id + "|" + .name' 2>"$errfile")
rv=$?
if [[ $rv -ne 0 || -z $jqoutput ]]; then
error "$rv Got bad data from initial project list query to API"
echo -e "${RED}${BOLD}Errors:${PLAIN}"
echo -en "${RED}"
cat "${errfile}" | sed -e 's/^/ /'
echo -en "${PLAIN}"
rm -f "$errfile"
exit 1
fi
rm -f "$errfile"
for x in $jqoutput; do
id=${x%|*}
name=${x#*|}
addproject $id $name
done
else
fail
error "Could not obtain initial project list from API"
echo -e "${RED}${BOLD}Output:${PLAIN}"
echo -en "${RED}"
echo "${output}" | sed -e 's/^/ /'
echo -en "${PLAIN}"
echo -e "${RED}${BOLD}Errors:${PLAIN}"
echo -en "${RED}"
cat "${errfile}" | sed -e 's/^/ /'
echo -en "${PLAIN}"
rm -f "$errfile"
exit 1
fi
if [[ ${#proj_id[@]} -eq 0 ]]; then
error "No projects found on server!!"
exit 1
fi
setproject $DEFPROJECT || exit 1 # defaults to first one
[[ -e $HISTFILE ]] && history -r "$HISTFILE"
loadcachefile $curlocs
dumpmsgq
trap cleanup EXIT
echo "Unofficial GNS3 cli v${VER}"
echo -e "${ITALIC}${YELLOW}Note: this script is still in development and may have bugs!${PLAIN}"
if [[ $# -gt 0 ]]; then
processcmd $*
else
DONE=0
while [[ $DONE -ne 1 ]]; do
lastqsecs=0
#echo -en "${pstr}"
getcmd cmd || break
set -f
processcmd $cmd
set +f
if [[ ! -z $cmd ]]; then
history -s "$cmd"
history -w "$HISTFILE"
fi
dumpmsgq # show any queued messages
done
fi
exit 0
#START_INLINE:model.jq
"UUID#" + .template_id,
"Name#" + .name,
"Usage#" + .usage,
"Category#" + .category,
"RAM#" + .ram + "MB",
"Action on close#" + .on_close,
"Firewall Type#" + .firewall_type,
"Image#" + .hda_disk_image,
"__END__"
#END_INLINE:model.jq
#START_INLINE:project.jq
"UUID#" + .project_id,
"Name#" + .name,
"Status#" + .status,
"Filename#" + .filename,
"Path#" + .path,
"Auto-Open#" + .auto_open,
"Auto-Close#" + .auto_close,
"Auto-Start#" + .auto_start,
"__END__"
#END_INLINE:project.jq
#START_INLINE:detail.awk
BEGIN {
FS="#"
nlines=0
BOLDBLUE="\033[1m\033[36m"
PLAIN="\033[0m"
}
{
key[nlines]=$1
val[nlines++]=$2
if (length($1)>max) max=length($1);
}
END {
nvalid=0
for (i=0;i<nlines;i++) {
if (length(key[i]) + length(val[i]) >= 1) {
printf( BOLDBLUE"%" max "s:" PLAIN " %s\n",key[i],val[i]);
nvalid++;
}
}
if (nvalid >= 1) printf(" \n");
}
#END_INLINE:detail.awk
#START_INLINE:node.jq
"Hostname#" + .name,
"UUID#" + .node_id,
"Model UUID#" + .template_id,
"Project UUID#" + .project_id,
"Status#" + .status,
"Console port#" + ((.console|tostring) // "n/a"),
"Locked#" + (.locked // "n/a"),
"Port format#" + .port_name_format,
"Port count#" + try (.ports[] | length) catch ("unknown"),
"__END__"
#END_INLINE:node.jq
#START_INLINE:link.jq
"UUID#" + .link_id,
"Type#" + .link_type,
"Project UUID#" + .project_uuid,
"A-Node#" + .node[0].node_id,
"A-Port#" + .node[0].label.text,
"Z-Node#" + .node[1].node_id,
"Z-Port#" + .node[1].label.text,
"Capturing#" + .capturing,
"Suspended#" + .suspend,
"__END__"
#END_INLINE:link.jq