-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
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:
- Set up a fake client for unit testing with some object, e.g. a Pod.
- Patch a finalizer in the pod.
- 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()
}
}