Skip to content

Fake client on version 0.23.0 fails to SSA Apply status after a finalizer is set #3423

@josvazg

Description

@josvazg

We might have found an issue with version 0.23.0's fake client implementation around the new SSA Apply support.

We noticed that if an object is patched with a finaliser and then the status is SSA applied, the apply operation fails with:

metadata.managedFields must be nil

The same apply works fine if there was no finaliser patch before.

It appears the apply finds the managed fields due to the previous unrelated finaliser patch.

Steps to reproduce:

  1. Set up a fake client for unit testing with some object, e.g. a Pod.
  2. Patch a finalizer in the pod.
  3. Attempt a SSA Apply on the Pod, maybe set the ready status.

Expected result: SSA Apply should work (as it does if there is no prior finalizer patch)

Actual result: SSA Apply fails with metadata.managedFields must be nil

We attach a minimal reproducing unit test:
(For brevity this is the structured or strongly typed version, but our unstructured variant also reproduced the same behaviour)

package test

import (
	"context"
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/require"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	corev1apply "k8s.io/client-go/applyconfigurations/core/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

var patchFinalizerTyped = true // with patching the bug appears

func TestControllerRuntimeSSABugTyped(t *testing.T) {
	ctx := context.Background()

	// Step 1: Create a default Pod object
	pod := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-pod-typed",
			Namespace: "default",
		},
		Spec: corev1.PodSpec{
			Containers: []corev1.Container{
				{
					Name:  "test-container",
					Image: "nginx:latest",
				},
			},
		},
		Status: corev1.PodStatus{
			Phase: corev1.PodPending,
		},
	}

	// Create fake client with the default Pod object
	scheme := runtime.NewScheme()
	require.NoError(t, corev1.AddToScheme(scheme))
	fakeClient := fake.NewClientBuilder().
		WithScheme(scheme).
		WithObjects(pod).
		Build()

	podKey := client.ObjectKeyFromObject(pod)

	// Fetch the pod first to get the current state
	currentPod := &corev1.Pod{}
	require.NoError(t, fakeClient.Get(ctx, podKey, currentPod))

	if patchFinalizerTyped {
		// Step 2: Patch a test finalizer using JSON patch
		// Add finalizer using controllerutil
		finalizerName := "test-finalizer"
		controllerutil.AddFinalizer(currentPod, finalizerName)

		// Create JSON patch for finalizers
		data, err := json.Marshal([]map[string]interface{}{{
			"op":    "replace",
			"path":  "/metadata/finalizers",
			"value": currentPod.GetFinalizers(),
		}})
		require.NoError(t, err, "Failed to marshal JSON patch")

		// Apply the patch
		require.NoError(t, fakeClient.Patch(ctx, currentPod, client.RawPatch(types.JSONPatchType, data)), "Failed to patch finalizer")
	}

	// Step 3: Use SSA apply on the status to set a ready condition
	// Create typed ApplyConfiguration for the status
	now := metav1.Now()
	podReadyType := corev1.PodReady
	conditionTrue := corev1.ConditionTrue
	readyCondition := corev1apply.PodCondition().
		WithType(podReadyType).
		WithStatus(conditionTrue).
		WithLastTransitionTime(now).
		WithReason("TestReady").
		WithMessage("Pod is ready for testing")

	statusApplyConfig := corev1apply.PodStatus().
		WithConditions(readyCondition)

	podApplyConfig := corev1apply.Pod(pod.Name, pod.Namespace).
		WithStatus(statusApplyConfig)

	if currentPod.ResourceVersion != "" {
		podApplyConfig.WithResourceVersion(currentPod.ResourceVersion)
	}

	// Apply the status using SSA
	err := fakeClient.SubResource("status").Apply(ctx, podApplyConfig, client.FieldOwner("test-controller"), client.ForceOwnership)

	// This is where the bug might manifest - the apply might fail or produce incorrect results
	if err != nil {
		t.Logf("SSA Apply on status failed (this is the bug): %v", err)
		t.Fail()
	}
}

Metadata

Metadata

Assignees

Labels

kind/bugCategorizes issue or PR as related to a bug.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions