diff --git a/internal/kubeexec/kubeexec.go b/internal/kubeexec/kubeexec.go index 783e800..1e7d4f7 100644 --- a/internal/kubeexec/kubeexec.go +++ b/internal/kubeexec/kubeexec.go @@ -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,