Skip to content

jsonMergePatch operations use Fabric8 JSON Patch semantics and can remove spec #3512

Description

@brianzilliz

What happened?

ResourceOperations.jsonMergePatch(...) and ResourceOperations.jsonMergePatchPrimary(...) are documented as applying RFC 7386 JSON Merge Patch, but they delegate to Fabric8's no-context .patch() method.

Fabric8's default .patch() intentionally fetches the current resource and computes an RFC 6902 JSON Patch diff. When the supplied resource is a partial metadata patch, the generated diff removes fields that are omitted from the partial object, including spec.

The status variants are not affected because Fabric8's patchStatus() explicitly selects PatchType.JSON_MERGE.

Versions verified

  • Java Operator SDK 5.3.5 with Fabric8 7.7.0
  • Java Operator SDK 5.5.0 with Fabric8 7.8.0

The behavior is still present in the current main branch.

Minimal reproduction

Assume this custom resource already exists:

apiVersion: example.io/v1
kind: Example
metadata:
  name: example
spec:
  value: keep-me

Apply a partial metadata patch through ResourceOperations:

Example patch = new Example();
patch.getMetadata().setName("example");
patch.getMetadata().setLabels(Map.of("resolved", "true"));

context.resourceOperations().jsonMergePatchPrimary(patch);

Fabric8 computes a JSON Patch equivalent to:

[
  {"op":"add","path":"/metadata/labels/resolved","value":"true"},
  {"op":"remove","path":"/spec"},
  {"op":"remove","path":"/status"}
]

For a CRD whose root spec is optional, the API server accepts the patch and permanently removes the spec. We encountered this in a real controller where subsequent reconciliation failed because getSpec() returned null.

Expected behavior

Methods named and documented as JSON Merge Patch should send application/merge-patch+json and preserve fields omitted from the partial resource.

Relevant implementation

JOSDK 5.5.0 delegates to .patch() without specifying a patch type:

Fabric8's default .patch() selects JSON Patch and diffs the current and supplied resources:

Suggested fix

Make the non-status JSON Merge Patch operations pass an explicit patch context, for example:

r -> context.getClient()
    .resource(r)
    .patch(PatchContext.of(PatchType.JSON_MERGE), r)

The cache and own-event filtering behavior can remain unchanged. Regression coverage should create a complete resource, apply a partial metadata merge patch, and assert that spec survives.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions