Last active
September 25, 2024 18:18
-
-
Save augustohp/deebc619c515463b9df6f46d8c7376af to your computer and use it in GitHub Desktop.
Execute a shell inside a running pod in kubernetes. Uses fzf (fuzzy search menu) as interface for options when they are not given.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env sh | |
# | |
# Allows you to execute a shell into a running pod inside k8s. You can | |
# execute this script/command without any argument and it will display | |
# all available namespaces, then all available pods in the chosen namespace | |
# and then enter the pod you chose. | |
# | |
# Choices are given using fzf, allowing you to fuzzy search among occurrences. | |
# | |
# Author: Augusto Pascutti <augusto.hp+oss@gmail> | |
# License: MIT | |
# URL: https://gist.github.com/augustohp/deebc619c515463b9df6f46d8c7376af | |
# vim: set ft=sh ts=4 sw=4 tw=0 noet: | |
# shellcheck disable=SC3043 | |
# TODO: Spawn new pod from deploysments options (-d) instead of using running pod | |
APP_NAME=$(basename "$0") | |
APP_VERSION="1.1.1" | |
APP_AUTHOR="[email protected]" | |
APP_DEPENDENCIES="kubectl awk fzf tr sort" | |
OPTION_NAMESPACE="" | |
OPTION_POD="" | |
OPTION_CONTAINER="" | |
OPTION_SHELL="/bin/sh" | |
set -e # Stops on failure | |
trap cleanup INT TERM EXIT | |
# Usage: cleanup | |
cleanup() | |
{ | |
exit 1 | |
} | |
# Usage: assert_env | |
assert_env() | |
{ | |
for dependency in $APP_DEPENDENCIES | |
do | |
if [ -n "$(command -v "$dependency")" ] | |
then | |
continue | |
fi | |
err "Missing dependency '$dependency', please install it." | |
exit 2 | |
done | |
} | |
# Usage: if empty "" && echo "empty" | |
empty() | |
{ | |
local value="$1" | |
if [ -z "$value" ] | |
then | |
return 0 | |
fi | |
return 1 | |
} | |
# Usage: kubectl get ns | strip_k8s_header | |
strip_k8s_header() | |
{ | |
grep -v "NAME" | |
} | |
# Usage: kubectl get ns | only_column 1 | |
only_column() | |
{ | |
local column="$1" | |
awk "{ print \$${column} }" | |
} | |
# Usage: echo " many spaces here" | change_space_to_line | |
change_space_to_line() | |
{ | |
tr -s '[:space:]' '\n' | |
} | |
# ----------------------------------------------------------------------------- | |
# Context | |
# Filters the input with fuzzy search, the output is the line selected. | |
# Usage: cat /etc/hosts | fzf_menu [prompt] | |
fzf_menu() | |
{ | |
local header prompt title | |
prompt="${1:->}: " | |
header="${2:-Available choices:}" | |
title="${3:-Choose an option below (search enabled): [arrows navigate, ENTER chooses, CTRL-c cancels]}" | |
fzf --header="$header" \ | |
--height=35% \ | |
--reverse \ | |
--border sharp --border-label "${title}" \ | |
--prompt "${prompt}" | |
} | |
# Usage: namespace="$(choose_namespace [namespace])" | |
choose_namespace() | |
{ | |
local given_ns | |
given_ns="${1:-}" | |
if ! empty "${given_ns}" | |
then | |
echo "${given_ns}" | |
return 0 | |
fi | |
kubectl get namespaces \ | |
| strip_k8s_header \ | |
| only_column 1 \ | |
| fzf_menu "Namespace" | |
} | |
# Usage: pod="$(choose_pod <namespace> [pod])" | |
choose_pod() | |
{ | |
local namespace pod | |
namespace="${1:-}" | |
pod="${2:-}" | |
test -z "$namespace" && { echo "Error: No namespace given!" >&2; return 2; } | |
if ! empty "$pod" | |
then | |
echo "${pod}" | |
return 0 | |
fi | |
kubectl get pods --namespace "$namespace" \ | |
| strip_k8s_header \ | |
| only_column 1 \ | |
| fzf_menu "Pod" | |
} | |
# Usage: container="$(choose_container <namespace> <pod> [container])" | |
choose_container() | |
{ | |
local namespace pod container | |
namespace="${1:-}" | |
pod="${2:-}" | |
container="${3:-}" | |
test -z "$namespace" && { echo "Error: No namespace given!" >&2; return 2; } | |
test -z "$pod" && { echo "Error: No pod given!" >&2; return 2; } | |
if ! empty "$container" | |
then | |
echo "${container}" | |
return 0 | |
fi | |
kubectl get pods --namespace "$namespace" "$pod" -o jsonpath="{.spec.containers[*].name}" \ | |
| change_space_to_line \ | |
| sort \ | |
| fzf_menu "Container" | |
} | |
# Usage: enter_shell <namespace> <pod> <shell> | |
enter_shell() | |
{ | |
local namespace="$1" | |
local pod="$2" | |
local container="$3" | |
local shell="$4" | |
echo kubectl exec -n "$namespace" -c "$container" --stdin --tty "$pod" -- "$shell" | |
kubectl exec -n "$namespace" -c "$container" --stdin --tty "$pod" -- "$shell" | |
} | |
# Usage: pod_state <namespace> <pod> | |
pod_state() | |
{ | |
local namespace="$1" | |
local pod="$2" | |
kubectl get pod -n "$namespace" "$pod" 2> /dev/null \ | |
| strip_k8s_header \ | |
| only_column 3 | |
} | |
# Usage: if pod_not_running <namespace> <pod> && echo "NOT running" | |
pod_not_running() | |
{ | |
local namespace="$1" | |
local pod="$2" | |
state="$(pod_state "$namespace" "$pod")" | |
if [ "$state" = "Running" ] | |
then | |
return 1 | |
fi | |
return 0 | |
} | |
# ----------------------------------------------------------------------------- | |
# Execution | |
# Usage: display_help | |
display_help() | |
{ | |
cat <<-EOF | |
Usage: $APP_NAME [options] | |
$APP_NAME --bash --pod "my-app" --namespace "default" | |
Helps you execute a shell inside a running pod, using fzf to help | |
you choose among existing options (namespaces, pods). | |
fzf allows you to fuzzy search among lists of namespaces or options | |
and is a pre-requisite for this script to work. | |
Options: | |
-h, --help This message. | |
-v, --version Version information. | |
-x, --debug Displays debug information using -x to | |
execute this script. Prints line by line | |
instructions. | |
--bash, --sh What shell/binary to execute. | |
-p, --pod <pod> Name of the pod to execute shell. | |
-n, --namespace <name> Namespace the pod is into. If none | |
is passed, fzf is used to choose among | |
available. | |
-c, --container <name> Name of the container to execute commands | |
inside. If none is given, fzf is used to | |
choose among available ones. | |
Bugs and/or suggestions to $APP_AUTHOR. | |
EOF | |
} | |
# Usage: main "#@" | |
main() | |
{ | |
while : | |
do | |
case "${1:-}" in | |
-h | --help) | |
display_help | |
exit 1 | |
;; | |
-v | --version) | |
echo "$APP_VERSION" | |
exit 1 | |
;; | |
-x | --debug) | |
set -x | |
;; | |
-n | -ns | --ns | --namespace) | |
OPTION_NAMESPACE="$2" | |
shift | |
;; | |
-c | --container) | |
OPTION_CONTAINER="$2" | |
shift | |
;; | |
--sh) | |
OPTION_SHELL="/bin/sh" | |
;; | |
--bash) | |
OPTION_SHELL="/bin/bash" | |
;; | |
-p | --pod) | |
OPTION_POD="$2" | |
shift | |
;; | |
-?*) | |
echo "Error: Unknown option '$1'." >&2 | |
exit 2 | |
;; | |
*) | |
break | |
;; | |
esac | |
shift | |
done | |
OPTION_NAMESPACE="$(choose_namespace "$OPTION_NAMESPACE")" | |
OPTION_POD="$(choose_pod "$OPTION_NAMESPACE" "$OPTION_POD")" | |
if pod_not_running "$OPTION_NAMESPACE" "$OPTION_POD" | |
then | |
echo "Error: '$OPTION_POD' is not running." >&2 | |
exit 3 | |
fi | |
OPTION_CONTAINER="$(choose_container "$OPTION_NAMESPACE" "$OPTION_POD" "$OPTION_CONTAINER")" | |
enter_shell "$OPTION_NAMESPACE" "$OPTION_POD" "$OPTION_CONTAINER" "$OPTION_SHELL" | |
return 0 | |
} | |
assert_env | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment