apiserver: return 4xx for invalid patch

Add interpretPatchError to return appropriate http code
(400 or 422) according to the error type.

We add this function in apiserver because we don't want
to mention the http code in apimachinery. The apimachinery
code is also used in kubectl. The client should not return
a server error.

Add a test to validate the http error code and error message.

Kubernetes-commit: e0a2168ecbf8b4e43f932a32fa55cd55215123cc
This commit is contained in:
Nikhita Raghunath 2017-10-24 17:26:03 +05:30 committed by Kubernetes Publisher
parent b5a75d8847
commit 6f448f398c
2 changed files with 43 additions and 8 deletions

View File

@ -186,7 +186,7 @@ func patchResource(
case types.JSONPatchType, types.MergePatchType: case types.JSONPatchType, types.MergePatchType:
originalJS, patchedJS, err := patchObjectJSON(patchType, codec, currentObject, patchJS, objToUpdate, versionedObj) originalJS, patchedJS, err := patchObjectJSON(patchType, codec, currentObject, patchJS, objToUpdate, versionedObj)
if err != nil { if err != nil {
return nil, err return nil, interpretPatchError(err)
} }
originalObjJS, originalPatchedObjJS = originalJS, patchedJS originalObjJS, originalPatchedObjJS = originalJS, patchedJS
@ -198,13 +198,13 @@ func patchResource(
// Compute once // Compute once
originalPatchBytes, err = strategicpatch.CreateTwoWayMergePatch(originalObjJS, originalPatchedObjJS, versionedObj) originalPatchBytes, err = strategicpatch.CreateTwoWayMergePatch(originalObjJS, originalPatchedObjJS, versionedObj)
if err != nil { if err != nil {
return nil, err return nil, interpretPatchError(err)
} }
} }
// Return a fresh map every time // Return a fresh map every time
originalPatchMap := make(map[string]interface{}) originalPatchMap := make(map[string]interface{})
if err := json.Unmarshal(originalPatchBytes, &originalPatchMap); err != nil { if err := json.Unmarshal(originalPatchBytes, &originalPatchMap); err != nil {
return nil, err return nil, errors.NewBadRequest(err.Error())
} }
return originalPatchMap, nil return originalPatchMap, nil
} }
@ -242,7 +242,7 @@ func patchResource(
getOriginalPatchMap = func() (map[string]interface{}, error) { getOriginalPatchMap = func() (map[string]interface{}, error) {
patchMap := make(map[string]interface{}) patchMap := make(map[string]interface{})
if err := json.Unmarshal(patchJS, &patchMap); err != nil { if err := json.Unmarshal(patchJS, &patchMap); err != nil {
return nil, err return nil, errors.NewBadRequest(err.Error())
} }
return patchMap, nil return patchMap, nil
} }
@ -277,7 +277,7 @@ func patchResource(
var err error var err error
currentPatchMap, err = strategicpatch.CreateTwoWayMergeMapPatch(originalObjMap, currentObjMap, versionedObj) currentPatchMap, err = strategicpatch.CreateTwoWayMergeMapPatch(originalObjMap, currentObjMap, versionedObj)
if err != nil { if err != nil {
return nil, err return nil, interpretPatchError(err)
} }
} else { } else {
// Compute current patch. // Compute current patch.
@ -287,11 +287,11 @@ func patchResource(
} }
currentPatch, err := strategicpatch.CreateTwoWayMergePatch(originalObjJS, currentObjJS, versionedObj) currentPatch, err := strategicpatch.CreateTwoWayMergePatch(originalObjJS, currentObjJS, versionedObj)
if err != nil { if err != nil {
return nil, err return nil, interpretPatchError(err)
} }
currentPatchMap = make(map[string]interface{}) currentPatchMap = make(map[string]interface{})
if err := json.Unmarshal(currentPatch, &currentPatchMap); err != nil { if err := json.Unmarshal(currentPatch, &currentPatchMap); err != nil {
return nil, err return nil, errors.NewBadRequest(err.Error())
} }
} }
@ -422,7 +422,7 @@ func strategicPatchObject(
patchMap := make(map[string]interface{}) patchMap := make(map[string]interface{})
if err := json.Unmarshal(patchJS, &patchMap); err != nil { 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 { if err := applyPatchToObject(codec, defaulter, originalObjMap, patchMap, objToUpdate, versionedObj); err != nil {
@ -456,3 +456,15 @@ func applyPatchToObject(
return nil 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
}
}

View File

@ -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 { type testPatcher struct {
t *testing.T t *testing.T