homelab: v0.3.1 — fix k8s db PG target (resolve CNPG primary pod, not the Service)
`k8s db <app>` (Postgres path) execed `pg-cluster-rw`, which is the CNPG read-write SERVICE, not a pod — so kubectl exec failed with `pods "pg-cluster-rw" not found`. The unit test only checked the plan; the verb was never fired at live state (the gap flagged in v0.2), so it shipped broken. Fix: the PG plan now carries a label selector (cnpg.io/instanceRole=primary) instead of a pod name, and k8s db resolves the actual primary POD via `kubectl get pod -l <selector>` before exec. MySQL path (real pod mysql-standalone-0) unchanged. Live-verified both paths (psql + mysql). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
90c944a265
commit
787ce4edfa
4 changed files with 25 additions and 6 deletions
|
|
@ -1 +1 @@
|
||||||
v0.3.0
|
v0.3.1
|
||||||
|
|
|
||||||
|
|
@ -152,11 +152,19 @@ func k8sDB(args []string) error {
|
||||||
return fmt.Errorf(`usage: homelab k8s db <app> [--mysql] [--db NAME] -- "<SQL>"`)
|
return fmt.Errorf(`usage: homelab k8s db <app> [--mysql] [--db NAME] -- "<SQL>"`)
|
||||||
}
|
}
|
||||||
p := planDBExec(app, dbName, sql, mysql)
|
p := planDBExec(app, dbName, sql, mysql)
|
||||||
|
pod := p.pod
|
||||||
|
if pod == "" && p.selector != "" {
|
||||||
|
resolved, err := kubectlCapture(p.ns, "get", "pod", "-l", p.selector, "-o", "jsonpath={.items[0].metadata.name}")
|
||||||
|
if err != nil || resolved == "" {
|
||||||
|
return fmt.Errorf("could not resolve db pod in %s (selector %q): %v", p.ns, p.selector, err)
|
||||||
|
}
|
||||||
|
pod = resolved
|
||||||
|
}
|
||||||
exec := []string{"exec"}
|
exec := []string{"exec"}
|
||||||
if sql == "" {
|
if sql == "" {
|
||||||
exec = append(exec, "-it") // interactive client when no SQL given
|
exec = append(exec, "-it") // interactive client when no SQL given
|
||||||
}
|
}
|
||||||
exec = append(exec, p.pod)
|
exec = append(exec, pod)
|
||||||
if p.container != "" {
|
if p.container != "" {
|
||||||
exec = append(exec, "-c", p.container)
|
exec = append(exec, "-c", p.container)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
cli/k8s.go
15
cli/k8s.go
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -19,6 +20,12 @@ func kubectlStream(ns string, args ...string) error {
|
||||||
return runStreamingIn("", "kubectl", kubectlBase(ns, args...)...)
|
return runStreamingIn("", "kubectl", kubectlBase(ns, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// kubectlCapture runs kubectl and returns trimmed stdout (for resolving pods).
|
||||||
|
func kubectlCapture(ns string, args ...string) (string, error) {
|
||||||
|
out, err := exec.Command("kubectl", kubectlBase(ns, args...)...).Output()
|
||||||
|
return strings.TrimSpace(string(out)), err
|
||||||
|
}
|
||||||
|
|
||||||
// k8sTarget is the parsed `<app>` + selectors shared by the k8s verbs.
|
// k8sTarget is the parsed `<app>` + selectors shared by the k8s verbs.
|
||||||
type k8sTarget struct {
|
type k8sTarget struct {
|
||||||
app string
|
app string
|
||||||
|
|
@ -96,13 +103,15 @@ func (t k8sTarget) objectRef() string {
|
||||||
|
|
||||||
type dbPlan struct {
|
type dbPlan struct {
|
||||||
ns string
|
ns string
|
||||||
pod string
|
pod string // explicit pod (e.g. mysql-standalone-0)
|
||||||
|
selector string // resolve the pod by this label when pod == "" (CNPG primary)
|
||||||
container string // "" = default container
|
container string // "" = default container
|
||||||
argv []string // command + args to run inside the pod
|
argv []string // command + args to run inside the pod
|
||||||
}
|
}
|
||||||
|
|
||||||
// planDBExec builds the in-pod command to run sql against app's database.
|
// planDBExec builds the in-pod command to run sql against app's database.
|
||||||
// PG (default): CNPG primary pg-cluster-rw, psql -U postgres -d <db>.
|
// PG (default): CNPG primary POD (resolved by label — pg-cluster-rw is a
|
||||||
|
// Service, not an exec target), psql -U postgres -d <db>.
|
||||||
// MySQL: mysql-standalone-0, password from env (never on the command line).
|
// MySQL: mysql-standalone-0, password from env (never on the command line).
|
||||||
// dbName defaults to app. sql empty => interactive client.
|
// dbName defaults to app. sql empty => interactive client.
|
||||||
func planDBExec(app, dbName, sql string, mysql bool) dbPlan {
|
func planDBExec(app, dbName, sql string, mysql bool) dbPlan {
|
||||||
|
|
@ -120,7 +129,7 @@ func planDBExec(app, dbName, sql string, mysql bool) dbPlan {
|
||||||
if sql != "" {
|
if sql != "" {
|
||||||
argv = append(argv, "-tAc", sql)
|
argv = append(argv, "-tAc", sql)
|
||||||
}
|
}
|
||||||
return dbPlan{ns: "dbaas", pod: "pg-cluster-rw", container: "postgres", argv: argv}
|
return dbPlan{ns: "dbaas", selector: "cnpg.io/instanceRole=primary", container: "postgres", argv: argv}
|
||||||
}
|
}
|
||||||
|
|
||||||
// shellQuote single-quotes s for safe embedding in a bash -c string.
|
// shellQuote single-quotes s for safe embedding in a bash -c string.
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@ func TestK8sTargetObjectRef(t *testing.T) {
|
||||||
|
|
||||||
func TestPlanDBExecPostgresDefault(t *testing.T) {
|
func TestPlanDBExecPostgresDefault(t *testing.T) {
|
||||||
p := planDBExec("fire-planner", "", "SELECT 1", false)
|
p := planDBExec("fire-planner", "", "SELECT 1", false)
|
||||||
if p.ns != "dbaas" || p.pod != "pg-cluster-rw" || p.container != "postgres" {
|
// pg-cluster-rw is a Service, so the PG plan resolves the primary POD by
|
||||||
|
// label rather than naming an (un-exec-able) Service.
|
||||||
|
if p.ns != "dbaas" || p.pod != "" || p.selector != "cnpg.io/instanceRole=primary" || p.container != "postgres" {
|
||||||
t.Fatalf("unexpected pg target: %+v", p)
|
t.Fatalf("unexpected pg target: %+v", p)
|
||||||
}
|
}
|
||||||
// db name defaults to the app; SQL passed via -tAc
|
// db name defaults to the app; SQL passed via -tAc
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue