#!/usr/bin/env bash # # infra/devcontainer/kubeconfig.sh — build ONE merged kubeconfig that can address BOTH clusters # under unambiguous names, and select which of them is current WITHOUT mutating shared state. # # kubeconfig.sh # regenerate the merged file, mint a per-invocation selector, # # print the KUBECONFIG *chain* to use (selector:merged) # # WHY THIS EXISTS — the name collision that makes the obvious fix dangerous # infra/ansible/.cache/dev.kubeconfig and .cache/prod.kubeconfig each name their context, their # cluster AND their user `default`. k3s writes them that way. So the "obvious" fix — # KUBECONFIG=dev.kubeconfig:prod.kubeconfig — is actively unsafe: kubectl's merge keeps the FIRST # occurrence of a duplicated name, so `default` silently resolves to DEV and `--context default` # can NEVER select prod. An operator (or an agent) would believe they were on prod while issuing # commands against the live dev cluster. # # Therefore we RENAME on merge rather than relying on file order: # dev.kubeconfig -> cluster/context `k3s-dev`, user `k3s-dev-admin` # prod.kubeconfig -> cluster/context `k3s-prod`, user `k3s-prod-admin` # The rename is done with `yq` (structural, YAML-aware). It is NOT done with sed: the cluster and # the user entry are both `- name: default` at identical indent, so a textual rename corrupts the # file. The two renamed copies are then merged by kubectl itself, which is safe once the names are # distinct. # # WHY THE MERGED FILE HAS NO `current-context` — the concurrency bug this shape fixes # This script used to stamp `current-context: k3s-` into the ONE shared merged file. That # file lives in the repo's .cache, which every dev.sh container bind-mounts, so it is shared # mutable state across concurrent invocations. Two `dev.sh` runs with different CLUSTER values # therefore raced: `CLUSTER=prod dev.sh run