gnscli/gnscli.sh

2606 lines
80 KiB
Bash
Raw Normal View History

2021-12-19 23:31:58 +11:00
#!/bin/bash
# add node
# issue with json post data
# rename project
# delete node
2021-12-19 23:31:58 +11:00
# 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))
2021-12-19 23:31:58 +11:00
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))
2021-12-19 23:31:58 +11:00
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() {
local force=0
if [[ $1 == "-f" ]]; then
shift
force=1
fi
2021-12-19 23:31:58 +11:00
if [[ $VERBOSE -eq 1 ]]; then
if [[ $force -eq 1 ]]; then
echo -e "${PURPLE}${BOLD}${FUNCNAME[1]}(): ${PLAIN}${PURPLE}$*${PLAIN}" >/dev/stderr
echo -e "${PURPLE}${BOLD}${FUNCNAME[1]}(): ${PLAIN}${PURPLE}$*${PLAIN}" >/tmp/a
else
echo -e "${PURPLE}${BOLD}${FUNCNAME[1]}(): ${PLAIN}${PURPLE}$*${PLAIN}" 1>&2
fi
2021-12-19 23:31:58 +11:00
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 runcurldatapost() { # location api_endpoint "curl data"
local thisapi thisurl curlres rv
local loc api_endpoint obname ovmethod
local data=""
loc="$1"
api_endpoint="$2"
data="$3"
thisapi=$(get $loc api)
[[ -z $thisapi ]] && echo "cant find api for '$loc'" && return 1
#thisauthdom=$(get $loc authdomain)
debug -f "data is $data"
thisurl="$thisapi/$api_endpoint"
thisurl=${thisurl/_CURPROJECT_/$curprojid}
debug -f "curl -XPOST -sk --header 'Content-type: application/xml' --header 'Accept: application/json' -d \"$data\" $thisurl"
set -x
curlres=$(curl -XPOST -sk --header 'Content-type: application/xml' --header 'Accept: application/json' -d "$data" $thisurl)
set +x
rv=$?
[[ $rv -ne 0 ]] && error "curl POST to $thisurl failed"
echo "$curlres"
return $rv
}
function runcurlaction() { # location api_endpoint(ovname) obname ovmethod ["curl data"]
2021-12-19 23:31:58 +11:00
local thisapi thisurl curlres rv
#local thisauthdom u p thisauthstr
local loc api_endpoint obname ovmethod
local data=""
#local data="<action/>"
2021-12-19 23:31:58 +11:00
loc="$1"
api_endpoint="$2"
obname="$3"
ovmethod="$4"
data="$5"
2021-12-19 23:31:58 +11:00
thisapi=$(get $loc api)
[[ -z $thisapi ]] && echo "cant find api for '$loc'" && return 1
#thisauthdom=$(get $loc authdomain)
debug -f "data is $data"
thisurl="$thisapi/$api_endpoint/$obname"
[[ -n $ovmethod ]] && thisurl="${thisurl}/$ovmethod"
2021-12-19 23:31:58 +11:00
thisurl=${thisurl/_CURPROJECT_/$curprojid}
#u=$(head -1 "$AUTHFILE")
#p=$(tail -1 "$AUTHFILE")
#thisauthstr="$u@$thisauthdom:$p"
#unset u
#unset p
debug -f "curl -XPOST -sk --header 'Content-type: application/xml' --header 'Accept: application/json' -d \"$data\" $thisurl"
2021-12-19 23:31:58 +11:00
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";;
add) echo "";;
2021-12-19 23:31:58 +11:00
#migrate) echo "migrate";;
*)
echo
rv=1;
;;
esac
return $rv
}
function makejson() { # makejson key1:val1^key2:val2^...
local plaindata tok toks key val tnum=1
plaindata="$1"
printf "%s" '{'
IFS='^' read -ra toks <<< "$plaindata"
for tok in "${toks[@]}"; do
[[ $tnum -ne 1 ]] && printf "%s" ","
key=${tok%:*}
val=${tok#*:}
printf '"%s":"%s"' "$key" "$val"
tnum=$((tnum + 1))
done
printf "%s\n" '}'
}
function runaction_loc() { # runaction_loc syd|etc api_endpoint action_name ob_uuid ob_name "extra info" [options]
2021-12-19 23:31:58 +11:00
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
local curldata="" crv
2021-12-19 23:31:58 +11:00
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 )
debug -f "extrainfo : $extrainfo"
debug -f "remaining opts: $opts"
if [[ $actionname == "add" ]]; then
curldata=$(makejson "$extrainfo")
fi
2021-12-19 23:31:58 +11:00
# 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 -f "location: ${loc}"
debug -f "api endpoint: ${api_endpoint}"
debug -f "jq obj: ${jq_obj}"
debug -f "actionname: ${actionname} (gns method: '$method')"
debug -f "obname: ${ob}"
debug -f "options: ${opts}"
debug -f "curldata: ${curldata}"
if [[ $action == "add" ]]; then
curlres=$(runcurldatapost $loc $api_endpoint "$curldata" )
crv=$?
else
curlres=$(runcurlaction $loc $api_endpoint $ob $method "$curldata" )
crv=$?
fi
if [[ $crv -ne 0 ]]; then
2021-12-19 23:31:58 +11:00
rv=1
fi
debug -f "curlres is [$curlres]"
2021-12-19 23:31:58 +11:00
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))
2021-12-19 23:31:58 +11:00
prof_last="$prof_start"
}
function profile_mark() {
local now secs
now=$(($($GDATE +%s%N)/1000))
2021-12-19 23:31:58 +11:00
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))
2021-12-19 23:31:58 +11:00
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"
2021-12-19 23:31:58 +11:00
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-)
debug "extrainfo is: $extrainfo"
runaction_loc $loc $epidx $actionname $obuuid $ob "$extrainfo" $opts > "$TMPDIR/run,$loc,$ob" &
2021-12-19 23:31:58 +11:00
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=""
2021-12-19 23:31:58 +11:00
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
elif [[ ${ACTIONRES_MSG[$n]} == *message* ]]; then
goterror=1
2021-12-19 23:31:58 +11:00
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 "^$")
thiscsv_bad=$(echo "${ACTIONRES_MSG[$n]}" | jq -r . | sed -e "s/_DC_/${ACTIONRES_LOC[$n]}/g" | sed -e "s/_OB_/${ACTIONRES_OB[$n]}/" | egrep -v "^$")
2021-12-19 23:31:58 +11:00
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}"
echo -e "${RED}"
echo "$allbadresults" | sed -e 's/^/ /'
echo -e "${PLAIN}"
2021-12-19 23:31:58 +11:00
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))
2021-12-19 23:31:58 +11:00
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 ignorecase=0
2021-12-19 23:31:58 +11:00
start=$(($($GDATE +%s%N)/1000))
2021-12-19 23:31:58 +11:00
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 "-c" && ignorecase=1
2021-12-19 23:31:58 +11:00
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=".*"
if [[ -n $refilter ]]; then
obfilter="select(.name|test(\"^$refilter$\""
if [[ $ignorecase -eq 1 ]]; then
obfilter="${obfilter}; \"i\"))"
else
obfilter="${obfilter}))"
fi
else
obfilter="."
fi
2021-12-19 23:31:58 +11:00
[[ -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))
2021-12-19 23:31:58 +11:00
lastqsecs=$(echo "scale=2; ($end - $start) / 1000000;" | bc)
start=$(($($GDATE +%s%N)/1000))
2021-12-19 23:31:58 +11:00
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))
2021-12-19 23:31:58 +11:00
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 endpoint newname newtype newtype_uuid
2021-12-19 23:31:58 +11:00
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
endpoint=${arg_array[0]} && unset 'arg_array[0]'
whattolist=${endpoint}
2021-12-19 23:31:58 +11:00
[[ ${#arg_array[@]} -ge 1 ]] && opts+=("-f${arg_array[@]}")
elif [[ $cmd == "list" ]]; then
endpoint=${arg_array[0]} && unset 'arg_array[0]'
whattolist=${endpoint}
2021-12-19 23:31:58 +11:00
[[ ${#arg_array[@]} -ge 1 ]] && opts+=("-f${arg_array[@]}")
elif [[ $cmd == "action" ]]; then
endpoint=${arg_array[0]} && unset 'arg_array[0]'
whattolist=${endpoint}
2021-12-19 23:31:58 +11:00
actionname=${arg_array[1]} && unset 'arg_array[1]'
if [[ $actionname == "add" ]]; then
newname=${arg_array[2]}
if [[ -z $newname ]]; then
error "Name of new $whattolist not provided."
return 1
fi
# ie. look for models named 'IOS'
# oooo ...if we find one, check its template_type
# if it's qemu then use template_id to figure out appliance id???
# otherwise just use template_type
whattolist="model"
newtype=".*${arg_array[3]}.*"
actionfilter="-f${newtype}"
2021-12-19 23:31:58 +11:00
else
# start/stop/etc
if [[ ${#arg_array[@]} -ge 1 ]]; then
actionfilter="-f${arg_array[@]}"
obname="${arg_array[@]}"
else
error "Name of $whattolist not provided."
return 1
fi
2021-12-19 23:31:58 +11:00
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 ${endpoint} $actionname
2021-12-19 23:31:58 +11:00
if [[ $? -ne 0 ]]; then
error "'$actionname' is not a valid action for ${endpoint}s"
2021-12-19 23:31:58 +11:00
return 1
fi
epidx=$(getepidx $endpoint)
2021-12-19 23:31:58 +11:00
if [[ $? -ne 0 ]]; then
error "'$endpoint' is not a valid endpoint"
2021-12-19 23:31:58 +11:00
return 1
fi
# Get a list of objects to operate on
# ie. turn regexp into a list of dcs and obnames first
getdata ${whattolist} list $actionfilter $showerroropt -c -s -q >"$TMPFILE"
2021-12-19 23:31:58 +11:00
rv=$?
if [[ $rv -ne 0 ]]; then
error "Query for matching ${whattolist}s failed."
2021-12-19 23:31:58 +11:00
rv=1
elif [[ ! -e $TMPFILE ]]; then
error "No matching ${whattolist}s found."
2021-12-19 23:31:58 +11:00
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
elif [[ $actionname == "add" ]]; then
local allobs alluuids o_arr ou_arr
allobs=$(echo "$data" | awk -F, '{ print $2 }' | sort -u)
alluuids=$(echo "$data" | awk -F, '{ print $3 }' | sort -u)
o_arr=($allobs)
ou_arr=($alluuids)
if [[ $nobs -gt 1 ]]; then
local o n
info "Matched multiple ${whattolist}s:"
newtype=""
while [[ -z $newtype ]]; do
echo
n=1
while read -r o; do
printf "%3d. %s\n" $n "${o_arr[$n]}"
n=$((n + 1))
done <<<"$allobs"
echo
getstr ":" "" "Select one (q to abort)"
if [[ -n $retstr ]]; then
if [[ $retstr == "q" ]]; then
break
elif [[ $retstr =~ ^[0-9]*$ ]]; then
if [[ $retstr -le 0 || $retstr -ge $n ]]; then
error "Invalid selection"
else
newtype="${o_arr[$retstr]}"
newtype_uuid="${ou_arr[$retstr]}"
fi
else
local matched=0 x allmatches="" this thisuuid
for x in ${!o_arr[@]}; do
this="${o_arr[$x]}"
thisuuid="${ou_arr[$x]}"
shopt -s nocasematch
if [[ ${this} =~ $retstr ]]; then
newtype="$this"
newtype_uuid="$thisuuid"
allmatches="$allmatches [$this]"
matched=$((matched + 1))
fi
shopt -u nocasematch
done
if [[ $matched -eq 0 ]]; then
error "'$retstr' doesn't match any choice"
newtype=""
elif [[ $matched -gt 1 ]]; then
error "'$retstr' matched multiple choices: $allmatches"
newtype=""
fi
fi
fi
done
else
newtype="$allobs"
newtype_uuid="$alluuids"
fi
if [[ -z $newtype ]]; then
confirm=0
else
notify_nodots "Adding a new ${BOLD}$newtype${PLAIN}${PURPLE} named $BOLD$newname${PLAIN}"
confirm=1
fi
2021-12-19 23:31:58 +11:00
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
if [[ $actionname == "add" ]]; then
# We use alternate values for the actiontarget string here:
# loc=servername (normal)
# ob=name of new ob to add
# obuuid=uuid of model
# extrainfo=curl post data
actiontargets="$curlocs,$newname,$newtype_uuid"
# ooo
#actiontargets="${actiontargets},'{\"name\": \"$newname\", \"node_type\": \"$newtype\", \"compute_id\": \"local\"}'"
actiontargets="${actiontargets},name:$newname^node_type:$newtype^compute_id:local"
else
actiontargets=$(echo "$data")
fi
2021-12-19 23:31:58 +11:00
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 " obtype = $endpoint"
2021-12-19 23:31:58 +11:00
debug " actionname = $actionname"
debug " actiontargets = $actiontargets"
debug " opts = ${opts[@]}"
debug " outputfile = ${TMPFILE}"
runaction ${endpoint} $actionname "$actiontargets" ${opts[@]} >"$TMPFILE"
2021-12-19 23:31:58 +11:00
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
2021-12-23 16:40:48 +11:00
local hw os ok=0 confirm=0
local alt rv
2021-12-23 16:40:48 +11:00
2021-12-19 23:31:58 +11:00
what=$1
pkg_name=${2:-$what}
os=$(uname -s)
2021-12-23 16:40:48 +11:00
hw=$(uname -m)
if [[ $hw == "aarch64" ]]; then # Android Termuz
alias which="command -v"
fi
2021-12-19 23:31:58 +11:00
which $what >/dev/null 2>&1
rv=$?
if [[ $rv -ne 0 && $what == "gdate" ]]; then
date --version 2>/dev/null | grep -q GNU 2>/dev/null
2021-12-23 16:29:41 +11:00
if [[ $? -eq 0 ]]; then
GDATE=$(which date 2>/dev/null)
what=${GDATE}
rv=0
fi
fi
2021-12-19 23:31:58 +11:00
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 add
2021-12-19 23:31:58 +11:00
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" ""
addcmdalias "add" "action node add" ""
2021-12-19 23:31:58 +11:00
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
GDATE="gdate"
2021-12-19 23:31:58 +11:00
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 jq || exit 1
2021-12-19 23:31:58 +11:00
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,
"Port format#" + .port_name_format,
"Locked#" + (.locked // "n/a"),
"Console port#" + ((.console|tostring) // "n/a"),
2021-12-19 23:31:58 +11:00
"Port count#" + try (.ports[] | length) catch ("unknown"),
#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