diff --git a/go/core/internal/controller/translator/agent/manifest_builder.go b/go/core/internal/controller/translator/agent/manifest_builder.go index 802b7b698..f43a1a890 100644 --- a/go/core/internal/controller/translator/agent/manifest_builder.go +++ b/go/core/internal/controller/translator/agent/manifest_builder.go @@ -106,7 +106,7 @@ func (a *adkApiTranslator) BuildManifest( if podRuntime.skillsInitConfigMap != nil { outputs.Manifest = append(outputs.Manifest, podRuntime.skillsInitConfigMap) // Folded into the same rollout-trigger hash as the rest of the pod - // config — the PodSpec only names the ConfigMap, so Kubernetes + // config, since the PodSpec only names the ConfigMap, so Kubernetes // wouldn't otherwise restart the pod when its rendered config changes. skillsInitCfg = []byte(podRuntime.skillsInitConfigMap.Data[skillsinit.ConfigMapKey]) } @@ -342,8 +342,7 @@ func buildPodRuntime( }) } - envVars := append([]corev1.EnvVar{}, manifestCtx.deployment.Env...) - envVars = append(envVars, sharedEnv...) + envVars := mergeEnv(manifestCtx.deployment.Env, sharedEnv) return &podRuntimeInputs{ initContainers: initContainers, @@ -356,6 +355,23 @@ func buildPodRuntime( }, nil } +// mergeEnv appends shared entries whose name is not already set in userEnv, +// so a user-supplied env var always wins over a controller-injected one with +// the same name instead of being silently overwritten by append order. +func mergeEnv(userEnv, sharedEnv []corev1.EnvVar) []corev1.EnvVar { + set := make(map[string]bool, len(userEnv)) + for _, e := range userEnv { + set[e.Name] = true + } + merged := append([]corev1.EnvVar{}, userEnv...) + for _, e := range sharedEnv { + if !set[e.Name] { + merged = append(merged, e) + } + } + return merged +} + func needsSRTSettings(agent v1alpha2.AgentObject, sandboxCfg *v1alpha2.SandboxConfig) bool { spec := agent.GetAgentSpec() if spec.Type == v1alpha2.AgentType_BYO { diff --git a/go/core/internal/controller/translator/agent/manifest_builder_test.go b/go/core/internal/controller/translator/agent/manifest_builder_test.go index 8c01a2af5..a07ab8943 100644 --- a/go/core/internal/controller/translator/agent/manifest_builder_test.go +++ b/go/core/internal/controller/translator/agent/manifest_builder_test.go @@ -5,9 +5,33 @@ import ( "testing" "github.com/kagent-dev/kagent/go/api/v1alpha2" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +func TestMergeEnv_UserValueWins(t *testing.T) { + userEnv := []corev1.EnvVar{{Name: "OTEL_SERVICE_NAME", Value: "my-agent"}} + sharedEnv := []corev1.EnvVar{ + {Name: "OTEL_SERVICE_NAME", Value: "controller-default"}, + {Name: "KAGENT_NAME", Value: "agent-1"}, + } + + got := mergeEnv(userEnv, sharedEnv) + + want := map[string]string{ + "OTEL_SERVICE_NAME": "my-agent", + "KAGENT_NAME": "agent-1", + } + if len(got) != len(want) { + t.Fatalf("mergeEnv() = %v, want %d entries", got, len(want)) + } + for _, e := range got { + if e.Value != want[e.Name] { + t.Errorf("mergeEnv() entry %s = %q, want %q", e.Name, e.Value, want[e.Name]) + } + } +} + func TestBuildSRTSettingsJSON_DefaultDenyConfig(t *testing.T) { got, err := buildSRTSettingsJSON(nil) if err != nil {