#!/usr/bin/env bash # This small helper util helps select an available Kubernetes context using the arrow keys # using one simple command rather than two seperate kubectl commands to first show, then select contexts. # It uses the mapfile shim found here: https://github.com/dosentmatter/bash-mapfile-shim # which itself uses upvars by Freddy Vulto: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference # These are only needed for bash versions < 4, otherwise the native functions are used. # # Finally, the small select_option function was found on this Stackoverflow thread: # https://stackoverflow.com/questions/11426529/reading-output-of-a-command-into-an-array-in-bash # I included all source code I used as is. # Bash: Passing variables by reference # Copyright (C) 2010 Freddy Vulto # Version: upvars-0.9.dev # See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Assign variable one scope above the caller # Usage: local "$1" && upvar $1 "value(s)" # Param: $1 Variable name to assign value to # Param: $* Value(s) to assign. If multiple values, an array is # assigned, otherwise a single value is assigned. # NOTE: For assigning multiple variables, use 'upvars'. Do NOT # use multiple 'upvar' calls, since one 'upvar' call might # reassign a variable to be used by another 'upvar' call. # Example: # # f() { local b; g b; echo $b; } # g() { local "$1" && upvar $1 bar; } # f # Ok: b=bar # upvar() { if unset -v "$1"; then # Unset & validate varname if (( $# == 2 )); then eval $1=\"\$2\" # Return single value else eval $1=\(\"\${@:2}\"\) # Return array fi fi } # Assign variables one scope above the caller # Usage: local varname [varname ...] && # upvars [-v varname value] | [-aN varname [value ...]] ... # Available OPTIONS: # -aN Assign next N values to varname as array # -v Assign single value to varname # Return: 1 if error occurs # Example: # # f() { local a b; g a b; declare -p a b; } # g() { # local c=( foo bar ) # local "$1" "$2" && upvars -v $1 A -a${#c[@]} $2 "${c[@]}" # } # f # Ok: a=A, b=(foo bar) # upvars() { if ! (( $# )); then echo "${FUNCNAME[0]}: usage: ${FUNCNAME[0]} [-v varname"\ "value] | [-aN varname [value ...]] ..." 1>&2 return 2 fi while (( $# )); do case $1 in -a*) # Error checking [[ ${1#-a} ]] || { echo "bash: ${FUNCNAME[0]}: \`$1': missing"\ "number specifier" 1>&2; return 1; } printf %d "${1#-a}" &> /dev/null || { echo "bash:"\ "${FUNCNAME[0]}: \`$1': invalid number specifier" 1>&2 return 1; } # Assign array of -aN elements [[ "$2" ]] && unset -v "$2" && eval $2=\(\"\${@:3:${1#-a}}\"\) && shift $((${1#-a} + 2)) || { echo "bash: ${FUNCNAME[0]}:"\ "\`$1${2+ }$2': missing argument(s)" 1>&2; return 1; } ;; -v) # Assign single value [[ "$2" ]] && unset -v "$2" && eval $2=\"\$3\" && shift 3 || { echo "bash: ${FUNCNAME[0]}: $1: missing"\ "argument(s)" 1>&2; return 1; } ;; --help) echo "\ Usage: local varname [varname ...] && ${FUNCNAME[0]} [-v varname value] | [-aN varname [value ...]] ... Available OPTIONS: -aN VARNAME [value ...] assign next N values to varname as array -v VARNAME value assign single value to varname --help display this help and exit --version output version information and exit" return 0 ;; --version) echo "\ ${FUNCNAME[0]}-0.9.dev Copyright (C) 2010 Freddy Vulto License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law." return 0 ;; *) echo "bash: ${FUNCNAME[0]}: $1: invalid option" 1>&2 return 1 ;; esac done } # use mapfile_function if mapfile not found mapfile() { if type -f mapfile &>/dev/null; then command mapfile "$@" else mapfile_function "$@" fi } # Behaves like bash 4.x mapfile. mapfile_function() { local DELIM=$'\n' local REMOVE_TRAILING_DELIM='false' local OUTPUT_ARRAY_NAME='MAPFILE' local OPTIND OPTARG OPTERR local option OPTERR=0 while getopts ':d:n:O:s:tu:C:c:' option; do case "$option" in d) DELIM=$OPTARG ;; t) REMOVE_TRAILING_DELIM='true' ;; :) echo "Option requires an argument: -$OPTARG" >&2 return 1 ;; \?) echo "Illegal option: -$OPTARG" >&2 return 2 ;; ?) echo "Option unimplemented: -$option" >&2 return 3 ;; esac done shift "$((OPTIND - 1))" if (($# >= 1)); then OUTPUT_ARRAY_NAME=$1 fi local output_array=() local null_end='false' local REPLY if [[ -n "$DELIM" ]]; then # stops at first null when DELIM is not null IFS= read -r -d '' && null_end='true' fi while IFS= read -r -d "$DELIM"; do if [[ "$REMOVE_TRAILING_DELIM" = 'true' ]]; then output_array+=("$REPLY") else output_array+=("$REPLY$DELIM") fi done < <( if [[ -n "$DELIM" ]]; then echo -n "$REPLY" [[ "$null_end" = 'true' ]] && printf '%b' '\0' else cat fi ) if [[ -n "$REPLY" ]] || [[ "$null_end" = 'true' ]]; then output_array+=("$REPLY") fi local "$OUTPUT_ARRAY_NAME" && upvars -a"${#output_array[@]}" "$OUTPUT_ARRAY_NAME" "${output_array[@]}" } # mapfile according to word splitting rules. # This isn't used by the shim. # Empty lines aren't included in array if # DELIM is whitespace (space, tab, newline) or null. mapfile_IFS() { local DELIM=$'\n' local REMOVE_TRAILING_DELIM='false' local OUTPUT_ARRAY_NAME='MAPFILE' local OPTIND OPTARG OPTERR local option OPTERR=0 while getopts ':d:n:O:s:tu:C:c:' option; do case "$option" in d) DELIM=$OPTARG ;; t) REMOVE_TRAILING_DELIM='true' ;; :) echo "Option requires an argument: -$OPTARG" >&2 return 1 ;; \?) echo "Illegal option: -$OPTARG" >&2 return 2 ;; ?) echo "Option unimplemented: -$option" >&2 return 3 ;; esac done shift "$((OPTIND - 1))" if (($# >= 1)); then OUTPUT_ARRAY_NAME=$1 fi local output_array=() local null_end='false' local delim_end='false' local REPLY local array_front local array_last if [[ -n "$DELIM" ]]; then IFS= read -r -d '' && null_end='true' [[ "$REPLY" = *"$DELIM" ]] && delim_end='true' IFS=$DELIM read -r -d '' -a 'output_array' < <( echo -n "$REPLY" [[ "$null_end" = 'true' ]] && printf '%b' '\0' ) if [[ "$REMOVE_TRAILING_DELIM" = 'false' ]]; then array_front=("${output_array[@]:0:${#output_array[@]} - 1}") array_last=${output_array[${#output_array[@]} - 1]} array_front=("${array_front[@]/%/$DELIM}") if [[ "$delim_end" = 'true' ]]; then array_last+=$DELIM fi output_array=("${array_front[@]}" "$array_last") fi else while IFS=$DELIM read -r -d ''; do # no need to append null trailing delim because vars can't hold null [[ -n "$REPLY" ]] && output_array+=("$REPLY") done [[ -n "$REPLY" ]] && output_array+=("$REPLY") fi local "$OUTPUT_ARRAY_NAME" && upvars -a"${#output_array[@]}" "$OUTPUT_ARRAY_NAME" "${output_array[@]}" } # Renders a text based list of options that can be selected by the # user using up, down and enter keys and returns the chosen option. # # Arguments : list of options, maximum of 256 # "opt1" "opt2" ... # Return value: selected index (0 for opt1, 1 for opt2 ...) function select_option { # little helpers for terminal print control and key input ESC=$( printf "\033") cursor_blink_on() { printf "$ESC[?25h"; } cursor_blink_off() { printf "$ESC[?25l"; } cursor_to() { printf "$ESC[$1;${2:-1}H"; } print_option() { printf " $1 "; } print_selected() { printf " $ESC[7m $1 $ESC[27m"; } get_cursor_row() { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; } key_input() { read -s -n3 key 2>/dev/null >&2 if [[ $key = $ESC[A ]]; then echo up; fi if [[ $key = $ESC[B ]]; then echo down; fi if [[ $key = "" ]]; then echo enter; fi; } # initially print empty new lines (scroll down if at bottom of screen) for opt; do printf "\n"; done # determine current screen position for overwriting the options local lastrow=`get_cursor_row` local startrow=$(($lastrow - $#)) # ensure cursor and input echoing back on upon a ctrl+c during read -s trap "cursor_blink_on; stty echo; printf '\n'; exit" 2 cursor_blink_off local selected=0 while true; do # print options by overwriting the last lines local idx=0 for opt; do cursor_to $(($startrow + $idx)) if [ $idx -eq $selected ]; then print_selected "$opt" else print_option "$opt" fi ((idx++)) done # user key control case `key_input` in enter) break;; up) ((selected--)); if [ $selected -lt 0 ]; then selected=$(($# - 1)); fi;; down) ((selected++)); if [ $selected -ge $# ]; then selected=0; fi;; esac done # cursor position back to normal cursor_to $lastrow printf "\n" cursor_blink_on return $selected } mapfile -t available_contexts < <( kubectl config get-contexts -oname ) echo "Select context using up/down keys and enter to confirm:" echo select_option "${available_contexts[@]}" choice=$? kubectl config use-context ${available_contexts[$choice]}