diff --git a/internal/utils/jsonpatch/patch.go b/internal/utils/jsonpatch/patch.go index 8c14ae19f4..868c9c2f32 100644 --- a/internal/utils/jsonpatch/patch.go +++ b/internal/utils/jsonpatch/patch.go @@ -11,7 +11,6 @@ import ( "fmt" jsonpatchv5 "github.com/evanphx/json-patch/v5" - "sigs.k8s.io/yaml" "github.com/envoyproxy/gateway/internal/ir" ) @@ -58,38 +57,18 @@ func ApplyJSONPatches(document json.RawMessage, patches ...ir.JSONPatchOperation } for _, path := range jsonPointers { - op := ir.JSONPatchOperation{ - Path: &path, - Op: p.Op, - Value: p.Value, - From: p.From, - } - - // Convert patch to JSON - // The patch library expects an array so convert it into one - y, err := yaml.Marshal([]ir.JSONPatchOperation{op}) - if err != nil { - tErr := fmt.Errorf("unable to marshal patch %+v, err: %s", op, err.Error()) - tErrs = errors.Join(tErrs, tErr) - continue - } - jsonBytes, err := yaml.YAMLToJSON(y) + operation, err := toPatchOperation(path, p) if err != nil { - tErr := fmt.Errorf("unable to convert patch to json %s, err: %s", string(y), err.Error()) - tErrs = errors.Join(tErrs, tErr) - continue - } - patchObj, err := jsonpatchv5.DecodePatch(jsonBytes) - if err != nil { - tErr := fmt.Errorf("unable to decode patch %s, err: %s", string(jsonBytes), err.Error()) - tErrs = errors.Join(tErrs, tErr) + tErrs = errors.Join(tErrs, err) continue } + patch := jsonpatchv5.Patch{operation} + // Apply patch - document, err = patchObj.ApplyWithOptions(document, opts) + document, err = patch.ApplyWithOptions(document, opts) if err != nil { - tErr := fmt.Errorf("unable to apply patch:\n%s on resource:\n%s, err: %s", string(jsonBytes), string(document), err.Error()) + tErr := fmt.Errorf("unable to apply patch: op=%s path=%s err: %s", string(p.Op), path, err.Error()) tErrs = errors.Join(tErrs, tErr) continue } @@ -97,3 +76,52 @@ func ApplyJSONPatches(document json.RawMessage, patches ...ir.JSONPatchOperation } return document, tErrs } + +func toPatchOperation(path string, original ir.JSONPatchOperation) (jsonpatchv5.Operation, error) { + operation := make(jsonpatchv5.Operation, 4) + + rawOp, err := marshalJSONString(string(original.Op)) + if err != nil { + return nil, fmt.Errorf("unable to marshal patch op %q: %w", string(original.Op), err) + } + operation["op"] = rawOp + + rawPath, err := marshalJSONString(path) + if err != nil { + return nil, fmt.Errorf("unable to marshal patch path %q: %w", path, err) + } + operation["path"] = rawPath + + if original.From != nil { + rawFrom, err := marshalJSONString(*original.From) + if err != nil { + return nil, fmt.Errorf("unable to marshal patch from %q: %w", *original.From, err) + } + operation["from"] = rawFrom + } + + if original.Value != nil { + operation["value"] = cloneRawJSON(original.Value.Raw) + } + + return operation, nil +} + +func marshalJSONString(value string) (*json.RawMessage, error) { + b, err := json.Marshal(value) + if err != nil { + return nil, err + } + raw := json.RawMessage(b) + return &raw, nil +} + +func cloneRawJSON(src []byte) *json.RawMessage { + if src == nil { + rawNull := json.RawMessage([]byte("null")) + return &rawNull + } + cloned := make(json.RawMessage, len(src)) + copy(cloned, src) + return &cloned +} diff --git a/internal/xds/translator/jsonpatch.go b/internal/xds/translator/jsonpatch.go index c8b2a7141a..e4e3ceb361 100644 --- a/internal/xds/translator/jsonpatch.go +++ b/internal/xds/translator/jsonpatch.go @@ -17,7 +17,6 @@ import ( tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" "google.golang.org/protobuf/encoding/protojson" - "sigs.k8s.io/yaml" "github.com/envoyproxy/gateway/internal/gatewayapi/status" "github.com/envoyproxy/gateway/internal/ir" @@ -68,17 +67,15 @@ func processJSONPatches(tCtx *types.ResourceVersionTable, envoyPatchPolicies []* // If Path and JSONPath is "" and op is "add", unmarshal and add the patch as a complete // resource if p.Operation.Op == ir.JSONPatchOpAdd && p.Operation.IsPathNilOrEmpty() && p.Operation.IsJSONPathNilOrEmpty() { - // Convert patch to JSON - // The patch library expects an array so convert it into one - y, err := yaml.Marshal(p.Operation.Value) - if err != nil { - tErr := fmt.Errorf("unable to marshal patch %+v, err: %s", p.Operation.Value, err.Error()) + if p.Operation.Value == nil { + tErr := fmt.Errorf("missing value for add operation with empty path") tErrs = errors.Join(tErrs, tErr) continue } - jsonBytes, err := yaml.YAMLToJSON(y) - if err != nil { - tErr := fmt.Errorf("unable to convert patch to json %s, err: %s", string(y), err.Error()) + + jsonBytes := p.Operation.Value.Raw + if len(jsonBytes) == 0 { + tErr := fmt.Errorf("empty value for add operation with empty path") tErrs = errors.Join(tErrs, tErr) continue }