mirror of
https://github.com/dredx/prole.git
synced 2026-09-24 18:54:32 +00:00
Update conf/service/prole.cfg to persist the k3s kube context as knoe.dev.prole.org and replace host-specific absolute paths with $HOME-based paths for portability. Include all pending project changes: local_user role package and screenrc provisioning updates, plus tools/k3s-cluster-rename.sh and tests/etc/test_k3s_cluster_rename.sh. Co-authored-by: Junie <junie@jetbrains.com>
229 lines
6.6 KiB
Bash
Executable File
229 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
OLD_CONTEXT_DEFAULT="prole-k3s"
|
|
NEW_CONTEXT_DEFAULT="knoe.dev.prole.org"
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: k3s-cluster-rename.sh [--kubeconfig PATH] [--from OLD] [--to NEW] [--no-backup]
|
|
|
|
Rename/repair a k3s kubeconfig context so kubectx can switch to the new name.
|
|
|
|
Defaults:
|
|
--from prole-k3s
|
|
--to knoe.dev.prole.org
|
|
|
|
Notes:
|
|
- If --from is missing but kubeconfig has exactly one context, that context is used.
|
|
- Default kubeconfig target is the active shell config source:
|
|
- first path in $KUBECONFIG (if set)
|
|
- otherwise ~/.kube/config
|
|
- By default a timestamped backup is written next to kubeconfig.
|
|
EOF
|
|
}
|
|
|
|
log() {
|
|
printf '%s\n' "$*" >&2
|
|
}
|
|
|
|
die() {
|
|
log "ERROR: $*"
|
|
exit 1
|
|
}
|
|
|
|
require_cmd() {
|
|
local cmd="$1"
|
|
command -v "$cmd" >/dev/null 2>&1 || die "required command not found: $cmd"
|
|
}
|
|
|
|
KUBECONFIG_PATH=""
|
|
OLD_CONTEXT="$OLD_CONTEXT_DEFAULT"
|
|
NEW_CONTEXT="$NEW_CONTEXT_DEFAULT"
|
|
CREATE_BACKUP=1
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--kubeconfig)
|
|
[[ $# -ge 2 ]] || die "missing value for --kubeconfig"
|
|
KUBECONFIG_PATH="$2"
|
|
shift 2
|
|
;;
|
|
--from)
|
|
[[ $# -ge 2 ]] || die "missing value for --from"
|
|
OLD_CONTEXT="$2"
|
|
shift 2
|
|
;;
|
|
--to)
|
|
[[ $# -ge 2 ]] || die "missing value for --to"
|
|
NEW_CONTEXT="$2"
|
|
shift 2
|
|
;;
|
|
--no-backup)
|
|
CREATE_BACKUP=0
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
die "unknown argument: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
require_cmd kubectl
|
|
require_cmd python3
|
|
|
|
if [[ -z "$KUBECONFIG_PATH" ]]; then
|
|
if [[ -n "${KUBECONFIG:-}" ]]; then
|
|
KUBECONFIG_PATH="${KUBECONFIG%%:*}"
|
|
[[ -n "$KUBECONFIG_PATH" ]] || KUBECONFIG_PATH="${HOME}/.kube/config"
|
|
else
|
|
KUBECONFIG_PATH="${HOME}/.kube/config"
|
|
fi
|
|
fi
|
|
|
|
[[ -n "$KUBECONFIG_PATH" ]] || die "could not resolve kubeconfig path"
|
|
[[ -f "$KUBECONFIG_PATH" ]] || die "kubeconfig not found: $KUBECONFIG_PATH"
|
|
[[ -r "$KUBECONFIG_PATH" ]] || die "kubeconfig is not readable: $KUBECONFIG_PATH"
|
|
[[ -w "$KUBECONFIG_PATH" ]] || die "kubeconfig is not writable: $KUBECONFIG_PATH"
|
|
|
|
tmp_input=$(mktemp "${TMPDIR:-/tmp}/k3s-cluster-rename-input.XXXXXX.json")
|
|
tmp_output=$(mktemp "${TMPDIR:-/tmp}/k3s-cluster-rename-output.XXXXXX.json")
|
|
cleanup() {
|
|
rm -f "$tmp_input" "$tmp_output"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
KUBECONFIG="$KUBECONFIG_PATH" kubectl config view --raw -o json >"$tmp_input"
|
|
|
|
python3 - "$OLD_CONTEXT" "$NEW_CONTEXT" "$tmp_input" "$tmp_output" <<'PY'
|
|
import json
|
|
import sys
|
|
|
|
source_name, target_name, in_path, out_path = sys.argv[1:5]
|
|
|
|
with open(in_path, "r", encoding="utf-8") as fh:
|
|
data = json.load(fh)
|
|
|
|
contexts = data.get("contexts") or []
|
|
context_names = [c.get("name", "") for c in contexts if isinstance(c, dict)]
|
|
|
|
def get_context_obj(name: str):
|
|
for ctx in contexts:
|
|
if isinstance(ctx, dict) and ctx.get("name") == name:
|
|
return ctx
|
|
return None
|
|
|
|
primary_name = None
|
|
if source_name in context_names:
|
|
primary_name = source_name
|
|
elif target_name in context_names:
|
|
primary_name = target_name
|
|
elif len(context_names) == 1:
|
|
primary_name = context_names[0]
|
|
sys.stderr.write(
|
|
f"INFO: context '{source_name}' not found, using only context '{primary_name}' for repair.\n"
|
|
)
|
|
else:
|
|
sys.stderr.write(
|
|
f"ERROR: context '{source_name}' not found and auto-repair is ambiguous (contexts={context_names}).\n"
|
|
)
|
|
sys.exit(2)
|
|
|
|
target_exists = target_name in context_names
|
|
if not target_exists and primary_name != target_name:
|
|
primary_ctx = get_context_obj(primary_name)
|
|
if primary_ctx is None:
|
|
sys.stderr.write(f"ERROR: could not locate primary context '{primary_name}'.\n")
|
|
sys.exit(2)
|
|
primary_ctx["name"] = target_name
|
|
if data.get("current-context") == primary_name:
|
|
data["current-context"] = target_name
|
|
|
|
canonical_ctx = get_context_obj(target_name)
|
|
if canonical_ctx is None:
|
|
canonical_ctx = get_context_obj(primary_name)
|
|
|
|
if canonical_ctx is None:
|
|
sys.stderr.write("ERROR: failed to identify context to normalize.\n")
|
|
sys.exit(2)
|
|
|
|
ctx_body = canonical_ctx.get("context") or {}
|
|
old_cluster = ctx_body.get("cluster")
|
|
old_user = ctx_body.get("user")
|
|
|
|
clusters = data.get("clusters") or []
|
|
users = data.get("users") or []
|
|
contexts = data.get("contexts") or []
|
|
|
|
cluster_names = {c.get("name") for c in clusters if isinstance(c, dict)}
|
|
user_names = {u.get("name") for u in users if isinstance(u, dict)}
|
|
|
|
if old_cluster and old_cluster != target_name and target_name not in cluster_names:
|
|
for cluster in clusters:
|
|
if isinstance(cluster, dict) and cluster.get("name") == old_cluster:
|
|
cluster["name"] = target_name
|
|
break
|
|
for ctx in contexts:
|
|
body = ctx.get("context") if isinstance(ctx, dict) else None
|
|
if isinstance(body, dict) and body.get("cluster") == old_cluster:
|
|
body["cluster"] = target_name
|
|
elif old_cluster:
|
|
ctx_body["cluster"] = target_name if target_name in cluster_names else old_cluster
|
|
|
|
if old_user and old_user != target_name and target_name not in user_names:
|
|
for user in users:
|
|
if isinstance(user, dict) and user.get("name") == old_user:
|
|
user["name"] = target_name
|
|
break
|
|
for ctx in contexts:
|
|
body = ctx.get("context") if isinstance(ctx, dict) else None
|
|
if isinstance(body, dict) and body.get("user") == old_user:
|
|
body["user"] = target_name
|
|
elif old_user:
|
|
ctx_body["user"] = target_name if target_name in user_names else old_user
|
|
|
|
data["current-context"] = target_name
|
|
|
|
with open(out_path, "w", encoding="utf-8") as fh:
|
|
json.dump(data, fh, indent=2)
|
|
fh.write("\n")
|
|
PY
|
|
|
|
found_context=0
|
|
while IFS= read -r ctx_name; do
|
|
if [[ "$ctx_name" == "$NEW_CONTEXT" ]]; then
|
|
found_context=1
|
|
break
|
|
fi
|
|
done < <(KUBECONFIG="$tmp_output" kubectl config get-contexts -o name)
|
|
|
|
[[ "$found_context" -eq 1 ]] || die "rename failed: context '$NEW_CONTEXT' not present in transformed kubeconfig"
|
|
|
|
if [[ "$CREATE_BACKUP" -eq 1 ]]; then
|
|
backup_path="${KUBECONFIG_PATH}.bak.$(date +%Y%m%d%H%M%S)"
|
|
cp "$KUBECONFIG_PATH" "$backup_path"
|
|
log "Backup written: $backup_path"
|
|
fi
|
|
|
|
cp "$tmp_output" "$KUBECONFIG_PATH"
|
|
chmod 600 "$KUBECONFIG_PATH" 2>/dev/null || true
|
|
|
|
log "Kubeconfig updated: $KUBECONFIG_PATH"
|
|
log "Context/cluster/user target: $NEW_CONTEXT"
|
|
|
|
if command -v kubectx >/dev/null 2>&1; then
|
|
if KUBECONFIG="$KUBECONFIG_PATH" kubectx "$NEW_CONTEXT" >/dev/null 2>&1; then
|
|
log "kubectx switch verification succeeded for '$NEW_CONTEXT' (using KUBECONFIG=$KUBECONFIG_PATH)."
|
|
else
|
|
die "kubectx could not switch to '$NEW_CONTEXT' for kubeconfig '$KUBECONFIG_PATH'"
|
|
fi
|
|
else
|
|
log "kubectx not found; skipped kubectx switch verification."
|
|
fi
|
|
|
|
log "Done." |