From f7241af4f92ea2724cc9e1295c2ddb24f356277d Mon Sep 17 00:00:00 2001 From: Joey Goldman Date: Tue, 20 Oct 2026 09:28:09 +0100 Subject: [PATCH 1/2] fix(planner): hash template before controller changes With unsafeCustomBuildID set, the in-place update stored a hash of the template after controller env vars and volumes were applied, but creation and the drift check hash the user template before them, so the hashes never matched. Hash the user template first, and deep-copy it so controller changes don't mutate. --- internal/planner/planner.go | 6 ++++-- internal/planner/planner_test.go | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/planner/planner.go b/internal/planner/planner.go index 030b4851..c1cdf6f4 100644 --- a/internal/planner/planner.go +++ b/internal/planner/planner.go @@ -622,6 +622,9 @@ func updateDeploymentWithPodTemplateSpec( connection temporaliov1alpha1.ConnectionSpec, ) { wdDepSpec := spec.DeploymentSpec() + // Hash the user template, excluding controller-managed fields. + podTemplateSpecHash := k8s.ComputePodTemplateSpecHash(wdDepSpec.Template) + wdDepSpec.Template = *wdDepSpec.Template.DeepCopy() // Extract the build ID from the deployment's labels (with nil safety) var buildID string @@ -659,8 +662,7 @@ func updateDeploymentWithPodTemplateSpec( podAnnotations[k] = v } podAnnotations[k8s.ConnectionSpecHashAnnotation] = k8s.ComputeConnectionSpecHash(connection) - // Store the new pod template spec hash - podAnnotations[k8s.PodTemplateSpecHashAnnotation] = k8s.ComputePodTemplateSpecHash(wdDepSpec.Template) + podAnnotations[k8s.PodTemplateSpecHashAnnotation] = podTemplateSpecHash // Preserve existing pod labels and add/update required labels podLabels := make(map[string]string) diff --git a/internal/planner/planner_test.go b/internal/planner/planner_test.go index bd02c1e2..5f329496 100644 --- a/internal/planner/planner_test.go +++ b/internal/planner/planner_test.go @@ -3388,7 +3388,9 @@ func TestCheckAndUpdateDeploymentPodTemplateSpec(t *testing.T) { k8sState.Deployments[buildID] = tt.existingDeployment } + originalSpec := tt.newSpec.DeepCopy() result := checkAndUpdateDeploymentPodTemplateSpec(buildID, k8sState, tt.newSpec, tt.connection) + assert.Equal(t, originalSpec, tt.newSpec, "Controller modifications must not mutate the worker spec") if !tt.expectUpdate { assert.Nil(t, result, "Expected no update, but got deployment") @@ -3415,6 +3417,9 @@ func TestCheckAndUpdateDeploymentPodTemplateSpec(t *testing.T) { } } assert.True(t, found, "Should find TEMPORAL_WORKER_BUILD_ID environment variable") + + assert.Nil(t, checkAndUpdateDeploymentPodTemplateSpec(buildID, k8sState, tt.newSpec, tt.connection), + "An unchanged pod template should not trigger another update") }) } } From 9337ab470c69a84cec39a00b034218faada6b2ef Mon Sep 17 00:00:00 2001 From: Joey Goldman Date: Wed, 21 Oct 2026 08:48:49 +0100 Subject: [PATCH 2/2] fix(planner): add comment explaining deepcopy --- internal/planner/planner.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/planner/planner.go b/internal/planner/planner.go index c1cdf6f4..db4d7b9f 100644 --- a/internal/planner/planner.go +++ b/internal/planner/planner.go @@ -624,6 +624,8 @@ func updateDeploymentWithPodTemplateSpec( wdDepSpec := spec.DeploymentSpec() // Hash the user template, excluding controller-managed fields. podTemplateSpecHash := k8s.ComputePodTemplateSpecHash(wdDepSpec.Template) + // DeploymentSpec() shares the template with spec on the legacy spec.template path; + // copy so the modifications below don't mutate spec. wdDepSpec.Template = *wdDepSpec.Template.DeepCopy() // Extract the build ID from the deployment's labels (with nil safety)