Change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! If the matter is security related, please disclose it privately via --Patching an object without finalizers using results in the given finalizers being . is idempotent in case there are other list elements than the removed one. I expected to also be idempotent in case the list is already empty. an arbitrary object: an arbitrary finalizer to the object via strategic merge patch: the finalizer again by using the directive: the same patch again: Kubernetes version (use ):
/sig api-machinery
Strange. It looks like attempting to delete a nonexistent primitive actually adds it. Below is a slightly different take on your example, client-side only and without any finalizers to start with. It tries to delete which is nonexistent and it ends up being : I also tested with some previous versions of kubectl (back to 1.10.2 even) and saw the same thing, so it appears to have always been this way.
I just checked: the same thing happens when directly talking to the API server (so it's not a kubectl-only bug):
that's good to know. But I guess, this is not expected behavior, right?
I don't think it is expected behavior. It is surprising to me at least. I am curious to know what someone who has more experience with Strategic Merge Patch has to say though.
looks like this was reported in as well
/cc /triage accepted
The best workaround for now is probably to json merge patch the whole list with the resourceVersion set. I don't know much about the code nor the bug, but it really looks like a bug rather than a feature, so we should be able to fix it without causing too much surprise to other users. We generally have limited interest fixing smp but that seems worthy. Is somone interested in looking at it?
/assign

Discussion:
_, ok := original[k] if !ok { <del> // If it's not in the original document, just take the patch value. original[k] = patchV </del> <ins> if !isDeleteList { // If it's not in the original document, just take the patch value. original[k] = patchV } </ins> continue } originalType := reflect.TypeOf(original[k]) patchType := reflect.TypeOf(patchV) if originalType != patchType { <del> original[k] = patchV </del> <ins> if !isDeleteList { original[k] = patchV } </ins> continue } // If they're both maps or lists, recurse into the value.

ExpectedError: "does not contain declared merge key", }, }, <ins> { Description: "$deleteFromPrimitiveList of nonexistent item in primitive list should not add the item to the list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 `), TwoWay: []byte(` $deleteFromPrimitiveList/mergingIntList: - 3 `), Modified: []byte(` mergingIntList: - 1 - 2 `), }, }, { Description: "$deleteFromPrimitiveList on empty primitive list should not add the item to the list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: `), TwoWay: []byte(` $deleteFromPrimitiveList/mergingIntList: - 3 `), Modified: []byte(` mergingIntList: `), }, }, { Description: "$deleteFromPrimitiveList on nonexistent primitive list should not add the primitive list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` foo: - bar `), TwoWay: []byte(` $deleteFromPrimitiveList/mergingIntList: - 3 `), Modified: []byte(` foo: - bar `), }, }, </ins> } func TestCustomStrategicMergePatch(t *testing.T) {