Merge pull request #54477 from nikhita/invalid-patch-code
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. apiserver: return 4xx for invalid patch Fixes #54423 Currently, an invalid patch returns 500. The apiserver should return a 400 (`BadRequest`) or 422 (`Unprocessable Entity`). **Release note**: ```release-note NONE ``` Kubernetes-commit: 6659f2a7d894a404d59265a708a2d2bd03806e98
This commit is contained in:
		
						commit
						6841e913a1
					
				| 
						 | 
				
			
			@ -186,7 +186,7 @@ func patchResource(
 | 
			
		|||
			case types.JSONPatchType, types.MergePatchType:
 | 
			
		||||
				originalJS, patchedJS, err := patchObjectJSON(patchType, codec, currentObject, patchJS, objToUpdate, versionedObj)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
					return nil, interpretPatchError(err)
 | 
			
		||||
				}
 | 
			
		||||
				originalObjJS, originalPatchedObjJS = originalJS, patchedJS
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -198,13 +198,13 @@ func patchResource(
 | 
			
		|||
						// Compute once
 | 
			
		||||
						originalPatchBytes, err = strategicpatch.CreateTwoWayMergePatch(originalObjJS, originalPatchedObjJS, versionedObj)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							return nil, err
 | 
			
		||||
							return nil, interpretPatchError(err)
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					// Return a fresh map every time
 | 
			
		||||
					originalPatchMap := make(map[string]interface{})
 | 
			
		||||
					if err := json.Unmarshal(originalPatchBytes, &originalPatchMap); err != nil {
 | 
			
		||||
						return nil, err
 | 
			
		||||
						return nil, errors.NewBadRequest(err.Error())
 | 
			
		||||
					}
 | 
			
		||||
					return originalPatchMap, nil
 | 
			
		||||
				}
 | 
			
		||||
| 
						 | 
				
			
			@ -242,7 +242,7 @@ func patchResource(
 | 
			
		|||
				getOriginalPatchMap = func() (map[string]interface{}, error) {
 | 
			
		||||
					patchMap := make(map[string]interface{})
 | 
			
		||||
					if err := json.Unmarshal(patchJS, &patchMap); err != nil {
 | 
			
		||||
						return nil, err
 | 
			
		||||
						return nil, errors.NewBadRequest(err.Error())
 | 
			
		||||
					}
 | 
			
		||||
					return patchMap, nil
 | 
			
		||||
				}
 | 
			
		||||
| 
						 | 
				
			
			@ -277,7 +277,7 @@ func patchResource(
 | 
			
		|||
				var err error
 | 
			
		||||
				currentPatchMap, err = strategicpatch.CreateTwoWayMergeMapPatch(originalObjMap, currentObjMap, versionedObj)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
					return nil, interpretPatchError(err)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				// Compute current patch.
 | 
			
		||||
| 
						 | 
				
			
			@ -287,11 +287,11 @@ func patchResource(
 | 
			
		|||
				}
 | 
			
		||||
				currentPatch, err := strategicpatch.CreateTwoWayMergePatch(originalObjJS, currentObjJS, versionedObj)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
					return nil, interpretPatchError(err)
 | 
			
		||||
				}
 | 
			
		||||
				currentPatchMap = make(map[string]interface{})
 | 
			
		||||
				if err := json.Unmarshal(currentPatch, ¤tPatchMap); err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
					return nil, errors.NewBadRequest(err.Error())
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -422,7 +422,7 @@ func strategicPatchObject(
 | 
			
		|||
 | 
			
		||||
	patchMap := make(map[string]interface{})
 | 
			
		||||
	if err := json.Unmarshal(patchJS, &patchMap); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return errors.NewBadRequest(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := applyPatchToObject(codec, defaulter, originalObjMap, patchMap, objToUpdate, versionedObj); err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -456,3 +456,15 @@ func applyPatchToObject(
 | 
			
		|||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// interpretPatchError interprets the error type and returns an error with appropriate HTTP code.
 | 
			
		||||
func interpretPatchError(err error) error {
 | 
			
		||||
	switch err {
 | 
			
		||||
	case mergepatch.ErrBadJSONDoc, mergepatch.ErrBadPatchFormatForPrimitiveList, mergepatch.ErrBadPatchFormatForRetainKeys, mergepatch.ErrBadPatchFormatForSetElementOrderList:
 | 
			
		||||
		return errors.NewBadRequest(err.Error())
 | 
			
		||||
	case mergepatch.ErrNoListOfLists, mergepatch.ErrPatchContentNotMatchRetainKeys:
 | 
			
		||||
		return errors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false)
 | 
			
		||||
	default:
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,6 +98,29 @@ func TestPatchAnonymousField(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPatchInvalid(t *testing.T) {
 | 
			
		||||
	testGV := schema.GroupVersion{Group: "", Version: "v"}
 | 
			
		||||
	scheme.AddKnownTypes(testGV, &testPatchType{})
 | 
			
		||||
	codec := codecs.LegacyCodec(testGV)
 | 
			
		||||
	defaulter := runtime.ObjectDefaulter(scheme)
 | 
			
		||||
 | 
			
		||||
	original := &testPatchType{
 | 
			
		||||
		TypeMeta:         metav1.TypeMeta{Kind: "testPatchType", APIVersion: "v"},
 | 
			
		||||
		TestPatchSubType: TestPatchSubType{StringField: "my-value"},
 | 
			
		||||
	}
 | 
			
		||||
	patch := `barbaz`
 | 
			
		||||
	expectedError := "invalid character 'b' looking for beginning of value"
 | 
			
		||||
 | 
			
		||||
	actual := &testPatchType{}
 | 
			
		||||
	err := strategicPatchObject(codec, defaulter, original, []byte(patch), actual, &testPatchType{})
 | 
			
		||||
	if apierrors.IsBadRequest(err) == false {
 | 
			
		||||
		t.Errorf("expected HTTP status: BadRequest, got: %#v", apierrors.ReasonForError(err))
 | 
			
		||||
	}
 | 
			
		||||
	if err.Error() != expectedError {
 | 
			
		||||
		t.Errorf("expected %#v, got %#v", expectedError, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type testPatcher struct {
 | 
			
		||||
	t *testing.T
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue