diff --git a/cmd/felis/tui_smtp.go b/cmd/felis/tui_smtp.go index 1ad81e5..62b9fca 100644 --- a/cmd/felis/tui_smtp.go +++ b/cmd/felis/tui_smtp.go @@ -351,14 +351,18 @@ func applySMTPConfig(ctx context.Context, in smtpInputs) error { return kubectl(ctx, "-n", "felis", "rollout", "status", "deployment/felis-api", "--timeout=180s") } -// smtpSecretManifest renders the felis-smtp Secret (in the control namespace, -// via the caller's apply) the felis-api Deployment injects the relay password -// from. Rendered in-process and piped to `kubectl apply` — the password is -// never a command-line arg, so it never appears in the host process table. -func smtpSecretManifest(password string) ([]byte, error) { +// smtpSecretManifest renders the felis-smtp Secret for the given namespace, the +// one the receiving Deployment/CronJob resolves its secretKeyRef against (felis +// for felis-api, the workload namespace for the reaper's mirror). The namespace +// must be IN the manifest: kubectl rejects a manifest whose namespace conflicts +// with -n, so leaving the control namespace hardcoded made every workload-ns +// replica fail before it started. Rendered in-process and piped to +// `kubectl apply` — the password is never a command-line arg, so it never +// appears in the host process table. +func smtpSecretManifest(password, namespace string) ([]byte, error) { secret := &corev1.Secret{ TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Secret"}, - ObjectMeta: metav1.ObjectMeta{Name: platform.SMTPSecretName, Namespace: "felis"}, + ObjectMeta: metav1.ObjectMeta{Name: platform.SMTPSecretName, Namespace: namespace}, Type: corev1.SecretTypeOpaque, StringData: map[string]string{ platform.SMTPSecretPasswordKey: password, @@ -372,7 +376,7 @@ func smtpSecretManifest(password string) ([]byte, error) { } func applySMTPSecret(ctx context.Context, password string) error { - manifest, err := smtpSecretManifest(password) + manifest, err := smtpSecretManifest(password, "felis") if err != nil { return err } @@ -396,7 +400,7 @@ func replicateSMTPToWorkloadNamespace(ctx context.Context, password string) erro if ns == "" || ns == "felis" { return nil } - smtpManifest, err := smtpSecretManifest(password) + smtpManifest, err := smtpSecretManifest(password, ns) if err != nil { return err } diff --git a/cmd/felis/tui_smtp_test.go b/cmd/felis/tui_smtp_test.go new file mode 100644 index 0000000..97c78de --- /dev/null +++ b/cmd/felis/tui_smtp_test.go @@ -0,0 +1,37 @@ +package main + +import ( + "strings" + "testing" + + "sigs.k8s.io/yaml" +) + +// TestSMTPSecretManifestCarriesTargetNamespace pins the fix for the +// workload-namespace replica: kubectl refuses a manifest whose namespace +// conflicts with -n ("the namespace from the provided object ... does not +// match"), so the mirror must render felis-smtp with the TARGET namespace — +// otherwise the "configure email" refresh fails on the first apply and the +// felis-config mirror never runs at all. +func TestSMTPSecretManifestCarriesTargetNamespace(t *testing.T) { + for _, ns := range []string{"felis", "minecraft"} { + b, err := smtpSecretManifest("pw", ns) + if err != nil { + t.Fatalf("render for %s: %v", ns, err) + } + var got struct { + Metadata struct { + Namespace string `json:"namespace"` + } `json:"metadata"` + } + if err := yaml.Unmarshal(b, &got); err != nil { + t.Fatalf("unmarshal for %s: %v", ns, err) + } + if got.Metadata.Namespace != ns { + t.Fatalf("manifest namespace = %q, want %q", got.Metadata.Namespace, ns) + } + if !strings.Contains(string(b), "name: felis-smtp") { + t.Fatalf("manifest must still name felis-smtp: %s", b) + } + } +}