Rework kubeexec to take explicit --context per call instead of mutating global kubectl state; drop functions superseded by config templates

This commit is contained in:
Stefan Koelle
2026-08-09 14:01:06 +02:00
parent 929b073382
commit c02e145e4a
+33 -75
View File
@@ -2,6 +2,12 @@
// All functions here have side effects (they run external processes) and
// are therefore not covered by unit tests; the pure logic they depend on
// lives in the kctl package instead.
//
// Every kubectl-related function takes an explicit context argument
// (passed as --context) instead of relying on/mutating the globally
// active kubectl context. This lets the panel act on multiple resolved
// contexts (e.g. beta and prod) without switching global state back and
// forth.
package kubeexec
import (
@@ -21,45 +27,12 @@ func runOutput(name string, args ...string) (string, error) {
return strings.TrimSpace(string(out)), nil
}
// GetContexts returns all configured kubectl context names.
func GetContexts() ([]string, error) {
out, err := runOutput("kubectl", "config", "get-contexts", "-o", "name")
if err != nil {
return nil, err
// kubectlArgs prepends a --context flag when context is non-empty.
func kubectlArgs(context string, args ...string) []string {
if context == "" {
return args
}
if out == "" {
return []string{}, nil
}
return strings.Split(out, "\n"), nil
}
// GetCurrentContext returns the currently active kubectl context, or an
// empty string if none is set.
func GetCurrentContext() string {
out, _ := runOutput("kubectl", "config", "current-context")
return out
}
// GetCurrentNamespace returns the namespace bound to the current context,
// defaulting to "default" if unset.
func GetCurrentNamespace() string {
out, _ := runOutput("kubectl", "config", "view", "--minify", "-o", "jsonpath={..namespace}")
if out == "" {
return "default"
}
return out
}
// UseContext switches the active kubectl context.
func UseContext(ctx string) error {
_, err := runOutput("kubectl", "config", "use-context", ctx)
return err
}
// SetNamespace binds a namespace to the current kubectl context.
func SetNamespace(ns string) error {
_, err := runOutput("kubectl", "config", "set-context", "--current", "--namespace="+ns)
return err
return append([]string{"--context", context}, args...)
}
type nsItem struct {
@@ -74,9 +47,10 @@ type nsList struct {
}
// GetNamespacesWithLabels returns a map of namespace name -> labels for all
// namespaces visible in the current context.
func GetNamespacesWithLabels() (map[string]map[string]string, error) {
out, err := runOutput("kubectl", "get", "ns", "-o", "json")
// namespaces visible in the given context.
func GetNamespacesWithLabels(context string) (map[string]map[string]string, error) {
args := kubectlArgs(context, "get", "ns", "-o", "json")
out, err := runOutput("kubectl", args...)
if err != nil {
return nil, err
}
@@ -91,10 +65,11 @@ func GetNamespacesWithLabels() (map[string]map[string]string, error) {
return result, nil
}
// GetDeployments lists deployment names in the given namespace.
func GetDeployments(namespace string) ([]string, error) {
out, err := runOutput("kubectl", "-n", namespace, "get", "deploy",
// GetDeployments lists deployment names in the given context/namespace.
func GetDeployments(context, namespace string) ([]string, error) {
args := kubectlArgs(context, "-n", namespace, "get", "deploy",
"-o", `jsonpath={range .items[*]}{.metadata.name}{"\n"}{end}`)
out, err := runOutput("kubectl", args...)
if err != nil {
return nil, err
}
@@ -105,26 +80,22 @@ func GetDeployments(namespace string) ([]string, error) {
}
// RolloutRestart triggers a rolling restart of a deployment.
func RolloutRestart(namespace, deployment string) (string, error) {
return runOutput("kubectl", "-n", namespace, "rollout", "restart", "deploy/"+deployment)
func RolloutRestart(context, namespace, deployment string) (string, error) {
args := kubectlArgs(context, "-n", namespace, "rollout", "restart", "deploy/"+deployment)
return runOutput("kubectl", args...)
}
// RolloutStatus waits for and returns the rollout status of a deployment.
func RolloutStatus(namespace, deployment string) (string, error) {
return runOutput("kubectl", "-n", namespace, "rollout", "status", "deploy/"+deployment)
}
// GetSecretValueBase64 returns the raw (still base64-encoded) value of a
// single field in a Kubernetes secret.
func GetSecretValueBase64(namespace, secretName, field string) (string, error) {
path := fmt.Sprintf("jsonpath={.data.%s}", field)
return runOutput("kubectl", "-n", namespace, "get", "secret", secretName, "-o", path)
func RolloutStatus(context, namespace, deployment string) (string, error) {
args := kubectlArgs(context, "-n", namespace, "rollout", "status", "deploy/"+deployment)
return runOutput("kubectl", args...)
}
// GetSecretAllFields returns all fields of a Kubernetes secret, already
// base64-decoded into plain values.
func GetSecretAllFields(namespace, secretName string) (map[string]string, error) {
out, err := runOutput("kubectl", "-n", namespace, "get", "secret", secretName, "-o", "json")
func GetSecretAllFields(context, namespace, secretName string) (map[string]string, error) {
args := kubectlArgs(context, "-n", namespace, "get", "secret", secretName, "-o", "json")
out, err := runOutput("kubectl", args...)
if err != nil {
return nil, err
}
@@ -156,29 +127,16 @@ func DecodeBase64(value string) (string, error) {
// AnnotateForceSync sets the force-sync annotation on an ExternalSecret
// object to trigger an immediate re-sync from the upstream secret store.
func AnnotateForceSync(namespace, externalSecretName string, unixTimestamp int64) (string, error) {
func AnnotateForceSync(context, namespace, externalSecretName string, unixTimestamp int64) (string, error) {
annotation := fmt.Sprintf("force-sync=%d", unixTimestamp)
return runOutput("kubectl", "-n", namespace, "annotate", "externalsecret",
args := kubectlArgs(context, "-n", namespace, "annotate", "externalsecret",
externalSecretName, annotation, "--overwrite")
}
// ListAWSSecrets returns all AWS Secrets Manager secret names/IDs visible
// in the given region (subject to the caller's IAM permissions).
func ListAWSSecrets(region string) ([]string, error) {
out, err := runOutput("aws", "secretsmanager", "list-secrets",
"--region", region, "--query", "SecretList[].Name", "--output", "json")
if err != nil {
return nil, err
}
var names []string
if err := json.Unmarshal([]byte(out), &names); err != nil {
return nil, err
}
return names, nil
return runOutput("kubectl", args...)
}
// GetAWSSecretString fetches the SecretString of an AWS Secrets Manager
// secret via the aws-cli.
// secret via the aws-cli. The secret ID is computed from config templates
// (see internal/config), not looked up interactively.
func GetAWSSecretString(secretID, region string) (string, error) {
return runOutput("aws", "secretsmanager", "get-secret-value",
"--secret-id", secretID, "--region", region,