gnscli/gnscli.sh

3528 lines
111 KiB
Bash
Raw Normal View History

2021-12-19 23:31:58 +11:00
#!/bin/bash
## todo
# start/stop/del with multiple args broken
2022-01-03 16:01:21 +11:00
# rename node issue when matching multiple
# rename project
2021-12-19 23:31:58 +11:00
# node stop givesjq error
# connect xxx & [opens iterm (or whatever)]
2022-01-03 16:01:21 +11:00
VER=1.0
2021-12-19 23:31:58 +11:00
# 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
2022-01-03 16:01:21 +11:00
cecho -s "$CYAN" "Initialisation of ^b$CONFDIR^p complete."
2021-12-19 23:31:58 +11:00
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
[[ $VERBOSE -eq 1 ]] && info "Caching UUID data for $loc $epname"
2021-12-19 23:31:58 +11:00
epidx=$(getepidx $epname)
api_endpoint="${ep_apiendpoint[$epidx]}"
curlres=$(runcurlget $loc $api_endpoint)
[[ $? -ne 0 ]] && error "curl to $thisurl failed" && return 1
if [[ ${ep_name[$epidx]} == "links" ]]; then
echo "$curlres" | jq -r "try (. | to_entries | .[] | [ .value.${ep_idfield[$epidx]}, \"l\" + (.key|tostring) ] | @csv) catch empty" | tr -d '"' >> "$thisfile"
else
echo "$curlres" | jq -r "try (.[] | [ .${ep_idfield[$epidx]}, .name ] | @csv) catch empty" | tr -d '"' >> "$thisfile"
fi
2021-12-19 23:31:58 +11:00
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)
2021-12-19 23:31:58 +11:00
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
[[ $VERBOSE -eq 1 ]] && addmsgq $(info "Loaded cache files for all servers" 2>&1)
2021-12-19 23:31:58 +11:00
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=""
unset recache_pid
2021-12-19 23:31:58 +11:00
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
2022-01-03 17:35:47 +11:00
[[ -z $curproj ]] && return 2
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
[[ $VERBOSE -eq 1 ]] && info "Initiating UUID cache refresh for $curlocs..."
2021-12-19 23:31:58 +11:00
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=""
2022-01-03 17:35:47 +11:00
echo -n -e "${INFORMCOL}$prompt [${INFORMCOLB}$def$INFORMCOL]${lastchar}${PLAIN} "
2021-12-19 23:31:58 +11:00
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 pcol
2021-12-19 23:31:58 +11:00
varname=$1
if [[ -z $curproj ]]; then
prefix="no_project_selected"
pcol="$GREY"
2021-12-19 23:31:58 +11:00
else
prefix="${curproj}"
if [[ ${proj_isopen[$curprojidx]} -eq 1 ]]; then
pcol="$GREEN"
else
pcol="$GREY"
fi
2021-12-19 23:31:58 +11:00
fi
pstr="gnscli> "
2021-12-19 23:31:58 +11:00
[[ ! -z $CACHING ]] && info "$CACHING"
2022-01-03 16:01:21 +11:00
2021-12-19 23:31:58 +11:00
set -f
builtin read -p "$(echo -en $BOLD)[$(echo -en $PLAIN$pcol)${prefix}$(echo -e "$PLAIN$BOLD")] $pstr$(echo -e "${PLAIN}")" -e -r input
2021-12-19 23:31:58 +11:00
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]}
2022-01-03 16:01:21 +11:00
[[ $PROFILING -eq 1 ]] && cecho -s "$YELLOW" "^b${FUNCNAME[1]}(): ^p$*" 1>&2
2021-12-19 23:31:58 +11:00
}
function harddebug() {
2022-01-03 17:35:47 +11:00
echo -e "${YELLOW}${BOLD}$*${PLAIN}" >/dev/stderr
}
2021-12-19 23:31:58 +11:00
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
2022-01-03 16:01:21 +11:00
echo -e "${NOTIFYCOLB}${FUNCNAME[1]}(): ${NOTIFYCOL}$*${PLAIN}" >/dev/stderr
else
2022-01-03 16:01:21 +11:00
echo -e "${NOTIFYCOLB}${FUNCNAME[1]}(): ${NOTIFYCOL}$*${PLAIN}" 1>&2
fi
2021-12-19 23:31:58 +11:00
fi
}
2022-01-03 16:01:21 +11:00
# 'str' can contain:
# ^b bold
# ^i italic
# ^p plain
#
function cecho() { # [-n] [-s] col boldcol "str"
local col bcol str minusn="" autobold=0
while [[ ${1:0:1} == "-" ]]; do
if [[ $1 == "-n" ]]; then
minusn="-n"
fi
if [[ $1 == "-s" ]]; then
autobold=1
fi
shift
done
col="$1"
shift
if [[ $autobold -eq 1 ]]; then
bcol="$col$BOLD"
else
bcol="$1"
shift
fi
str="$*"
str=${str//\^b/${bcol}}
str=${str//\^p/${PLAIN}${col}}
str=${str//\^i/${col}${ITALIC}}
echo $minusn -e "${col}${str}${PLAIN}" 1>&2
}
2021-12-19 23:31:58 +11:00
function info() {
2022-01-03 16:01:21 +11:00
cecho -s "$CYAN" "^i[$*]" 1>&2
2021-12-19 23:31:58 +11:00
}
function error() {
[[ $innotify -eq 1 ]] && fail
2022-01-03 16:01:21 +11:00
cecho -s "$RED" "^bERROR: ^p$*" 1>&2
2021-12-19 23:31:58 +11:00
}
function notify() {
2022-01-03 16:01:21 +11:00
#echo -en "${NOTIFYCOLB}* ${NOTIFYCOL}$*...${PLAIN} " 1>&2
cecho -n "$NOTIFYCOL" "$NOTIFYCOLB" "^b* ^p$*... " 1>&2
2021-12-19 23:31:58 +11:00
innotify=1
}
2022-01-03 17:35:47 +11:00
function inform() {
#echo -e "${INFORMCOLB}* ${INFORMCOL}$*${PLAIN} " 1>&2
cecho "$INFORMCOL" "$INFORMCOLB" "^b* ^p$* " 1>&2
2021-12-19 23:31:58 +11:00
}
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() {
2022-01-03 16:01:21 +11:00
cecho -s "$YELLOW" "^bWarning: ^p$*" 1>&2
2021-12-19 23:31:58 +11:00
}
function usage() {
echo "usage: $0 OPTIONS command"
echo
echo " -g path Specify path to perl Graph::Easy binary"
2021-12-19 23:31:58 +11:00
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=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 get_next_linkid() {
local x num max=-1
for ((x=0; x<${#uuid_name[@]}; ++x)); do
if [[ "${uuid_name[$x]}" == l* ]]; then
num=${uuid_name[$x]/l/}
[[ $num -gt $max ]] && max=$num
fi
done
echo "l$((max + 1))"
}
function name_to_uuid() {
local x
for ((x=0; x<${#uuid_name[@]}; ++x)); do
if [[ "${uuid_name[$x]}" == "$1" ]]; then
echo "${uuid_id[$x]}"
return 0
fi
done
echo "$1"
return 1
}
2021-12-19 23:31:58 +11:00
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))
}
2022-01-03 17:35:47 +11:00
function loadprojectlist() {
local errfile output rv jqoutput x
local id name pstatus
# clear current data
unset nprojects proj_id proj_name proj_isopen
errfile="$TMPDIR"/err
output=$(getdata projects list -q -e -r 2>"$errfile")
rv=$?
debug "getdata rv is $rv"
#VERBOSE=$oldverbose
if [[ $rv -eq 0 ]]; then
ok
cat "$errfile"
rm -f "$errfile"
jqoutput=$(echo "$output" | jq -r '.[] | .project_id + "|" + .name + "|" + .status' 2>"$errfile")
rv=$?
if [[ $rv -ne 0 || -z $jqoutput ]]; then
error "$rv Got bad data from initial project list query to API"
cecho -s "${RED}" "^bErrors:^p"
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=$(echo "$x" | awk -F'|' '{print $1}')
name=$(echo "$x" | awk -F'|' '{print $2}')
pstatus=$(echo "$x" | awk -F'|' '{print $3}')
addproject $id $name $pstatus
done
# did current project's index change?
if [[ -n $curproj ]]; then
local newidx
newidx=$(getprojidx ${curproj})
if [[ $? -eq 0 ]]; then
curprojidx=${newidx}
curprojid=${proj_id[$newidx]}
else
warn "Current project no longer exists."
curproj=""
curprojid=""
curprojidx=""
fi
fi
else
fail
error "Could not obtain initial project list from API"
cecho -s "${RED}" "^bOutput:^p"
echo -en "${RED}"
echo "${output}" | sed -e 's/^/ /'
echo -en "${PLAIN}"
cecho -s "${RED}" "^bErrors:^p"
echo -en "${RED}"
cat "${errfile}" | sed -e 's/^/ /'
echo -en "${PLAIN}"
rm -f "$errfile"
return 1
fi
return 0
}
function addproject() { # id name [openclosed_status]
2021-12-19 23:31:58 +11:00
[[ -z $nprojects ]] && nprojects=0
proj_id[$nprojects]="$1"
proj_name[$nprojects]="$2"
2022-01-03 17:35:47 +11:00
if [[ -n $3 ]]; then
proj_isopen[$nprojects]=1
else
proj_isopen[$nprojects]=""
fi
2021-12-19 23:31:58 +11:00
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
}
2022-01-02 21:44:09 +11:00
function addepactionusage() { # addepactions ep_name action_name "usage text"
local iepdx aname text
epidx=$(getepidx $1)
if [[ $? -ne 0 ]]; then
error "appepactionusage(): can't find endpoint named '$1'"
return 1
fi
shift
aname="$1"
text="$2"
actionusage_epidx[$nactusage]="$epidx"
actionusage_name[$nactusage]="$aname"
actionusage_text[$nactusage]="$text"
nactusage=$((nactusage + 1))
}
function getactionusage() { # epidx actionname
local x
for x in ${!actionusage_name[@]}; do
if [[ ${actionusage_epidx[$x]} == $1 && ${actionusage_name[$x]} == $2 ]]; then
echo "${actionusage_text[$x]}"
return 0
fi
done
return 1
}
2021-12-19 23:31:58 +11:00
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"
}
2022-01-02 21:44:09 +11:00
function addcmdoption() {
2021-12-19 23:31:58 +11:00
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
}
2022-01-02 21:44:09 +11:00
2021-12-19 23:31:58 +11:00
function showcmdhelp() {
local real_cname cname idx text ln x format optname_arr optdesc_arr len
2022-01-02 21:15:35 +11:00
local otheraliases
2021-12-19 23:31:58 +11:00
cname="$1"
shift
idx=$(getcmdidx $cname)
if [[ $? -ne 0 ]]; then
2022-01-02 21:15:35 +11:00
# is it an alias?
otheraliases=$(listcommandaliases $cname)
if [[ $? -eq 0 ]]; then
echo "${cname} is an alias:"
echo "$otheraliases" | sed 's/^/ /'
rv=0
else
error "Invalid command '$cname'"
rv=1
fi
return $rv
2021-12-19 23:31:58 +11:00
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
2022-01-02 21:15:35 +11:00
echo "Alternative names for '${real_cname}': ${cmd_aliases[$idx]}"
fi
otheraliases=$(listcommandaliases $real_cname)
if [[ $? -eq 0 ]]; then
echo
echo "Aliases related to '${real_cname}':"
echo "$otheraliases" | sed 's/^/ /'
2021-12-19 23:31:58 +11:00
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 getfield() { # endpoint jqfilter fieldname
getdata "$1" list -q -e -R"$3" -r -F -f"$2"
}
2021-12-19 23:31:58 +11:00
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 endpoint_uuid "curl data"
local thisapi thisurl curlres rv
local loc api_endpoint obname ovmethod
local endpoint_uuid
local curlmethod="POST"
local data=""
loc="$1"
api_endpoint="$2"
endpoint_uuid="$3"
data="$4"
thisapi=$(get $loc api)
[[ -z $thisapi ]] && echo "cant find api for '$loc'" && return 1
#thisauthdom=$(get $loc authdomain)
thisurl="$thisapi/$api_endpoint"
thisurl=${thisurl/\/v2\/templates/\/v2\/projects\/_CURPROJECT_\/templates} # special case
thisurl=${thisurl/_CURPROJECT_/$curprojid}
if [[ -n $endpoint_uuid ]]; then
thisurl="$thisurl/$endpoint_uuid"
fi
if [[ $GLOBALACTION == "mv" ]]; then
curlmethod="PUT"
else
curlmethod="POST"
fi
debug -f "loc is $loc"
debug -f "api_endpoint is $api_endpoint"
debug -f "endpoint_uuid is $endpoint_uuid"
debug -f "data is $data"
debug -f "thisapi is $thisapi"
debug -f "thisurl is $thisurl"
debug -f "curlmethod is $curlmethod"
debug -f "curl -X${curlmethod} -sk --header 'Content-type: application/xml' --header 'Accept: application/json' -d \"$data\" $thisurl"
curlres=$(curl -X${curlmethod} -sk --header 'Content-type: application/xml' --header 'Accept: application/json' -d "$data" $thisurl)
rv=$?
[[ $rv -ne 0 ]] && error "curl ${curlmethod} 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 ovmethou
2021-12-19 23:31:58 +11:00
local data=""
local curlmethod="POST"
#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
if [[ $GLOBALACTION == "del" ]]; then
curlmethod="DELETE"
else
curlmethod="POST"
fi
debug -f "curl -X$curlmethod -sk --header 'Content-type: application/xml' --header 'Accept: application/json' -d \"$data\" $thisurl"
curlres=$(curl -X$curlmethod -sk --header 'Content-type: application/xml' --header 'Accept: application/json' -d "$data" $thisurl)
2021-12-19 23:31:58 +11:00
rv=$?
[[ $rv -ne 0 ]] && error "curl ${curlmethod} to $thisurl failed"
2021-12-19 23:31:58 +11:00
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]}
[[ $localdb -eq 1 ]] && info " 1st is $this"
optval=${this##-$optname} # strip "-x" from the very start
optval=${optval##* -$optname} # strip up to " -x" from the start
[[ $localdb -eq 1 ]] && info " 2nd is $optval"
2021-12-19 23:31:58 +11:00
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 "";;
del) echo "";;
mv) 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 format
plaindata="$1"
printf "%s" '{'
IFS='^' read -ra toks <<< "$plaindata"
for tok in "${toks[@]}"; do
[[ $tnum -ne 1 ]] && printf "%s" ","
key=${tok%:*}
val=${tok#*:}
if [[ $val =~ ^[0-9]+$ ]]; then
format='"%s":%s'
else
format='"%s":"%s"'
fi
printf "$format" "$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 obuuid
2021-12-19 23:31:58 +11:00
rv=0
loc="$1"
epidx="$2"
actionname="$3"
obuuid="$4"
2021-12-19 23:31:58 +11:00
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 "arglist - loc: $loc"
debug -f "arglist - epidx: $epidx"
debug -f "arglist - actionname: $actionname"
debug -f "arglist - obuuid: $obuuid"
debug -f "arglist - obname: $obname"
debug -f "arglist - extrainfo : $extrainfo"
debug -f "arglist: remaining opts: $opts"
if [[ $actionname == "add" || $actionname == "mv" ]]; then
if [[ -n $RAWJSONPOSTDATA ]]; then
debug -f "using RAWJSONPOSTDATA for curldata"
curldata="${RAWJSONPOSTDATA}"
else
curldata=$(makejson "$extrainfo")
fi
fi
RAWJSONPOSTDATA=""
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 server '$curlocs'"
2021-12-19 23:31:58 +11:00
return 1
fi
2021-12-19 23:31:58 +11:00
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}"
if [[ -z $extrainfo ]]; then
# just reattach to previous session
trv=0
else
# create or attach to window
tmux neww -S -n $obname telnet ${loc_engine[$locidx]} ${extrainfo}
trv=$?
fi
2021-12-19 23:31:58 +11:00
else
if [[ -z $extrainfo ]]; then
error "No existing session to attach"
return 1
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
2021-12-19 23:31:58 +11:00
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 '$actionname'"
2021-12-19 23:31:58 +11:00
return 1
fi
debug -f "ABOUT TO CALL CURL FUNCTION...."
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 "obuuid: ${obuuid}"
debug -f "options: ${opts}"
debug -f "curldata: ${curldata}"
if [[ $action == "add" || $action == "mv" ]]; then
# note: "obuuid" is empty in some cases, eg:
# adding a builtin node like VPCS
# adding a link
curlres=$(runcurldatapost "$loc" "$api_endpoint" "$obuuid" "$curldata" )
crv=$?
else
curlres=$(runcurlaction "$loc" "$api_endpoint" "$obuuid" "$method" "$curldata" )
crv=$?
fi
if [[ $curlres =~ $CURLERRORSTRINGS ]]; then
#if jq -e . >/dev/null 2>&1 <<< "$curlres"; then
# error "curl returned json error:"
# echo -en "$RED" >/dev/stderr
# echo "$curlres" | jq . | sed -e 's/^/ /' >/dev/stderr
# echo -e "$PLAIN" >/dev/stderr
#else
# error "curl returned error: [$curlres]"
#fi
crv=99
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
}
# uses globals jqscrjpt, jqfilter, awkscript
function apply_filters() { # retvar wantquotes [f][s][a]
local retvar which data wantquotes rawopt qstr
retvar="$1"
wantquotes="${2:-0}"
debug "wantquotes is $wantquotes"
if [[ $wantquotes -eq 0 ]]; then
rawopt="-r"
qstr="without quotes (jq -r)"
elif [[ $wantquotes -eq -1 ]]; then
rawopt="-r"
qstr="REALLY without quotes (jq -r | sed_remove_quotes)"
else
rawopt=""
qstr="with quotes"
fi
[[ -z $retvar ]] && return 1
which="${3:-sfa}"
eval "data=\"\$$retvar\""
debug "applying: $which $qstr"
debug " wantquotes is $wantquotes"
debug " jqfilter is $jqfilter"
debug " jqscript is $jqscript"
debug " awkscript is $awkscript"
debug " rawscript is $rawscript"
if [[ $which == *f* && -n $jqfilter ]]; then
local thiswq=$wantquotes thisro=$rawopt
# override wantquotes if we're hsing more jq later
if [[ -n $jqscript || -n $rawscript ]]; then
thiswq=1
thisro=""
fi
#debug "$retvar pre jqfilter is $data"
data=$(echo "$data" | jq $thisro "$jqfilter" 2>/dev/null)
if [[ $thiswq -eq -1 ]]; then
data=$(echo "$data" | tr -d '"')
fi
#debug "$retvar post jqfilter is $data"
fi
if [[ $which == *s* && -n $jqscript ]]; then
debug "jqscript contents: $(cat $jqscript)"
debug "$retvar pre jqscript is $data"
data=$(echo "$data" | jq $rawopt -f "$jqscript")
debug "$retvar post jqscript is $data"
fi
if [[ $which == *r* && -n $rawscript ]]; then
debug "rawscript is: $rawscript"
debug "$retvar pre rawscript is $data"
data=$(echo "$data" | jq $rawopt "$rawscript")
debug "$retvar post jqscript is $data"
fi
if [[ $which == *a* && -n $awkscript ]]; then
debug "$retvar pre awkscript is $data"
data=$(echo "$data" | awk -f "$awkscript")
fi
eval $retvar=\"\$data\"
return 0
}
2021-12-19 23:31:58 +11:00
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}"
rawscript=$(getarrayopt opts_arr R)
2021-12-19 23:31:58 +11:00
curlres=$(runcurlget $loc $api_endpoint)
[[ $? -ne 0 ]] && error "curl to $loc API failed" && return 1
debug "outmode is $outmode"
debug "jqscript is $jqscript"
debug "jqfilter is $jqfilter"
debug "awkscript is $awkscript"
debug "rawscript is $rawscript"
2021-12-19 23:31:58 +11:00
if [[ $outmode == "raw" ]]; then
r1="$curlres"
# only apply jqfilter unless -R is given
if [[ -n $rawscript ]]; then
jqscript="$rawscript" apply_filters r1 $QUOTES fr
else
apply_filters r1 $QUOTES f
fi
[[ -n $r1 ]] && echo "$r1" || rv=2
2021-12-19 23:31:58 +11:00
elif [[ $outmode == "verbose" ]]; then
r1="$curlres"
apply_filters r1 $REALLYNOQUOTES
[[ -n $r1 ]] && echo "$r1" || rv=2
2021-12-19 23:31:58 +11:00
else # namesonly
r1=$(echo "$curlres")
apply_filters r1
2021-12-19 23:31:58 +11:00
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,/")
else
r1=$(echo "$r1" | egrep -v "^$")
fi
fi
debug "final r1 is [$r1]"
[[ -n $r1 ]] && echo "$r1" | sed 's/__END__://' || rv=2
fi
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 "$filterarg" ]] && refilter="select(.${ep_defaultfield[$epidx]}|test(\"^$filterarg$\"))" || refilter="."
#if [[ ${ep_name[$epidx]} == "links" ]]; then
# refilter="${refilter} | to_entries"
#fi
2021-12-19 23:31:58 +11:00
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="not_found"
2021-12-19 23:31:58 +11:00
[[ $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
2021-12-19 23:31:58 +11:00
local col colidx
profile_start
ndatalines="$1"
shift 1
msg="$*"
table=$(echo -e "$msg" | column -t -s,)
2021-12-19 23:31:58 +11:00
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 without UUID replacement
debug "table before repl [\n$table]"
2021-12-19 23:31:58 +11:00
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))
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"
2021-12-19 23:31:58 +11:00
profile_mark "replaced uuids with names"
table="${table2}" ; unset table2 # save RAM
debug "table after repl [\n$table]"
# re-render table since field lengths may have changed
table=$(flatten_table "$table")
debug "table in csv is: [$table]"
table=$(echo -e "$table" | column -t -s, )
debug "table reformatted is: [$table]"
# add underlines
table=$(echo -e "$table" | 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; } }')
debug "table with underline: [$table]"
debug "table lines is $(echo "$table" | awk 'BEGIN { FS=" "; } END { print NR }')"
2021-12-19 23:31:58 +11:00
profile_mark "created colourisation sed script"
table2=$(echo -e "$table" | "$GSED" "$ADD_COLOURS")
table="${table2}" ; unset table2 # save RAM
echo -e "$table"
2021-12-19 23:31:58 +11:00
profile_mark "added colours"
}
# Process the first line of CSV contents and try to figure out which
# columns contain an object UUID based on the heading.
2021-12-19 23:31:58 +11:00
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 flatten_table() {
local awkscript max
# find max line length
max=$(echo -e "$*" | awk ' { if (length($0) > max) max=length($0); } END { print max }')
read -d '' awkscript << 'EOF'
(NR == 1) {
for (i=1; i<=NF; i++) {
pos=index($0, $i)
if (i>1) {
len=pos-lastpos;
wids = wids " " (pos-lastpos)
}
lastpos=pos;
}
#len=length($0)-lastpos+1;
#wids = wids " " (length($0)-lastpos+1);
lastwid = MAX - lastpos;
wids = wids " " lastwid;
FIELDWIDTHS=wids
}
{
OFS=","
$1=$1
for (i=1; i<=NF; i++) {
if (NR == 1) gsub("_"," ",$i);
gsub(" +$","",$i);
}
if (NR == 1) {
$NF=sprintf("%-" lastwid "s", $NF);
}
print
}
EOF
echo -e "$*" | awk -v MAX=$max "$awkscript"
}
function json_extract() { # json_extract "jsondata" jsonfield1 var1 jsonfield2 var2 ...
local jsondata jsonfield varname val rv=0
jsondata="$1"
shift 1
while [[ $# -ge 2 ]]; do
local thisrv
jsonfield="$1"
varname="$2"
shift 2
val=$(echo "$jsondata" | jq -r "${jsonfield}" 2>/dev/null)
thisrv=$?
[[ -z $val ]] && $thisrv=1
[[ $thisrv -ne 0 ]] && val=""
eval "$varname=\"$val\""
rv=$(($rv + $thisrv))
done
return $rv
}
2021-12-19 23:31:58 +11:00
# populates global JSON_RESULTS
2021-12-19 23:31:58 +11:00
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
2022-01-03 16:01:21 +11:00
# oooo change this
2021-12-19 23:31:58 +11:00
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"
GLOBALACTION="$actionname"
2021-12-19 23:31:58 +11:00
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
JSON_RESULTS=""
2021-12-19 23:31:58 +11:00
if [[ $foreground -eq 1 ]]; then
local thistmpfile
2021-12-19 23:31:58 +11:00
# 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-)"
thistmpfile="$TMPDIR/run,$loc,$ob"
runaction_loc $loc $epidx $actionname $obuuid $ob "$extrainfo" $opts > "$thistmpfile"
jqres=$(cat ${thistmpfile} | jq . 2>/dev/null)
if [[ $? -eq 0 ]]; then
JSON_RESULTS="${JSON_RESULTS}$jqres"
fi
rm -f $TMPDIR/run,*
2021-12-19 23:31:58 +11:00
else
pids=""
while read -r line ; do
debug -f "processing line: [$line]"
2021-12-19 23:31:58 +11:00
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 -f "--> loc = $loc"
debug -f "--> ob = $ob"
debug -f "--> obuuid = $obuuid"
debug -f "--> extrainfo = $extrainfo"
2022-01-03 17:35:47 +11:00
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
2022-01-03 17:35:47 +11:00
set +f
files=( $(ls $TMPDIR/run,*) )
2021-12-19 23:31:58 +11:00
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}
[[ $objecttype == "Model" ]] && objecttype="Node"
# figure out jq based on action
if [[ $actionname == "add" && $endpoint == "model" ]]; then
allgoodresults="Server,${objecttype},Ports"
jqf='[ "_DC_", "_OB_", .properties.adapters ] | @csv'
elif [[ $actionname == "add" && $endpoint == "link" ]]; then
local id
# hack: local ID won't be generated ubtil we
# recache, but weknow ot will be the last current
# id plus one.
id=$(get_next_linkid)
allgoodresults="Server,${objecttype},Predicted_ID"
jqf="[ \"_DC_\", .link_id, \"${id}\" ] | @csv"
elif [[ $actionname == "del" ]]; then
allgoodresults=""
jqf=''
else
#allgoodresults="Server,${objecttype},JobStatus"
#jqf='[ "_DC_", "_OB_", .status ] | @csv'
allgoodresults=""
jqf=''
fi
allbadresults=""
2021-12-19 23:31:58 +11:00
for f in ${!files[@]} ; do
local thiscsv
thisfile="${files[$f]}"
2022-01-03 17:35:47 +11:00
#if [[ -e "$thisfile" ]]; then
debug "processing file $thisfile"
debug "ls of file: $(ls -l $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"
JSON_RESULTS="${JSON_RESULTS}$jqres"
else
ACTIONRES_MSG[$n]=$(cat ${thisfile})
fi
2021-12-19 23:31:58 +11:00
2022-01-03 17:35:47 +11:00
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
else
goterror=0
fi
2021-12-19 23:31:58 +11:00
2022-01-03 17:35:47 +11:00
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 "^$")
allbadresults="${allbadresults}\n${thiscsv_bad}"
2021-12-19 23:31:58 +11:00
2022-01-03 17:35:47 +11:00
errs=$((errs + 1))
else
if [[ -n $jqf ]]; then
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}"
fi
2021-12-19 23:31:58 +11:00
2022-01-03 17:35:47 +11:00
good=$((good + 1))
#echo -e "$GREEN" >/dev/stderr
fi
#fi
2021-12-19 23:31:58 +11:00
n=$((n + 1))
done
echo
if [[ $errs -gt 0 ]]; then
local fullres_bad
2022-01-03 17:35:47 +11:00
fail
2022-01-03 16:01:21 +11:00
cecho -s "${RED}" "$errs x '^b$actionname^p' actions failed:"
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
2022-01-03 17:35:47 +11:00
ok
2022-01-03 16:01:21 +11:00
cecho -s "${GREEN}" "$good x '^b$actionname^p' actions submitted successfully."
2021-12-19 23:31:58 +11:00
# More than just a heading?
if [[ $(echo -e "$allgoodresults" | wc -l | bc) -gt 1 ]]; then
fullres=$(csv_to_table $(($good + 1)) "$allgoodresults")
echo "$fullres"
fi
2021-12-19 23:31:58 +11:00
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"
debug "validating action '$action' on obtype '$what'"
2021-12-19 23:31:58 +11:00
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
elif [[ $cmd == "net" ]]; then
outmode=verbose # global
2021-12-19 23:31:58 +11:00
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 "cmd is $cmd"
2021-12-19 23:31:58 +11:00
debug "opts is $opts"
outfile=$(getarrayopt opts_a o)
refilter=$(getarrayopt opts_a f)
# Allow standard globs rather than regexp globs
2021-12-19 23:31:58 +11:00
[[ $refilter == "*" ]] && refilter=".*"
if [[ -n $refilter ]]; then
obfilter="select(.${ep_defaultfield[$epidx]}|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$"
debug "refilter is $refilter"
debug "obfilter is $obfilter"
2021-12-19 23:31:58 +11:00
# 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
elif [[ $outmode == "raw" ]]; then
[[ $obfilter != "." ]] && basefilter=".[] | $obfilter"
2021-12-19 23:31:58 +11:00
fi
elif [[ $cmd == "net" ]]; then
jqscript="$SCRIPTDIR/diagram.jq"
#awkscript="$SCRIPTDIR/detail.awk"
2021-12-19 23:31:58 +11:00
elif [[ $cmd == "show" ]]; then
if [[ $outmode == "verbose" ]]; then
error "Verbose mode not supported for show command yet"
return 1
elif [[ $outmode == "raw" ]]; then
error "Raw mode not supported for show command yet - try 'list' instead"
2021-12-19 23:31:58 +11:00
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=""
[[ $quiet -eq 0 ]] && notify "Obtaining data from gns API"
2021-12-19 23:31:58 +11:00
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
if [[ $outmode == "raw" ]]; then
all=$(cat "$TMPDIR"/get.* 2>/dev/null)
else
all=$(cat "$TMPDIR"/get.* 2>/dev/null | tr -d : | egrep -v 'DC|Server' | sed '/^[[:space:]]*$/d')
fi
2021-12-19 23:31:58 +11:00
if [[ -z $all ]]; then
fail
cat "$TMPDIR"/err.*
rm -f "$TMPDIR"/get.*
rm -f "$TMPDIR"/err.*
return 1
fi
[[ $quiet -eq 0 ]] && ok
2021-12-19 23:31:58 +11:00
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]"
2021-12-19 23:31:58 +11:00
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
2022-01-03 16:01:21 +11:00
cecho -s "$YELLOW" " ^b(no results)"
2021-12-19 23:31:58 +11:00
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
elif [[ $cmd == "net" ]]; then
local dotscript dev port x repl
local updated_result=""
# replace uuids with names
fullres=$(cat $TMPDIR/get.*)
while read -r line ; do
if [[ $line =~ .*,.* ]]; then
dev[0]=$(echo "$line" | awk -F, '{ print $1 }')
port[0]=$(echo "$line" | awk -F, '{ print $2 }')
dev[1]=$(echo "$line" | awk -F, '{ print $3 }')
port[1]=$(echo "$line" | awk -F, '{ print $4 }')
for x in ${!dev[@]}; do
repl="$(uuid_to_name ${dev[$x]})"
if [[ $? -eq 0 ]]; then
dev[$x]="${repl}"
fi
done
#updated_result="${updated_result} \"${dev[0]}\":\"${port[0]}\" -- \"${dev[1]}\":\"${port[1]}\"\n"
updated_result="${updated_result} \"${dev[0]}\" -- \"${dev[1]}\"[label=\"${dev[0]}:${port[0]}\\\n${dev[1]}:${port[1]}\"]\n"
fi
done <<< "$fullres"
dotscript="digraph {\n$(echo "$updated_result")\n}"
debug "dotscript:\n${dotscript}"
if [[ -n $GRAPHEASY ]]; then
updated_result="${UNDERLINE}Network diagram for ${BOLD}$curproj${PLAIN}"
updated_result="$updated_result\n\n$(echo -e "$dotscript" | $GRAPHEASY --from=dot --as_ascii)"
else
error "graph-easy not installed - try 'sudo cpan Graph::Easy then use -g to specify path."
fi
2021-12-19 23:31:58 +11:00
else
# ie. show
fullres=$(cat $TMPDIR/get.*)
if [[ $outmode == "raw" ]]; then
echo "$fullres"
else
local updated_result=""
2021-12-19 23:31:58 +11:00
# Replace UUIDS with names from cache
# and colourise certain words
greenwords="true up enabled"
redwords="false down paused uninitialized disabled"
yellowwords="migrating"
2022-01-03 16:01:21 +11:00
uuidcol="$NOTIFYCOL"
2021-12-19 23:31:58 +11:00
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
2021-12-23 18:45:10 +11:00
updated_result=$(echo "$updated_result" | "$GSED" "$ADD_COLOURS")
2021-12-19 23:31:58 +11:00
echo -e "$updated_result"
rm -f $TMPDIR/get.*
else
# serial
cp /dev/null "$outfile"
[[ $quiet -eq 0 ]] && notify "Obtaining data from gns API into file"
2021-12-19 23:31:58 +11:00
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
}
2022-01-02 21:15:35 +11:00
function listcommandaliases() { # [filter]
local x format filter output="" rv=0
filter="$1"
format=" %-12s|%-24s|%-36s${PLAIN}\n"
for x in ${!cmdalias_src[@]}; do
2022-01-02 21:15:35 +11:00
if [[ -z $filter || ${cmdalias_dst[$x]} == *$filter* ]]; then
output="${output}$(printf "$format" "${cmdalias_src[$x]}" "${cmdalias_dst[$x]}" "${cmdalias_defaultarg[$x]}")\n"
fi
done
2022-01-02 21:15:35 +11:00
if [[ -n $output ]]; then
printf "${UNDERLINE}${BOLD}$format" "Alias" "Command" "Default Args"
echo -e "$output"
rv=0
else
echo "No command aliases for '$filter'"
rv=1
fi
return $rv
}
2021-12-19 23:31:58 +11:00
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 getnodetypeforadd() {
local nt
nt=$(echo "$1" | tr 'A-Z' 'a-z' | tr ' ' '_')
echo "$nt"
}
# populates globals:
# data
# ndcs
# nobs
# dc_ess
# ob_ess
# data
# allobs
# alluids
# o_arr
# ou_arr
function validate_action_obs() { # infoname whattolist actionfilter showerroropt multiple_obs_allowed [retvar_selobname] [retvar_selobuuid]
local infoname whattolist actionfilter showerroropt
local selobname="" selobuuid=""
local retvar_obname retvar_obuuid multiallowed
infoname="$1"
whattolist="$2"
actionfilter="$3"
showerroropt="$4"
multiallowed="$5"
retvar_obname="$6"
retvar_obuuid="$7"
debug "whattolist=$whattolist"
debug "actionfilter=$actionfilter"
debug "showerroropt=$showerroropt"
debug "multiallowed=$multiallowed"
debug "retvar_obname=$retvar_obname"
debug "retvar_obuuid=$retvar_obuuid"
notify "Validating ${infoname}"
getdata ${whattolist} list "${actionfilter}.*" $showerroropt -c -s -q >"$TMPFILE"
rv=$?
debug "actionfilter=$actionfilter"
if [[ $rv -ne 0 ]]; then
2022-01-03 17:35:47 +11:00
error "Can't find any ${whattolist}s matching '^b$actionfilter^p'."
elif [[ ! -e $TMPFILE ]]; then
error "No matching ${whattolist}s found."
elif grep -q "no results" $TMPFILE; then
error "No ${whattolist}s found matching '$obname'."
elif [[ ! -e $TMPFILE ]]; then
error "Results file does not exist."
else
ok
data=$(cat "$TMPFILE" | egrep -v "^$")
ndcs=$( printf %d $(echo "$data" | awk -F, '{ print $1 }' | sort -u | wc -l))
nobs=$( printf %d $(echo "$data" | awk -F, '{ print $2 }' | sort -u | wc -l))
[[ $ndcs -eq 1 ]] && dc_ess="" || dc_ess="s"
[[ $nobs -eq 1 ]] && ob_ess="" || ob_ess="s"
allobs=$(echo "$data" | awk -F, '{ print $2 }' | sort -u)
alluuids=$(echo "$data" | awk -F, '{ print $3 }' | sort -u)
debug "allobs is $allobs"
debug "alluuids is $alluuids"
o_arr=($allobs)
ou_arr=($alluuids)
if [[ $nobs -gt 1 && $multiallowed -eq 0 ]]; then
debug "actionfilter=$actionfilter"
2022-01-03 17:35:47 +11:00
inform "'${INFORMCOLB}${actionfilter}${INFORMCOL}' matched multiple ${whattolist}s"
narrowdown "$whattolist" "$allobs" "$alluuids" selobname selobuuid || rv=1
else
selobname="$allobs"
selobuuid="$alluuids"
fi
fi
[[ -n $retvar_obname ]] && eval "$retvar_obname=\"$selobname\""
[[ -n $retvar_obuuid ]] && eval "$retvar_obuuid=\"$selobuuid\""
rm -f "$TMPFILE"
return $rv
}
function narrowdown() { # whattolist "allobs" "all_uuids" retvar_newnodetype retvar_newnodetype_uuid
local whattolist allobs o_arr alluuids ou_arr
local o n rv=0
local newnodetype="" newnodetype_uuid=""
local retvar_newnodetype retvar_newnodetype_uuid
whattolist="$1"
allobs="$2"
alluuids="$3"
retvar_newnodetype="$4"
retvar_newnodetype_uuid="$5"
o_arr=($allobs)
ou_arr=($alluuids)
while [[ -z $newnodetype ]]; do
echo
n=1
while read -r o; do
printf "%3d. %s\n" $n "$o"
n=$((n + 1))
done <<<"$allobs"
echo
getstr ":" "" "Select one (q to abort)"
if [[ -n $retstr ]]; then
if [[ $retstr == "q" ]]; then
rv=1
break
elif [[ $retstr =~ ^[0-9]*$ ]]; then
if [[ $retstr -le 0 || $retstr -ge $n ]]; then
error "Invalid selection"
else
newnodetype="${o_arr[$((retstr - 1))]}"
newnodetype_uuid="${ou_arr[$((retstr - 1))]}"
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
newnodetype="$this"
newnodetype_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"
newnodetype=""
elif [[ $matched -gt 1 ]]; then
error "'$retstr' matched multiple choices: $allmatches"
newnodetype=""
fi
fi
fi
done
eval "$retvar_newnodetype=\"$newnodetype\""
eval "$retvar_newnodetype_uuid=\"$newnodetype_uuid\""
return $rv
}
function action_triggers_recache() {
local trigger=0
case "$1" in
add) trigger=1;;
del) trigger=1;;
mv) trigger=1;;
esac
return $((1 - $trigger))
}
# populates global CLIDS_RES
function convert_link_ids() { # endpoint str
local u ep
ep="$1"
u="$2"
CLIDS_RES=""
if [[ $ep == "link" && ${u} == l* ]]; then
u=$(name_to_uuid ${u})
if [[ $? -ne 0 ]]; then
2022-01-03 17:35:47 +11:00
error "Can't find link ID ^b$u^p - recache may be needed."
return 1
fi
fi
CLIDS_RES="$u"
return 0
}
function addline() { # varname "string_to_append"
local newstr varname extra
varname="$1"
extra="$2"
eval "newstr=\"\$$varname\""
[[ -n $newstr ]] && newstr="${newstr}\n"
newstr="${newstr}${extra}"
eval "$varname=\"${newstr}\""
}
2021-12-19 23:31:58 +11:00
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 origendpoint builtin newname
local newnodex=0 newnodey=0
local dev devuuid port portnum adapnum
local BUILTINMODELS="Cloud|VPCS|NAT|Frame Relay switch|Ethernet hub|Ethernet switch"
local oldname olduuid
2021-12-19 23:31:58 +11:00
cmd=$1
shift
arg="$*"
shift
RAWJSONPOSTDATA=""
2021-12-19 23:31:58 +11:00
[[ -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 [[ $cmd == "action" && $arg == "node connect" ]]; then
debug "special case - ignoring arg count for conne t action"
elif [[ $gotargs -lt ${cmd_minargs[$idx]} || $gotargs -gt ${cmd_maxargs[$idx]} ]]; then
2021-12-19 23:31:58 +11:00
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}
if [[ ${#arg_array[@]} -ge 1 ]]; then
convert_link_ids $endpoint "${arg_array[@]}" || return 1
opts+=("-f${CLIDS_RES}")
fi
2021-12-19 23:31:58 +11:00
elif [[ $cmd == "list" ]]; then
endpoint=${arg_array[0]} && unset 'arg_array[0]'
whattolist=${endpoint}
if [[ ${#arg_array[@]} -ge 1 ]]; then
convert_link_ids $endpoint "${arg_array[@]}" || return 1
opts+=("-f${CLIDS_RES}")
fi
elif [[ $cmd == "net" ]]; then
whattolist="links"
opts+="-e"
2021-12-19 23:31:58 +11:00
elif [[ $cmd == "action" ]]; then
endpoint=${arg_array[0]} && unset 'arg_array[0]'
origendpoint="$endpoint"
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
if [[ $endpoint == "node" ]]; then
newnodetype="${arg_array[3]}" # eg. VPCS, Cisco IOS, etc
# is it a builtin appliance?
if [[ $newnodetype =~ ^($BUILTINMODELS)$ ]]; then
debug "adding a gns3 built-in node ($newnodetype)"
builtin=1
whattolist="model"
actionfilter="-f.*${newnodetype}.*"
else
debug "adding a gns3 appliance ($newnodetype)"
builtin=0
# we will lookup the template ID of "nodetype" later...
whattolist="model"
actionfilter="-f.*${newnodetype}.*"
fi
elif [[ $endpoint == "link" ]]; then
local x idx failed=0
idx=2
for x in 0 1; do
dev[$x]=${arg_array[$idx]}; idx=$((idx + 1))
port[$x]=${arg_array[$idx]}; idx=$((idx + 1))
# replace * with .*
port[$x]=$(echo "${port[$x]}" | sed 's#[^\.]\*#\.\*#g')
if [[ -z ${dev[$x]} || -z ${port[$x]} ]]; then
failed=1
fi
done
if [[ $failed -eq 1 ]]; then
error "usage: action link add adev aport zdev zport"
return 1
fi
2022-01-03 17:35:47 +11:00
elif [[ $endpoint == "project" ]]; then
debug "adding a project named $newname"
else
error "Don't know how to add a $endpoint yet"
return 1
fi
elif [[ $actionname == "mv" ]]; then
oldname=${arg_array[2]}
newname=${arg_array[3]}
actionfilter="-f.*${oldname}.*"
2021-12-19 23:31:58 +11:00
else
# start/stop/del/etc
# oooo does thiss work with multi args?
if [[ ${#arg_array[@]} -ge 1 ]]; then
convert_link_ids $endpoint "${arg_array[@]}" || return 1
actionfilter="-f${CLIDS_RES}"
obname="${CLIDS_RES}"
elif [[ $cmd != "action" || $arg != "node connect" ]]; then
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=$?
;;
c)
start_recache
rv=$?
2022-01-03 17:35:47 +11:00
if [[ $rv -eq 2 ]]; then
error "No project selected."
elif [[ $rv -ne 0 ]]; then
error "UUID re-cache is already in progress (PID $recache_pid)"
fi
2021-12-19 23:31:58 +11:00
;;
sc)
if [[ -z ${arg} ]]; then
for x in ${!uuid_id[@]}; do
echo "UUID: ${uuid_id[$x]} -> ${uuid_name[$x]}"
done
else
local res rv
echo -e "$PLAIN"
res=$(uuid_to_name ${arg})
if [[ $? -eq 0 ]]; then
2022-01-03 16:01:21 +11:00
cecho -s "${CYAN}" " UUID ^b${arg}^p -> ${res}"
else
res=$(name_to_uuid ${arg})
if [[ $? -eq 0 ]]; then
2022-01-03 16:01:21 +11:00
cecho -s "${CYAN}" " UUID ${res} -> ^b${arg}^p"
else
error "No cache results found for ${arg}"
fi
fi
2021-12-19 23:31:58 +11:00
fi
;;
q)
DONE=1
;;
la)
2022-01-02 21:15:35 +11:00
listcommandaliases "$arg"
;;
2021-12-19 23:31:58 +11:00
help)
listcommands ADMIN
;;
*)
error "Admin command '$cmd' not implemented"
rv=1
;;
esac
else
case ${cmd} in
list|show|net)
2021-12-19 23:31:58 +11:00
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"
;;
2022-01-03 17:35:47 +11:00
project)
setproject "${arg}"
rv=$?
if [[ $rv -eq 0 ]]; then
loadcachefile $curlocs || start_recache
fi
;;
2021-12-19 23:31:58 +11:00
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
if [[ -n $whattolist ]]; then
local data confirm=0
# Get a list of objects to operate on
# ie. turn regexp into a list of dcs and obnames first
echo
if [[ $actionname == "connect" || $actionname == "mv" ]]; then
if [[ $actionname == "connect" && -z $actionfilter ]]; then
nobs=0
confirm=1
else
validate_action_obs "$whattolist name" ${whattolist} "$actionfilter" "$showerroropt" $NOMULTI || return $?
fi
if [[ $nobs -gt 1 ]]; then
local allobs=$(echo "$data" | awk -F, '{ print $2 }' | sort -u | tr '\n' ',')
error "Can't run '$actionname' with multiple objects (matched: ${allobs%,})"
return 1
else
confirm=1
fi
elif [[ $actionname == "add" ]]; then
if [[ $endpoint == "node" ]]; then
validate_action_obs "node type" ${whattolist} "$actionfilter" "$showerroropt" $NOMULTI newnodetype newnodetype_uuid
if [[ -z $newnodetype ]]; then
confirm=0
else
debug "newnodetype is $newnodetype"
debug "newnodeuuid is $newnodetype_uuid"
2022-01-03 17:35:47 +11:00
inform "Adding a new ${INFORMCOLB}$newnodetype${INFORMCOL} named ${INFORMCOLB}$newname${PLAIN}"
confirm=1
fi
elif [[ $endpoint == "link" ]]; then
local x json adaplist portnumlist portlist nports newn newu jqs
local jqs_name jqs_num err=0 errstr=""
for x in 0 1; do
local letter portlower
[[ $x -eq 0 ]] && letter=A || letter=Z
debug "about to validate link $x"
validate_action_obs "${letter}-end node" "node" "-f${dev[$x]}" "$showerroropt" $NOMULTI newn newu || return $?
dev[$x]="$newn"
devuuid[$x]="$newu"
debug "${letter}-node ${dev[$x]} is OK (${dev[$x]})"
debug "getting node $x ports"
notify "Validating ${letter}-end port"
json=$(getdata nodes list -q -e -r "-f${dev[$x]}")
portlower=$(echo ${port[$x]} | tr 'A-Z' 'a-z')
jqs=".ports[] | select(.name|ascii_downcase|test(\".*${portlower}.*\"))"
jqs_name="${jqs} | .short_name"
jqs_num="${jqs} | .port_number"
jqs_adapnum="${jqs} | .adapter_number"
portlist=$(echo "$json" | jq -r "$jqs_name")
portnumlist=$(echo "$json" | jq -r "$jqs_num")
adapnumlist=$(echo "$json" | jq -r "$jqs_adapnum")
debug "portlist is $portlist"
debug "portnumlist is $portnumlist"
debug "adapnumlist is $adapnumlist"
#debug "raw port data is\n$json"
nports=$(wc -l <<< "$portlist" | bc)
if [[ -z $portlist || $nports -eq 0 ]]; then
fail
addline errstr "$(error "${letter}-node ^b${dev[$x]}^p has no ports matching '^b${port[$x]}^p'." 2>&1)"
err=1
elif [[ $nports -gt 1 ]]; then
fail
addline errstr "$(error "${letter}-node ^b${dev[$x]}^p has multiple ports matching '^b${port[$x]}^p':" 2>&1)"
addline errstr "$(echo -e "${RED}$portlist${PLAIN}" | sed -e 's/^/ /' 2>&1)"
err=1
else
ok
port[$x]="$portlist"
portnum[$x]="$portnumlist"
adapnum[$x]="$adapnumlist"
debug "${letter}-node ${dev[$x]} port ${port[$x]} is OK (${dev[$x]})"
fi
done
if [[ $err -eq 0 ]]; then
inform "Adding a link from ^b${dev[0]} ${port[0]}^p to ^b${dev[1]} ${port[1]}"
confirm=1
else
[[ -n $errstr ]] && echo -e "$errstr"
confirm=0
fi
2022-01-03 17:35:47 +11:00
elif [[ $endpoint == "project" ]]; then
local existidx
existidx=$(getprojidx ${newname})
if [[ $? -eq 0 ]]; then
error "A project named '^b$newname^p already exists"
return 1
fi
inform "Creating a project named '^b$newname^p'"
confirm=1
2022-01-03 17:35:47 +11:00
else
error "Adding a $endpoint not implemented yet"
return 1
2021-12-19 23:31:58 +11:00
fi
else # action is not add/connect
validate_action_obs "${whattolist} name" ${whattolist} "$actionfilter" "$showerroropt" $MULTI || return $?
if [[ $whattolist == "link" ]]; then
local line srv linkuuid d p n newdata=""
# show link deacription instead of uuid
while read -r line; do
srv="${line%%,*}"
linkuuid="${line##*,}"
for n in 0 1; do
d[$n]=$(getfield links "$linkuuid" ".nodes[$n].node_id" | tr -d '"')
d[$n]=$(uuid_to_name "${d[$n]}")
p[$n]=$(getfield links "$linkuuid" ".nodes[$n].label.text" | tr -d '"')
done
[[ -n $newdata ]] && newdata="${newdata}\n"
newdata="${newdata}${srv},${d[0]} ${p[0]} <-> ${d[1]} ${p[1]}"
done <<<"$data"
textdata="$newdata"
else
textdata="$data"
fi
2022-01-03 17:35:47 +11:00
cecho "$INFORMCOL" "$INFORMCOLB" "About to run '^b$actionname^p' on ^b${nobs}^p ${whattolist}${ob_ess} on ^b${ndcs}^p server${dc_ess}:"
echo -e "$textdata" | awk -F, -v EP="$endpoint" "BEGIN {lastdc=\"\"} { if (\$1 != lastdc) { print \" ${YELLOW}- ${BOLD}\" \$1 (EP == \"project\") ? \"\" : \" -> project=${curproj}${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)
2022-01-03 17:35:47 +11:00
inform "Confirmation code is: ^b${confirmcode}^p"
getstr ":" "n" "Enter the above code to proceed, anything else will abort"
entered_string="$retstr"
[[ $entered_string == $confirmcode ]] && confirm=1 || confirm=0
fi
fi
else
# we dont need to do a data query
newnodetype_uuid=""
confirm=1
fi
2021-12-19 23:31:58 +11:00
if [[ $confirm -eq 1 ]]; then
if [[ $actionname == "add" ]]; then
local postdata
if [[ $endpoint == "node" ]]; 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 in the form of key1:val1^key2:val2...
# the endpoint we call depends on whether
# we are adding a gns3 builtin node or an appliance
if [[ $builtin -eq 1 ]]; then
local nodetypeforadd
# gns3 wants the nodetype in the post data and in a
# special format.
nodetypeforadd=$(getnodetypeforadd $newnodetype)
postdata="name:$newname^node_type:$nodetypeforadd^compute_id:local^template_id:$newnodetype_uuid"
# ...and NOT in the URL
newnodetype=""
newnodetype_uuid=""
else
# ie. use an appliance template
postdata="x:$newnodex^y:$newnodey"
endpoint="model"
fi
actiontargets="$curlocs,$newname,$newnodetype_uuid,$postdata"
#actiontargets="${actiontargets},$postdata"
elif [[ $endpoint == "link" ]]; then
#POST 'http://localhost:3080/v2/projects/bfb83f16-dab4-445a-a572-d7cc1801222e/links' -d '{"nodes": [{"adapter_number": 0, "label": {"text": "Text", "x": 42, "y": 0}, "node_id": "d3601934-e39a-4865-9cf5-3e46e1fe24e2", "port_number": 3}, {"adapter_number": 0, "node_id": "d3601934-e39a-4865-9cf5-3e46e1fe24e2", "port_number": 4}]}'
RAWJSONPOSTDATA="{\"nodes\": ["
for n in 0 1; do
RAWJSONPOSTDATA="${RAWJSONPOSTDATA}{\"node_id\": \"${devuuid[$n]}\", \"adapter_number\": ${adapnum[$n]}, \"port_number\": ${portnum[$n]}}"
[[ $n -eq 0 ]] && RAWJSONPOSTDATA="${RAWJSONPOSTDATA},"
done
RAWJSONPOSTDATA="${RAWJSONPOSTDATA}]}"
actiontargets="$curlocs,,,"
2022-01-03 17:35:47 +11:00
elif [[ $endpoint == "project" ]]; then
postdata="name:$newname"
actiontargets="$curlocs,$newname,,$postdata"
2021-12-19 23:31:58 +11:00
fi
elif [[ $actionname == "mv" ]]; then
oldname=$(echo "$data" | awk -F, '{ print $2 }' | sort -u)
olduuid=$(echo "$data" | awk -F, '{ print $3 }' | sort -u)
actiontargets="$curlocs,$oldname,$olduuid,name:$newname"
else
actiontargets=$(echo "$data")
fi
if [[ $actionname == "connect" ]]; then
local devname sevname srvport
if [[ -z $data ]]; then
inform "Resuming previous session"
devname="no_dev"
srvname="no_dev"
srvport="no_dev"
data="no_dev,no_dev,no_dev"
actiontargets="no_dev,no_dev,no_dev"
else
devname=$(echo "$data" | awk -F, '{ print $2 }')
srvname=$(echo "$data" | awk -F, '{ print $1 }')
srvport=$(echo "$data" | awk -F, '{ print $4 }')
inform "Connecting to $devname ($srvname:$srvport)"
fi
opts+=("-F")
else
notify "Submitting actions to gns API"
fi
rm -f "$TMPFILE"
2021-12-19 23:31:58 +11:00
debug "about to call runaction with:"
debug " endpoint = $endpoint"
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"
rv=$?
if [[ $actionname == "add" && $origendpoint == "node" && $rv -eq 0 && $builtin -eq 0 ]]; then
local fail=0
# we now need to rename the newly created object - gns3
# will always generate a name based on the template
debug "json is $JSON_RESULTS"
json_extract "$JSON_RESULTS" .name oldname .node_id olduuid
if [[ $? -eq 0 ]]; then
runaction ${origendpoint} mv "$curlocs,$oldname,$olduuid,name:$newname^" -F
[[ $? -ne 0 ]] && fail=1
else
fail=1
fi
if [[ $fail -eq 1 ]]; then
warn "Failed to rename new object to '$newname' - please check."
fi
fi
if [[ $actionname != "connect" ]]; then
ok
[[ -e $TMPFILE ]] && cat "$TMPFILE" | $pipe
if [[ $actionname == "add" && $origendpoint == "link" && $rv -eq 0 ]]; then
warn "Use '${ITALIC}l l${PLAIN}${YELLOW}' to confirm predicted ID"
fi
echo
info $(printf "action submission time: %s seconds" "$lastqsecs")
2021-12-19 23:31:58 +11:00
fi
if [[ $rv -eq 0 ]]; then
2022-01-03 17:35:47 +11:00
if [[ $actionname =~ ^add|del$ && $endpoint == "project" ]]; then
loadprojectlist || exit 1
fi
if action_triggers_recache $actionname; then
start_recache
fi
fi
fi # end if confirm == 1
2021-12-19 23:31:58 +11:00
rm -f "$TMPFILE"
;;
exit)
DONE=1
;;
help)
listcommands NOADMIN
;;
*)
error "Command '$cmd' not implemented"
rv=1
;;
esac
fi
return $rv
}
function updateprojstatus() { # projidx
local status idx
idx="$1"
# get open/close status
status=$(getfield projects "${proj_name[$idx]}" .status)
[[ $status == *opened* ]] && proj_isopen[$idx]=1 || proj_isopen[$idx]=0
}
function setproject() { # setproject [-q] [project_name]
local newproj x err rv idx status quiet=0
[[ $1 == "-q" ]] && quiet=1 && shift
2021-12-19 23:31:58 +11:00
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]}
curprojidx=${idx}
2021-12-19 23:31:58 +11:00
if [[ $err -eq 0 ]]; then
curproj="${newproj}"
2022-01-03 17:35:47 +11:00
[[ $quiet -eq 0 ]] && inform "Project set to: ^b$curproj^p ($curprojid)"
2021-12-19 23:31:58 +11:00
rv=0
updateprojstatus ${idx}
if [[ ${proj_isopen[$curprojidx]} -ne 1 ]]; then
2022-01-03 17:35:47 +11:00
echo -n " "
warn "This project is closed - use '^b^iopen^p' to open it."
fi
2021-12-19 23:31:58 +11:00
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
2021-12-23 18:45:10 +11:00
local alt="" rv globvar=""
local realpath=""
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)
2021-12-23 18:54:11 +11:00
if [[ $hw == "aarch64" ]]; then # Android Termux
2021-12-23 16:40:48 +11:00
alias which="command -v"
fi
if [[ $what == "gdate" ]]; then
alt=date
globvar=GDATE
elif [[ $what == "gsed" ]]; then
alt=sed
globvar=GSED
elif [[ $what == "graph-easy" ]]; then
globvar=GRAPHEASY
fi
realpath=$(which $what 2>&1)
rv=$?
2021-12-23 18:45:10 +11:00
if [[ $rv -ne 0 ]]; then
if [[ -n $alt ]]; then
$alt --version 2>/dev/null | grep -q GNU 2>/dev/null
if [[ $? -eq 0 ]]; then
realpath=$(which $alt 2>/dev/null)
2021-12-23 18:45:10 +11:00
rv=0
fi
fi
fi
2021-12-23 18:36:12 +11:00
if [[ $rv -eq 0 ]]; then
if [[ -n $globvar ]]; then
debug "found $what at $realpath - have set \$$globvar"
eval "$globvar=\"$realpath\""
else
debug "found $what at $realpath - no globvar"
fi
2021-12-19 23:31:58 +11:00
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
2021-12-23 18:54:11 +11:00
elif [[ $hw == "aarch64" ]]; then
apt install $pkg_name && ok=1
else
error "Don't know how to install stuff on OS '$os' HW '$hw'"
2021-12-19 23:31:58 +11:00
fi
;;
*)
2021-12-23 18:54:11 +11:00
error "Don't know how to install stuff on OS '$os' HW '$hw'"
2021-12-19 23:31:58 +11:00
;;
esac
[[ $ok -eq 1 ]] && info "Successfully installed $pkg_name" || error "Failed to install $pkg_name"
fi
fi
which $what >/dev/null 2>&1
rv=$?
[[ $rv -eq 0 && -n $globvar ]] && eval "$globvar=\"$realpath\""
return $rv
2021-12-19 23:31:58 +11:00
}
function killtmux() {
tmux kill-session -t "$1" >/dev/null 2>&1
}
function cleanup() {
local x
echo
2022-01-03 17:35:47 +11:00
set +f
2021-12-19 23:31:58 +11:00
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
g)
GRAPHEASY="${OPTARG}"
if [[ ! -x "$GRAPHEASY" ]]; then
error "Provided graph-easy binary not found or not executable ($GRAPHEASY)"
exit 1
fi
;;
2021-12-19 23:31:58 +11:00
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"
MAGENTA="\033[35m"
2021-12-19 23:31:58 +11:00
CYAN="\033[36m"
2022-01-03 16:01:21 +11:00
ORANGE="${PLAIN}\033[38;2;255;165;0m"
ORANGEBOLD="${BOLD}\033[38;2;255;220;0m"
MAGENTARGB="${PLAIN}\033[38;2;208;65;126m"
MAGENTARGBBOLD="${BOLD}\033[38;2;255;135;196m"
2022-01-03 17:35:47 +11:00
INFORMCOL="$ORANGE"
INFORMCOLB="$ORANGEBOLD"
2022-01-03 16:01:21 +11:00
NOTIFYCOL="$MAGENTARGB"
NOTIFYCOLB="$MAGENTARGBBOLD"
GREY="\033[38;2;110;110;110m"
2021-12-19 23:31:58 +11:00
LINK="$BLUE$UNDERLINE"
UNDERLINE_PRINTED=$(echo -en "$UNDERLINE")
UNDERLINE_LENGTH=${#UNDERLINE_PRINTED}
NOMULTI=0
MULTI=1
NOQUOTES=0
QUOTES=1
REALLYNOQUOTES=-1
CURLERRORSTRINGS="(^40.:|error|status.*40.)"
2021-12-19 23:31:58 +11:00
# 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}" )
2022-01-03 16:01:21 +11:00
UUIDCOL="$NOTIFYCOL"
2021-12-19 23:31:58 +11:00
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
2022-01-02 21:44:09 +11:00
nactusage=0
2021-12-19 23:31:58 +11:00
# 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"
2022-01-03 17:35:47 +11:00
addepactions projects open close add del
2021-12-19 23:31:58 +11:00
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 del mv
2022-01-02 21:44:09 +11:00
addepactionusage nodes "add" "new_node_name new_node_model"
addepactionusage nodes "del" "node_name"
addepactionusage nodes "mv" "old_node_name new_node_name"
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" "LinkID_UUID"
addepfields links ".link_type" ".nodes[0].node_id" ".nodes[0].label.text" ".nodes[1].node_id" ".nodes[1].label.text" ".link_id"
addepactions links add del
2022-01-02 21:44:09 +11:00
addepactionusage links "add" "a_nodename a_portname b_nodename b_portname"
addepactionusage links "del" "link_uuid"
2021-12-19 23:31:58 +11:00
addendpoint models templates model template_id name
addepalias models m
addeptitles models "Name" "Category" "UUID"
addepfields models ".name" ".category" ".template_id"
addendpoint appliances appliances appliance template_id name
addepalias appliances a
addeptitles appliances "Name" "Category" "Ports"
addepfields appliances ".name" ".category" ".qemu.adapters"
2021-12-19 23:31:58 +11:00
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 net "Show network diagram (requires perl Graph::Easy)"
addcmd action "Perform action on given object" 3 act do
2021-12-19 23:31:58 +11:00
declare -a lines
for x in ${!ep_jqobj[@]}; do
if [[ -z ${ep_validactions[$x]} ]]; then
lines+=("${ep_jqobj[$x]} actions: n/a")
else
2022-01-02 21:44:09 +11:00
lines+=("${ep_jqobj[$x]} actions:")
for va in ${ep_validactions[$x]}; do
usage=$(getactionusage $x $va)
lines+=(" $va $usage")
done
2021-12-19 23:31:58 +11:00
fi
done
addcmdusage action "object_type action_type regexp_filter [extrainfo]" "object_type can be: [${ep_jqobj[*]}]" "" "${lines[@]}"
2021-12-19 23:31:58 +11:00
addcmdoption action "-f" "Force - don't ask for confirmation"
2022-01-03 17:35:47 +11:00
addcmd project "Select project filter (comma or space separated list)" 1 setproject setp p
addcmdusage p "project_list" "project_list is a comma-separated list made up of [${loc_name[*]}]"
2021-12-19 23:31:58 +11:00
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" ""
addcmdalias "rm" "action node del" ""
addcmdalias "del" "action node del" ""
addcmdalias "mv" "action node mv" ""
addcmdalias "link" "action link add" ""
addcmdalias "unlink" "action link del" ""
addcmdalias "delink" "action link del" ""
2022-01-03 17:35:47 +11:00
addcmdalias "addp" "action project add" ""
addcmdalias "delp" "action project del" ""
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 la "List command aliases" 0
2021-12-19 23:31:58 +11:00
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="$RCARGS $f"
2021-12-19 23:31:58 +11:00
done < <(egrep -v "(^#|^$)" $RCFILE)
fi
VALIDARGS="g:hiIp:Pv"
2021-12-19 23:31:58 +11:00
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
2021-12-23 18:36:12 +11:00
checkfor gdate coreutils || exit 1
checkfor jq || exit 1
2021-12-23 18:54:11 +11:00
checkfor gsed || 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
2021-12-19 23:31:58 +11:00
2022-01-03 17:35:47 +11:00
loadprojectlist || exit 1
2021-12-19 23:31:58 +11:00
if [[ ${#proj_id[@]} -eq 0 ]]; then
error "No projects found on server!!"
exit 1
fi
setproject -q $DEFPROJECT || exit 1 # defaults to first one
2021-12-19 23:31:58 +11:00
[[ -e $HISTFILE ]] && history -r "$HISTFILE"
loadcachefile $curlocs
dumpmsgq
trap cleanup EXIT
echo -e "${UNDERLINE}Unofficial GNS3 cli v${VER}${PLAIN}"
2022-01-03 16:01:21 +11:00
#echo -e "${ITALIC}${YELLOW}Note: this script is still in development and may have bugs!${PLAIN}"
2021-12-19 23:31:58 +11:00
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#" + .nodes[0].node_id,
"A-Port#" + .nodes[0].label.text,
"Z-Node#" + .nodes[1].node_id,
"Z-Port#" + .nodes[1].label.text,
"Capturing#" + (.capturing|tostring),
"Suspended#" + (.suspend|tostring),
2021-12-19 23:31:58 +11:00
"__END__"
#END_INLINE:link.jq
#START_INLINE:diagram.jq
.[] | .nodes[0].node_id + "," + .nodes[0].label.text + "," + .nodes[1].node_id + "," + .nodes[1].label.text
#END_INLINE:diagram.jq