3549 lines
113 KiB
Bash
Executable File
3549 lines
113 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
## todo
|
|
# start/stop/del with multiple args broken
|
|
# rename node issue when matching multiple
|
|
# rename project
|
|
# node stop givesjq error
|
|
# configure docker node IP/mask/gateway
|
|
# configure docker node /etc/hosts
|
|
# configure docker node env vars
|
|
|
|
VER=1.0
|
|
SPINNERFRAMES='|/-\'
|
|
|
|
. ${HOME}/code/bashtools/bashtools.sh
|
|
if [[ -z $HAVE_BASHTOOLS ]]; then
|
|
echo "ERROR: bashtools not installed download from https://git.nethack.net/rob/bashtools" >/dev/stderr
|
|
exit 1
|
|
fi
|
|
|
|
# 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
|
|
csecho "$CYAN" "Initialisation of ^b$CONFDIR^p complete."
|
|
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"
|
|
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
|
|
done
|
|
done
|
|
|
|
kill -SIGUSR1 $mainpid
|
|
}
|
|
|
|
function loadcachefile() { # loadcachefile [dc1 dc2 ...]
|
|
local f line dc added
|
|
if [[ ! -d "$CACHEDIR" ]]; then
|
|
return 1
|
|
fi
|
|
clear_cache
|
|
|
|
added=0
|
|
if [[ $# -ge 1 ]]; then
|
|
for dc in $*; do
|
|
f=${CACHEFILEBASE}.${dc}
|
|
if [[ -e ${f} ]]; then
|
|
while read -r line ; do
|
|
adduuid "${line%%,*}" "${line##*,}"
|
|
if [[ $? -eq 0 ]]; then
|
|
added=$((added + 1))
|
|
fi
|
|
done < "${f}"
|
|
fi
|
|
done
|
|
addmsgq $(info "Loaded $added UUIDs from cache files for $*" 2>&1)
|
|
else
|
|
ls ${CACHEFILEBASE}.* >/dev/null 2>&1
|
|
if [[ $? -eq 0 ]]; then
|
|
for f in ${CACHEFILEBASE}.*; do
|
|
while read -r line ; do
|
|
adduuid ${line%%,*} ${line##*,}
|
|
done < "$f"
|
|
done
|
|
[[ $VERBOSE -eq 1 ]] && addmsgq $(info "Loaded cache files for all servers" 2>&1)
|
|
fi
|
|
fi
|
|
}
|
|
|
|
function uuid_callback() {
|
|
nuuids=0
|
|
loadcachefile $curlocs
|
|
uuidend=$(($($GDATE +%s%N)/1000))
|
|
uuidsecs=$(echo "scale=2; ($uuidend - $uuidstart) / 1000000;" | bc)
|
|
|
|
if [[ $nuuids -ge 1 ]]; then
|
|
addmsgq $(info "Cached $nuuids UUID(s) for $curlocs in $uuidsecs seconds." 2>&1)
|
|
else
|
|
addmsgq $(error "No UUIDs found for [$curlocs]" 2>&1)
|
|
fi
|
|
CACHING=""
|
|
unset recache_pid
|
|
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
|
|
|
|
[[ -z $curproj ]] && return 2
|
|
|
|
uuidstart=$(($($GDATE +%s%N)/1000))
|
|
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..."
|
|
CACHING="background task: refreshing cache for $curlocs"
|
|
cache_uuids $$ $* &
|
|
recache_pid=$!
|
|
rv=0
|
|
else
|
|
rv=1
|
|
fi
|
|
return $rv
|
|
}
|
|
|
|
# populates global retstr
|
|
function getstr() { # getstr last_prompt_char default_answer "prompt string"
|
|
local lastchar def prompt rv
|
|
lastchar="$1"
|
|
shift
|
|
def="$1"
|
|
shift
|
|
prompt="$*"
|
|
retstr=""
|
|
echo -n -e "${INFORMCOL}$prompt [${INFORMCOLB}$def$INFORMCOL]${lastchar}${PLAIN} "
|
|
read retstr
|
|
echo -e "$PLAIN"
|
|
[[ -z $retstr ]] && retstr="$def"
|
|
}
|
|
|
|
# populates global yn, returns 0 for yes, 1 for no
|
|
function getyn() { # getyn default_answer "prompt string"
|
|
local def prompt rv
|
|
def="$1"
|
|
shift
|
|
prompt="$*"
|
|
yn=""
|
|
while [[ $yn != "y" && $yn != "n" ]]; do
|
|
getstr "?" "$def" "$prompt"
|
|
yn="$retstr"
|
|
done
|
|
if [[ $yn == "y" ]]; then
|
|
rv=0
|
|
else
|
|
rv=1
|
|
fi
|
|
return $rv
|
|
}
|
|
|
|
function arraycontains() { # arraycontains arrayname lookfor wildcard
|
|
local arr lookfor varname wild lookfor_modified
|
|
varname="$1"
|
|
lookfor="$2"
|
|
shift 2
|
|
|
|
eval arr=( '"${'${varname}'[@]}"' )
|
|
if [[ $# -ge 1 ]]; then
|
|
lookfor_modified=" $lookfor"
|
|
else
|
|
lookfor_modified=" $lookfor "
|
|
fi
|
|
[[ " ${arr[@]} " =~ $lookfor_modified ]] && return 0
|
|
return 1
|
|
|
|
}
|
|
|
|
function getcmd() {
|
|
local prefix pstr input varname rv pcol
|
|
varname=$1
|
|
if [[ -z $curproj ]]; then
|
|
prefix="no_project_selected"
|
|
pcol="$GREY"
|
|
else
|
|
prefix="${curproj}"
|
|
if [[ ${proj_isopen[$curprojidx]} -eq 1 ]]; then
|
|
pcol="$GREEN"
|
|
else
|
|
pcol="$GREY"
|
|
fi
|
|
fi
|
|
pstr="gnscli> "
|
|
|
|
[[ ! -z $CACHING ]] && info "$CACHING"
|
|
|
|
set -f
|
|
builtin read -p "$(echo -en $BOLD)[$(echo -en $PLAIN$pcol)${prefix}$(echo -e "$PLAIN$BOLD")] $pstr$(echo -e "${PLAIN}")" -e -r input
|
|
set +f
|
|
rv=$?
|
|
eval "$varname='$input'"
|
|
return $rv
|
|
}
|
|
|
|
function listprojects() {
|
|
local x format
|
|
format=" %-24s|%-36s${PLAIN}\n"
|
|
printf "${UNDERLINE}${BOLD}$format" "Project" "UUID"
|
|
for x in ${!proj_name[@]}; do
|
|
printf "$format" "${proj_name[$x]}" "${proj_id[$x]}"
|
|
done
|
|
}
|
|
|
|
function listservers() {
|
|
local x format
|
|
format=" %-12s|%-24s|%-34s${PLAIN}\n"
|
|
printf "${UNDERLINE}${BOLD}$format" "Location" "Host" "API URL"
|
|
for x in ${!loc_name[@]}; do
|
|
printf "$format" "${loc_name[$x]}" "${loc_engine[$x]}" "${loc_api[$x]}"
|
|
done
|
|
}
|
|
|
|
function profile() {
|
|
local fname
|
|
fname=${FUNCNAME[2]}
|
|
[[ -z $fname ]] && fname=${FUNCNAME[1]}
|
|
[[ $PROFILING -eq 1 ]] && csecho "$YELLOW" "^b${FUNCNAME[1]}(): ^p$*" 1>&2
|
|
}
|
|
|
|
function harddebug() {
|
|
local minusn=""
|
|
if [[ $1 == "-n" ]]; then
|
|
minusn="-n"
|
|
shift
|
|
fi
|
|
echo -e $minusn "${YELLOW}${BOLD}$*${PLAIN}" >/dev/stderr
|
|
}
|
|
|
|
function harddebug_v() { # varname
|
|
local varname str
|
|
varname="$1"
|
|
eval "str=\"$varname is [\$$varname]\""
|
|
harddebug "$str"
|
|
}
|
|
|
|
function debug() {
|
|
local force=0
|
|
if [[ $1 == "-f" ]]; then
|
|
shift
|
|
force=1
|
|
fi
|
|
if [[ $VERBOSE -eq 1 ]]; then
|
|
if [[ $force -eq 1 ]]; then
|
|
echo -e "${NOTIFYCOLB}${FUNCNAME[1]}(): ${NOTIFYCOL}$*${PLAIN}" >/dev/stderr
|
|
else
|
|
echo -e "${NOTIFYCOLB}${FUNCNAME[1]}(): ${NOTIFYCOL}$*${PLAIN}" 1>&2
|
|
fi
|
|
fi
|
|
}
|
|
|
|
function usage() {
|
|
echo "usage: $0 OPTIONS command"
|
|
echo
|
|
echo " -g path Specify path to perl Graph::Easy binary"
|
|
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
|
|
}
|
|
|
|
function uuid_to_idx() {
|
|
local x
|
|
for x in ${!uuid_id[@]}; do
|
|
if [[ ${uuid_id[$x]} == $1 ]]; then
|
|
echo "${$x}"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
function addloc() {
|
|
[[ -z $nlocs ]] && nlocs=0
|
|
loc_name[$nlocs]="$1"
|
|
if [[ $nlocs -eq 0 ]]; then
|
|
curlocs="$1"
|
|
fi
|
|
loc_engine[$nlocs]="$2"
|
|
loc_port[$nlocs]="$3"
|
|
|
|
loc_api[$nlocs]="http://${loc_engine[$nlocs]}:${loc_port[$nlocs]}/v2"
|
|
|
|
nlocs=$((nlocs + 1))
|
|
}
|
|
|
|
|
|
function 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"
|
|
csecho "${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"
|
|
csecho "${RED}" "^bOutput:^p"
|
|
echo -en "${RED}"
|
|
echo "${output}" | sed -e 's/^/ /'
|
|
echo -en "${PLAIN}"
|
|
csecho "${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]
|
|
[[ -z $nprojects ]] && nprojects=0
|
|
proj_id[$nprojects]="$1"
|
|
proj_name[$nprojects]="$2"
|
|
if [[ -n $3 ]]; then
|
|
proj_isopen[$nprojects]=1
|
|
else
|
|
proj_isopen[$nprojects]=""
|
|
fi
|
|
|
|
nprojects=$((nprojects + 1))
|
|
}
|
|
|
|
# Don't use '#'. Use ^ for spaces.
|
|
function addepfields() { # addepfields ep_name field1 field2 field3 ...
|
|
local idx
|
|
idx=$(getepidx $1)
|
|
if [[ $? -ne 0 ]]; then
|
|
error "appepfields(): can't find endpoint named '$1'"
|
|
return 1
|
|
fi
|
|
shift
|
|
while [[ $# -ge 1 ]]; do
|
|
[[ -z ${ep_fields[$idx]} ]] && ep_fields[$idx]="${1}" || ep_fields[$idx]="${ep_fields[$idx]}#${1}"
|
|
shift
|
|
done
|
|
}
|
|
|
|
# Don't use '#'. Use ^ for spaces.
|
|
function addeptitles() { # addeptitles ep_name title1 title2 title3 ... # Don't use '#' or spaces.
|
|
local idx
|
|
idx=$(getepidx $1)
|
|
if [[ $? -ne 0 ]]; then
|
|
error "appeptitles(): can't find endpoint named '$1'"
|
|
return 1
|
|
fi
|
|
shift
|
|
while [[ $# -ge 1 ]]; do
|
|
ep_titles[$idx]="${ep_titles[$idx]}#${1}"
|
|
shift
|
|
done
|
|
}
|
|
|
|
function addendpoint() { # addendpoint ep_name ep_apiendpoint ep_jqobjectname ep_idfieldname [defaultfieldname]
|
|
[[ -z $neps ]] && neps=0
|
|
|
|
ep_name[$neps]="$1"
|
|
ep_apiendpoint[$neps]="$2"
|
|
ep_jqobj[$neps]="$3"
|
|
ep_idfield[$neps]="$4"
|
|
ep_defaultfield[$neps]="${5:-name}"
|
|
ep_alias[$neps]=""
|
|
ep_fields[$neps]=""
|
|
ep_titles[$neps]="Server"
|
|
ep_validactions[$neps]=""
|
|
|
|
neps=$((neps + 1))
|
|
}
|
|
|
|
function addepalias() { # addeptitles ep_name alias1 alias2 alias3 ...
|
|
local idx
|
|
idx=$(getepidx $1)
|
|
if [[ $? -ne 0 ]]; then
|
|
error "appepalias(): can't find endpoint named '$1'"
|
|
return 1
|
|
fi
|
|
shift
|
|
while [[ $# -ge 1 ]]; do
|
|
ep_alias[$idx]="${ep_alias[$idx]} ${1} "
|
|
shift
|
|
done
|
|
}
|
|
|
|
function addepactions() { # addepactions ep_name action_name [action_name2] ...
|
|
local idx
|
|
idx=$(getepidx $1)
|
|
if [[ $? -ne 0 ]]; then
|
|
error "appepaction(): can't find endpoint named '$1'"
|
|
return 1
|
|
fi
|
|
shift
|
|
while [[ $# -ge 1 ]]; do
|
|
ep_validactions[$idx]="${ep_validactions[$idx]} ${1} "
|
|
shift
|
|
done
|
|
}
|
|
|
|
|
|
function 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
|
|
}
|
|
|
|
function getcmdidx() { # getcmdidx commandname adminonly(1|0)
|
|
local x adm lookfor match
|
|
lookfor=$1
|
|
adm=$2
|
|
|
|
match=0
|
|
for x in ${!cmd_name[@]}; do
|
|
#echo "check alias '${cmd_aliases[$x]}' against '* $lookfor *'" >/dev/stderr
|
|
if [[ ${cmd_name[$x]} == $lookfor ]]; then
|
|
match=1
|
|
elif [[ ${cmd_aliases[$x]} == *\ $lookfor\ * ]]; then
|
|
match=1
|
|
else
|
|
match=0
|
|
fi
|
|
if [[ $match -eq 1 ]]; then
|
|
if [[ -z $adm || ${cmd_adm[$x]} == $adm ]]; then
|
|
echo "$x"
|
|
return 0
|
|
fi
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
function addcmdusage() { # addcmdusage cmd_name "usage goes here" "line 2" "line 3" ...
|
|
local cname idx text
|
|
cname="$1"
|
|
shift
|
|
idx=$(getcmdidx $cname)
|
|
[[ $? -ne 0 ]] && return 1
|
|
text=""
|
|
while [[ $# -ge 1 ]]; do
|
|
[[ -z $text ]] && text="$1" || text="$text\n$1"
|
|
shift 1
|
|
done
|
|
cmd_usage[$idx]="$text"
|
|
}
|
|
|
|
function addcmdoption() {
|
|
local cname idx text
|
|
cname="$1"
|
|
shift
|
|
idx=$(getcmdidx $cname)
|
|
if [[ $? -ne 0 ]]; then
|
|
error "Tried to addcmdoption() for nonexistant command '$cname'"
|
|
kill $$
|
|
fi
|
|
while [[ $# -ge 1 ]]; do
|
|
[[ -z ${cmd_optionname[$idx]} ]] && cmd_optionname[$idx]="$1" || cmd_optionname[$idx]="${cmd_optionname[$idx]}#$1"
|
|
[[ ${#1} -gt ${cmd_maxoptnamelen[$idx]} ]] && cmd_maxoptnamelen[$idx]=${#1}
|
|
shift 1
|
|
|
|
[[ -z ${cmd_optiondesc[$idx]} ]] && cmd_optiondesc[$idx]="$1" || cmd_optiondesc[$idx]="${cmd_optiondesc[$idx]}#$1"
|
|
shift 1
|
|
done
|
|
}
|
|
|
|
function showcmdhelp() {
|
|
local real_cname cname idx text ln x format optname_arr optdesc_arr len
|
|
local otheraliases
|
|
cname="$1"
|
|
shift
|
|
idx=$(getcmdidx $cname)
|
|
if [[ $? -ne 0 ]]; then
|
|
|
|
|
|
# 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
|
|
fi
|
|
real_cname="${cmd_name[$idx]}"
|
|
[[ ${cmd_adm[$idx]} -eq 1 ]] && real_cname="\\${real_cname}"
|
|
|
|
# make array of options
|
|
IFS='#' read -r -a optname_arr <<< "${cmd_optionname[$idx]}"
|
|
IFS='#' read -r -a optdesc_arr <<< "${cmd_optiondesc[$idx]}"
|
|
len=${cmd_maxoptnamelen[$idx]}
|
|
|
|
|
|
echo -n "Usage: ${real_cname} "
|
|
[[ $len -gt 0 ]] && echo -n "[OPTIONS] "
|
|
|
|
# show usage if we have it
|
|
if [[ -n ${cmd_usage[$idx]} ]]; then
|
|
echo -e "${cmd_usage[$idx]}" | awk '(NR == 1) { print }'
|
|
else
|
|
echo
|
|
fi
|
|
echo " ${cmd_desc[$idx]}."
|
|
|
|
if [[ -n ${cmd_usage[$idx]} ]]; then
|
|
echo
|
|
echo -e "${cmd_usage[$idx]}" | awk '(NR > 1) { printf(" %s\n",$0); }'
|
|
fi
|
|
|
|
# show options
|
|
if [[ $len -gt 0 ]]; then
|
|
echo
|
|
format=" %-${len}s %s\n"
|
|
for x in ${!optname_arr[@]}; do
|
|
printf "$format" "${optname_arr[$x]}" "${optdesc_arr[$x]}"
|
|
done
|
|
fi
|
|
|
|
# Show aliases
|
|
if [[ -n ${cmd_aliases[$idx]// /} ]]; then
|
|
echo
|
|
echo "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/^/ /'
|
|
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"
|
|
}
|
|
|
|
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"]
|
|
local thisapi thisurl curlres rv
|
|
#local thisauthdom u p thisauthstr
|
|
local loc api_endpoint obname ovmethou
|
|
local data=""
|
|
local curlmethod="POST"
|
|
#local data="<action/>"
|
|
loc="$1"
|
|
api_endpoint="$2"
|
|
obname="$3"
|
|
ovmethod="$4"
|
|
data="$5"
|
|
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"
|
|
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)
|
|
|
|
rv=$?
|
|
[[ $rv -ne 0 ]] && error "curl ${curlmethod} to $thisurl failed"
|
|
echo "$curlres"
|
|
return $rv
|
|
}
|
|
|
|
# getarrayopt arrname f
|
|
#
|
|
# ie. if myvar=("aaa" "-z" "-ofirst_test" "123" "-osecond_test"
|
|
#
|
|
# then "getarrayopt myvar o" will output "first_test"
|
|
|
|
function getarrayopt() { # getarrayopt arrname optname
|
|
local arrname optname arrtext optval rv newarr
|
|
local localdb
|
|
localdb=0
|
|
arrname="$1"
|
|
optname="$2"
|
|
[[ $localdb -eq 1 ]] && info "look for -$optname in \$$arrname"
|
|
# fix in case array indices aren't sequential
|
|
arrtext=$(eval "echo \"\${${arrname}[@]}\"")
|
|
newarr=( $arrtext )
|
|
[[ $localdb -eq 1 ]] && info "arrtext is '$arrtext'"
|
|
if arraycontains $arrname "-$optname" WILDCARD; then
|
|
local this
|
|
# get array index of given option
|
|
idx=$(echo "${arrtext}" | awk -v lookfor="-$optname" '{ for (x=1;x<=NF;x++) { if (index($x,lookfor) == 1) print (x-1); } }')
|
|
[[ $localdb -eq 1 ]] && info " found at idx $idx"
|
|
this=${newarr[$idx]}
|
|
[[ $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"
|
|
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 "";;
|
|
dupe) echo "duplicate";;
|
|
#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 action_needs_curldata() { # actionname
|
|
local a
|
|
a="$1"
|
|
case $a in
|
|
add) return 0;;
|
|
mv) return 0;;
|
|
dupe) return 0;;
|
|
esac
|
|
return 1;
|
|
}
|
|
|
|
function runaction_loc() { # runaction_loc syd|etc api_endpoint action_name ob_uuid ob_name "extra info" [options]
|
|
local thisgrid thisauthdom thisurl thisauthstr curlres loc u p
|
|
local api_endpoint jq_obj opts opts_arr deffield epidx r1 r2 rv
|
|
local actionname method trv extrainfo
|
|
local curldata="" crv obuuid
|
|
rv=0
|
|
|
|
loc="$1"
|
|
epidx="$2"
|
|
actionname="$3"
|
|
obuuid="$4"
|
|
obname="$5"
|
|
extrainfo="$6"
|
|
|
|
thisgrid=$(get $loc grid)
|
|
api_endpoint="${ep_apiendpoint[$epidx]}"
|
|
jq_obj="${ep_jqobj[$epidx]}"
|
|
deffield="${ep_defaultfield[$epidx]}"
|
|
|
|
shift 6
|
|
opts="$*"
|
|
opts_arr=( $opts )
|
|
|
|
debug -f "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 action_needs_curldata $actionname; then
|
|
debug -f "action '$actionname' needs curldata"
|
|
if [[ -n $RAWJSONPOSTDATA ]]; then
|
|
debug -f "-->using RAWJSONPOSTDATA for curldata"
|
|
curldata="${RAWJSONPOSTDATA}"
|
|
else
|
|
debug -f "-->making curldata from [$extrainfo]"
|
|
curldata=$(makejson "$extrainfo")
|
|
fi
|
|
fi
|
|
|
|
RAWJSONPOSTDATA=""
|
|
|
|
# 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'"
|
|
return 1
|
|
fi
|
|
|
|
|
|
tmux ls 2>&1 | egrep -q "^$curproj:"
|
|
if [[ $? -eq 0 ]]; then
|
|
debug "using existing tmux session with: tmux neww -S -n $obname telnet ${loc_engine[$locidx]} ${extrainfo}"
|
|
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
|
|
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
|
|
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'"
|
|
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
|
|
rv=1
|
|
fi
|
|
debug -f "curlres is [$curlres]"
|
|
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
|
|
}
|
|
|
|
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)
|
|
|
|
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"
|
|
|
|
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
|
|
elif [[ $outmode == "verbose" ]]; then
|
|
r1="$curlres"
|
|
apply_filters r1 $REALLYNOQUOTES
|
|
[[ -n $r1 ]] && echo "$r1" || rv=2
|
|
else # namesonly
|
|
r1=$(echo "$curlres")
|
|
apply_filters r1
|
|
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
|
|
|
|
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"
|
|
[[ $x == ".status" ]] && thisdefault="project_closed"
|
|
x=${x//^/ }
|
|
#bf="${bf}, try(${x}) catch(\"xx\")"
|
|
bf="${bf}, ${x} // \"${thisdefault}\""
|
|
done
|
|
bf="${bf}]) | @csv"
|
|
echo "${bf}"
|
|
}
|
|
|
|
function profile_start() {
|
|
profile "start profiling"
|
|
prof_start=$(($($GDATE +%s%N)/1000))
|
|
prof_last="$prof_start"
|
|
}
|
|
|
|
function profile_mark() {
|
|
local now secs
|
|
now=$(($($GDATE +%s%N)/1000))
|
|
secs=$(echo "scale=2; ($now - $prof_last) / 1000000;" | bc)
|
|
profile "[+${secs} secs] $* "
|
|
prof_last="$now"
|
|
}
|
|
|
|
# Output CSV contents as a nicely formatted table.
|
|
# If a heading row is detected, it is bolded and underlines.
|
|
# The last table line is also underlined.
|
|
# A blank line is printed before each change of first column value - this is generally the data centre name.
|
|
function csv_to_table() { # csv_to_table num_data_lines_excluding_heading "csv_string_with_newlines"
|
|
local ndatalines msg
|
|
local table
|
|
local data ln
|
|
local uuidcolumn uuidcolumns thisuuid repl line
|
|
local col colidx
|
|
|
|
profile_start
|
|
|
|
ndatalines="$1"
|
|
shift 1
|
|
msg="$*"
|
|
|
|
table=$(echo -e "$msg" | column -t -s,)
|
|
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]"
|
|
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"
|
|
|
|
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 }')"
|
|
|
|
|
|
profile_mark "created colourisation sed script"
|
|
table2=$(echo -e "$table" | "$GSED" "$ADD_COLOURS")
|
|
table="${table2}" ; unset table2 # save RAM
|
|
echo -e "$table"
|
|
|
|
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.
|
|
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
|
|
}
|
|
|
|
# populates global JSON_RESULTS
|
|
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
|
|
# oooo change this
|
|
local jqf_bad='[ "_DC_", "_OB_", .fault.detail, .fault.reason, .status ] | @csv'
|
|
local objecttype extrainfo
|
|
|
|
start=$(($($GDATE +%s%N)/1000))
|
|
lastqsecs=""
|
|
lastprocsecs=""
|
|
what="$1"
|
|
shift
|
|
actionname="$1"
|
|
GLOBALACTION="$actionname"
|
|
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=""
|
|
|
|
if [[ $foreground -eq 1 ]]; then
|
|
local thistmpfile
|
|
# 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,*
|
|
else
|
|
pids=""
|
|
while read -r line ; do
|
|
debug -f "processing line: [$line]"
|
|
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"
|
|
|
|
runaction_loc "$loc" "$epidx" "$actionname" "$obuuid" "$ob" "$extrainfo" $opts > "$TMPDIR/run,$loc,$ob" &
|
|
pids="$pids $!"
|
|
done <<< "$targetlist"
|
|
|
|
wait $pids
|
|
|
|
set +f
|
|
files=( $(ls $TMPDIR/run,*) )
|
|
unset ACTIONRES_LOC
|
|
unset ACTIONRES_MSG
|
|
n=0
|
|
errs=0
|
|
good=0
|
|
rv=0
|
|
|
|
# Capitalise first letter
|
|
objecttype=$(echo ${what:0:1} | tr '[a-z]' '[A-Z]')${what:1}
|
|
|
|
[[ $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 we know it 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 == "dupe" && $endpoint == "project" ]]; then
|
|
allgoodresults="Server,${objecttype},New_Project_ID"
|
|
# return name of NEW object, not old one.
|
|
jqf='[ "_DC_", .name, .project_id ] | @csv'
|
|
elif [[ $actionname == "del" ]]; then
|
|
allgoodresults=""
|
|
jqf=''
|
|
else
|
|
#allgoodresults="Server,${objecttype},JobStatus"
|
|
#jqf='[ "_DC_", "_OB_", .status ] | @csv'
|
|
allgoodresults=""
|
|
jqf=''
|
|
fi
|
|
|
|
allbadresults=""
|
|
for f in ${!files[@]} ; do
|
|
local thiscsv
|
|
thisfile="${files[$f]}"
|
|
#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
|
|
|
|
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
|
|
|
|
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}"
|
|
|
|
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
|
|
|
|
good=$((good + 1))
|
|
#echo -e "$GREEN" >/dev/stderr
|
|
fi
|
|
#fi
|
|
n=$((n + 1))
|
|
done
|
|
|
|
echo
|
|
if [[ $errs -gt 0 ]]; then
|
|
local fullres_bad
|
|
fail
|
|
csecho "${RED}" "$errs x '^b$actionname^p' actions failed:"
|
|
echo -e "${RED}"
|
|
echo "$allbadresults" | sed -e 's/^/ /'
|
|
echo -e "${PLAIN}"
|
|
echo
|
|
fi
|
|
|
|
if [[ $good -ge 1 ]]; then
|
|
local fullres
|
|
ok
|
|
csecho "${GREEN}" "$good x '^b$actionname^p' actions submitted successfully."
|
|
|
|
# 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
|
|
fi
|
|
|
|
if [[ $good -ge 1 ]]; then
|
|
rv=0
|
|
else
|
|
rv=1
|
|
fi
|
|
|
|
rm -f $TMPDIR/run,*
|
|
fi
|
|
|
|
end=$(($($GDATE +%s%N)/1000))
|
|
lastqsecs=$(echo "scale=2; ($end - $start) / 1000000;" | bc)
|
|
return $rv
|
|
}
|
|
|
|
function validate_action() { # nodes/vms/etv actionname
|
|
local what acion epidx
|
|
what="$1"
|
|
action="$2"
|
|
debug "validating action '$action' on obtype '$what'"
|
|
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
|
|
|
|
start=$(($($GDATE +%s%N)/1000))
|
|
lastqsecs=""
|
|
lastprocsecs=""
|
|
what="$1"
|
|
shift
|
|
cmd="$1"
|
|
shift
|
|
set -f
|
|
opts="$*"
|
|
opts_a=( $* )
|
|
set +f
|
|
|
|
epidx=$(getepidx $what)
|
|
[[ $? -ne 0 ]] && error "unknown endpoint '$what'" && return 1
|
|
|
|
jq_obj="${ep_jqobj[$epidx]}"
|
|
api_endpoint="${ep_apiendpoint[$epidx]}"
|
|
[[ -z $api_endpoint ]] && error "no endpointname for endpoint '$what'" && return 1
|
|
|
|
#outmode=namesonly # global
|
|
if [[ $cmd == "show" ]]; then
|
|
outmode=namesonly # global
|
|
elif [[ $cmd == "net" ]]; then
|
|
outmode=verbose # global
|
|
else
|
|
outmode=verbose # global
|
|
fi
|
|
arraycontains opts_a "-r" && outmode=raw
|
|
arraycontains opts_a "-q" && quiet=1
|
|
arraycontains opts_a "-v" && outmode=verbose
|
|
arraycontains opts_a "-s" && outmode=namesonly
|
|
arraycontains opts_a "-c" && ignorecase=1
|
|
|
|
arraycontains opts_a "-n" && usecache=0 || usecache=1
|
|
arraycontains opts_a "-e" && errordebug=1
|
|
|
|
debug "cmd is $cmd"
|
|
debug "opts is $opts"
|
|
|
|
outfile=$(getarrayopt opts_a o)
|
|
refilter=$(getarrayopt opts_a f)
|
|
|
|
# Allow standard globs rather than regexp globs
|
|
[[ $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
|
|
[[ -n $refilter ]] && [[ $quiet -eq 0 ]] && info "${what} filter: ^$refilter$"
|
|
|
|
debug "refilter is $refilter"
|
|
debug "obfilter is $obfilter"
|
|
|
|
# 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"
|
|
fi
|
|
elif [[ $cmd == "net" ]]; then
|
|
jqscript="$SCRIPTDIR/diagram.jq"
|
|
#awkscript="$SCRIPTDIR/detail.awk"
|
|
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"
|
|
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"
|
|
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
|
|
if [[ -z $all ]]; then
|
|
fail
|
|
cat "$TMPDIR"/err.*
|
|
rm -f "$TMPDIR"/get.*
|
|
rm -f "$TMPDIR"/err.*
|
|
return 1
|
|
fi
|
|
[[ $quiet -eq 0 ]] && ok
|
|
|
|
if [[ $errordebug -eq 1 ]]; then
|
|
if [[ $( ls "$TMPDIR"/err.* | wc -l | bc) -gt 0 ]]; then
|
|
cat "$TMPDIR"/err.* >/dev/stderr
|
|
fi
|
|
fi
|
|
#debug "combined results: [$all]"
|
|
|
|
end=$(($($GDATE +%s%N)/1000))
|
|
lastqsecs=$(echo "scale=2; ($end - $start) / 1000000;" | bc)
|
|
start=$(($($GDATE +%s%N)/1000))
|
|
|
|
rescount=$(printf %d $(echo -n "$all" | wc -c))
|
|
if [[ $rescount -eq 0 ]]; then
|
|
csecho "$YELLOW" " ^b(no results)"
|
|
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
|
|
else
|
|
# ie. show
|
|
fullres=$(cat $TMPDIR/get.*)
|
|
if [[ $outmode == "raw" ]]; then
|
|
echo "$fullres"
|
|
else
|
|
local updated_result=""
|
|
# Replace UUIDS with names from cache
|
|
# and colourise certain words
|
|
greenwords="true up enabled"
|
|
redwords="false down paused uninitialized disabled"
|
|
yellowwords="migrating"
|
|
uuidcol="$NOTIFYCOL"
|
|
|
|
while read -r line ; do
|
|
key=${line%%:*}
|
|
val=${line##*:}
|
|
val=$(echo "$val" | sed -e 's/^.* //')
|
|
|
|
if [[ $key =~ [A-Za-z]+\ UUID ]]; then
|
|
repl="$(uuid_to_name ${val})"
|
|
if [[ $? -eq 0 ]]; then
|
|
line="${line/${val}/$repl} (${uuidcol}${val}${PLAIN})"
|
|
else
|
|
line="${line/$val/${uuidcol}${val}${PLAIN}}"
|
|
fi
|
|
#if [[ -n $repl ]]; then
|
|
# #line=$(echo "${line}" | sed -e 's/[A-Za-z]\+ UUID/UUID/')
|
|
# line="${line/${val}/$repl}"
|
|
#fi
|
|
elif [[ $key =~ UUID ]]; then
|
|
# colourise the entire key
|
|
line="${line/${val}/${uuidcol}${val}$PLAIN}"
|
|
fi
|
|
|
|
updated_result="${updated_result}${line}\n"
|
|
done <<< "$fullres"
|
|
fi
|
|
#cat $TMPDIR/get.*
|
|
fi
|
|
|
|
updated_result=$(echo "$updated_result" | "$GSED" "$ADD_COLOURS")
|
|
echo -e "$updated_result"
|
|
rm -f $TMPDIR/get.*
|
|
else
|
|
|
|
# serial
|
|
cp /dev/null "$outfile"
|
|
|
|
[[ $quiet -eq 0 ]] && notify "Obtaining data from gns API into file"
|
|
for loc in $curlocs; do
|
|
jqfilter=${basefilter/_LOCATION_/${loc}}
|
|
getdata_loc $loc $epidx $opts >> "$outfile"
|
|
locnum=$((locnum + 1))
|
|
done
|
|
|
|
if [[ $errordebug -eq 1 ]]; then
|
|
if [[ $( ls "$TMPDIR"/err.* | wc -l | bc) -gt 0 ]]; then
|
|
cat "$TMPDIR"/err.* >/dev/stderr
|
|
fi
|
|
fi
|
|
|
|
|
|
ok
|
|
#if [[ -e ${HEADINGFILE} ]]; then
|
|
# datawithheading=$(cat ${HEADINGFILE} ${outfile})
|
|
# echo "${datawithheading}" > "$outfile"
|
|
# rm -f $HEADINGFILE
|
|
#fi
|
|
|
|
|
|
|
|
info $(printf "%d line(s) written to %s" $(cat "$outfile" | wc -l) "$outfile" )
|
|
|
|
|
|
fi
|
|
|
|
#end=$(($(gdate +%s%N)/1000))
|
|
#lastqsecs=$(echo "scale=2; ($end - $start) / 1000000;" | bc)
|
|
end=$(($($GDATE +%s%N)/1000))
|
|
lastprocsecs=$(echo "scale=2; ($end - $start) / 1000000;" | bc)
|
|
|
|
}
|
|
|
|
function listcommands() {
|
|
local x wantadm
|
|
if [[ $1 == "ADMIN" ]]; then
|
|
wantadm=1
|
|
prefix='\'
|
|
else
|
|
wantadm=0
|
|
prefix=""
|
|
fi
|
|
|
|
for x in ${!cmd_name[@]}; do
|
|
if [[ ${cmd_adm[$x]} -eq $wantadm ]]; then
|
|
printf " %-10s %s\n" "${prefix}${cmd_name[$x]}" "${cmd_desc[$x]}"
|
|
if [[ $wantadm -eq 0 && ${cmd_name[$x]} == "help" ]]; then
|
|
# show that there is admin help available
|
|
printf " %-10s %s\n" "\\help" "List admin commands."
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
function listcommandaliases() { # [filter]
|
|
local x format filter output="" rv=0
|
|
filter="$1"
|
|
format=" %-12s|%-24s|%-36s${PLAIN}\n"
|
|
for x in ${!cmdalias_src[@]}; do
|
|
if [[ -z $filter || ${cmdalias_dst[$x]} == *$filter* ]]; then
|
|
output="${output}$(printf "$format" "${cmdalias_src[$x]}" "${cmdalias_dst[$x]}" "${cmdalias_defaultarg[$x]}")\n"
|
|
fi
|
|
done
|
|
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
|
|
}
|
|
|
|
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
|
|
local rawdefarg str
|
|
rawdefarg=${cmdalias_defaultarg[$x]}
|
|
debug "using default arg $defarg"
|
|
defarg=${rawdefarg/_CURPROJECT_/$curproj}
|
|
[[ $defarg == $rawdefarg ]] && str="$defarg" || str="$rawdefarg ($defarg)"
|
|
[[ -n $defarg ]] && warn "Using default object: ^b${str}^p"
|
|
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
|
|
local gotexactmatch=0
|
|
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
|
|
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"
|
|
if [[ -n $actionfilter ]]; then
|
|
local x
|
|
# do any of them match exactly?
|
|
for x in ${!o_arr[@]}; do
|
|
if [[ "-f${o_arr[$x]}" == $actionfilter ]]; then
|
|
selobname="${o_arr[$x]}"
|
|
selobuuid="${ou_arr[$x]}"
|
|
unset o_arr
|
|
unset ou_arr
|
|
o_arr=($selobname)
|
|
ou_arr=($selobuuid)
|
|
allobs="$selobname"
|
|
alluuids="$selobuuid"
|
|
nobs=1
|
|
nuuids=1
|
|
|
|
gotexactmatch=1
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [[ $gotexactmatch -ne 1 ]]; then
|
|
|
|
inform "'${INFORMCOLB}${actionfilter:2}${INFORMCOL}' matched multiple ${whattolist}s"
|
|
narrowdown "$whattolist" "$allobs" "$alluuids" selobname selobuuid || rv=1
|
|
fi
|
|
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;;
|
|
dupe) 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
|
|
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}\""
|
|
}
|
|
|
|
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
|
|
cmd=$1
|
|
shift
|
|
arg="$*"
|
|
shift
|
|
|
|
RAWJSONPOSTDATA=""
|
|
|
|
[[ -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" && $gotargs -eq 2 ]]; then
|
|
debug "special case - ignoring arg count for connect action"
|
|
elif [[ $cmd == "action" && $arg == "node start" && $gotargs -eq 2 ]]; then
|
|
debug "special case - ignoring arg count for connect action"
|
|
elif [[ $gotargs -lt ${cmd_minargs[$idx]} || $gotargs -gt ${cmd_maxargs[$idx]} ]]; then
|
|
error "${cmd_name[$idx]} requires ${cmd_needargs[$idx]} arguments (got $gotargs)"
|
|
return 1
|
|
fi
|
|
obname="" # global
|
|
if [[ $cmd == "show" ]]; then
|
|
endpoint=${arg_array[0]} && unset 'arg_array[0]'
|
|
whattolist=${endpoint}
|
|
if [[ ${#arg_array[@]} -ge 1 ]]; then
|
|
convert_link_ids $endpoint "${arg_array[@]}" || return 1
|
|
opts+=("-f${CLIDS_RES}")
|
|
fi
|
|
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"
|
|
elif [[ $cmd == "action" ]]; then
|
|
endpoint=${arg_array[0]} && unset 'arg_array[0]'
|
|
origendpoint="$endpoint"
|
|
whattolist=${endpoint}
|
|
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 to add 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
|
|
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" || $actionname == "dupe" ]]; then
|
|
oldname=${arg_array[2]}
|
|
newname=${arg_array[3]}
|
|
actionfilter="-f${oldname}"
|
|
obname="$oldname"
|
|
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}"
|
|
else
|
|
if [[ $cmd == "action" || $arg == "node connect" ]]; then
|
|
debug "connect with no arg - will just resume session"
|
|
elif [[ $cmd == "action" || $arg =~ \ start$ ]]; then
|
|
debug "start node with no arg - will start all nodes"
|
|
actionfilter="-f*"
|
|
else
|
|
error "Name of $whattolist not provided. cmd is $cmd, arg is $arg"
|
|
return 1
|
|
fi
|
|
fi
|
|
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=$?
|
|
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
|
|
|
|
;;
|
|
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
|
|
csecho "${CYAN}" " UUID ^b${arg}^p -> ${res}"
|
|
else
|
|
res=$(name_to_uuid ${arg})
|
|
if [[ $? -eq 0 ]]; then
|
|
csecho "${CYAN}" " UUID ${res} -> ^b${arg}^p"
|
|
else
|
|
error "No cache results found for ${arg}"
|
|
fi
|
|
fi
|
|
fi
|
|
;;
|
|
q)
|
|
DONE=1
|
|
;;
|
|
la)
|
|
listcommandaliases "$arg"
|
|
;;
|
|
help)
|
|
listcommands ADMIN
|
|
;;
|
|
*)
|
|
error "Admin command '$cmd' not implemented"
|
|
rv=1
|
|
;;
|
|
esac
|
|
else
|
|
case ${cmd} in
|
|
list|show|net)
|
|
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"
|
|
;;
|
|
project)
|
|
setproject "${arg}"
|
|
rv=$?
|
|
if [[ $rv -eq 0 ]]; then
|
|
loadcachefile $curlocs || start_recache
|
|
fi
|
|
;;
|
|
action)
|
|
# TODO: ooremove any output format opts
|
|
validate_action ${endpoint} $actionname
|
|
if [[ $? -ne 0 ]]; then
|
|
error "'$actionname' is not a valid action for ${endpoint}s"
|
|
return 1
|
|
fi
|
|
|
|
epidx=$(getepidx $endpoint)
|
|
if [[ $? -ne 0 ]]; then
|
|
error "'$endpoint' is not a valid endpoint"
|
|
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"
|
|
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
|
|
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
|
|
else
|
|
error "Adding a $endpoint not implemented yet"
|
|
return 1
|
|
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
|
|
|
|
if [[ ${actionname} == "dupe" ]]; then
|
|
if [[ ${endpoint} == "project" ]]; then
|
|
if [[ -z $newname ]]; then
|
|
newname="${oldname}2"
|
|
warn "Using default new name: ^b$newname^p "
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
|
|
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 NN="$newname" -v EP="$endpoint" "BEGIN {lastdc=\"\"} { if (\$1 != lastdc) { print \" ${YELLOW}- ${BOLD}\" \$1 (EP == \"project\") ? \"\" : \" -> project=${curproj}${PLAIN}\"; lastdc=\$1; } if (NN) { moreinfo = sprintf(\" (new name: ${BOLD}%s${PLAIN}${YELLOW})\",NN); } print \" ${YELLOW}- \" \$2 moreinfo \"${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)
|
|
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
|
|
|
|
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,,,"
|
|
elif [[ $endpoint == "project" ]]; then
|
|
postdata="name:$newname"
|
|
actiontargets="$curlocs,$newname,,$postdata"
|
|
fi
|
|
elif [[ $actionname == "mv" || $actionname == "dupe" ]]; 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"
|
|
|
|
debug "about to call runaction with:"
|
|
debug " endpoint = $endpoint"
|
|
debug " obtype = $endpoint"
|
|
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")
|
|
fi
|
|
|
|
if [[ $rv -eq 0 ]]; then
|
|
if action_triggers_recache $actionname; then
|
|
if [[ $endpoint == "project" ]]; then
|
|
loadprojectlist
|
|
fi
|
|
start_recache
|
|
fi
|
|
fi
|
|
fi # end if confirm == 1
|
|
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
|
|
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}
|
|
|
|
if [[ $err -eq 0 ]]; then
|
|
curproj="${newproj}"
|
|
[[ $quiet -eq 0 ]] && inform "Project set to: ^b$curproj^p ($curprojid)"
|
|
rv=0
|
|
updateprojstatus ${idx}
|
|
if [[ ${proj_isopen[$curprojidx]} -ne 1 ]]; then
|
|
echo -n " "
|
|
warn "This project is closed - use '^b^iopen^p' to open it."
|
|
fi
|
|
else
|
|
rv=1
|
|
fi
|
|
|
|
return $rv
|
|
}
|
|
|
|
function checkfor() { # checkfor name [pkg_name]
|
|
local what pkgname
|
|
local hw os ok=0 confirm=0
|
|
local alt="" rv globvar=""
|
|
local realpath=""
|
|
|
|
what=$1
|
|
pkg_name=${2:-$what}
|
|
os=$(uname -s)
|
|
hw=$(uname -m)
|
|
if [[ $hw == "aarch64" ]]; then # Android Termux
|
|
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=$?
|
|
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)
|
|
rv=0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
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
|
|
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
|
|
elif [[ $hw == "aarch64" ]]; then
|
|
apt install $pkg_name && ok=1
|
|
else
|
|
error "Don't know how to install stuff on OS '$os' HW '$hw'"
|
|
fi
|
|
;;
|
|
*)
|
|
error "Don't know how to install stuff on OS '$os' HW '$hw'"
|
|
;;
|
|
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
|
|
}
|
|
|
|
function killtmux() {
|
|
tmux kill-session -t "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
function cleanup() {
|
|
local x
|
|
echo
|
|
set +f
|
|
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
|
|
;;
|
|
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"
|
|
CYAN="\033[36m"
|
|
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"
|
|
|
|
|
|
INFORMCOL="$ORANGE"
|
|
INFORMCOLB="$ORANGEBOLD"
|
|
NOTIFYCOL="$MAGENTARGB"
|
|
NOTIFYCOLB="$MAGENTARGBBOLD"
|
|
|
|
GREY="\033[38;2;110;110;110m"
|
|
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.)"
|
|
|
|
# Generate sed script to add colours to certain words
|
|
UUIDCHAR="[0-9a-f]"
|
|
UUID_REGEXP=${UUIDCHAR}\{8\}-${UUIDCHAR}\{4\}-${UUIDCHAR}\{4\}-${UUIDCHAR}\{4\}-${UUIDCHAR}\{12\}
|
|
WORDCOLOURS=( "${GREEN}" "${RED}" "${YELLOW}" )
|
|
UUIDCOL="$NOTIFYCOL"
|
|
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
|
|
nactusage=0
|
|
|
|
|
|
|
|
|
|
# addendpoint ep_name ep_apiendpoint ep_jqobjectname idfieldname [defaultfieldname]
|
|
addendpoint projects projects project project_id name
|
|
addepalias projects p
|
|
addeptitles projects "Project " "UUID" "Status "
|
|
addepfields projects ".name" ".project_id" ".status"
|
|
addepactions projects open close add del dupe mv
|
|
addepactionusage projects "add" "new_projectname"
|
|
addepactionusage projects "del" "existing_projectname"
|
|
addepactionusage projects "open" "[existing_projectname]"
|
|
addepactionusage projects "close" "[existing_projectname]"
|
|
addepactionusage projects "dupe" "existing_projectname new_projectname"
|
|
addepactionusage projects "mv" "old_project_name new_project_name"
|
|
|
|
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
|
|
addepactionusage nodes "add" "new_node_name new_node_model"
|
|
addepactionusage nodes "del" "node_name"
|
|
addepactionusage nodes "mv" "old_node_name new_node_name"
|
|
|
|
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
|
|
addepactionusage links "add" "a_nodename a_portname b_nodename b_portname"
|
|
addepactionusage links "del" "link_uuid"
|
|
|
|
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"
|
|
|
|
|
|
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
|
|
declare -a lines
|
|
for x in ${!ep_jqobj[@]}; do
|
|
if [[ -z ${ep_validactions[$x]} ]]; then
|
|
lines+=("${ep_jqobj[$x]} actions: n/a")
|
|
else
|
|
lines+=("${ep_jqobj[$x]} actions:")
|
|
for va in ${ep_validactions[$x]}; do
|
|
usage=$(getactionusage $x $va)
|
|
lines+=(" $va $usage")
|
|
done
|
|
fi
|
|
done
|
|
addcmdusage action "object_type action_type regexp_filter [extrainfo]" "object_type can be: [${ep_jqobj[*]}]" "" "${lines[@]}"
|
|
addcmdoption action "-f" "Force - don't ask for confirmation"
|
|
|
|
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[*]}]"
|
|
|
|
|
|
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" ""
|
|
addcmdalias "padd" "action project add" ""
|
|
addcmdalias "pdel" "action project del" ""
|
|
addcmdalias "pdupe" "action project dupe" "_CURPROJECT_"
|
|
addcmdalias "pmv" "action project mv" ""
|
|
|
|
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
|
|
addcmd -a lp "List locally known 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"
|
|
CONFDIR="$HOME/.gnscli"
|
|
HISTFILE="$CONFDIR/history"
|
|
TMPDIR="$CONFDIR/tmp"
|
|
SCRIPTDIR="$CONFDIR/scripts"
|
|
#HEADINGFILE="$CONFDIR/head.tmp"
|
|
TMPFILE="$TMPDIR/temp.tmp"
|
|
TOTFILE="$TMPDIR/tot.tmp"
|
|
SPINNERFILE="${TMPDIR}/spinner.pid"
|
|
#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"
|
|
done < <(egrep -v "(^#|^$)" $RCFILE)
|
|
fi
|
|
|
|
VALIDARGS="g:hiIp:Pv"
|
|
while getopts "$VALIDARGS" i $RCARGS; do
|
|
processarg "$i"
|
|
done
|
|
OPTIND=0
|
|
while getopts "$VALIDARGS" i ; do
|
|
processarg "$i"
|
|
done
|
|
shift $((OPTIND - 1))
|
|
|
|
|
|
if [[ ! -e $TMPDIR ]]; then
|
|
error "Temporary file dir $TMPDIR doesn't exist. Use -i to create it."
|
|
exit 1
|
|
else
|
|
rm -fr ${TMPDIR}/*.tmp
|
|
rm -fr ${TMPDIR}/get.*
|
|
rm -fr ${TMPDIR}/run.*
|
|
fi
|
|
if [[ ! -d $CONFDIR ]]; then
|
|
error "Configuration directory $CONFDIR doesn't exist. Use -i to create it."
|
|
exit 1
|
|
fi
|
|
|
|
checkfor gdate coreutils || exit 1
|
|
checkfor jq || exit 1
|
|
checkfor gsed || exit 1
|
|
|
|
loadservers
|
|
if [[ $nlocs -lt 1 ]]; then
|
|
error "No servers found - check $SRVFILE"
|
|
exit 1
|
|
fi
|
|
alllocs=$(getalllocs)
|
|
|
|
# prepopulate list of projects
|
|
notify "Getting initial projectlist"
|
|
#oldverbose=$VERBOSE
|
|
#VERBOSE=1
|
|
|
|
|
|
loadprojectlist || exit 1
|
|
|
|
if [[ ${#proj_id[@]} -eq 0 ]]; then
|
|
error "No projects found on server!!"
|
|
exit 1
|
|
fi
|
|
|
|
setproject -q $DEFPROJECT || exit 1 # defaults to first one
|
|
|
|
[[ -e $HISTFILE ]] && history -r "$HISTFILE"
|
|
loadcachefile $curlocs
|
|
dumpmsgq
|
|
|
|
trap cleanup EXIT
|
|
|
|
echo -e "${UNDERLINE}Unofficial GNS3 cli v${VER}${PLAIN}"
|
|
#echo -e "${ITALIC}${YELLOW}Note: this script is still in development and may have bugs!${PLAIN}"
|
|
|
|
if [[ $# -gt 0 ]]; then
|
|
processcmd $*
|
|
else
|
|
DONE=0
|
|
while [[ $DONE -ne 1 ]]; do
|
|
lastqsecs=0
|
|
#echo -en "${pstr}"
|
|
getcmd cmd || break
|
|
set -f
|
|
processcmd $cmd
|
|
set +f
|
|
if [[ ! -z $cmd ]]; then
|
|
history -s "$cmd"
|
|
history -w "$HISTFILE"
|
|
fi
|
|
dumpmsgq # show any queued messages
|
|
done
|
|
fi
|
|
|
|
exit 0
|
|
|
|
#START_INLINE:model.jq
|
|
"UUID#" + .template_id,
|
|
"Name#" + .name,
|
|
"Usage#" + .usage,
|
|
"Category#" + .category,
|
|
"RAM#" + .ram + "MB",
|
|
"Action on close#" + .on_close,
|
|
"Firewall Type#" + .firewall_type,
|
|
"Image#" + .hda_disk_image,
|
|
"__END__"
|
|
#END_INLINE:model.jq
|
|
|
|
#START_INLINE:project.jq
|
|
"UUID#" + .project_id,
|
|
"Name#" + .name,
|
|
"Status#" + .status,
|
|
"Filename#" + .filename,
|
|
"Path#" + .path,
|
|
"Auto-Open#" + .auto_open,
|
|
"Auto-Close#" + .auto_close,
|
|
"Auto-Start#" + .auto_start,
|
|
"__END__"
|
|
#END_INLINE:project.jq
|
|
#START_INLINE:detail.awk
|
|
BEGIN {
|
|
FS="#"
|
|
nlines=0
|
|
BOLDBLUE="\033[1m\033[36m"
|
|
PLAIN="\033[0m"
|
|
}
|
|
{
|
|
key[nlines]=$1
|
|
val[nlines++]=$2
|
|
if (length($1)>max) max=length($1);
|
|
}
|
|
|
|
END {
|
|
nvalid=0
|
|
for (i=0;i<nlines;i++) {
|
|
if (length(key[i]) + length(val[i]) >= 1) {
|
|
printf( BOLDBLUE"%" max "s:" PLAIN " %s\n",key[i],val[i]);
|
|
nvalid++;
|
|
}
|
|
}
|
|
if (nvalid >= 1) printf(" \n");
|
|
}
|
|
#END_INLINE:detail.awk
|
|
#START_INLINE:node.jq
|
|
"Hostname#" + .name,
|
|
"UUID#" + .node_id,
|
|
"Model UUID#" + .template_id,
|
|
"Project UUID#" + .project_id,
|
|
"Status#" + .status,
|
|
"Port format#" + .port_name_format,
|
|
"Locked#" + (.locked // "n/a"),
|
|
"Console port#" + ((.console|tostring) // "n/a"),
|
|
"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),
|
|
"__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
|