Change:
Sending a patch built from a typed client to SSA will force a write to etcd (and bump resourceVersion) since creationTimestamp is serialized to null. This is a follow-up of which doesn't solve the issue properly. Sending a no-op SSA request with creationTimestamp to null shouldn't do anything Kubectl apply an object with creationTimestamp null twice and see that it updates the object. Probably something like this would cause it:
/sig api-machinery /wg api-expression /triage accepted /assign /priority important-soon

Discussion:
} transformers := []rest.TransformFunc{p.applyPatch, p.applyAdmission, dedupOwnerReferencesTransformer} <del> if scope.FieldManager != nil { transformers = append(transformers, fieldmanager.IgnoreManagedFieldsTimestampsTransformer) } </del> wasCreated := false p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, transformers...)

}) } <del> // Ignore changes that only affect managed fields // timestamps. FieldManager can't know about changes // like normalized fields, defaulted fields and other // mutations. // Only makes sense when SSA field manager is being used if scope.FieldManager != nil { transformers = append(transformers, fieldmanager.IgnoreManagedFieldsTimestampsTransformer) } </del> createAuthorizerAttributes := authorizer.AttributesRecord{ User:            userInfo, ResourceRequest: true,

"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" <ins> "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" </ins> genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest"

if err := rest.BeforeUpdate(e.UpdateStrategy, ctx, obj, existing); err != nil { return nil, nil, err } <ins> // Ignore changes that only affect managed fields timestamps. // FieldManager can't know about changes like normalized fields, defaulted // fields and other mutations. obj, err = fieldmanager.IgnoreManagedFieldsTimestampsTransformer(ctx, obj, existing) if err != nil { return nil, nil, err } </ins> // at this point we have a fully formed object.  It is time to call the validators that the apiserver // handling chain wants to enforce. if updateValidation != nil {

"time" "github.com/google/go-cmp/cmp" <ins> "github.com/stretchr/testify/require" </ins> "sigs.k8s.io/yaml" appsv1 "k8s.io/api/apps/v1"

return acc.GetResourceVersion(), nil } <ins> func TestNoopChangeCreationTime(t *testing.T) { client, closeFn := setup(t) defer closeFn() ssBytes := []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "myconfig", "creationTimestamp": null, "resourceVersion": null }, "data": { "key": "value" } }`) obj, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). Namespace("default"). Param("fieldManager", "apply_test"). Resource("configmaps"). Name("myconfig"). Body(ssBytes). Do(context.TODO()). Get() if err != nil { t.Fatalf("Failed to create object: %v", err) } require.NoError(t, err) // Sleep for one second to make sure that the times of each update operation is different. time.Sleep(1200 * time.Millisecond) newObj, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). Namespace("default"). Param("fieldManager", "apply_test"). Resource("configmaps"). Name("myconfig"). Body(ssBytes). Do(context.TODO()). Get() if err != nil { t.Fatalf("Failed to create object: %v", err) } require.NoError(t, err) require.Equal(t, obj, newObj) } </ins> // TestNoSemanticUpdateAppleSameResourceVersion makes sure that APPLY requests which makes no semantic changes // will not change the resource version (no write to etcd is done) //