kuberneteskubectlkubernetes-custom-resources

Error while applying JSON Patch to Kubernetes Custom Resource


I have an instance of a Kubernetes Custom Resource that I want to patch via the Kubernetes API using a JSON patch.

This is my PATCH request:

PATCH /apis/example.com/v1alpha1/namespaces/default/mycrd/test HTTP/1.1
Accept: application/json
Content-Type: application/json-patch+json
[other headers omitted for brevity...]

[
  {"op": "replace", "path": "/status/foo", value: "bar"}
]

I'm fairly certain that my request body is a valid JSON patch, and I've previously already successfully updated core (non-CRD) API resources using similar API calls. The CRD has a openAPIV3Schema defined that explicitly allows .status.foo to exist and to be of type string.

The request above is declined by the Kubernetes API server with the following response:

HTTP/1.1 422 Unprocessable Entity
Conent-Type: application/json
[other headers omitted for brevity...]

{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "the server rejected our request due to an error in our request",
  "reason": "Invalid",
  "details": {},
  "code": 422
}

According to the CRD documentation, CRDs should support PATCH request with the application/json-patch+json content type. But for some reason, the request appears to be invalid without Kubernetes bothering to tell me why. The API server pod did not have any relevant messages in its log stream, either.

The same error also occurs when using kubectl patch on the command line:

$ kubectl patch mycrd.example.com test --type=json -p '[{"op": "replace", "path": "/status/foo", "value": "bar"}]'   
The  "" is invalid

What are possible reasons for this error to occur? What options to I have for further debugging?


Solution

  • Found the (or at least, a partial) answer while still typing the question...

    The Kubernetes API server will not recursively create nested objects for a JSON patch input. This behaviour is consistent with the JSON Patch specification in RFC 6902, section A.12:

    A.12. Adding to a Nonexistent Target

    An example target JSON document:

    { "foo": "bar" }
    

    A JSON Patch document:

    [
      { "op": "add", "path": "/baz/bat", "value": "qux" }
    ]
    

    This JSON Patch document, applied to the target JSON document above, would result in an error (therefore, it would not be applied), because the "add" operation's target location that references neither the root of the document, nor a member of an existing object, nor a member of an existing array.

    This is why the original request fails, when the Custom Resources does not have a .status property to begin with. The following two subsequent calls (the second one being the original one) will complete successfully:

    $ kubectl patch mycrd.example.com test --type=json \
        -p '[{"op": "replace", "path": "/status", "value": {}}]'
    mycrd.example.com/test patched
    $ kubectl patch mycrd.example.com test --type=json \
        -p '[{"op": "replace", "path": "/status/foo", "value": "bar"}]'
    mycrd.example.com/test patched
    

    Obviously, replaceing the entire .status property with {} is not a good idea if that property already contains data that you want to keep.

    A suitable alternative to a JSON patch in this scenario is a JSON Merge Patch:

    PATCH /apis/example.com/v1alpha1/namespaces/default/mycrd/test HTTP/1.1
    Accept: application/json
    Content-Type: application/merge-patch+json
    [other headers omitted for brevity...]
    
    {
      "status": {
        "foo": "bar"
      }
    }
    

    Or, alternatively, using kubectl:

    $ kubectl patch mycrd.example.com test --type=merge \
        -p '{"status": {"foo": "bar"}}'
    mycrd.example.com/test patched