diff --git a/go.mod b/go.mod index cd84247fc45..97a40e231f8 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/envoyproxy/go-control-plane v0.11.1 github.com/envoyproxy/ratelimit v1.4.1-0.20230427142404-e2a87f41d3a7 + github.com/evanphx/json-patch/v5 v5.6.0 github.com/go-logr/logr v1.2.4 github.com/go-logr/zapr v1.2.4 github.com/golang/protobuf v1.5.3 @@ -25,6 +26,7 @@ require ( google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.27.3 + k8s.io/apiextensions-apiserver v0.27.2 k8s.io/apimachinery v0.27.3 k8s.io/cli-runtime v0.27.3 k8s.io/client-go v0.27.3 @@ -45,7 +47,6 @@ require ( github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.1 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect @@ -104,7 +105,6 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiextensions-apiserver v0.27.2 // indirect k8s.io/component-base v0.27.3 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 33381172f90..ddc91492f93 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -11,6 +11,7 @@ import ( "github.com/tetratelabs/multierror" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" @@ -54,6 +55,9 @@ type Xds struct { TCP []*TCPListener // UDP Listeners exposed by the gateway. UDP []*UDPListener + // JSONPatches are the JSON Patches that + // are to be applied to generaed Xds linked to the gateway. + JSONPatches []*JSONPatchConfig } // Validate the fields within the Xds structure. @@ -792,3 +796,28 @@ type OpenTelemetryAccessLog struct { Port uint32 `json:"port"` Resources map[string]string `json:"resources"` } + +// JSONPatchConfig defines the configuration for patching a Envoy xDS Resource +// using JSONPatch semantics +// +k8s:deepcopy-gen=true +type JSONPatchConfig struct { + // Type is the typed URL of the Envoy xDS Resource + Type string `json:"type"` + // Name is the name of the resource + Name string `json:"name"` + // Patch defines the JSON Patch Operation + Operation JSONPatchOperation `json:"operation"` +} + +// JSONPatchOperation defines the JSON Patch Operation as defined in +// https://datatracker.ietf.org/doc/html/rfc6902 +// +k8s:deepcopy-gen=true +type JSONPatchOperation struct { + // Op is the type of operation to perform + Op string `json:"op"` + // Path is the location of the target document/field where the operation will be performed + // Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. + Path string `json:"path"` + // Value is the new value of the path location. + Value apiextensionsv1.JSON `json:"value"` +} diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index d67ca2655d1..8bc0dd89ae8 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -376,6 +376,38 @@ func (in *JSONAccessLog) DeepCopy() *JSONAccessLog { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JSONPatchConfig) DeepCopyInto(out *JSONPatchConfig) { + *out = *in + in.Operation.DeepCopyInto(&out.Operation) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONPatchConfig. +func (in *JSONPatchConfig) DeepCopy() *JSONPatchConfig { + if in == nil { + return nil + } + out := new(JSONPatchConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JSONPatchOperation) DeepCopyInto(out *JSONPatchOperation) { + *out = *in + in.Value.DeepCopyInto(&out.Value) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONPatchOperation. +func (in *JSONPatchOperation) DeepCopy() *JSONPatchOperation { + if in == nil { + return nil + } + out := new(JSONPatchOperation) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JwtRequestAuthentication) DeepCopyInto(out *JwtRequestAuthentication) { *out = *in @@ -928,6 +960,17 @@ func (in *Xds) DeepCopyInto(out *Xds) { } } } + if in.JSONPatches != nil { + in, out := &in.JSONPatches, &out.JSONPatches + *out = make([]*JSONPatchConfig, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(JSONPatchConfig) + (*in).DeepCopyInto(*out) + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Xds. diff --git a/internal/xds/translator/jsonpatch.go b/internal/xds/translator/jsonpatch.go new file mode 100644 index 00000000000..d5588ac50f4 --- /dev/null +++ b/internal/xds/translator/jsonpatch.go @@ -0,0 +1,285 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package translator + +import ( + "fmt" + + clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" + endpointv3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" + listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" + jsonpatchv5 "github.com/evanphx/json-patch/v5" + "github.com/tetratelabs/multierror" + "google.golang.org/protobuf/encoding/protojson" + "sigs.k8s.io/yaml" + + "github.com/envoyproxy/gateway/internal/ir" + _ "github.com/envoyproxy/gateway/internal/xds/extensions" // register the generated types to support protojson unmarshalling + "github.com/envoyproxy/gateway/internal/xds/types" +) + +const ( + AddOperation = "add" + EmptyPath = "" +) + +// processJSONPatches applies each JSONPatch to the Xds Resources for a specific type. +func processJSONPatches(tCtx *types.ResourceVersionTable, jsonPatches []*ir.JSONPatchConfig) error { + var errs error + m := protojson.MarshalOptions{ + UseProtoNames: true, + } + + for _, p := range jsonPatches { + var ( + listener *listenerv3.Listener + routeConfig *routev3.RouteConfiguration + cluster *clusterv3.Cluster + endpoint *endpointv3.ClusterLoadAssignment + resourceJSON []byte + err error + ) + + // If Path is "" and op is "add", unmarshal and add the patch as a complete + // resource + if p.Operation.Op == AddOperation && p.Operation.Path == EmptyPath { + // 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 { + err := fmt.Errorf("unable to marshal patch %+v, err: %v", p.Operation.Value, err) + errs = multierror.Append(errs, err) + continue + } + jsonBytes, err := yaml.YAMLToJSON(y) + if err != nil { + err := fmt.Errorf("unable to convert patch to json %s, err: %v", string(y), err) + errs = multierror.Append(errs, err) + continue + } + switch p.Type { + case string(resourcev3.ListenerType): + temp := &listenerv3.Listener{} + if err = protojson.Unmarshal(jsonBytes, temp); err != nil { + err := fmt.Errorf("unable to unmarshal xds resource %+v, err:%v", p.Operation.Value, err) + errs = multierror.Append(errs, err) + continue + } + if err = temp.Validate(); err != nil { + err := fmt.Errorf("validation failed for xds resource %+v, err:%v", p.Operation.Value, err) + errs = multierror.Append(errs, err) + continue + } + + tCtx.AddXdsResource(resourcev3.ListenerType, temp) + case string(resourcev3.RouteType): + temp := &routev3.RouteConfiguration{} + if err = protojson.Unmarshal(jsonBytes, temp); err != nil { + err := fmt.Errorf("unable to unmarshal xds resource %+v, err:%v", p.Operation.Value, err) + errs = multierror.Append(errs, err) + continue + } + if err = temp.Validate(); err != nil { + err := fmt.Errorf("validation failed for xds resource %+v, err:%v", p.Operation.Value, err) + errs = multierror.Append(errs, err) + continue + } + tCtx.AddXdsResource(resourcev3.RouteType, temp) + case string(resourcev3.ClusterType): + temp := &clusterv3.Cluster{} + if err = protojson.Unmarshal(jsonBytes, temp); err != nil { + err := fmt.Errorf("unable to unmarshal xds resource %+v, err:%v", p.Operation.Value, err) + errs = multierror.Append(errs, err) + continue + } + if err = temp.Validate(); err != nil { + err := fmt.Errorf("validation failed for xds resource %+v, err:%v", p.Operation.Value, err) + errs = multierror.Append(errs, err) + continue + } + tCtx.AddXdsResource(resourcev3.ClusterType, temp) + case string(resourcev3.EndpointType): + temp := &endpointv3.ClusterLoadAssignment{} + if err = protojson.Unmarshal(jsonBytes, temp); err != nil { + err := fmt.Errorf("unable to unmarshal xds resource %+v, err:%v", p.Operation.Value, err) + errs = multierror.Append(errs, err) + continue + } + if err = temp.Validate(); err != nil { + err := fmt.Errorf("validation failed for xds resource %+v, err:%v", p.Operation.Value, err) + errs = multierror.Append(errs, err) + continue + } + tCtx.AddXdsResource(resourcev3.EndpointType, temp) + } + + // Skip further processing + continue + } + // Find the resource to patch and convert it to JSON + switch p.Type { + case string(resourcev3.ListenerType): + if listener = findXdsListener(tCtx, p.Name); listener == nil { + err = fmt.Errorf("unable to find xds resource %s: %s", p.Type, p.Name) + errs = multierror.Append(errs, err) + continue + } + + if resourceJSON, err = m.Marshal(listener); err != nil { + err = fmt.Errorf("unable to marshal xds resource %s: %s, err:%v", p.Type, p.Name, err) + errs = multierror.Append(errs, err) + continue + } + + case string(resourcev3.RouteType): + if routeConfig = findXdsRouteConfig(tCtx, p.Name); routeConfig == nil { + err := fmt.Errorf("unable to find xds resource %s: %s", p.Type, p.Name) + errs = multierror.Append(errs, err) + continue + } + + if resourceJSON, err = m.Marshal(routeConfig); err != nil { + err = fmt.Errorf("unable to marshal xds resource %s: %s, err:%v", p.Type, p.Name, err) + errs = multierror.Append(errs, err) + continue + } + + case string(resourcev3.ClusterType): + if cluster := findXdsCluster(tCtx, p.Name); cluster == nil { + err := fmt.Errorf("unable to find xds resource %s: %s", p.Type, p.Name) + errs = multierror.Append(errs, err) + continue + } + + if resourceJSON, err = m.Marshal(cluster); err != nil { + err = fmt.Errorf("unable to marshal xds resource %s: %s, err:%v", p.Type, p.Name, err) + errs = multierror.Append(errs, err) + continue + } + case string(resourcev3.EndpointType): + endpoint = findXdsEndpoint(tCtx, p.Name) + if endpoint == nil { + err = fmt.Errorf("unable to marshal xds resource %s: %s, err:%v", p.Type, p.Name, err) + errs = multierror.Append(errs, err) + continue + } + if resourceJSON, err = m.Marshal(endpoint); err != nil { + err = fmt.Errorf("unable to marshal xds resource %s: %s, err:%v", p.Type, p.Name, err) + errs = multierror.Append(errs, err) + continue + } + } + + // Convert patch to JSON + // The patch library expects an array so convert it into one + y, err := yaml.Marshal([]ir.JSONPatchOperation{p.Operation}) + if err != nil { + err := fmt.Errorf("unable to marshal patch %+v, err: %v", p.Operation, err) + errs = multierror.Append(errs, err) + continue + } + jsonBytes, err := yaml.YAMLToJSON(y) + if err != nil { + err := fmt.Errorf("unable to convert patch to json %s, err: %v", string(y), err) + errs = multierror.Append(errs, err) + continue + } + + patchObj, err := jsonpatchv5.DecodePatch(jsonBytes) + if err != nil { + err := fmt.Errorf("unable to decode patch %s, err: %v", string(jsonBytes), err) + errs = multierror.Append(errs, err) + continue + } + + // Apply patch + opts := jsonpatchv5.NewApplyOptions() + opts.EnsurePathExistsOnAdd = true + modifiedJSON, err := patchObj.ApplyWithOptions(resourceJSON, opts) + if err != nil { + err := fmt.Errorf("unable to apply patch:\n%s on resource:\n%s, err: %v", string(jsonBytes), string(resourceJSON), err) + errs = multierror.Append(errs, err) + continue + } + + // Unmarshal back to typed resource + // Use a temp staging variable that can be marshalled + // into and validated before saving it into the xds output resource + switch p.Type { + case string(resourcev3.ListenerType): + temp := &listenerv3.Listener{} + if err = protojson.Unmarshal(modifiedJSON, temp); err != nil { + err := fmt.Errorf("unable to unmarshal xds resource %s, err:%v", string(modifiedJSON), err) + errs = multierror.Append(errs, err) + continue + } + if err = temp.Validate(); err != nil { + err := fmt.Errorf("validation failed for xds resource %s, err:%v", string(modifiedJSON), err) + errs = multierror.Append(errs, err) + continue + } + if err = deepCopyPtr(temp, listener); err != nil { + err := fmt.Errorf("unable to copy xds resource %s, err:%v", string(modifiedJSON), err) + errs = multierror.Append(errs, err) + continue + } + case string(resourcev3.RouteType): + temp := &routev3.RouteConfiguration{} + if err = protojson.Unmarshal(modifiedJSON, temp); err != nil { + err := fmt.Errorf("unable to unmarshal xds resource %s, err:%v", string(modifiedJSON), err) + errs = multierror.Append(errs, err) + continue + } + if err = temp.Validate(); err != nil { + err := fmt.Errorf("validation failed for xds resource %s, err:%v", string(modifiedJSON), err) + errs = multierror.Append(errs, err) + continue + } + if err = deepCopyPtr(temp, routeConfig); err != nil { + err := fmt.Errorf("unable to copy xds resource %s, err:%v", string(modifiedJSON), err) + errs = multierror.Append(errs, err) + continue + } + case string(resourcev3.ClusterType): + temp := &clusterv3.Cluster{} + if err = protojson.Unmarshal(modifiedJSON, temp); err != nil { + err := fmt.Errorf("unable to unmarshal xds resource %s, err:%v", string(modifiedJSON), err) + errs = multierror.Append(errs, err) + continue + } + if err = temp.Validate(); err != nil { + err := fmt.Errorf("validation failed for xds resource %s, err:%v", string(modifiedJSON), err) + errs = multierror.Append(errs, err) + continue + } + if err = deepCopyPtr(temp, cluster); err != nil { + err := fmt.Errorf("unable to copy xds resource %s, err:%v", string(modifiedJSON), err) + errs = multierror.Append(errs, err) + continue + } + case string(resourcev3.EndpointType): + temp := &endpointv3.ClusterLoadAssignment{} + if err = protojson.Unmarshal(modifiedJSON, temp); err != nil { + err := fmt.Errorf("unable to unmarshal xds resource %s, err:%v", string(modifiedJSON), err) + errs = multierror.Append(errs, err) + continue + } + if err = temp.Validate(); err != nil { + err := fmt.Errorf("validation failed for xds resource %s, err:%v", string(modifiedJSON), err) + errs = multierror.Append(errs, err) + continue + } + if err = deepCopyPtr(temp, endpoint); err != nil { + err := fmt.Errorf("unable to copy xds resource %s, err:%v", string(modifiedJSON), err) + errs = multierror.Append(errs, err) + continue + } + } + } + return errs +} diff --git a/internal/xds/translator/testdata/in/xds-ir/jsonpatch.yaml b/internal/xds/translator/testdata/in/xds-ir/jsonpatch.yaml new file mode 100644 index 00000000000..baeec6e0562 --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/jsonpatch.yaml @@ -0,0 +1,67 @@ +jsonPatches: +- type: "type.googleapis.com/envoy.config.listener.v3.Listener" + name: "first-listener" + operation: + op: "add" + path: "/default_filter_chain/filters/0/typed_config/http_filters/0" + value: + name: "envoy.filters.http.ratelimit" + typed_config: + "@type": "type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit" + domain: "eg-ratelimit" + failure_mode_deny: true + timeout: 1s + rate_limit_service: + grpc_service: + envoy_grpc: + cluster_name: rate-limit-cluster + transport_api_version: V3 +- type: "type.googleapis.com/envoy.config.route.v3.RouteConfiguration" + name: "first-listener" + operation: + op: "add" + path: "/virtual_hosts/0/rate_limits" + value: + - actions: + - remote_address: {} +- type: "type.googleapis.com/envoy.config.cluster.v3.Cluster" + name: rate-limit-cluster + operation: + op: add + path: "" + value: + name: rate-limit-cluster + type: STRICT_DNS + connect_timeout: 10s + lb_policy: ROUND_ROBIN + http2_protocol_options: {} + load_assignment: + cluster_name: rate-limit-cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: ratelimit.svc.cluster.local + port_value: 8081 +- type: "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment" + name: "first-route" + operation: + op: "replace" + path: "/endpoints/0/load_balancing_weight" + value: "50" +http: +- name: "first-listener" + address: "0.0.0.0" + port: 10080 + hostnames: + - "*" + routes: + - name: "first-route" + headerMatches: + - name: user + stringMatch: + exact: "jason" + destinations: + - host: "1.2.3.4" + port: 50000 diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch.clusters.yaml new file mode 100644 index 00000000000..5a0733fda8c --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch.clusters.yaml @@ -0,0 +1,26 @@ +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: first-route + name: first-route + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- connectTimeout: 10s + http2ProtocolOptions: {} + loadAssignment: + clusterName: rate-limit-cluster + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: ratelimit.svc.cluster.local + portValue: 8081 + name: rate-limit-cluster + type: STRICT_DNS diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch.endpoints.yaml new file mode 100644 index 00000000000..ae7ea189da8 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch.endpoints.yaml @@ -0,0 +1,10 @@ +- clusterName: first-route + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 50 + locality: {} diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch.listeners.yaml new file mode 100644 index 00000000000..f99ed727079 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch.listeners.yaml @@ -0,0 +1,44 @@ +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - name: envoy.filters.http.ratelimit + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit + domain: eg-ratelimit + failureModeDeny: true + rateLimitService: + grpcService: + envoyGrpc: + clusterName: rate-limit-cluster + transportApiVersion: V3 + timeout: 1s + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: first-listener + statPrefix: http + upgradeConfigs: + - upgradeType: websocket + useRemoteAddress: true + name: first-listener + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch.routes.yaml new file mode 100644 index 00000000000..0482acadb08 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch.routes.yaml @@ -0,0 +1,19 @@ +- ignorePortInHostMatching: true + name: first-listener + virtualHosts: + - domains: + - '*' + name: first-listener + rateLimits: + - actions: + - remoteAddress: {} + routes: + - match: + headers: + - name: user + stringMatch: + exact: jason + prefix: / + name: first-route + route: + cluster: first-route diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index 9b364dc7748..40f6f603e50 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -11,6 +11,7 @@ import ( clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + endpointv3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" @@ -58,6 +59,10 @@ func (t *Translator) Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) return nil, err } + if err := processJSONPatches(tCtx, ir.JSONPatches); err != nil { + return nil, err + } + processClusterForAccessLog(tCtx, ir.AccessLog) // Check if an extension want to inject any clusters/secrets @@ -75,7 +80,7 @@ func (t *Translator) processHTTPListenerXdsTranslation(tCtx *types.ResourceVersi var xdsRouteCfg *routev3.RouteConfiguration // Search for an existing listener, if it does not exist, create one. - xdsListener := findXdsListener(tCtx, httpListener.Address, httpListener.Port, corev3.SocketAddress_TCP) + xdsListener := findXdsListenerByHostPort(tCtx, httpListener.Address, httpListener.Port, corev3.SocketAddress_TCP) if xdsListener == nil { xdsListener = buildXdsTCPListener(httpListener.Name, httpListener.Address, httpListener.Port, accesslog) tCtx.AddXdsResource(resourcev3.ListenerType, xdsListener) @@ -217,7 +222,7 @@ func processTCPListenerXdsTranslation(tCtx *types.ResourceVersionTable, tcpListe } } // Search for an existing listener, if it does not exist, create one. - xdsListener := findXdsListener(tCtx, tcpListener.Address, tcpListener.Port, corev3.SocketAddress_TCP) + xdsListener := findXdsListenerByHostPort(tCtx, tcpListener.Address, tcpListener.Port, corev3.SocketAddress_TCP) if xdsListener == nil { xdsListener = buildXdsTCPListener(tcpListener.Name, tcpListener.Address, tcpListener.Port, accesslog) tCtx.AddXdsResource(resourcev3.ListenerType, xdsListener) @@ -253,8 +258,8 @@ func processUDPListenerXdsTranslation(tCtx *types.ResourceVersionTable, udpListe } -// findXdsListener finds a xds listener with the same address, port and protocol, and returns nil if there is no match. -func findXdsListener(tCtx *types.ResourceVersionTable, address string, port uint32, +// findXdsListenerByHostPort finds a xds listener with the same address, port and protocol, and returns nil if there is no match. +func findXdsListenerByHostPort(tCtx *types.ResourceVersionTable, address string, port uint32, protocol corev3.SocketAddress_Protocol) *listenerv3.Listener { if tCtx == nil || tCtx.XdsResources == nil || tCtx.XdsResources[resourcev3.ListenerType] == nil { return nil @@ -272,6 +277,38 @@ func findXdsListener(tCtx *types.ResourceVersionTable, address string, port uint return nil } +// findXdsListener finds a xds listener with the same name and returns nil if there is no match. +func findXdsListener(tCtx *types.ResourceVersionTable, name string) *listenerv3.Listener { + if tCtx == nil || tCtx.XdsResources == nil || tCtx.XdsResources[resourcev3.ListenerType] == nil { + return nil + } + + for _, r := range tCtx.XdsResources[resourcev3.ListenerType] { + listener := r.(*listenerv3.Listener) + if listener.Name == name { + return listener + } + } + + return nil +} + +// findXdsRouteConfig finds an xds route with the name and returns nil if there is no match. +func findXdsRouteConfig(tCtx *types.ResourceVersionTable, name string) *routev3.RouteConfiguration { + if tCtx == nil || tCtx.XdsResources == nil || tCtx.XdsResources[resourcev3.RouteType] == nil { + return nil + } + + for _, r := range tCtx.XdsResources[resourcev3.RouteType] { + route := r.(*routev3.RouteConfiguration) + if route.Name == name { + return route + } + } + + return nil +} + // findXdsCluster finds a xds cluster with the same name, and returns nil if there is no match. func findXdsCluster(tCtx *types.ResourceVersionTable, name string) *clusterv3.Cluster { if tCtx == nil || tCtx.XdsResources == nil || tCtx.XdsResources[resourcev3.ClusterType] == nil { @@ -288,6 +325,22 @@ func findXdsCluster(tCtx *types.ResourceVersionTable, name string) *clusterv3.Cl return nil } +// findXdsEndpoint finds a xds endpoint with the same cluster name, and returns nil if there is no match. +func findXdsEndpoint(tCtx *types.ResourceVersionTable, name string) *endpointv3.ClusterLoadAssignment { + if tCtx == nil || tCtx.XdsResources == nil || tCtx.XdsResources[resourcev3.EndpointType] == nil { + return nil + } + + for _, r := range tCtx.XdsResources[resourcev3.EndpointType] { + endpoint := r.(*endpointv3.ClusterLoadAssignment) + if endpoint.ClusterName == name { + return endpoint + } + } + + return nil +} + func addXdsCluster(tCtx *types.ResourceVersionTable, args addXdsClusterArgs) { xdsCluster := buildXdsCluster(args.name, args.tSocket, args.protocol, args.endpoint) xdsEndpoints := buildXdsClusterLoadAssignment(args.name, args.destinations) @@ -300,22 +353,6 @@ func addXdsCluster(tCtx *types.ResourceVersionTable, args addXdsClusterArgs) { tCtx.AddXdsResource(resourcev3.ClusterType, xdsCluster) } -// findXdsRouteConfig finds an xds route with the name and returns nil if there is no match. -func findXdsRouteConfig(tCtx *types.ResourceVersionTable, name string) *routev3.RouteConfiguration { - if tCtx == nil || tCtx.XdsResources == nil || tCtx.XdsResources[resourcev3.RouteType] == nil { - return nil - } - - for _, r := range tCtx.XdsResources[resourcev3.RouteType] { - route := r.(*routev3.RouteConfiguration) - if route.Name == name { - return route - } - } - - return nil -} - type addXdsClusterArgs struct { name string destinations []*ir.RouteDestination diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index 2050201a1f1..cff7c49bd58 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -148,6 +148,9 @@ func TestTranslateXds(t *testing.T) { { name: "accesslog", }, + { + name: "jsonpatch", + }, } for _, tc := range testCases {