Skip to content

Commit 7a68eac

Browse files
authored
Merge pull request kubernetes#91873 from kwiesmueller/fix-crd-update-bug
Fix FieldManager Conversion Error for CRD Updates
2 parents 74c7b98 + bd96178 commit 7a68eac

File tree

3 files changed

+52
-14
lines changed

3 files changed

+52
-14
lines changed

staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,16 @@ func (e *Store) Update(ctx context.Context, name string, objInfo rest.UpdatedObj
473473
// deleteObj is only used in case a deletion is carried out
474474
var deleteObj runtime.Object
475475
err = e.Storage.GuaranteedUpdate(ctx, key, out, true, storagePreconditions, func(existing runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) {
476+
existingResourceVersion, err := e.Storage.Versioner().ObjectResourceVersion(existing)
477+
if err != nil {
478+
return nil, nil, err
479+
}
480+
if existingResourceVersion == 0 {
481+
if !e.UpdateStrategy.AllowCreateOnUpdate() && !forceAllowCreate {
482+
return nil, nil, apierrors.NewNotFound(qualifiedResource, name)
483+
}
484+
}
485+
476486
// Given the existing object, get the new object
477487
obj, err := objInfo.UpdatedObject(ctx, existing)
478488
if err != nil {
@@ -483,20 +493,13 @@ func (e *Store) Update(ctx context.Context, name string, objInfo rest.UpdatedObj
483493
// the user does not have a resource version, then we populate it with
484494
// the latest version. Else, we check that the version specified by
485495
// the user matches the version of latest storage object.
486-
resourceVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj)
496+
newResourceVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj)
487497
if err != nil {
488498
return nil, nil, err
489499
}
490-
doUnconditionalUpdate := resourceVersion == 0 && e.UpdateStrategy.AllowUnconditionalUpdate()
500+
doUnconditionalUpdate := newResourceVersion == 0 && e.UpdateStrategy.AllowUnconditionalUpdate()
491501

492-
version, err := e.Storage.Versioner().ObjectResourceVersion(existing)
493-
if err != nil {
494-
return nil, nil, err
495-
}
496-
if version == 0 {
497-
if !e.UpdateStrategy.AllowCreateOnUpdate() && !forceAllowCreate {
498-
return nil, nil, apierrors.NewNotFound(qualifiedResource, name)
499-
}
502+
if existingResourceVersion == 0 {
500503
creating = true
501504
creatingObj = obj
502505
if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil {
@@ -529,15 +532,15 @@ func (e *Store) Update(ctx context.Context, name string, objInfo rest.UpdatedObj
529532
} else {
530533
// Check if the object's resource version matches the latest
531534
// resource version.
532-
if resourceVersion == 0 {
535+
if newResourceVersion == 0 {
533536
// TODO: The Invalid error should have a field for Resource.
534537
// After that field is added, we should fill the Resource and
535538
// leave the Kind field empty. See the discussion in #18526.
536539
qualifiedKind := schema.GroupKind{Group: qualifiedResource.Group, Kind: qualifiedResource.Resource}
537-
fieldErrList := field.ErrorList{field.Invalid(field.NewPath("metadata").Child("resourceVersion"), resourceVersion, "must be specified for an update")}
540+
fieldErrList := field.ErrorList{field.Invalid(field.NewPath("metadata").Child("resourceVersion"), newResourceVersion, "must be specified for an update")}
538541
return nil, nil, apierrors.NewInvalid(qualifiedKind, name, fieldErrList)
539542
}
540-
if resourceVersion != version {
543+
if newResourceVersion != existingResourceVersion {
541544
return nil, nil, apierrors.NewConflict(qualifiedResource, name, fmt.Errorf(OptimisticLockErrorMsg))
542545
}
543546
}

test/integration/apiserver/apply/apply_crd_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,39 @@ spec:
369369
t.Fatalf("failed to add a new list item to the object as a different applier: %v:\n%v", err, string(result))
370370
}
371371
verifyNumPorts(t, result, 2)
372+
373+
// UpdateOnCreate
374+
notExistingYAMLBody := []byte(fmt.Sprintf(`
375+
{
376+
"apiVersion": "%s",
377+
"kind": "%s",
378+
"metadata": {
379+
"name": "%s",
380+
"finalizers": [
381+
"test-finalizer"
382+
]
383+
},
384+
"spec": {
385+
"cronSpec": "* * * * */5",
386+
"replicas": 1,
387+
"ports": [
388+
{
389+
"name": "x",
390+
"containerPort": 80
391+
}
392+
]
393+
},
394+
"protocol": "TCP"
395+
}`, apiVersion, kind, "should-not-exist"))
396+
_, err = rest.Put().
397+
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
398+
Name("should-not-exist").
399+
Param("fieldManager", "apply_test").
400+
Body(notExistingYAMLBody).
401+
DoRaw(context.TODO())
402+
if !apierrors.IsNotFound(err) {
403+
t.Fatalf("create on update should fail with notFound, got %v", err)
404+
}
372405
}
373406

374407
// TestApplyCRDNonStructuralSchema tests that when a CRD has a non-structural schema in its validation field,

test/integration/auth/node_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ func TestNodeAuthorizer(t *testing.T) {
480480
expectNotFound(t, deleteNode2MirrorPod(node1Client))
481481
expectNotFound(t, createNode2MirrorPodEviction(node1Client))
482482
expectForbidden(t, createNode2(node1Client))
483-
expectForbidden(t, updateNode2Status(node1Client))
483+
expectNotFound(t, updateNode2Status(node1Client))
484484
expectForbidden(t, deleteNode2(node1Client))
485485

486486
// related object requests from node2 fail
@@ -500,6 +500,8 @@ func TestNodeAuthorizer(t *testing.T) {
500500
expectAllowed(t, updateNode2Status(node2Client))
501501
// self deletion is not allowed
502502
expectForbidden(t, deleteNode2(node2Client))
503+
// modification of another node's status is not allowed
504+
expectForbidden(t, updateNode2Status(node1Client))
503505
// clean up node2
504506
expectAllowed(t, deleteNode2(superuserClient))
505507

0 commit comments

Comments
 (0)