diff --git a/api/v1alpha1/envoypatchpolicy_types.go b/api/v1alpha1/envoypatchpolicy_types.go index 98fabb7a6aa..6fe1366cd97 100644 --- a/api/v1alpha1/envoypatchpolicy_types.go +++ b/api/v1alpha1/envoypatchpolicy_types.go @@ -6,6 +6,7 @@ package v1alpha1 import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" ) @@ -107,8 +108,7 @@ type JSONPatchOperation struct { // 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. - // +kubebuilder:validation:MinLength=1 - Value string `json:"value"` + Value apiextensionsv1.JSON `json:"value"` } // EnvoyPatchPolicyStatus defines the state of EnvoyPatchPolicy diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 73b0014df35..2237b268296 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -113,7 +113,7 @@ func (in *ClaimToHeader) DeepCopy() *ClaimToHeader { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvoyJSONPatchConfig) DeepCopyInto(out *EnvoyJSONPatchConfig) { *out = *in - out.Operation = in.Operation + in.Operation.DeepCopyInto(&out.Operation) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyJSONPatchConfig. @@ -191,7 +191,9 @@ func (in *EnvoyPatchPolicySpec) DeepCopyInto(out *EnvoyPatchPolicySpec) { if in.JSONPatches != nil { in, out := &in.JSONPatches, &out.JSONPatches *out = make([]EnvoyJSONPatchConfig, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } in.TargetRef.DeepCopyInto(&out.TargetRef) } @@ -278,6 +280,7 @@ func (in *HeaderMatch) DeepCopy() *HeaderMatch { // 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. diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoypatchpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoypatchpolicies.yaml index 9e200005a10..799da6b3d01 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoypatchpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoypatchpolicies.yaml @@ -65,8 +65,7 @@ spec: type: string value: description: Value is the new value of the path location. - minLength: 1 - type: string + x-kubernetes-preserve-unknown-fields: true required: - op - path diff --git a/docs/latest/api/extension_types.md b/docs/latest/api/extension_types.md index 8fb1a67da57..977cd2ac861 100644 --- a/docs/latest/api/extension_types.md +++ b/docs/latest/api/extension_types.md @@ -218,7 +218,7 @@ _Appears in:_ | --- | --- | | `op` _[JSONPatchOperationType](#jsonpatchoperationtype)_ | Op is the type of operation to perform | | `path` _string_ | 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. | -| `value` _string_ | Value is the new value of the path location. | +| `value` _[JSON](#json)_ | Value is the new value of the path location. | ## JSONPatchOperationType diff --git a/internal/gatewayapi/address.go b/internal/gatewayapi/address.go index 673ea2081ba..b8d7d507563 100644 --- a/internal/gatewayapi/address.go +++ b/internal/gatewayapi/address.go @@ -18,7 +18,7 @@ type AddressesTranslator interface { func (t *Translator) ProcessAddresses(gateways []*GatewayContext, xdsIR XdsIRMap, infraIR InfraIRMap, resources *Resources) { for _, gateway := range gateways { // Infra IR already exist - irKey := irStringKey(gateway.Gateway) + irKey := irStringKey(gateway.Gateway.Namespace, gateway.Gateway.Name) gwInfraIR := infraIR[irKey] var ipAddr []string diff --git a/internal/gatewayapi/envoypatchpolicy.go b/internal/gatewayapi/envoypatchpolicy.go new file mode 100644 index 00000000000..ecd7863c988 --- /dev/null +++ b/internal/gatewayapi/envoypatchpolicy.go @@ -0,0 +1,58 @@ +// 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 gatewayapi + +import ( + "sort" + + gwv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/ir" +) + +func (t *Translator) ProcessEnvoyPatchPolicies(envoyPatchPolicies []*egv1a1.EnvoyPatchPolicy, xdsIR XdsIRMap) { + // Sort based on priority + sort.Slice(envoyPatchPolicies, func(i, j int) bool { + return envoyPatchPolicies[i].Spec.Priority < envoyPatchPolicies[j].Spec.Priority + }) + + for _, policy := range envoyPatchPolicies { + // Ensure policy can only target a Gateway + if policy.Spec.TargetRef.Group != gwv1b1.GroupName || policy.Spec.TargetRef.Kind != KindGateway { + // TODO: Update Status + continue + } + + // Ensure Policy and target Gateway are in the same namespace + targetNs := policy.Spec.TargetRef.Namespace + if targetNs == nil || policy.Namespace != string(*targetNs) { + // TODO: Update Status + continue + } + + // Get the IR + // It must exist since the gateways have already been processed + irKey := irStringKey(string(*targetNs), string(policy.Spec.TargetRef.Name)) + gwXdsIR, ok := xdsIR[irKey] + if !ok { + // TODO: Update Status + continue + } + + // Save the patch + for _, patch := range policy.Spec.JSONPatches { + irPatch := ir.JSONPatchConfig{} + irPatch.Type = string(patch.Type) + irPatch.Name = patch.Name + irPatch.Operation.Op = string(patch.Operation.Op) + irPatch.Operation.Path = patch.Operation.Path + irPatch.Operation.Value = patch.Operation.Value + + gwXdsIR.JSONPatches = append(gwXdsIR.JSONPatches, &irPatch) + } + } +} diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go index 00139d20824..e31a4a03fa0 100644 --- a/internal/gatewayapi/helpers.go +++ b/internal/gatewayapi/helpers.go @@ -385,8 +385,8 @@ type crossNamespaceTo struct { name string } -func irStringKey(gateway *v1beta1.Gateway) string { - return fmt.Sprintf("%s-%s", gateway.Namespace, gateway.Name) +func irStringKey(gatewayNs, gatewayName string) string { + return fmt.Sprintf("%s-%s", gatewayNs, gatewayName) } func irHTTPListenerName(listener *ListenerContext) string { diff --git a/internal/gatewayapi/listener.go b/internal/gatewayapi/listener.go index 16b73ec9756..450a9df4b99 100644 --- a/internal/gatewayapi/listener.go +++ b/internal/gatewayapi/listener.go @@ -31,7 +31,7 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap // to the Xds IR. for _, gateway := range gateways { // init IR per gateway - irKey := irStringKey(gateway.Gateway) + irKey := irStringKey(gateway.Gateway.Namespace, gateway.Gateway.Name) gwXdsIR := &ir.Xds{} gwInfraIR := ir.NewInfra() gwInfraIR.Proxy.Name = irKey diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 85e92660b98..dee403e2ef7 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -535,7 +535,7 @@ func (t *Translator) processHTTPRouteParentRefListener(route RouteContext, route } } - irKey := irStringKey(listener.gateway) + irKey := irStringKey(listener.gateway.Namespace, listener.gateway.Name) irListener := xdsIR[irKey].GetHTTPListener(irHTTPListenerName(listener)) if irListener != nil { if GetRouteType(route) == KindGRPCRoute { @@ -629,7 +629,7 @@ func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resour hasHostnameIntersection = true - irKey := irStringKey(listener.gateway) + irKey := irStringKey(listener.gateway.Namespace, listener.gateway.Name) containerPort := servicePortToContainerPort(int32(listener.Port)) // Create the TCP Listener while parsing the TLSRoute since // the listener directly links to a routeDestination. @@ -764,7 +764,7 @@ func (t *Translator) processUDPRouteParentRefs(udpRoute *UDPRouteContext, resour continue } accepted = true - irKey := irStringKey(listener.gateway) + irKey := irStringKey(listener.gateway.Namespace, listener.gateway.Name) containerPort := servicePortToContainerPort(int32(listener.Port)) // Create the UDP Listener while parsing the UDPRoute since // the listener directly links to a routeDestination. @@ -896,7 +896,7 @@ func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resour continue } accepted = true - irKey := irStringKey(listener.gateway) + irKey := irStringKey(listener.gateway.Namespace, listener.gateway.Name) containerPort := servicePortToContainerPort(int32(listener.Port)) // Create the TCP Listener while parsing the TCPRoute since // the listener directly links to a routeDestination. diff --git a/internal/gatewayapi/testdata/envoypatchpolicy-valid.in.yaml b/internal/gatewayapi/testdata/envoypatchpolicy-valid.in.yaml new file mode 100644 index 00000000000..59c899834de --- /dev/null +++ b/internal/gatewayapi/testdata/envoypatchpolicy-valid.in.yaml @@ -0,0 +1,56 @@ +envoyPatchPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + namespace: envoy-gateway + name: edit-conn-buffer-bytes + spec: + type: "JSONPatch" + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + jsonPatches: + - type: "type.googleapis.com/envoy.config.listener.v3.Listener" + name: "envoy-gateway-gateway-1-http" + operation: + op: replace + path: "/per_connection_buffer_limit_bytes" + value: "1024" +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + namespace: envoy-gateway + name: edit-ignore-global-limit + spec: + type: "JSONPatch" + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + # Higher priority + priority: -1 + jsonPatches: + - type: "type.googleapis.com/envoy.config.listener.v3.Listener" + name: "envoy-gateway-gateway-1-http" + operation: + op: replace + path: "/ignore_global_conn_limit" + value: "true" +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Same diff --git a/internal/gatewayapi/testdata/envoypatchpolicy-valid.out.yaml b/internal/gatewayapi/testdata/envoypatchpolicy-valid.out.yaml new file mode 100644 index 00000000000..96d17a4a306 --- /dev/null +++ b/internal/gatewayapi/testdata/envoypatchpolicy-valid.out.yaml @@ -0,0 +1,72 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Same + status: + listeners: + - name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute + attachedRoutes: 0 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Sending translated listener configuration to the data plane + - type: Accepted + status: "True" + reason: Accepted + message: Listener has been successfully translated +xdsIR: + envoy-gateway-gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - name: envoy-gateway-gateway-1-http + address: 0.0.0.0 + port: 10080 + hostnames: + - "*" + jsonPatches: + - type: "type.googleapis.com/envoy.config.listener.v3.Listener" + name: "envoy-gateway-gateway-1-http" + operation: + op: replace + path: "/ignore_global_conn_limit" + value: "true" + - type: "type.googleapis.com/envoy.config.listener.v3.Listener" + name: "envoy-gateway-gateway-1-http" + operation: + op: replace + path: "/per_connection_buffer_limit_bytes" + value: "1024" +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + name: envoy-gateway-gateway-1 + listeners: + - address: "" + ports: + - name: http + protocol: "HTTP" + servicePort: 80 + containerPort: 10080 diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 75295b450a1..49c20a664bf 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -120,6 +120,9 @@ func (t *Translator) Translate(resources *Resources) *TranslateResult { // Process all Listeners for all relevant Gateways. t.ProcessListeners(gateways, xdsIR, infraIR, resources) + // Process EnvoyPatchPolicies + t.ProcessEnvoyPatchPolicies(resources.EnvoyPatchPolicies, xdsIR) + // Process all Addresses for all relevant Gateways. t.ProcessAddresses(gateways, xdsIR, infraIR, resources)