penguin/utils

my env utils

commit a07ad6018168a5bfc46e52b1ca605a49b5337d81

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-07-07T09:24:46Z
subjectAdded kcreate to create debug pods
commit a07ad6018168a5bfc46e52b1ca605a49b5337d81
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-07-07T09:24:46Z

    Added kcreate to create debug pods
---
 bash/dist/rbashrc         |   2 +-
 bash/dist/rbashrc.asc     |  10 +-
 bash/dist/sources.list    |   2 +-
 bash/sources/19_k8s_funcs | 282 +++++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 288 insertions(+), 8 deletions(-)

diff --git a/bash/dist/rbashrc b/bash/dist/rbashrc
index 7bedf5f..43ad6ef 100755
--- a/bash/dist/rbashrc
+++ b/bash/dist/rbashrc
@@ -18,7 +18,7 @@ RBASH_CONFIG="$RBASH_HOME/config"
 RBASH_ENV="$RBASH_HOME/env"
 RBASH_MERGED="$RBASH_CACHE_DIR/merged"
 RBASH_BIN="$RBASH_HOME/bin"
-RBASH_SOURCES_SUM="e221c016049e096ecef46d03b52864fe99ec84f6dba560707ca4f1a44368e6a9"
+RBASH_SOURCES_SUM="53c4c6eaceb4526d0940873d22ff9978f8eb49d23b6431a5cf814893e086bb23"
 
 
 # ---- rbash: bash/core/00_env.bash ----
diff --git a/bash/dist/rbashrc.asc b/bash/dist/rbashrc.asc
index 06df515..774203b 100644
--- a/bash/dist/rbashrc.asc
+++ b/bash/dist/rbashrc.asc
@@ -1,8 +1,8 @@
 -----BEGIN PGP SIGNATURE-----
 
-iJEEABYKADkWIQR8HpSNhBJUxWnt+97Vz1+xz60nwwUCakCGbRsUgAAAAAAEAA5t
-YW51MiwyLjUrMS4xMiwwLDMACgkQ1c9fsc+tJ8OCjQD/bHWhIGbbuB2RS4oNu7Ur
-LSIgPDSUqHH1hM3TDT6PijEBANUZYsLndu92wjLa6rtQnv9A6ZTYsBR+P6D24teq
-FbcA
-=6ZYt
+iJEEABYKADkWIQR8HpSNhBJUxWnt+97Vz1+xz60nwwUCakzH+RsUgAAAAAAEAA5t
+YW51MiwyLjUrMS4xMiwwLDMACgkQ1c9fsc+tJ8OgegD/cmVsnne+vp67XFOigKRB
+jPWPdbROVndkYmG4DyqLKVIA/jocAoCry9fe6hY8Iv8FvWjFViyF1d6oCQ+SY4I/
+qMYB
+=zzsS
 -----END PGP SIGNATURE-----
diff --git a/bash/dist/sources.list b/bash/dist/sources.list
index 2a2cbc9..29e7d06 100644
--- a/bash/dist/sources.list
+++ b/bash/dist/sources.list
@@ -6,7 +6,7 @@ a0d62f10f39fa988d8ff8ce13a1751488e6bad1662c3b97ab8ef3fa09c702666  sources/15_gpg
 4b3c7bdc57fbc94e072e74399cd800c3693ff635fa9064f9e5d05c1ac92e255c  sources/16_keystore
 570a67928ad4eb2ae343ebc3eee39413989087388fe35e0cd675573add6730b0  sources/17_kcontext
 f45e3d068475a40b838336f675948d07b544967d03fbf491abd70e131db38fb0  sources/18_kotp
-9d26839015cf799a0cb2661c49be3f1197fbbc5faf954302eeac93696c85a221  sources/19_k8s_funcs
+7ce7ab97af4aa3f6681981d0d2f687b3eb9b7e7beda5e3b74b651210f7c6fac9  sources/19_k8s_funcs
 60c468b70dbcfa2d198362b90278e4a5e86fb1784c85e846402527df7406774d  sources/21_win-greps
 e007fe16e8ed65c5dca0effcd7227ed52008ca42fe3b6f1b7344a685c17ac02f  sources/22_win-setups
 b5a488efc0558e2b795a1558e2c91a8aeecc3fd441d437aff3a4749d5eb0bdfc  sources/23_win-binaries
diff --git a/bash/sources/19_k8s_funcs b/bash/sources/19_k8s_funcs
index 9ac90f3..5012cca 100644
--- a/bash/sources/19_k8s_funcs
+++ b/bash/sources/19_k8s_funcs
@@ -1,6 +1,5 @@
 #!/bin/bash
 
-# Written with the help of chatgpt o3-mini
 function klabels {
 	local header=
 	local padding=
@@ -65,3 +64,284 @@ function klabels {
 		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"
+}