Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions pkg/controller/deployment/deployment_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ type DeploymentController struct {
runtimeClient client.Client
}

// patchDeploymentStatus patches the Deployment status subresource using a merge diff.
// Only changed fields are sent, which avoids overwriting unknown fields from newer API versions.
func (dc *DeploymentController) patchDeploymentStatus(ctx context.Context, oldD, newD *apps.Deployment) error {
patch := client.MergeFrom(oldD)
return dc.runtimeClient.Status().Patch(ctx, newD, patch)
}

// getReplicaSetsForDeployment uses ControllerRefManager to reconcile
// ControllerRef by adopting and orphaning.
// It returns the list of ReplicaSets that this Deployment should manage.
Expand Down Expand Up @@ -100,10 +107,13 @@ func (dc *DeploymentController) syncDeployment(ctx context.Context, deployment *
if reflect.DeepEqual(d.Spec.Selector, &everything) {
dc.eventRecorder.Eventf(d, v1.EventTypeWarning, "SelectingAll", "This deployment is selecting all pods. A non-empty selector is required.")
if d.Status.ObservedGeneration < d.Generation {
d.Status.ObservedGeneration = d.Generation
err := dc.runtimeClient.Status().Update(ctx, d)
if err != nil {
klog.Errorf("Failed to update deployment status: %v", err)
// Use Patch instead of Update to avoid erasing unknown status fields
// that a newer API server may have set (version-skew safety).
dCopy := d.DeepCopy()
dCopy.Status.ObservedGeneration = d.Generation
if err := dc.runtimeClient.Status().Patch(ctx, dCopy, client.MergeFrom(d)); err != nil {

klog.Errorf("Failed to patch deployment status: %v", err)
}
}
return nil
Expand Down
38 changes: 38 additions & 0 deletions pkg/controller/deployment/deployment_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (

apps "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

intstrutil "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/tools/record"
"k8s.io/utils/pointer"
Expand Down Expand Up @@ -236,3 +238,39 @@ func TestSyncDeployment(t *testing.T) {
})
}
}

func TestSyncDeploymentSelectingAll(t *testing.T) {
deployment := generateDeployment("busybox")
deployment.Spec.Selector = &metav1.LabelSelector{}
deployment.Generation = 7
deployment.Status.ObservedGeneration = 3
deployment.Annotations = map[string]string{
"test-unknown-field": "kept",
}

fakeCtrlClient := ctrlfake.NewClientBuilder().
WithStatusSubresource(&apps.Deployment{}).
WithObjects(&deployment).
Build()

dc := &DeploymentController{
eventRecorder: record.NewFakeRecorder(10),
runtimeClient: fakeCtrlClient,
}

if err := dc.syncDeployment(context.TODO(), &deployment); err != nil {
t.Fatalf("syncDeployment returned unexpected error: %v", err)
}

var result apps.Deployment
if err := fakeCtrlClient.Get(context.TODO(), ctrlclient.ObjectKeyFromObject(&deployment), &result); err != nil {
t.Fatalf("get deployment failed: %v", err)
}

if result.Status.ObservedGeneration != result.Generation {
t.Fatalf("expected observedGeneration=%d, got %d", result.Generation, result.Status.ObservedGeneration)
}
if got := result.Annotations["test-unknown-field"]; got != "kept" {
t.Fatalf("expected annotation test-unknown-field=kept, got %q", got)
}
}
Loading
Loading