Add kubectl bootstrap script #30

Closed
opened 2026-09-08 15:07:16 -07:00 by tepichord · 1 comment
Owner
  • Similar to use-talos.sh, create a script that makes interactions with kubectl easier.
- Similar to use-talos.sh, create a script that makes interactions with kubectl easier.
tepichord added this to the MVP project 2026-09-08 15:07:16 -07:00
Author
Owner

Naina:

Overview

Add a control-plane/use-kubectl.sh script that wires kubectl to the Milner Talos cluster, mirroring use-talos.sh patterns. The script reads the kubeconfig from Terraform output, manages a named context in the user's kubeconfig, and stays idempotent across repeated runs.

Technical Approach

  1. Create control-plane/use-kubectl.sh following the use-talos.sh skeleton:

    #!/usr/bin/env bash
    set -e
    cd "$(dirname "$0")"
    # Color helpers, cd to script dir
    

    Verified (in-repo): use-talos.sh exists at control-plane/use-talos.sh with #!/usr/bin/env bash, set -e, cd "$(dirname "$0")", and color helpers (RED, GREEN, YELLOW, NC, log_info, log_warn, log_error)

  2. Fetch kubeconfig from Terraform output:

    kubeconfig_out="$(terraform output -raw kubeconfig 2>/dev/null || true)"
    if [ -z "$kubeconfig_out" ]; then
        log_error "No kubeconfig found. Run 'terraform apply' first, then re-run this script."
        exit 1
    fi
    tmp_config="$(mktemp)"
    trap 'rm -f "$tmp_config"' EXIT
    printf '%s\n' "$kubeconfig_out" > "$tmp_config"
    
  3. Extract context name from the kubeconfig:

    context="$(sed -n 's/^current-context: *//p' "$tmp_config" | head -n1)"
    # Falls back to cluster name if current-context missing
    

    The kubeconfig YAML has current-context: milner — same as the context/cluster/user names.
    Unverified: The current-context: milner claim depends on Terraform output which cannot be inspected without state. The resource naming (talos_cluster_kubeconfig.this) makes this plausible but not confirmed.

  4. Set target kubeconfig path:

    target_config="${KUBECONFIG:-$HOME/.kube/config}"
    
  5. Merge strategy — KUBECONFIG env var + --flatten:
    kubectl config merge does not exist (kubernetes/kubectl#1868). Use the KUBECONFIG merge trick instead:

    # Fresh config: just write it
    if [ ! -f "$target_config" ]; then
        mkdir -p "$(dirname "$target_config")"
        cp "$tmp_config" "$target_config"
        chmod 600 "$target_config"
        log_info "Milner kubeconfig written (context: $context)."
        kubectl config use-context "$context"
        exit 0
    fi
    
    # Existing config: remove stale entries, then merge via KUBECONFIG
    kubectl config delete-context "$context" 2>/dev/null || true
    kubectl config delete-cluster "$context" 2>/dev/null || true
    kubectl config delete-user "$context" 2>/dev/null || true
    tmp_merged="$(mktemp)"
    trap 'rm -f "$tmp_config" "$tmp_merged"' EXIT
    KUBECONFIG="${target_config}:${tmp_config}" kubectl config view --flatten > "$tmp_merged"
    cp "$tmp_merged" "$target_config"
    chmod 600 "$target_config"
    

    Verified (external): kubectl config merge does not exist — kubernetes/kubectl#1868 is an open feature request

  6. Set current context and warn on switch:

    previous_context="$(kubectl config current-context 2>/dev/null || true)"
    kubectl config use-context "$context"
    if [ "$previous_context" != "$context" ] && [ -n "$previous_context" ]; then
        log_warn "Switched current context from '$previous_context' to '$context'."
    fi
    
  7. Update tests/test_static.py — add to the shellcheck script list:

    scripts = [
        "control-plane/cleanup.sh",
        "control-plane/use-kubectl.sh",   # <-- add this
        "control-plane/use-talos.sh",
        "worker/cleanup.sh",
    ]
    

    Verified (in-repo): tests/test_static.py exists with scripts list at line 17 — note: the plan says "edit line 13" but the scripts list starts at line 17 (line 13 is the @requires("shellcheck") decorator)

  8. Update README.md — replace the manual kubeconfig step in runbook section 3:

    # --- 3. Daily use ---
    ./control-plane/use-talos.sh             # refresh the `milner` context in ~/.talos/config
    talosctl health
    ./control-plane/use-kubectl.sh           # refresh the `milner` context in ~/.kube/config
    kubectl get nodes
    

    Verified (in-repo): README.md runbook section 3 at line 58-62 currently has the manual terraform -chdir=control-plane output -raw kubeconfig > kubeconfig step that needs replacement

Implementation Details

File Action Notes
control-plane/use-kubectl.sh Create ~60 lines, mirrors use-talos.sh structure
tests/test_static.py Edit line 13 Add "control-plane/use-kubectl.sh" to scripts list — note: the scripts list is actually at line 17, not 13
README.md Edit runbook step 3 Replace terraform -chdir=control-plane output -raw kubeconfig > kubeconfig + kubectl --kubeconfig kubeconfig get nodes with ./control-plane/use-kubectl.sh + kubectl get nodes

Out-of-scope notes:

  • No credential refresh needed on each run — Talos kubeconfig uses mTLS certs valid for ~1 year
    Verified (external): Talos kubeconfig uses mTLS certs with 1-year validity — terraform-provider-talos#47 comment confirms `validity_period_hours = 24 * 365 # same 1 year period that talosctl kubeconfig uses
  • No --kubeconfig flag passthrough — the script writes directly to the default config, like use-talos.sh does for talosctl
    Verified (in-repo): use-talos.sh uses talosctl --talosconfig "$target_config" and writes directly via cp "$tmp_config" "$target_config" — control-plane/use-talos.sh
  • The kubeconfig output is sensitive (sensitive = true in Terraform); the script never commits or logs it
    Verified (in-repo): sensitive = true at control-plane/main.tf:419

Acceptance Criteria

  • control-plane/use-kubectl.sh exists, is chmod +x, and has #!/usr/bin/env bash + set -e
  • Script calls terraform output -raw kubeconfig and exits with a clear error when output is empty
  • Script extracts the context name from the kubeconfig YAML (not hardcoded)
  • Fresh ~/.kube/config (or $KUBECONFIG) is created with correct permissions (0600)
  • Existing kubeconfig: stale context/cluster/user for milner are removed before merge — no duplicates accumulate
  • shellcheck control-plane/use-kubectl.sh passes clean
  • test_shellcheck test passes with the new script in the list
  • README runbook step 3 references the new script instead of the manual kubeconfig workflow

Testing Plan

  • Unit-ish: shellcheck control-plane/use-kubectl.sh — catches quoting, globbing, unused-variable issues
  • Integration (requires Terraform state): Run ./control-plane/use-kubectl.sh after terraform apply, then kubectl get nodes returns the control-plane node
  • Idempotency: Run the script twice in a row, verify no duplicate context entries via kubectl config get-contexts
  • Stale cleanup: Manually add a duplicate milner context, run the script, verify only one milner context remains
  • Fresh config: Move ~/.kube/config aside, run the script, verify it creates a new config with the milner context set as current
  • Issue #29 — use-talos.sh (the reference implementation this mirrors)

Research Findings

  • kubectl config merge does not exist. It's an open feature request (kubernetes/kubectl#1868). The idiomatic workaround is KUBECONFIG=a:b kubectl config view --flatten > merged — supported since kubectl v1.0.
    Verified (external): kubectl#1868 confirms the feature does not exist
  • Talos kubeconfig uses mTLS certs, not bearer tokens. Client certs are valid ~1 year; no need to refresh on each run, but re-running is good hygiene for endpoint/secret rotation scenarios (source: Talos docs).
    Verified (external): terraform-provider-talos#47 comment confirms 1-year validity period
  • kubeconfig YAML structure: current-context: milner, context/cluster/user all named milner — extracted via sed -n 's/^current-context: *//p' (source: terraform-provider-talos issues #47).
    Unverified: The exact current-context: milner structure depends on Terraform state and provider behavior; the extraction method is sound but the value cannot be confirmed without running terraform output
Naina: ## Overview Add a `control-plane/use-kubectl.sh` script that wires kubectl to the Milner Talos cluster, mirroring `use-talos.sh` patterns. The script reads the kubeconfig from Terraform output, manages a named context in the user's kubeconfig, and stays idempotent across repeated runs. ## Technical Approach 1. **Create `control-plane/use-kubectl.sh`** following the `use-talos.sh` skeleton: ```bash #!/usr/bin/env bash set -e cd "$(dirname "$0")" # Color helpers, cd to script dir ``` <sup>Verified (in-repo): `use-talos.sh` exists at [control-plane/use-talos.sh](https://git.gloom.garden/tepichord/milner/src/commit/e7521360b7f61983968249854415e1e938e6f5cf/control-plane/use-talos.sh?display=source#L1-L34) with `#!/usr/bin/env bash`, `set -e`, `cd "$(dirname "$0")"`, and color helpers (RED, GREEN, YELLOW, NC, log_info, log_warn, log_error)</sup> 2. **Fetch kubeconfig from Terraform output:** ```bash kubeconfig_out="$(terraform output -raw kubeconfig 2>/dev/null || true)" if [ -z "$kubeconfig_out" ]; then log_error "No kubeconfig found. Run 'terraform apply' first, then re-run this script." exit 1 fi tmp_config="$(mktemp)" trap 'rm -f "$tmp_config"' EXIT printf '%s\n' "$kubeconfig_out" > "$tmp_config" ``` 3. **Extract context name from the kubeconfig:** ```bash context="$(sed -n 's/^current-context: *//p' "$tmp_config" | head -n1)" # Falls back to cluster name if current-context missing ``` The kubeconfig YAML has `current-context: milner` — same as the context/cluster/user names. <sup>Unverified: The `current-context: milner` claim depends on Terraform output which cannot be inspected without state. The resource naming (`talos_cluster_kubeconfig.this`) makes this plausible but not confirmed.</sup> 4. **Set target kubeconfig path:** ```bash target_config="${KUBECONFIG:-$HOME/.kube/config}" ``` 5. **Merge strategy — KUBECONFIG env var + `--flatten`:** `kubectl config merge` does not exist (kubernetes/kubectl#1868). Use the KUBECONFIG merge trick instead: ```bash # Fresh config: just write it if [ ! -f "$target_config" ]; then mkdir -p "$(dirname "$target_config")" cp "$tmp_config" "$target_config" chmod 600 "$target_config" log_info "Milner kubeconfig written (context: $context)." kubectl config use-context "$context" exit 0 fi ``` ```bash # Existing config: remove stale entries, then merge via KUBECONFIG kubectl config delete-context "$context" 2>/dev/null || true kubectl config delete-cluster "$context" 2>/dev/null || true kubectl config delete-user "$context" 2>/dev/null || true tmp_merged="$(mktemp)" trap 'rm -f "$tmp_config" "$tmp_merged"' EXIT KUBECONFIG="${target_config}:${tmp_config}" kubectl config view --flatten > "$tmp_merged" cp "$tmp_merged" "$target_config" chmod 600 "$target_config" ``` <sup>Verified (external): `kubectl config merge` does not exist — [kubernetes/kubectl#1868](https://github.com/kubernetes/kubectl/issues/1868) is an open feature request</sup> 6. **Set current context and warn on switch:** ```bash previous_context="$(kubectl config current-context 2>/dev/null || true)" kubectl config use-context "$context" if [ "$previous_context" != "$context" ] && [ -n "$previous_context" ]; then log_warn "Switched current context from '$previous_context' to '$context'." fi ``` 7. **Update `tests/test_static.py`** — add to the shellcheck script list: ```python scripts = [ "control-plane/cleanup.sh", "control-plane/use-kubectl.sh", # <-- add this "control-plane/use-talos.sh", "worker/cleanup.sh", ] ``` <sup>Verified (in-repo): `tests/test_static.py` exists with `scripts` list at [line 17](https://git.gloom.garden/tepichord/milner/src/commit/e7521360b7f61983968249854415e1e938e6f5cf/tests/test_static.py?display=source#L17-L22) — note: the plan says "edit line 13" but the `scripts` list starts at line 17 (line 13 is the `@requires("shellcheck")` decorator)</sup> 8. **Update `README.md`** — replace the manual kubeconfig step in runbook section 3: ``` # --- 3. Daily use --- ./control-plane/use-talos.sh # refresh the `milner` context in ~/.talos/config talosctl health ./control-plane/use-kubectl.sh # refresh the `milner` context in ~/.kube/config kubectl get nodes ``` <sup>Verified (in-repo): README.md runbook section 3 at [line 58-62](https://git.gloom.garden/tepichord/milner/src/commit/e7521360b7f61983968249854415e1e938e6f5cf/README.md?display=source#L58-L62) currently has the manual `terraform -chdir=control-plane output -raw kubeconfig > kubeconfig` step that needs replacement</sup> ## Implementation Details | File | Action | Notes | |---|---|---| | `control-plane/use-kubectl.sh` | **Create** | ~60 lines, mirrors `use-talos.sh` structure | | `tests/test_static.py` | **Edit line 13** | Add `"control-plane/use-kubectl.sh"` to `scripts` list — <sup>note: the `scripts` list is actually at line 17, not 13</sup> | | `README.md` | **Edit runbook step 3** | Replace `terraform -chdir=control-plane output -raw kubeconfig > kubeconfig` + `kubectl --kubeconfig kubeconfig get nodes` with `./control-plane/use-kubectl.sh` + `kubectl get nodes` | **Out-of-scope notes:** - No credential refresh needed on each run — Talos kubeconfig uses mTLS certs valid for ~1 year <sup>Verified (external): Talos kubeconfig uses mTLS certs with 1-year validity — [terraform-provider-talos#47](https://github.com/siderolabs/terraform-provider-talos/issues/47) comment confirms `validity_period_hours = 24 * 365 # same 1 year period that talosctl kubeconfig uses</sup> - No `--kubeconfig` flag passthrough — the script writes directly to the default config, like `use-talos.sh` does for talosctl <sup>Verified (in-repo): `use-talos.sh` uses `talosctl --talosconfig "$target_config"` and writes directly via `cp "$tmp_config" "$target_config"` — [control-plane/use-talos.sh](https://git.gloom.garden/tepichord/milner/src/commit/e7521360b7f61983968249854415e1e938e6f5cf/control-plane/use-talos.sh?display=source#L36-L89)</sup> - The kubeconfig output is sensitive (`sensitive = true` in Terraform); the script never commits or logs it <sup>Verified (in-repo): `sensitive = true` at [control-plane/main.tf:419](https://git.gloom.garden/tepichord/milner/src/commit/e7521360b7f61983968249854415e1e938e6f5cf/control-plane/main.tf?display=source#L419)</sup> ## Acceptance Criteria - [ ] `control-plane/use-kubectl.sh` exists, is `chmod +x`, and has `#!/usr/bin/env bash` + `set -e` - [ ] Script calls `terraform output -raw kubeconfig` and exits with a clear error when output is empty - [ ] Script extracts the context name from the kubeconfig YAML (not hardcoded) - [ ] Fresh `~/.kube/config` (or `$KUBECONFIG`) is created with correct permissions (0600) - [ ] Existing kubeconfig: stale context/cluster/user for `milner` are removed before merge — no duplicates accumulate - [ ] `shellcheck control-plane/use-kubectl.sh` passes clean - [ ] `test_shellcheck` test passes with the new script in the list - [ ] README runbook step 3 references the new script instead of the manual kubeconfig workflow ## Testing Plan - **Unit-ish:** `shellcheck control-plane/use-kubectl.sh` — catches quoting, globbing, unused-variable issues - **Integration (requires Terraform state):** Run `./control-plane/use-kubectl.sh` after `terraform apply`, then `kubectl get nodes` returns the control-plane node - **Idempotency:** Run the script twice in a row, verify no duplicate context entries via `kubectl config get-contexts` - **Stale cleanup:** Manually add a duplicate `milner` context, run the script, verify only one `milner` context remains - **Fresh config:** Move `~/.kube/config` aside, run the script, verify it creates a new config with the `milner` context set as current ## Related Issues/PRs - [Issue #29](https://git.gloom.garden/tepichord/milner/issues/29) — `use-talos.sh` (the reference implementation this mirrors) ## Research Findings - **`kubectl config merge` does not exist.** It's an open feature request ([kubernetes/kubectl#1868](https://github.com/kubernetes/kubectl/issues/1868)). The idiomatic workaround is `KUBECONFIG=a:b kubectl config view --flatten > merged` — supported since kubectl v1.0. <sup>Verified (external): kubectl#1868 confirms the feature does not exist</sup> - **Talos kubeconfig uses mTLS certs, not bearer tokens.** Client certs are valid ~1 year; no need to refresh on each run, but re-running is good hygiene for endpoint/secret rotation scenarios (source: Talos docs). <sup>Verified (external): [terraform-provider-talos#47](https://github.com/siderolabs/terraform-provider-talos/issues/47) comment confirms 1-year validity period</sup> - **kubeconfig YAML structure:** `current-context: milner`, context/cluster/user all named `milner` — extracted via `sed -n 's/^current-context: *//p'` (source: terraform-provider-talos issues #47). <sup>Unverified: The exact `current-context: milner` structure depends on Terraform state and provider behavior; the extraction method is sound but the value cannot be confirmed without running terraform output</sup>
tepichord 2026-09-08 16:21:03 -07:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
tepichord/milner#30
No description provided.