Skip to content

Instantly share code, notes, and snippets.

@vegassor
Last active April 25, 2025 07:00
Show Gist options
  • Save vegassor/fa0e6dccd1c93abb2977773466b4e14b to your computer and use it in GitHub Desktop.
Save vegassor/fa0e6dccd1c93abb2977773466b4e14b to your computer and use it in GitHub Desktop.
KEDA kubectl plugin

kubectl-keda

This is the plugin for kubectl to pause/unpause KEDA's ScaledObjects and also to check their status.

WARNING: The plugin is coded with AI.

Requirements

  • bash
  • kubectl
  • jq

Installation

Put kubectl-keda script to somewhere in $PATH, e.g. to /usr/local/bin and make it executable.

chmod +x kubectl-keda
mv kubectl-keda /usr/local/bin/kubectl-keda

Usage

$ kubectl keda
No command specified

Usage: kubectl keda <command> [arguments]
Commands:
  version
  pause <scaled-object-name1> [<scaled-object-name2>...] [--replicas <count>]
  unpause <scaled-object-name1> [<scaled-object-name2>...]
  status <scaled-object-name1> [<scaled-object-name2>...]

Global options:
  -n, --namespace: Kubernetes namespace (default: current context's namespace)
  --context: Kubernetes context to use (default: current context)
  --kubeconfig: Kubernetes kubeconfig to use (default: current kubeconfig)

Examples:
  kubectl keda pause my-scaled-object                # Pause the scaled object
  kubectl keda pause my-scaled-object --replicas 10  # Set 10 replicas and pause the scaled object
  kubectl keda unpause my-scaled-object              # Unpause the scaled object
  kubectl keda status my-scaled-object               # Get the status of the scaled object
  kubectl keda status my-so-1 my-so-2                # Get the status of multiple scaled objects
  kubectl keda status -n my-namespace                # Get the status of all scaled objects in a specific namespace
#!/bin/bash
COMMAND=""
NAMESPACE=""
CONTEXT=""
KUBECONFIG=""
COMMAND_ARGS=()
COMMAND_OPTIONS=()
# kubectl() {
# echo "kubectl $@" >> /tmp/kubectl-keda.log
# /usr/local/sbin/kubectl $@
# }
# Function to parse command line arguments
# Arguments: "$@" - all command line arguments
# Returns: Sets global variables COMMAND, NAMESPACE, CONTEXT, KUBECONFIG, COMMAND_ARGS, COMMAND_OPTIONS
parse_arguments() {
local args=("$@")
local i=0
local current_arg=""
# Reset global variables
COMMAND=""
NAMESPACE=""
CONTEXT=""
KUBECONFIG=""
COMMAND_ARGS=()
COMMAND_OPTIONS=()
# Process all arguments
while ((i < ${#args[@]})); do
current_arg="${args[$i]}"
case "$current_arg" in
"-n"|"--namespace")
if ((i + 1 >= ${#args[@]})); then
usage "Namespace value is required for $current_arg"
return 1
fi
NAMESPACE="${args[$((i+1))]}"
((i++))
;;
"--context")
if ((i + 1 >= ${#args[@]})); then
usage "Context value is required for $current_arg"
return 1
fi
CONTEXT="${args[$((i+1))]}"
((i++))
;;
"--kubeconfig")
if ((i + 1 >= ${#args[@]})); then
usage "Kubeconfig value is required for $current_arg"
return 1
fi
KUBECONFIG="${args[$((i+1))]}"
((i++))
;;
"--"*)
if ((i + 1 >= ${#args[@]})); then
usage "Value is required for $current_arg"
return 1
fi
COMMAND_OPTIONS+=("$current_arg" "${args[$((i+1))]}")
((i++))
;;
-*)
usage "Unknown option: $current_arg"
return 1
;;
*)
if [[ -z "$COMMAND" ]]; then
COMMAND="$current_arg"
else
COMMAND_ARGS+=("$current_arg")
fi
;;
esac
((i++))
done
return 0
}
# Function to update ScaledObject configuration
# Arguments:
# $1 - ScaledObject name
# $2 - JSON patch
# $3 - Namespace (optional)
# $4 - Context (optional)
# $5 - Kubeconfig (optional)
# Returns: 0 on success, 1 on failure
update_scaled_object() {
local name="$1"
local patch="$2"
local namespace="$3"
local context="$4"
local kubeconfig="$5"
local kubectl_args=()
if [[ -z "$name" ]]; then
handle_error "ScaledObject name is required" 1 false
return 1
fi
if [[ -z "$patch" ]]; then
handle_error "JSON patch is required" 1 false
return 1
fi
kubectl_args+=("patch" "scaledobject" "$name" "--type=merge" "-p" "$patch")
if [[ -n "$namespace" ]]; then
kubectl_args+=("--namespace" "$namespace")
fi
if [[ -n "$context" ]]; then
kubectl_args+=("--context" "$context")
fi
if [[ -n "$kubeconfig" ]]; then
kubectl_args+=("--kubeconfig" "$kubeconfig")
fi
if ! output=$(kubectl "${kubectl_args[@]}"); then
echo "$output"
return 1
fi
echo "$output"
return 0
}
# Function to display usage information
# Arguments:
# $1 - Additional message to display before usage (optional)
usage() {
local message="$1"
if [[ -n "$message" ]]; then
echo "$message"
echo
fi
echo "Usage: kubectl keda <command> [arguments]"
echo "Commands:"
echo " version"
echo " pause <scaled-object-name1> [<scaled-object-name2>...] [--replicas <count>]"
echo " unpause <scaled-object-name1> [<scaled-object-name2>...]"
echo " status <scaled-object-name1> [<scaled-object-name2>...]"
echo
echo "Global options:"
echo " -n, --namespace: Kubernetes namespace (default: current context's namespace)"
echo " --context: Kubernetes context to use (default: current context)"
echo " --kubeconfig: Kubernetes kubeconfig to use (default: current kubeconfig)"
echo
echo "Examples:"
echo " kubectl keda pause my-scaled-object # Pause the scaled object"
echo " kubectl keda pause my-scaled-object --replicas 10 # Set 10 replicas and pause the scaled object"
echo " kubectl keda unpause my-scaled-object # Unpause the scaled object"
echo " kubectl keda status my-scaled-object # Get the status of the scaled object"
echo " kubectl keda status my-so-1 my-so-2 # Get the status of multiple scaled objects"
echo " kubectl keda status -n my-namespace # Get the status of all scaled objects in a specific namespace"
}
# Function to handle errors
# Arguments:
# $1 - Error message
# $2 - Exit code (optional, defaults to 1)
# $3 - Show usage (optional, defaults to false)
handle_error() {
local message="$1"
local exit_code="${2:-1}"
local show_usage="${3:-true}"
echo "Error: $message"
# Show usage if requested
if [[ "$show_usage" == "true" ]]; then
usage
fi
exit "$exit_code"
}
# Function to pause a ScaledObject
# Arguments:
# $1 - Comma-separated list of ScaledObject names
# $2 - Number of replicas to maintain (optional)
# $3 - Namespace (optional)
# $4 - Context (optional)
# $5 - Kubeconfig (optional)
pause_so() {
local names=()
local replicas="$2"
local namespace="$3"
local context="$4"
local kubeconfig="$5"
local kubectl_args=()
IFS=',' read -ra names <<< "$1"
if [[ ${#names[@]} -eq 0 ]]; then
handle_error "At least one ScaledObject name is required" 1 true
return 1
fi
if [[ -n "$namespace" ]]; then
kubectl_args+=("--namespace" "$namespace")
fi
if [[ -n "$context" ]]; then
kubectl_args+=("--context" "$context")
fi
local patch='{"metadata":{"annotations":{"autoscaling.keda.sh/paused":"true"}}}'
if [[ -n "$replicas" ]]; then
if ! [[ "$replicas" =~ ^[0-9]+$ ]]; then
handle_error "Replicas must be a positive number"
return 1
fi
patch=$(echo "$patch" | jq --arg reps "$replicas" '.metadata.annotations["autoscaling.keda.sh/paused-replicas"] = $reps')
fi
local success=true
for name in "${names[@]}"; do
local update_output
if ! update_output=$(update_scaled_object "$name" "$patch" "$namespace" "$context" "$kubeconfig" 2>&1); then
echo "Error: Failed to pause ScaledObject '$name': $update_output" >&2
success=false
else
echo "Successfully paused ScaledObject '$name'"
if [[ -n "$replicas" ]]; then
echo "Maintaining $replicas replicas while paused"
fi
fi
done
if [[ "$success" == "false" ]]; then
return 1
fi
return 0
}
# Function to unpause a ScaledObject
# Arguments:
# $1 - Comma-separated list of ScaledObject names
# $2 - Namespace (optional)
# $3 - Context (optional)
# $4 - Kubeconfig (optional)
unpause_so() {
local names=()
local namespace="$2"
local context="$3"
local kubeconfig="$4"
local kubectl_args=()
IFS=',' read -ra names <<< "$1"
if [[ ${#names[@]} -eq 0 ]]; then
handle_error "At least one ScaledObject name is required" 1 true
return 1
fi
if [[ -n "$namespace" ]]; then
kubectl_args+=("--namespace" "$namespace")
fi
if [[ -n "$context" ]]; then
kubectl_args+=("--context" "$context")
fi
local patch='{"metadata":{"annotations":{"autoscaling.keda.sh/paused":null,"autoscaling.keda.sh/paused-replicas":null}}}'
local success=true
for name in "${names[@]}"; do
local update_output
if ! update_output=$(update_scaled_object "$name" "$patch" "$namespace" "$context" "$kubeconfig" 2>&1); then
echo "Error: Failed to unpause ScaledObject '$name': $update_output" >&2
success=false
else
echo "Successfully unpaused ScaledObject '$name'"
fi
done
if [[ "$success" == "false" ]]; then
return 1
fi
return 0
}
# Function to get status of ScaledObject(s)
# Arguments:
# $1 - Comma-separated list of ScaledObject names (optional)
# $2 - Namespace (optional)
# $3 - Context (optional)
# $4 - Kubeconfig (optional)
status_so() {
local names=()
local namespace="$2"
local context="$3"
local kubeconfig="$4"
local kubectl_args=()
if [[ -n "$namespace" ]]; then
kubectl_args+=("--namespace" "$namespace")
fi
if [[ -n "$context" ]]; then
kubectl_args+=("--context" "$context")
fi
if [[ -n "$kubeconfig" ]]; then
kubectl_args+=("--kubeconfig" "$kubeconfig")
fi
local so_list
if [[ -n "$1" ]]; then
IFS=',' read -ra names <<< "$1"
local name_args=()
for name in "${names[@]}"; do
name_args+=("$name")
done
if ! so_list=$(kubectl get scaledobject "${name_args[@]}" -o json "${kubectl_args[@]}" 2>&1); then
echo "Error: Failed to get ScaledObjects: $so_list" >&2
return 1
fi
# If single resource, wrap it in a List object
if [[ ${#names[@]} -eq 1 ]]; then
so_list=$(echo "$so_list" | jq '{apiVersion: "v1", kind: "List", items: [.]}')
fi
else
if ! so_list=$(kubectl get scaledobject -o json "${kubectl_args[@]}" 2>&1); then
echo "Error: Failed to get ScaledObjects: $so_list" >&2
return 1
fi
local item_count=$(echo "$so_list" | jq '.items | length')
if [[ "$item_count" -eq 0 ]]; then
echo "No ScaledObjects found in namespace ${namespace:-default}"
return 0
fi
fi
local scale_targets=()
while IFS= read -r target; do
if [[ -n "$target" ]]; then
scale_targets+=("$target")
fi
done < <(echo "$so_list" | jq -r '.items[] | "\(.spec.scaleTargetRef.kind // "Deployment") \(.spec.scaleTargetRef.name)"')
local scale_target_json
if [[ ${#scale_targets[@]} -gt 0 ]]; then
# Get all scale target resources in a single API call
local kubectl_get_args=()
for target in "${scale_targets[@]}"; do
read -r kind name <<< "$target"
kubectl_get_args+=("$kind/$name")
done
if ! scale_target_json=$(kubectl get "${kubectl_get_args[@]}" -o json "${kubectl_args[@]}" 2>&1); then
echo "Error: Failed to get scale target resources: $scale_target_json" >&2
return 1
fi
if [[ ${#scale_targets[@]} -eq 1 ]]; then
scale_target_json=$(echo "$scale_target_json" | jq '{apiVersion: "v1", kind: "List", items: [.]}')
fi
fi
local success=true
while IFS= read -r so_json; do
local name=$(echo "$so_json" | jq -r '.metadata.name')
local namespace=$(echo "$so_json" | jq -r '.metadata.namespace')
local min_replicas=$(echo "$so_json" | jq -r '.spec.minReplicaCount // 0')
local max_replicas=$(echo "$so_json" | jq -r '.spec.maxReplicaCount // 100')
local is_paused=$(echo "$so_json" | jq -r '.metadata.annotations["autoscaling.keda.sh/paused"] // "false"')
local paused_replicas=$(echo "$so_json" | jq -r '.metadata.annotations["autoscaling.keda.sh/paused-replicas"] // ""')
local scale_target_ref=$(echo "$so_json" | jq -r '.spec.scaleTargetRef.name')
local scale_target_kind=$(echo "$so_json" | jq -r '.spec.scaleTargetRef.kind // "Deployment"')
local current_replicas=$(echo "$scale_target_json" | jq -r --arg kind "$scale_target_kind" --arg name "$scale_target_ref" '
.items[] | select(.kind == $kind and .metadata.name == $name) | .status.replicas // 0
')
echo "ScaledObject: $namespace/$name"
echo " Min Replicas: $min_replicas"
echo " Max Replicas: $max_replicas"
echo " Paused: $is_paused"
echo " Paused Replicas: $paused_replicas"
echo " Current Replicas: $current_replicas"
echo
done < <(echo "$so_list" | jq -c '.items[]')
if [[ "$success" == "false" ]]; then
return 1
fi
return 0
}
# Function to extract command option value by option name
# Arguments:
# $1 - Option name without dashes (e.g., "replicas" for "--replicas")
# Returns: The value of the option if found, empty string if not found
get_option_value() {
local option_name="$1"
local i
for ((i=0; i<${#COMMAND_OPTIONS[@]}; i+=2)); do
if [[ "${COMMAND_OPTIONS[$i]}" == "--$option_name" ]]; then
echo "${COMMAND_OPTIONS[$((i+1))]}"
return 0
fi
done
echo ""
return 1
}
# Main script execution
main() {
# Parse all arguments
parse_arguments "$@"
if [[ -z "$COMMAND" ]]; then
usage "No command specified"
return 1
fi
case "$COMMAND" in
"version")
echo "kubectl KEDA plugin for managing KEDA ScaledObjects"
echo "The plugin is vibe-coded, it can be buggy."
echo "Version: v0.3.2"
;;
"pause")
local replicas
replicas=$(get_option_value "replicas")
pause_so "$(IFS=,; echo "${COMMAND_ARGS[*]}")" "$replicas" "$NAMESPACE" "$CONTEXT" "$KUBECONFIG"
;;
"unpause")
unpause_so "$(IFS=,; echo "${COMMAND_ARGS[*]}")" "$NAMESPACE" "$CONTEXT" "$KUBECONFIG"
;;
"status")
status_so "$(IFS=,; echo "${COMMAND_ARGS[*]}")" "$NAMESPACE" "$CONTEXT" "$KUBECONFIG"
;;
*)
usage "Unknown command: $COMMAND"
return 1
;;
esac
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment