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