State file¶
kcm keeps all its local metadata in a single YAML file:
- Unix:
$XDG_CONFIG_HOME/kubeconfig-manager/config.yaml(defaults to~/.config/kubeconfig-manager/config.yaml) - Windows:
%APPDATA%\kubeconfig-manager\config.yaml
File mode is 0o600 on unix. Writes are atomic (temp + rename) and serialized via a flock on config.yaml.lock so concurrent kcm invocations don't corrupt it.
Schema (v1)¶
version: 1
kubeconfig_dir: /home/you/.kube # optional; informational
available_tags: # global tag palette (allow-list for tag assignment)
- prod
- staging
- dev
- eu
- us
- critical
helm_guard: # global helm values-path / context mismatch detector
enabled: true # tri-state: absent field = default ON; explicit false = opt out
patterns: # ordered list; first match wins
- "clusters/{name}/"
- "environments/{name}/"
global_fallback: true # if no pattern matches, tokenize the raw path and compare
env_tokens: [prod, production, staging, stg, stage, dev, test, qa, sandbox]
entries:
sha256:9f04fe2c...: # key = stable-topology SHA-256 of the kubeconfig
path_hint: prod.yaml # last-known filename, informational
display_name: "Prod EU" # future: TUI label (stored but not yet edited in CLI)
tags: [prod, eu, critical]
alerts: # file-level (applies to every context in the file)
enabled: true
require_confirmation: true
confirm_cluster_name: false
blocked_verbs: [delete, drain, cordon, uncordon, taint, replace, patch]
context_alerts: # per-context override map (takes precedence over file-level)
prod-eu:
enabled: true
confirm_cluster_name: true # stricter policy for this one context
blocked_verbs: [delete, drain, patch, apply]
prod-us:
enabled: false # explicitly disabled for this context even though file-level is on
context_tags: # per-context additions on top of file-level tags
prod-eu: [eu-primary]
context_tag_exclusions: # file-level tags to suppress for a specific context
prod-us: [eu]
helm_guard: # optional per-entry helm-guard override
enabled: false # explicit disable overrides the global policy for this entry
updated_at: 2026-04-17T12:00:00Z
sha256:218b0740...:
path_hint: staging.yaml
tags: [staging]
helm_guard: # override just the pattern list for this one kubeconfig
enabled: true
patterns: ["legacy/{name}.yaml"]
updated_at: 2026-04-17T12:05:00Z
Per-context alert resolution¶
For a given kubectl invocation, kcm resolves the active context (from --context <name> on the args, or the kubeconfig's current-context) and then picks the alert policy in this order:
entries[hash].context_alerts[<active-context>]if present — the per-context policy wins, even if it setsenabled: falseto explicitly suppress a file-level policy.- Otherwise,
entries[hash].alerts— the file-level policy applies. - Otherwise, no alert fires.
helm-guard resolution¶
The helm values-path guard has its own two-scope resolution:
entries[hash].helm_guardif present — per-entry policy. A nil/absent field inherits the global block. A struct withenabled: falseexplicitly suppresses the guard for this entry.- Otherwise, the root
helm_guardblock. Itspatternslist is tried in order, first match wins. When none match andglobal_fallback: true, the raw values-file path is tokenized and compared directly against the active context/cluster + environment tokens. - Otherwise, the guard defaults to ON — it's a safety feature, so a fresh install with no config protects the user without opt-in. The
enabledfield is tri-state: absent (→ default ON), explicittrue, or explicitfalse.
global_fallback is OR-merged: either per-entry OR global being true enables the fallback. There's no tri-state, so a per-entry explicit false doesn't suppress the global fallback — if you need that, disable the entry entirely.
Legacy single-pattern field. State files written by v0.10.x used a scalar pattern: "foo" field instead of patterns: [...]. kcm still reads that form on load and migrates it into patterns on first save — no user action required.
Why content-hash keys?¶
Because metadata follows the file, not its location:
kcm rename prod.yaml prod-eu.yaml— file moves, hash stays identical, metadata still binds.kubectl config use-context prod-usflippingcurrent-context— the stable hash ignores that flip, so metadata survives.- The same kubeconfig copied to another machine produces the same hash, so future cloud-sync will correctly unify entries across hosts.
Since v0.9.2, the key is specifically the stable topology hash — SHA-256 of the kubeconfig's logical clusters/users/contexts, ignoring ordering, whitespace, and the volatile current-context pointer. A content-hash fallback read ensures old state files from v0.8.x load and rekey on first mutation.
Rotating credentials (e.g. new cluster certificate data) changes the topology and produces a new stable hash. The old entry is orphaned — by design, so rotated credentials don't silently inherit a dangerous alert policy. kcm prune lists and removes orphans.
Versioning¶
version: 1 is the current schema. kcm refuses to load a state file with a higher version — forward-compatibility comes via an explicit migration, not silent best-effort parsing.
Unknown top-level keys are tolerated (yaml.v3 non-strict mode) so users can hand-edit comments or future-proofed fields without kcm choking on them.
If you ever need to reset metadata, delete the file; kcm recreates it on next write.
Audit log¶
kcm also appends one line per guard prompt (kubectl or helm) to a separate log:
- Unix:
$XDG_DATA_HOME/kubeconfig-manager/audit.log - Windows:
%LOCALAPPDATA%\kubeconfig-manager\audit.log
Format is timestamp key=value ... with single-quoting for values that contain whitespace. Inspect with kcm audit or grep context=prod-eu audit.log.
Concurrency¶
Each kcm invocation that mutates state goes through Mutate(), which:
- Creates the config directory if missing.
- Acquires an exclusive
flockonconfig.yaml.lockwith a small retry loop. - Re-reads the file inside the lock.
- Applies the caller's change function.
- Writes atomically (temp + rename) and releases the lock.
This means two kcm commands racing on different shells won't lose updates. The lock file is intentionally separate from the config file so editors watching the config aren't disturbed.
Future: cloud sync¶
The Store interface in internal/state is designed to accept additional implementations (S3, Git-backed, Vault) without changing any CLI or TUI code. The stable-hash keying and updated_at per entry are the two pieces you need for last-writer-wins sync across machines. See roadmap.md.