From fae4ccf75ee11831b27c5706b6a5ab943efd10ec Mon Sep 17 00:00:00 2001 From: Wenhao Ji Date: Tue, 30 Jun 2020 21:09:58 +0800 Subject: [PATCH] Added option `--session-mode` --- README.md | 1 + bin/kubectl-tmux_exec | 44 +++++++++++++++++++++++++++++++++++------- test/exec-one-pod.bats | 33 ++----------------------------- test/nested-tmux.bats | 25 ++++++++++++++++++++++++ test/test-helper.bash | 37 +++++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 38 deletions(-) create mode 100644 test/nested-tmux.bats diff --git a/README.md b/README.md index 223d835..ad54311 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ Flag | Usage `-d`
`--detach` | Make the Tmux session detached `--remain-on-exit` | Remain Tmux window on exit `--select-layout` | One of the five Tmux preset layouts: even-horizontal, even-vertical, main-horizontal, main-vertical, or tiled. +`--session-mode` | Where tmux is opened: auto, new-session, current-session The usage of these options is also available by `--help`. diff --git a/bin/kubectl-tmux_exec b/bin/kubectl-tmux_exec index 4abdde8..e5ec988 100755 --- a/bin/kubectl-tmux_exec +++ b/bin/kubectl-tmux_exec @@ -24,9 +24,9 @@ set -euf -o pipefail # Stack Overflow: https://stackoverflow.com/a/246128/1122665 SOURCE="${BASH_SOURCE[0]}" while [[ -h "$SOURCE" ]]; do - PROG_DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )" - SOURCE="$(readlink "$SOURCE")" - [[ $SOURCE != /* ]] && SOURCE="$PROG_DIR/$SOURCE" + PROG_DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ $SOURCE != /* ]] && SOURCE="$PROG_DIR/$SOURCE" done PROG_DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )" @@ -63,6 +63,12 @@ declare -ra TMUX_LAYOUTS=( 'tiled' ) +declare -ra SESSION_MODES=( + 'auto' + 'new-session' + 'current-session' +) + function usage() { cat << EOF Execute a command in all containers that match the label selector using Tmux. @@ -87,6 +93,10 @@ Options: -l, --selector: Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2) -f, --file: Read pod names line-by-line from a file -d, --detach=false: Make the Tmux session detached + --session-mode=auto: Where tmux is opened: + new-session (always in a new session no matter whether there is an existing or not) + current-session (re-use the current session) + auto ('current-session' if there is an existing one, otherwise 'new-session') --remain-on-exit=false: Remain Tmux window on exit --select-layout=tiled: One of the five Tmux preset layouts: even-horizontal, even-vertical, main-horizontal, main-vertical, or tiled. @@ -157,7 +167,7 @@ function array_contains() { function main() { local opts opts=$(ggetopt -o hVitdc:l:f:"$(printf '%s:' "${KUBECTL_SHORT_OPTS[@]}")" --long \ - help,version,stdin,tty,detach,container:,selector:,remain-on-exit,select-layout:,file:,"$(printf '%s:,' "${KUBECTL_LONG_OPTS[@]}")","$(printf '%s,' "${KUBECTL_NOARG_LONG_OPTS[@]}")" -- "$@") + help,version,stdin,tty,detach,container:,selector:,remain-on-exit,select-layout:,session-mode:,file:,"$(printf '%s:,' "${KUBECTL_LONG_OPTS[@]}")","$(printf '%s,' "${KUBECTL_NOARG_LONG_OPTS[@]}")" -- "$@") eval set -- $opts local selector @@ -167,6 +177,7 @@ function main() { local tmux_layout='tiled' local pod_list_file local tmux_detach=0 + local session_mode='auto' while [[ $# -gt 0 ]]; do local opt="$1" case "${opt}" in @@ -202,6 +213,10 @@ function main() { shift tmux_layout="$1" ;; + --session-mode) + shift + session_mode="$1" + ;; -f|--file) shift pod_list_file="$1" @@ -250,7 +265,11 @@ function main() { if [[ -z "${tmux_layout}" ]] || ! array_contains "${tmux_layout}" "${TMUX_LAYOUTS[@]}"; then error_and_exit "Unknown layout: ${tmux_layout}" fi - + + if [[ -z "${session_mode}" ]] || ! array_contains "${session_mode}" "${SESSION_MODES[@]}"; then + error_and_exit "Unknown session mode: ${session_mode}" + fi + local commands=("$@") local kubectl_exec_opts='-i -t' @@ -294,10 +313,21 @@ function main() { for pod in "${pods[@]}"; do local cmd="kubectl ${kubectl_opts[@]:-} exec ${kubectl_exec_opts} ${pod} -- ${exec_cmd_str}" if [[ "${#tmux_commands[@]}" -eq 0 ]]; then + local open_command + if [[ "${session_mode}" == 'new-session' ]]; then + open_command='new-session' + elif [[ "${session_mode}" == 'current-session' ]]; then + open_command='new-window' + elif [[ -z "${TMUX:-}" ]]; then + open_command='new-session' + else + open_command='new-window' + fi + if [[ "${tmux_detach}" -eq 0 ]]; then - tmux_commands+=('new-session' "${cmd}" ';') + tmux_commands+=("${open_command}" "${cmd}" ';') else - tmux_commands+=('new-session' '-d' "${cmd}" ';') + tmux_commands+=("${open_command}" '-d' "${cmd}" ';') fi else tmux_commands+=('split-window' "${cmd}" ';') diff --git a/test/exec-one-pod.bats b/test/exec-one-pod.bats index 2aedd10..7275422 100644 --- a/test/exec-one-pod.bats +++ b/test/exec-one-pod.bats @@ -2,41 +2,12 @@ load test-helper -readonly POD_NAME='alpine' -readonly POD_APP_LABEL='alpine' - function setup() { - kubectl apply -f - << EOF -apiVersion: v1 -kind: Pod -metadata: - name: ${POD_NAME} - labels: - app: ${POD_APP_LABEL} -spec: - containers: - - name: alpine - image: alpine - command: - - sleep - - infinite -EOF - local pod_status='' - local retries=30 - while [[ "${pod_status}" != 'Running' ]]; do - sleep 1 - pod_status="$(kubectl get pods "${POD_NAME}" -o custom-columns=':status.phase' --no-headers)" - echo "The pod status is ${pod_status}." - (( --retries )) || { - echo 'Timed out.' - exit 1 - } - done + setup_one_pod } function teardown() { - kubectl delete pod "${POD_NAME}" - sleep 1 + teardown_one_pod } @test "Exec one pod by label" { diff --git a/test/nested-tmux.bats b/test/nested-tmux.bats new file mode 100644 index 0000000..4140b5a --- /dev/null +++ b/test/nested-tmux.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats + +load test-helper + +function setup() { + setup_one_pod +} + +function teardown() { + teardown_one_pod + tmux kill-server || true +} + +@test "Reuse tmux session" { + tmux new-session -d bin/kubectl-tmux_exec -l app="${POD_APP_LABEL}" sh + [ "$(tmux ls | wc -l)" -eq 1 ] +} + +@test "Nest tmux session" { + tmux new-session -d bin/kubectl-tmux_exec -l app="${POD_APP_LABEL}" --session-mode new-session sh + sleep 2 + local tmux_exit_code=0 + tmux ls || tmux_exit_code="$?" + [ "${tmux_exit_code}" -eq 1 ] +} diff --git a/test/test-helper.bash b/test/test-helper.bash index d55bcad..fe92963 100644 --- a/test/test-helper.bash +++ b/test/test-helper.bash @@ -9,4 +9,41 @@ function wait_until_no_sessions() { } sleep 1 done +} + +readonly POD_NAME='alpine' +readonly POD_APP_LABEL='alpine' + +function setup_one_pod() { + kubectl apply -f - << EOF +apiVersion: v1 +kind: Pod +metadata: + name: ${POD_NAME} + labels: + app: ${POD_APP_LABEL} +spec: + containers: + - name: alpine + image: alpine + command: + - sleep + - infinite +EOF + local pod_status='' + local retries=30 + while [[ "${pod_status}" != 'Running' ]]; do + sleep 1 + pod_status="$(kubectl get pods "${POD_NAME}" -o custom-columns=':status.phase' --no-headers)" + echo "The pod status is ${pod_status}." + (( --retries )) || { + echo 'Timed out.' + exit 1 + } + done +} + +function teardown_one_pod() { + kubectl delete pod "${POD_NAME}" + sleep 5 } \ No newline at end of file