#!/bin/bash

if [ -z "${BASH_VERSINFO}" ] || [ -z "${BASH_VERSINFO[0]}" ] || [ ${BASH_VERSINFO[0]} -lt 3 ]; then
    cat <<EOF

**********************************************************************
               Shortyk8s requires Bash version >= 3
**********************************************************************

EOF
fi

eval $(kubectl version --client -ojson | \
           awk -F\" '/major/{print "_kubectl_majver="$4} /minor/{print "_kubectl_minver="$4}')
if [[ $_kubectl_majver -lt 1 ]] || [[ $_kubectl_majver -eq 1 && $_kubectl_minver -lt 11 ]]; then
    cat <<EOF

**********************************************************************
               Shortyk8s requires Kubectl version >= 1.11
**********************************************************************

EOF
fi

# show only the names (different than -oname which includes the kind of resource as a prefix)
knames='--no-headers -ocustom-columns=:metadata.name'

_KPUB=()

# main entry point for all shortyk8s commands
function k()
{
    if [[ $# -lt 1 ]]; then
        local of=2
        [[ -t 1 ]] || of=1 # allow redirect into a pipe
        cat <<EOF >&$of

  Expansions:

    a <f>    apply --filename=<f>
    g        get
    d        describe
    del      delete
    ex       exec
    exi      exec -ti
    l        logs
    s <r>    scale --replicas=<r>
    draini   drain --delete-local-data --ignore-daemonsets
    sec [.]  get secret [<resource_name> [<secret_name>]]

${_KCMDS_HELP}
    pc       get pods and containers
    ni       get nodes and private IP addresses
    pi       get pods and container images

    tn       top node
    tp       top pod --containers

    all      --all-namespaces
    any      --all-namespaces
    w        -owide
    y        -oyaml

    .<pod_match>        replace with matching pods (will include "kind" prefix)
    ^<pod_match>        replace with FIRST matching pod
    @<container_match>  replace with FIRST matching container in pod (requires ^<pod_match>)
    ,<node_match>       replace with matching nodes
    ~<alt_command>      replace \`kubectl\` with \`<alt_command> --context $(shortyk8s_ctx) -n $(shortyk8s_kns)\`

  Examples:

    k po                       # => kubectl get pods
    k g .odd y                 # => kubectl get pods oddjob-2231453331-sj56r -oyaml
    k repl ^web @nginx ash     # => kubectl exec -ti webservice-3928615836-37fv4 -c nginx ash
    k l ^job --tail=5          # => kubectl logs bgjobs-1444197888-7xsgk --tail=5
    k s 8 dep web              # => kubectl scale --replicas=8 deployments webservice
    k ~stern ^job --tail 5     # => stern --context usw1 -n prod bgjobs-1444197888-7xsgk --tail 5
    k cp notes.txt ^web:/tmp   # => kubectl cp notes.txt web-55b79cccb9-cjv2s:/tmp -c web

EOF
        return 1
    fi

    local a c pod res caret atsign nc=false cmd=kubectl args=()
    local orig_ctx=$_K8S_CTX orig_ns=$_K8S_NS revert_ctx=false revert_ns=false

    if [[ " $@ " = ' all ' ]]; then
        # simple request to get all resources
        _kcmd kubectl get all
        return
    fi

    while [[ $# -gt 0 ]]; do
        a=$1; shift
        case "$a" in
            a)
                if ! [[ -f "$1" ]]; then
                    echo "\"apply\" requires a path to a YAML file (tried \"$1\")" >&2
                    return 2
                fi
                args+=(apply --filename="$1"); shift
                ;;
            any|all) args+=(--all-namespaces);;
            d|desc) args+=(describe);;
            del) args+=(delete);;
            draini) args+=(drain --delete-local-data --ignore-daemonsets);;
            ev)
                _kget events
                nc=true
                ;;
            ex) args+=('exec');;
            exi) args+=('exec' -ti);;
            g) args+=(get);;
            l) args+=(logs);;
            ni)
                _kget nodes
                args+=('-ocustom-columns=NAME:.metadata.name,'`
                      `'CONDITIONS:.status.conditions[?(@.status=="True")].type,'`
                      `'INTERNAL_IP:.status.addresses[?(@.type=="InternalIP")].address')
                ;;
            pc)
                _kget pods
                args+=('-ocustom-columns=NAME:.metadata.name,'`
                      `'CONTAINERS:.status.containerStatuses[*].name,'`
                      `'READY:.status.containerStatuses[*].ready,'`
                      `'RESTARTS:.status.containerStatuses[*].restartCount,'`
                      `'HOST_IP:.status.hostIP')
                ;;
            pi)
                _kget pods
                args+=('-ocustom-columns=NAME:.metadata.name,STATUS:.status.phase,'`
                      `'IMAGES:.status.containerStatuses[*].image')
                ;;
            pn)
                _kget pods
                args+=('-ocustom-columns=NAME:.metadata.name,STATUS:.status.phase,'`
                      `'IMAGES:.status.containerStatuses[*].image')
                ;;
            s)
                if ! [[ "$1" =~ ^[[:digit:]]+$ ]]; then
                    echo "\"scale\" requires replicas (\"$1\" is not a number)" >&2
                    return 3
                fi
                args+=(scale --replicas=$1); shift
                ;;
            sec)
                _kgetsecret "$@"
                return
                ;;
            tn) args+=(top node);;
            tp) args+=(top pod --containers);;
            version)
                _kcmd kubectl version "$@"
                _kversioninfo "$@"
                return
                ;;
            w) args+=(-owide);;
            y) nc=true; args+=(-oyaml);;
            ,*) args+=($(_knamegrep nodes "${a:1}"));;
            .*) args+=($(_knamegrep pods "${a:1}"));;
            ^*) caret="$a";;
            @*) atsign="$a";;
            ~*)
                cmd=${a:1}
                case "$cmd" in
                    helm) _KSAFE=true; args+=(--kube-context "$(shortyk8s_ctx)");;
                    *) args+=(--context "$(shortyk8s_ctx)" -n "$(shortyk8s_kns)")
                esac
                ;;
            --context)
                # set one-time session context (so subcommands use it for lookups)
                _K8S_CTX=$1
                $revert_ns || _K8S_NS=''
                revert_ctx=true
                shift
                ;;
            -n|--namespace)
                # set one-time session namespace (so subcommands use it for lookups)
                _K8S_NS=$1
                $revert_ctx || K8S_CTX=''
                revert_ns=true
                shift
                ;;
            -w|-h*|-o*)
                args+=("$a")
                nc=true
                ;;
            *)
                found=false
                for i in ${!_KCMDS_AKA[@]}; do
                    if [[ "$a" = "${_KCMDS_AKA[$i]}" ]]; then
                        found=true
                        c="${_KCMDS[$i]}"
                        if [[ "$c" = shortyk8s_* ]]; then
                            args+=("$@")
                            if [[ -t 1 && "$a" == 'ap' ]]; then
                                "$c" "${args[@]}" | _kcolorize
                            else
                                "$c" "${args[@]}"
                            fi
                            # for now, all public helpers handle their own args...
                            return
                        fi
                        _kget "${_KCMDS[$i]}"
                        break
                    fi
                done
                $found || args+=("$a")
        esac
    done

    if [[ -n "${caret}" ]]; then
        _kgetpodcon "${caret}" "${atsign}" -m1 || return $?
        args+=("${pods[0]}")
        if [[ -n "${con}" && ! " ${args[@]} " =~ ' delete ' ]]; then
            args+=(-c "${con}")
        fi
    fi

    local fmtr argstr=" ${args[@]} "
    if [[ -t 1 && "${argstr}" =~ ' get ' ]]; then
        # stdout is a tty and using a simple get...
        fmtr='_kcolorize'
        $nc && fmtr+=' -nc'
    fi

    [[ " ${args[@]} " =~ ' delete ' || " ${args[@]} " =~ ' scale ' ]] && _KCONFIRM=true

    if [[ -n "$fmtr" ]]; then
        _kcmd "$cmd" "${args[@]}" | $fmtr
    else
        _kcmd "$cmd" "${args[@]}"
    fi
    local rc=$?

    $revert_ctx && _K8S_CTX=$orig_ctx
    $revert_ns && _K8S_NS=$orig_ns

    return $rc
}

_KPUB+=('')

_KPUB+=('a=ap;c=shortyk8s_allpods;d="report all pods grouped by nodes"')
function shortyk8s_allpods()
{
    if [[ $# -lt 1 ]]; then
        echo 'usage: allpods <node_match> [<namespace_match> [<pod_match>]]' >&2
        return 1
    fi
    local match='$8 ~ /'"$1"'/'
    [[ $# -gt 1 ]] && match+=' && $1 ~ /'"$2"'/'
    [[ $# -gt 2 ]] && match+=' && $2 ~ /'"$3"'/'
    # sort by node then by namespace then by name
    _kcmd kubectl get --all-namespaces pods -owide | \
        awk 'NR==1{print;next};'"${match}"'{print|"sort -b -k8 -k1 -k2"}' | \
        awk 'NR==1{print;next};{ x[$8]++; if (x[$8] == 1) print "---"; print}'
}

_KPUB+=('a=eachnode;c=shortyk8s_eachnode;d="run a command on each node"')
function shortyk8s_eachnode()
{
    local _keach_usage='eachnode [OPTIONS] <node_match>' _keach_resources='_knodeips'

    function _keach_prepare() {
        e_args+=(ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no '{}')
        if $interactive; then
            remote_cmd+='TERM=term'
            e_args+=(-t)
        fi
    }

    _keach "$@"
}

_KPUB+=('a=uptime;c=shortyk8s_uptime;d="get uptimes for all nodes (highest load at top)"')
function shortyk8s_uptime()
{
    shortyk8s_eachnode "$@" uptime | sort -rnk 11
}

_KPUB+=('a=mem;c=shortyk8s_mem;d="get memory usage for all nodes (smallest available at top)"')
function shortyk8s_mem()
{
    printf '%15s %s\n' '[megabytes]' '             total       used       free     shared    buffers     cached'
    shortyk8s_eachnode "$@" 'free -m | grep ^Mem' | sort -nk 5
}

_KPUB+=('a=df;c=shortyk8s_df;d="get file system usage for all nodes (smallest available at top)"')
function shortyk8s_df()
{
    printf '%-15s %s\n' 'Host' 'Filesystem              Size  Used Avail Use% Mounted on'
    shortyk8s_eachnode "$@" 'df -h / /var/lib/docker | sed 1d' | sort -rnk 6
}

_KPUB+=('')

_KPUB+=('a=u;c=shortyk8s_use;d="use a different context and/or namespace"')
function shortyk8s_use()
{
    local ctx ns code session=false

    if [[ $# -lt 1 ]]; then
        _kctxs -hl
        return
    fi

    if [[ "$1" =~ ^--?h ]]; then
        _ku_usage
        return 1
    fi

    if [[ "$1" = 'reset' ]]; then
        rm -f "$_KPROMPT_FN" "$_KSESSION_FN"
        unset _K8S_CTX _K8S_NS
        [[ "$2" = '-q' ]] || shortyk8s_use
        return
    fi

    if [[ "$1" = '-s' ]]; then
        session=true
        shift
    fi

    [[ -n "${_K8S_NS}" ]] && session=true

    if [[ $# -eq 1 ]]; then
        # try namespace match first, then context
        ns=$(_knamegrep ns -m1 "$1")
        if [[ -z "${ns}" ]]; then
            ctx=$(_kctxgrep -m1 "$1")
            if [[ -z "${ctx}" ]]; then
                echo 'no match found' >&2
                return 2
            fi
            ns=$(_kctxs | awk '$1=="'"${ctx}"'"{print $4}' )
        else
            ctx=$(shortyk8s_ctx)
        fi
    elif [[ $# -eq 2 ]]; then
        # switch to context and namespace
        ctx=$(_kctxgrep -m1 "$1")
        if [[ -z "$ctx" ]]; then
            echo 'no match found' >&2
            return 3
        fi
        ns=$(kubectl --context "$ctx" get ns $knames | egrep -m1 "$2")
        if [[ -z "$ns" ]]; then
            echo 'no match found' >&2
            return 4
        fi
    else
        _ku_usage
        return 5
    fi

    if $session; then
        rm -f "$_KPROMPT_FN"
        _K8S_CTX=$ctx
        _K8S_NS=$ns
        cat <<EOF > "$_KSESSION_FN"
_K8S_CTX='${_K8S_CTX}'
_K8S_NS='${_K8S_NS}'
EOF
    else
        shortyk8s_use reset -q
        kubectl config set-context "$ctx" --namespace "$ns" | \
            sed 's/\.$/ using namespace "'"${ns}"'"./'
        kubectl config use-context "$ctx"
    fi

    shortyk8s_use
}

_KPUB+=('a=prompt;c=shortyk8s_prompt;d="provide info for shell prompt"')
function shortyk8s_prompt()
{
    # Large configs can take hundreds of milliseconds for kubectl config to process which will cause
    # a grossly slow prompt. Using a file to store the value to allow caller to invoke in subshell.
    local prompt_ts=$(_kfilets "$_KPROMPT_FN")
    local config_ts=$(_kfilets "${KUBECONFIG:-${HOME}/.kube/config}")
    if [[ $prompt_ts -le $config_ts ]]; then
        prompt="$*$(shortyk8s_ctx)/$(shortyk8s_kns)"
        [[ -n "$_K8S_CTX" ]] && prompt+='[tmp]'
        echo "$prompt" > "$_KPROMPT_FN"
    else
        prompt="$(cat "$_KPROMPT_FN")"
    fi
    echo "$prompt"
}

_KPUB+=('a=ctx;c=shortyk8s_ctx;d="report current context"')
function shortyk8s_ctx()
{
    if [[ -n "${_K8S_CTX}" ]]; then
        echo "${_K8S_CTX}"
    else
        kubectl config current-context
    fi
}

_KPUB+=('a=kns;c=shortyk8s_kns;d="report current namespace"')
function shortyk8s_kns()
{
    if [[ -n "${_K8S_NS}" ]]; then
        echo "${_K8S_NS}"
    else
        kubectl config get-contexts | awk '$1 == "*" {print $5}'
    fi
}

_KPUB+=('a=eachctx;c=shortyk8s_eachctx;d="invoke action for matching context names"')
function shortyk8s_eachctx()
{
    local _keach_usage='eachctx [OPTIONS] <context_match>' _keach_resources='_kctxgrep'

    function _keach_prepare() {
        if $interactive; then e_args+=(bash -ic); else e_args+=(bash -c); fi
        prefix_cmd='{}'
        remote_cmd=". ${BASH_SOURCE[0]};ctx='{}';${remote_cmd}"
    }

    _keach "$@"
}

_KPUB+=('')

_KPUB+=('a=repl;c=shortyk8s_repl;d="execute an interactive REPL on a container"')
function shortyk8s_repl()
{
    if [[ $# -lt 1 ]]; then
        cat <<EOF >&2
usage: repl <pod_match> [@<container_match>] [<command> [<args>...]]

  Default container match will use the pod match.

  Default command will try to determine the best shell available (bash || ash || sh).

EOF
        return 1
    fi

    local cmd pods con cnt e_args=('exec' -ti)

    _KSTATUS=Running _kgetpodcon "$1" "$2" -m1 || return $?
    shift $cnt

    e_args+=("${pods[0]}")
    [[ -n "${con}" ]] && e_args+=(-c "${con}")

    if [[ $# -eq 0 ]]; then
        cmd=' bash || ash || sh'
    else
        cmd=" $*"
    fi

    e_args+=(-- sh -c "KREPL=${USER};TERM=xterm;PS1=\"\$(hostname -s) $ \";export TERM PS1;${cmd}")
    _kcmd kubectl "${e_args[@]}"
}

_KPUB+=('a=each;c=shortyk8s_each;d="run commands on one or more containers"')
function shortyk8s_each()
{
    local _keach_usage='each [OPTIONS] <pod_match> [@<container_name>]'
    local con

    function _keach_resources() {
        local pods
        _KSTATUS=Running _kgetpodcon "$1" "$2" || return $?
        resources=("${pods[@]}")
        match_shift=$cnt
    }

    function _keach_prepare() {
        local shell_cmd=(/bin/sh -c)
        _KNOOP=true _kcmd kubectl
        e_args+=($_KCMD exec '{}')
        [[ -n "$con" ]] && e_args+=(-c "$con")
        if $interactive; then
            shell_cmd=('TERM=term' "${shell_cmd[@]}")
            e_args+=(-ti)
        fi
        e_args+=(-- "${shell_cmd[@]}")
    }

    _keach "$@"
}

_KPUB+=('a=watch;c=shortyk8s_watch;d="watch events and pods concurrently"')
function shortyk8s_watch()
{
    ( # run in a subshell to trap control-c keyboard interrupt for cleanup of bg procs
        shortyk8s_evw --new &
        while true; do sleep 10; echo; echo ">>> $(date) <<<"; done &
        trap 'kill %1 %2' EXIT
        k pc -w
    )
}

_KPUB+=('a=evw;c=shortyk8s_evw;d="watch events sorted by most recent report"')
function shortyk8s_evw()
{
    local new=false
    if [[ "$1" = '--new' ]]; then
        shift; new=true
    fi
    local args=(get ev --no-headers --sort-by=.lastTimestamp \
        -ocustom-columns='TIMESTAMP:.lastTimestamp,COUNT:.count,KIND:.involvedObject.kind,'`
                        `'NAME:.involvedObject.name,MESSAGE:.message' "$@")
    # kubectl get ev --watch ignores `sort-by` for the first listing
    $new || _kcmd kubectl "${args[@]}"
    _kcmd kubectl "${args[@]}" --watch-only
}

_KPUB+=('a=report;c=shortyk8s_report;d="report all interesting resources"')
function shortyk8s_report()
{
    local c res ns=$(shortyk8s_kns)
    local ign='all|events|clusterroles|clusterrolebindings|customresourcedefinition|namespaces|'`
             `'nodes|persistentvolumeclaims|storageclasses'
    _kapi_resources
    for vals in "${_KAPI_RESOURCES[@]}"; do
        eval "${vals}"
        if [[ "$c" = 'persistentvolumes' ]]; then
            res=$(_kcmd kubectl get "$c" | awk 'NR==1{print};$6 ~ /^'"${ns}"'/{print}')
        else
            res=$(_kcmd kubectl get "$c" 2>&1)
        fi
        [[ $? -eq 0 ]] || continue
        [[ $(echo "$res" | wc -l ) -lt 2 ]] && continue
        cat <<EOF

----------------------------------------------------------------------
$(upcase "$c")

${res}
EOF
    done

    unset _KAPI_RESOURCES
}

_KPUB+=('')

_KPUB+=('a=update;c=shortyk8s_update;d="get the latest version of shortyk8s"')
function shortyk8s_update()
{
    if ! which git >/dev/null 2>&1; then
        echo 'Git is required for updating (or installing)' 2>&1
        return 1
    fi

    if [[ -d "${_KHOME}/.git" ]]; then
        git -C "${_KHOME}" pull || return
    else
        git clone -q https://github.com/bradrf/shortyk8s.git "${_KHOME}" || return
    fi
}

######################################################################
# PRIVATE - internal helpers

_KHOME="${HOME}/.shortyk8s"

_KSESSIONS_DIR="${_KHOME}/sessions"
[[ -d "$_KSESSIONS_DIR" ]] || mkdir -p "$_KSESSIONS_DIR"

_KHI=$(echo -e '\033[30;43m') # black fg, yellow bg
_KOK=$(echo -e '\033[01;32m') # bold green fg
_KWN=$(echo -e '\033[01;33m') # bold yellow fg
_KER=$(echo -e '\033[01;31m') # bold red fg
_KNM=$(echo -e '\033[00;00m') # normal

_KCOLORIZE='
BEGIN {
    OK = "'"${_KOK}"'"
    WN = "'"${_KWN}"'"
    ER = "'"${_KER}"'"
    NM = "'"${_KNM}"'"
}

NR == 1 {
    for (i = 1; i <= NF; ++i) {
        if (match($i, /READY/)) {
            ready_col = i
            $ready_col = NM $ready_col NM
        } else if (match($i, /STATUS/)) {
            status_col = i
            $status_col = NM $status_col NM
        } else if (match($i, /RESTART/)) {
            restart_col = i
            $restart_col = NM $restart_col NM
        }
    }
    print
}

NR > 1 {
    if (ready_col > 0) {
        n = split($ready_col, a, "/")
        if (n == 2) {
            if (a[1] == a[2]) {
                $ready_col = OK $ready_col NM
            } else {
                $ready_col = ER $ready_col NM
            }
        } else {
            $ready_col = NM $ready_col NM
        }
    }
    if (status_col > 0) {
        if (match($status_col, /Disabled|Pending|Creating|Init/)) {
            $status_col = WN $status_col NM
        } else if (match($status_col, /NotReady/)) {
            $status_col = ER $status_col NM
        } else if (match($status_col, /Running|Ready|Active|Succeeded|Completed|true/)) {
            $status_col = OK $status_col NM
        } else {
            $status_col = ER $status_col NM
        }
    }
    if (restart_col > 0) {
        # split array will not be in order, so look for anomolies and highlight whole entry
        split($restart_col, cnts, ",")
        cnt = 0
        for (k in cnts) {
            v = cnts[k]
            if (v > cnt) cnt = v
        }
        if (cnt > 10)
            $restart_col = ER $restart_col NM
        else if (cnt > 0)
            $restart_col = WN $restart_col NM
        else
            $restart_col = NM $restart_col NM
    }
    print
}
'

function _kcolorize()
{
    if [[ "$1" = '-nc' ]]; then
        awk "${_KCOLORIZE}"
    else
        awk "${_KCOLORIZE}" | column -xt
    fi
}

# load these at source time to reflect running state in memory, not state of the repo
_KMAJVER=1
_KMINVER=0
_KPATVER=$(TZ=UTC git -C "${_KHOME}" log -1 --format=%cd --date='format-local:%Y%m%d%H%M')
_KGITSTS=$(git -C "${_KHOME}" status --porcelain --untracked-files=no 2>/dev/null)
_KGITCMT=$(git -C "${_KHOME}" log -1 --format=%H)

function _kversion()
{
    echo "v${_KMAJVER}.${_KMINVER}.${_KPATVER}"
}

function _kversioninfo()
{
    local state
    if [[ " $@ " =~ ' --short' ]]; then
        echo "Shortyk8s Version: $(_kversion)"
    else
        if [[ -z "$_KGITSTS" ]]; then state='clean'; else state='dirty'; fi
        cat <<EOF
Shortyk8s Version: version.Info{Major:"${_KMAJVER}", Minor:"${_KMINVER}", \
GitVersion:"$(_kversion)", GitCommit:"${_KGITCMT}", GitTreeState:"${state}", \
BashVersion:"${BASH_VERSION}", Platform:"$(uname -s -m)"}
EOF
    fi
}

function _kapi_resources()
{
    # use cached version of api-resources as this requires a query to the cluster
    local fn="${_KSESSIONS_DIR}/api_resources.${_kubectl_majver}_${_kubectl_minver}"
    if [[ -s "${fn}" ]]; then
        _KAPI_RESOURCES=($(cat "${fn}"))
    else
        local str='NR==1{i=index($0,"SHORTNAMES")}; NR>1{a=substr($0,i,1);if(a!=" "){a=$2};print "c="$1";a="a}'
        _KAPI_RESOURCES=($(kubectl api-resources --cached=true | awk "$str" | tee "${fn}"))
    fi
}

function _ksessions_set()
{
    _KPID=$1
    _KSESSION_FN="${_KSESSIONS_DIR}/${_KPID}_session.sh"
    [[ -r "$_KSESSION_FN" ]] && source "$_KSESSION_FN"
    _KPROMPT_TS=0
    _KPROMPT_FN="${_KSESSIONS_DIR}/${_KPID}_prompt.sh"
}

function _ksessions_clean()
{
    local fn pid
    for fn in "${_KSESSIONS_DIR}/"*_prompt.sh; do
        pid=$(basename "${fn}" | tr -dC '[:digit:]')
        [[ -z "${pid}" ]] || kill -0 "${pid}" 2>/dev/null || \rm -f "${fn}"
    done
}

function _kfilets()
{
    date -r "$1" +%s 2>/dev/null || echo 0
}

function _kjointmpl()
{
    echo '{{range $i, $e := '"${1}"'}}{{if $i}},{{end}}{{$e.'"${2}"'}}{{end}}'
}

function _ktabletmpl()
{
    local tmpl q s=''
    tmpl='{{println "'"${1}"'"}}{{range .items}}'; shift
    for q in "$@"; do
        [[ "$q" = '{{'* ]] || q="{{${q}}}"
        tmpl+="${s}${q}"
        s=$'\t'
    done
    tmpl+='{{println}}{{end}}'
    echo "-ogo-template=$tmpl"
}

# internal helper to support lookup of item or items as a go-template
function _kitemtmpl()
{
    echo "-ogo-template={{if .items}}{{range .items}}$1{{end}}{{end}}{{if not .items}}$1{{end}}"
}

# internal helper to list all internal node IPs
function _knodeips()
{
    local names=()
    [[ $# -gt 0 ]] && \
        names=($(_KQUIET=true _kcmd kubectl get nodes --show-labels --no-headers | \
                     awk "/$*/{print \$1}"))
    _kcmd kubectl get nodes "${names[@]}" \
          "$(_kitemtmpl '{{range .status.addresses}}{{if eq .type "InternalIP"}}{{.address}} {{end}}{{end}}')"
}

# internal helper to show contexts without the first column, indicating session changes, optionally
# highlighting current context
function _kctxs()
{
    local hl='1' ctx=$(shortyk8s_ctx)
    if [[ "$1" = '-hl' ]]; then
        shift
        # print highlighted if "selected"...
        hl="{if(\$1==\"${ctx}\"){print \"${_KHI}\" \$0 \"${_KNM}\"}else{print}}"
    fi
    # remove first column...
    code+='sub(/^CURRENT *|^\*? */,"",$0);'
    # optionally replace the namespace...
    [[ -n "${_K8S_NS}" ]] && code+='if($1=="'"${ctx}"'"){$4="'"${_K8S_NS}"'";$5="[temporary]"};'
    kubectl config get-contexts | awk "{${code};print}" | column -xt | awk "${hl}"
}

function _ku_usage()
{
    cat <<EOF >&2
usage: use [-s] <namespace>
       use [-s] <context> <namespace>
       use reset

  Use "-s" to start a "session" that only changes context or namespace for this terminal.  The
  session is "sticky" until a "reset" is invoked in the same terminal to revert back to using the
  current configured context.

EOF
}

# internal helper to match a pod and optionally a container
# (intentionally exposes `pod`, `con`, `cnt` variables for caller;
#  status value is number of args to shift for caller)
function _kgetpodcon()
{
    local pod_match=$1; shift
    local container_match=$1; shift
    local grep_args=($@)

    if [[ "${pod_match::1}" = '^' ]] || [[ "${pod_match::1}" = '.' ]]; then
        pod_match="${pod_match:1}"
    fi

    # split on colon to allow POD:PATH variables (for logs)
    local pod_match_a
    IFS=: read -ra pod_match_a <<< "${pod_match}"
    pod_match="${pod_match_a[0]}"

    # expose the `pods` array to caller
    pods=($(_knamegrep pods "${grep_args[@]}" "${pod_match}"))
    if [[ ${#pods[@]} -lt 1 ]]; then
        echo 'no match found' >&2
        return 11
    fi

    if [[ "${container_match::1}" = '@' ]]; then
        # expose the `con` value to caller
        con="$(_kcongrep "${pods[0]}" -m1 "${container_match:1}")"
        if [[ -z "${con}" ]]; then
            echo 'no match found' >&2
            return 22
        fi
        # expose the `cnt` value to caller (for argument shifting)
        cnt=2
    else
        # try finding a matching container based on the first pod
        con="$(_kcongrep "${pods[0]}" -m1 "${pod_match%%-*}")"
        cnt=1
    fi

    if [[ ${#pod_match_a[@]} -gt 1 ]]; then
        # append the path to the pods found
        local pod orig_pods=("${pods[@]}")
        pods=()
        for pod in "${orig_pods[@]}"; do
            pods+=("${pod}:${pod_match_a[1]}")
        done
    fi
}

# internal helper to list matching context names
function _kctxgrep()
{
    kubectl config get-contexts -oname | egrep "$@"
}

# internal helper to list matching pod names
function _knamegrep()
{
    local res=$1; shift
    local args=(kubectl get $knames)
    if [[ "$res" = po* && -n "$_KSTATUS" ]]; then
       args+=("--field-selector=status.phase=${_KSTATUS}")
    fi
    _KQUIET=true _kcmd "${args[@]}" "$res" | egrep "$@"
}

# internal helper to list matching container names for a given pod
function _kcongrep()
{
    local pod=$1; shift
    _KQUIET=true _kcmd kubectl get pod "${pod}" -oyaml -o'jsonpath={.spec.containers[*].name}' \
        | tr ' ' '\n' | egrep "$@"
}

# internal helper to provide get unless another action has already been requested
function _kget()
{
    # FIXME: once Bash 4 is more widely in use (*cough*OS X*cough*) leverage local -n
    if [[ " ${args[@]} " =~ ' get '      || \
          " ${args[@]} " =~ ' create '   || \
          " ${args[@]} " =~ ' describe ' || \
          " ${args[@]} " =~ ' edit '     || \
          " ${args[@]} " =~ ' delete '   || \
          " ${args[@]} " =~ ' scale ' ]]; then
        args+=("$@")
    else
        args+=(get "$@")
    fi
}

# internal helper to list secrets available or decode one
function _kgetsecret()
{
    local args=(_kcmd kubectl get secret)
    if [[ $# -lt 1 ]]; then
        ${args[@]} -ogo-template='{{range .items}}{{$n := .metadata.name}}{{range $k, $v := .data}}{{println $n $k}}{{end}}{{end}}'
    elif [[ $# -eq 1 ]]; then
        ${args[@]} "$1" -ogo-template='{{range $k, $v := .data}}{{println $k}}{{end}}'
    elif [[ $# -eq 2 ]]; then
        ${args[@]} "$1" -ogo-template='{{index .data "'"$2"'"}}' | base64 -D
    else
        return 1
    fi
}

# internal helper to execute "each" commands using common options and behavior
function _keach()
{
    local opt async=false interactive=false prefix=false quiet=true

    OPTIND=1

    while getopts 'aipv' opt; do
        case $opt in
            a) async=true;;
            i) interactive=true;;
            p) prefix=true;;
            v) quiet=false;;
            \?) return 2;;
        esac
    done

    shift "$((OPTIND-1))"

    if [[ $# -gt 1 ]]; then
        local match_shift=1
        local resources
        if declare -F _keach_resources >/dev/null; then
            # caller defined "closure" to get the resources
            _keach_resources "$@"
        else
            resources=($(_KQUIET=$quiet "${_keach_resources}" "$1"))
        fi
        shift $match_shift
    fi

    if [[ $# -lt 1 ]]; then
        cat <<EOF >&2

usage: ${_keach_usage} <command> [<arguments>...]

        -a    run the command asynchronously
        -i    run the command interactive with a TTY allocated
        -p    prefix the command output with a name
        -v    show the command line used

EOF
        return 1
    fi

    local remote_cmd="$*" prefix_cmd='`hostname -s`' x_args=() e_args=()

    _keach_prepare

    unset _keach_usage _keach_resources _keach_prepare # remove "closures"

    $prefix && remote_cmd="( ${remote_cmd} ) 2>&1 | awk -v h=\"${prefix_cmd}:\" '{print h,\$0}'"
    $quiet || x_args+=(-t)
    $async && x_args+=(-P ${#resources[@]})
    x_args+=(-I'{}' -n1)

    xargs "${x_args[@]}" -- "${e_args[@]}" -- "$remote_cmd" <<< "${resources[@]}"
}

# internal helper to build a command line (setting context/namespace when appropriate)
function _kcmd()
{
    local cmd=$1; shift
    local args=("$@")
    local rc

    if ! $_KSAFE && [[ ! " ${args[@]} " =~ ' --context' ]]; then
        if [[ ! " ${args[@]} " =~ ' --namespace' && \
                  ! " ${args[@]} " =~ ' -n ' && \
                  ! " ${args[@]} " =~ ' --all-namespaces ' ]]; then
            [[ -n "${_K8S_NS}" ]] && args=(-n "${_K8S_NS}" "${args[@]}")
        fi
        [[ -n "${_K8S_CTX}" ]] && args=(--context "${_K8S_CTX}" "${args[@]}")
    fi

    if [[ ${#args[@]} -gt 0 ]]; then
        _KCMD="${cmd}$(printf ' %q' "${args[@]}")"
    else
        _KCMD=$cmd
    fi

    $_KNOOP && return
    $_KQUIET || echo "${_KCMD}" >&2

    if $_KCONFIRM; then
        read -r -p 'Are you sure? [y/N] ' res
        case "$res" in
            [yY][eE][sS]|[yY]) : ;;
            *) return 11
        esac
    fi

    "$cmd" "${args[@]}"
    _kcmd_reset $?
}

# internal helper to reset temporary overrides for _kcmd()
function _kcmd_reset()
{
    _KNOOP=false
    _KQUIET=false
    _KCONFIRM=false
    _KSAFE=false
    _KSTATUS=''
    return $1
}

_kcmd_reset 0

# first, preload the list of known kubectl get commands
_KCMDS=()
_KCMDS_AKA=()
_KCMDS_HELP=''
_kapi_resources
for vals in "${_KAPI_RESOURCES[@]}"; do
    eval "$vals"
    a=${a/%,*/} # use only first option in a comma list
    [[ "$a" = 'deploy' ]] && a='dep'
    [[ "$a" = 'limits' ]] && a='lim'
    [[ "$a" = 'netpol' ]] && a='net'
    _KCMDS+=("$c")
    _KCMDS_AKA+=("${a:-$c}")
    _KCMDS_HELP+=$(printf '    %-8s get %s' "$a" "$c")$'\n'
done

# then, add our own "public" helpers
for vals in "${_KPUB[@]}"; do
    if [[ -z "$vals" ]]; then
        _KCMDS_HELP+=$'\n' # add a spacer
        continue
    fi
    eval "$vals"
    _KCMDS+=("$c")
    _KCMDS_AKA+=("${a:-$c}")
    _KCMDS_HELP+=$(printf '    %-8s %s' "$a" "$d")$'\n'
done

unset vals c a d _KPUB _KAPI_RESOURCES

################################################################################
# handle when script is executed

if [[ "$(basename -- "$0")" = 'shortyk8s.sh' ]]; then
    if [[ "$1" = 'install' ]]; then
        shortyk8s_update --install || exit
        cat <<EOF

Shortyk8s has been downloaded and is ready for use.

If you're a Bash user, have shortyk8s sourced from your init file like this:

  $ echo ". '${_KHOME}/shortyk8s.sh'" >> "${HOME}/.bashrc"
  $ source "${HOME}/.bashrc"

If you use any other shell (e.g. fish, ksh, tcsh, zsh, etc.), or if you don't want to have shortyk8s
pollute your shell namespace, it can be run as a script:

  $ ${_KHOME}/shortyk8s.sh

And then try this to show the current kubectl configuration contexts:

  $ k u

...or...

  $ ${_KHOME}/shortyk8s.sh u

EOF
    else
        _ksessions_set $PPID
        k "$@"
        exit $!
    fi
elif [[ "$0" = "bash" && "$1" == 'install' ]]; then
    # invoked from curl piped to bash (README instructions)
    shortyk8s_update --install
fi

[[ -z "$_KSESSION_FN" ]] && _ksessions_set $$
_ksessions_clean

: