#!/bin/bash function klabels { local header= local padding= local trim_length= local line_text= local first_line= local max_key_length=0 local max_label_length=0 local key= local value= local filter_value="$1" local filter_key="$2" while IFS= read -r line; do if [ -z "$header" ]; then header="$line" padding="${header//LABELS/}" padding="${padding//?/ }" echo "$header" trim_length=${#padding} continue fi line_text="${line:0:trim_length}" first_line= IFS=',' read -ra labels <<< "${line:trim_length}" # Calculate the maximum key length to align key=value pairs for label in "${labels[@]}"; do key="${label%%=*}" max_key_length=$(( ${#key} > max_key_length ? ${#key} : max_key_length )) done # Loop through labels and align them for label in "${labels[@]}"; do key="${label%%=*}" value="${label#*=}" if [ -n "$filter_key" ] && ! [[ "$key" == *$filter_key* ]] ; then continue fi if [ -n "$filter_value" ] && ! [[ "$value" == *$filter_value* ]] ; then continue fi # Format the label to align the '=' sign printf -v formatted_label "%-${max_key_length}s = %s" "$key" "$value" if [ -z "$first_line" ]; then first_line=1 # Print a line separator based on the longest key length separator_length=$((max_key_length + trim_length)) printf "%${separator_length}s\n" | tr ' ' "-" echo "$line_text$formatted_label" else echo "$padding$formatted_label" fi done done } function __kcreate_help { cat <<'EOH' usage: kcreate [-n NAMESPACE] [options] TARGET_POD NEW_POD [-exec [--] CMD...] examples: kcreate -n www app-pod debug-pod kcreate -n www app-pod debug-pod -exec -- bash -l kcreate -n www -c app --rm app-pod debug-pod -exec -- bash -l options: -n, --namespace NS Namespace. Defaults to current kubectl namespace, then default. -c, --container NAME Container to wait for / exec into. -e, -exec, --exec After creating the Pod, wait for container(s) to run, then kubectl exec -it. --rm Delete the new Pod after the exec command exits. --sleep, --sleep-infinity Start copied containers with: sleep infinity. --force Delete NEW_POD first if it already exists. --dry-run Print the generated Pod manifest instead of creating it. --copy-labels Copy labels from the target Pod. Dangerous: Services may route traffic to it. --copy-annotations Copy annotations from the target Pod. Usually not needed. --same-node Keep spec.nodeName, forcing the new Pod onto the same node. --no-probes Remove liveness/readiness/startup probes from copied containers. --timeout SECONDS Exec wait timeout. Default: 90. EOH } function __kcreate_current_namespace { local ns ns=$(kubectl config view --minify -o 'jsonpath={..namespace}' 2>/dev/null) if [ -z "$ns" ]; then ns=default fi printf '%s\n' "$ns" } function __kcreate_wait_container_running { local ns="$1" local pod="$2" local container="$3" local timeout="$4" local end local ok end=$((SECONDS + timeout)) while [ "$SECONDS" -le "$end" ]; do if [ -n "$container" ]; then ok=$( kubectl -n "$ns" get pod "$pod" -o json 2>/dev/null | jq -r --arg c "$container" ' [.status.containerStatuses[]? | select(.name == $c and (.state.running != null))] | length > 0 ' ) || ok=false else ok=$( kubectl -n "$ns" get pod "$pod" -o json 2>/dev/null | jq -r ' [.status.containerStatuses[]?] as $s | ($s | length > 0) and ([ $s[] | select(.state.running != null) ] | length == ($s | length)) ' ) || ok=false fi if [ "$ok" = true ]; then return 0 fi sleep 1 done echo "kcreate: timed out waiting for pod/$pod container(s) to be running" >&2 kubectl -n "$ns" get pod "$pod" >&2 return 1 } function kcreate { local ns= local target_pod= local new_pod= local container= local sleep_forever=false local exec_after=false local rm_after=false local force=false local dry_run=false local copy_labels=false local copy_annotations=false local same_node=false local no_probes=false local timeout=90 local manifest local rc local exec_cmd=() local exec_args=() while [ "$#" -gt 0 ]; do case "$1" in -h|--help) __kcreate_help return 0 ;; -n|--namespace) if [ -z "$2" ]; then echo "kcreate: $1 needs a namespace" >&2; return 2; fi ns="$2" shift 2 ;; -c|--container) if [ -z "$2" ]; then echo "kcreate: $1 needs a container name" >&2; return 2; fi container="$2" shift 2 ;; --sleep|--sleep-infinity) sleep_forever=true shift ;; -e|-exec|--exec) exec_after=true shift if [ "${1:-}" = "--" ]; then shift; fi exec_cmd=("$@") break ;; --rm) rm_after=true shift ;; --force) force=true shift ;; --dry-run) dry_run=true shift ;; --copy-labels) copy_labels=true shift ;; --copy-annotations) copy_annotations=true shift ;; --same-node) same_node=true shift ;; --no-probes) no_probes=true shift ;; --timeout) if [ -z "$2" ]; then echo "kcreate: $1 needs seconds" >&2; return 2; fi timeout="$2" shift 2 ;; -*) echo "kcreate: unknown option: $1" >&2 return 2 ;; *) if [ -z "$target_pod" ]; then target_pod="$1" elif [ -z "$new_pod" ]; then new_pod="$1" else echo "kcreate: unexpected argument: $1" >&2 return 2 fi shift ;; esac done if [ -z "$ns" ]; then ns=$(__kcreate_current_namespace) fi if [ -z "$target_pod" ] || [ -z "$new_pod" ]; then __kcreate_help >&2 return 2 fi if ! command -v kubectl >/dev/null 2>&1; then echo "kcreate: kubectl is required" >&2 return 127 fi if ! command -v jq >/dev/null 2>&1; then echo "kcreate: jq is required" >&2 return 127 fi if ! [[ "$timeout" =~ ^[0-9]+$ ]]; then echo "kcreate: --timeout must be plain seconds, e.g. 90" >&2 return 2 fi manifest=$( kubectl -n "$ns" get pod "$target_pod" -o json | jq \ --arg name "$new_pod" \ --arg src "$target_pod" \ --arg srcns "$ns" \ --argjson sleep_forever "$sleep_forever" \ --argjson copy_labels "$copy_labels" \ --argjson copy_annotations "$copy_annotations" \ --argjson same_node "$same_node" \ --argjson no_probes "$no_probes" ' { apiVersion: "v1", kind: "Pod", metadata: { name: $name, labels: (if $copy_labels then (.metadata.labels // {}) else {"app.kubernetes.io/managed-by": "kcreate"} end), annotations: (if $copy_annotations then (.metadata.annotations // {}) else {} end) }, spec: .spec } | .metadata.annotations["kcreate/source-pod"] = $src | .metadata.annotations["kcreate/source-namespace"] = $srcns | del(.metadata.labels["pod-template-hash"]?) | del(.spec.priority?) | del(.spec.serviceAccount?) | if $same_node then . else del(.spec.nodeName?) end | if $no_probes then (.spec.containers |= map(del(.livenessProbe?, .readinessProbe?, .startupProbe?))) else . end | if $sleep_forever then (.spec.containers |= map( .command = ["sleep"] | .args = ["infinity"] | del(.livenessProbe?, .readinessProbe?, .startupProbe?) )) else . end ' ) || return $? if [ "$dry_run" = true ]; then printf '%s\n' "$manifest" return 0 fi if [ "$copy_labels" = true ]; then echo "kcreate: warning: --copy-labels may let Services route traffic to pod/$new_pod" >&2 fi if [ "$force" = true ]; then kubectl -n "$ns" delete pod "$new_pod" --ignore-not-found --wait=true || return $? fi printf '%s\n' "$manifest" | kubectl -n "$ns" create -f - || return $? if [ "$exec_after" != true ]; then kubectl -n "$ns" get pod "$new_pod" return $? fi if [ "${#exec_cmd[@]}" -eq 0 ]; then exec_cmd=(sh) fi __kcreate_wait_container_running "$ns" "$new_pod" "$container" "$timeout" || return $? if [ -n "$container" ]; then exec_args=(-c "$container") fi kubectl -n "$ns" exec -it "$new_pod" "${exec_args[@]}" -- "${exec_cmd[@]}" rc=$? if [ "$rm_after" = true ]; then kubectl -n "$ns" delete pod "$new_pod" --ignore-not-found >/dev/null fi return "$rc" }