From 145288971aeaacb430f8c0833bdbc0a9422f904f Mon Sep 17 00:00:00 2001 From: Stefan Koelle <50440224+skoelle@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:36:44 +0200 Subject: [PATCH] Add separate k8s_secret_name_template, since Kubernetes secret names follow a different pattern than AWS secret names --- config.example.yaml | 8 ++++++++ internal/config/config.go | 22 ++++++++++++++++++++++ internal/config/config_test.go | 22 ++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/config.example.yaml b/config.example.yaml index 794af53..3c281d1 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -34,6 +34,14 @@ aws_account_id: "123456789012" # environment. Available placeholders: {namespace}, {env}. secret_name_template: "tf-{namespace}-{env}-secrets" +# Builds the Kubernetes secret name from the chosen namespace. Kept as a +# separate template from secret_name_template above because the AWS side +# and the Kubernetes side commonly follow different naming conventions +# (e.g. the Kubernetes secret is per-namespace only, without an env +# segment, because each environment already has its own cluster). +# Available placeholders: {namespace}. +k8s_secret_name_template: "{namespace}-common-secrets" + # Builds the actual kubectl context name/ARN from region, account ID, env, # and context. Available placeholders: {region}, {account_id}, {env}, # {context}. Adjust the literal parts ("tf-", "-1", cluster naming, ARN diff --git a/internal/config/config.go b/internal/config/config.go index 8fb5e21..ca571f8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -41,6 +41,12 @@ type Config struct { // namespace and env, e.g. "tf-{namespace}-{env}-secrets". SecretNameTemplate string `yaml:"secret_name_template"` + // K8sSecretNameTemplate builds the Kubernetes secret name from a + // namespace, e.g. "{namespace}-common-secrets". Kept separate from + // SecretNameTemplate because the two sides commonly follow different + // naming conventions. + K8sSecretNameTemplate string `yaml:"k8s_secret_name_template"` + // ContextTemplate builds the actual kubectl context name/ARN from // region, account_id, env, and context, e.g. // "arn:aws:eks:{region}:{account_id}:cluster/tf-{env}-{context}-1". @@ -96,6 +102,22 @@ func (c Config) ResolveSecretName(namespace, env string) string { }) } +// ResolveK8sSecretName builds the Kubernetes secret name for a given +// namespace using K8sSecretNameTemplate. Falls back to +// SecretNameTemplate resolved without an env placeholder if +// K8sSecretNameTemplate is not configured, so existing configs keep +// working, though setting it explicitly is recommended since the two +// naming conventions usually differ. +func (c Config) ResolveK8sSecretName(namespace string) string { + template := c.K8sSecretNameTemplate + if template == "" { + template = c.SecretNameTemplate + } + return kctl.ResolveTemplate(template, map[string]string{ + "namespace": namespace, + }) +} + // DefaultPath returns the default config file location: ~/.kctl-tui/config.yaml func DefaultPath() (string, error) { home, err := os.UserHomeDir() diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 51bd652..d4dbd40 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -28,6 +28,7 @@ envs: aws_region: "eu-central-1" aws_account_id: "123456789012" secret_name_template: "tf-{namespace}-{env}-secrets" +k8s_secret_name_template: "{namespace}-common-secrets" context_template: "arn:aws:eks:{region}:{account_id}:cluster/tf-{env}-{context}-1" team_label_key: "example.org/team" `) @@ -46,6 +47,9 @@ team_label_key: "example.org/team" if len(cfg.Contexts) != 2 || len(cfg.Envs) != 2 { t.Fatalf("unexpected contexts/envs: %+v", cfg) } + if cfg.K8sSecretNameTemplate != "{namespace}-common-secrets" { + t.Fatalf("unexpected k8s secret name template: %q", cfg.K8sSecretNameTemplate) + } } func TestEffectiveDefaultContext(t *testing.T) { @@ -87,6 +91,24 @@ func TestResolveSecretName(t *testing.T) { } } +func TestResolveK8sSecretName_ExplicitTemplate(t *testing.T) { + cfg := Config{K8sSecretNameTemplate: "{namespace}-common-secrets"} + got := cfg.ResolveK8sSecretName("example-ns") + want := "example-ns-common-secrets" + if got != want { + t.Fatalf("got %q, want %q", got, want) + } +} + +func TestResolveK8sSecretName_FallsBackToSecretNameTemplate(t *testing.T) { + cfg := Config{SecretNameTemplate: "tf-{namespace}-{env}-secrets"} + got := cfg.ResolveK8sSecretName("example-ns") + want := "tf-example-ns-{env}-secrets" // {env} intentionally left unresolved here + if got != want { + t.Fatalf("got %q, want %q", got, want) + } +} + func TestLoginCommand_DefaultsWhenUnset(t *testing.T) { cfg := Config{} if got := cfg.LoginCommand(); got != DefaultAWSSSOLoginCommand {