From 3581b975611c9823c856c76e7974db8d3c2dc48b Mon Sep 17 00:00:00 2001 From: David Alger Date: Thu, 11 Apr 2024 09:09:51 -0500 Subject: [PATCH] Pluralize backendRefs --- api/v1alpha1/accesslogging_types.go | 9 +- api/v1alpha1/zz_generated.deepcopy.go | 8 +- .../gateway.envoyproxy.io_envoyproxies.yaml | 142 +++++++++--------- site/content/en/latest/api/extension_types.md | 3 +- test/cel-validation/envoyproxy_test.go | 134 +++++++++++++++-- 5 files changed, 213 insertions(+), 83 deletions(-) diff --git a/api/v1alpha1/accesslogging_types.go b/api/v1alpha1/accesslogging_types.go index 12e9dba405f8..e1311566eebc 100644 --- a/api/v1alpha1/accesslogging_types.go +++ b/api/v1alpha1/accesslogging_types.go @@ -112,12 +112,15 @@ const ( // - `x-accesslog-text` - The access log format string when a Text format is used. // - `x-accesslog-attr` - JSON encoded key/value pairs when a JSON format is used. // -// +kubebuilder:validation:XValidation:message="BackendRef only supports Service Kind.",rule="!has(self.backendRef.kind) || self.backendRef.kind == 'Service'" // +kubebuilder:validation:XValidation:rule="self.type == 'HTTP' || !has(self.http)",message="The http field may only be set when type is HTTP." type ALSEnvoyProxyAccessLog struct { - // BackendRef references a Kubernetes object that represents the gRPC service to which + // BackendRefs references a Kubernetes object that represents the gRPC service to which // the access logs will be sent. Currently only Service is supported. - BackendRef gwapiv1.BackendObjectReference `json:"backendRef"` + // + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=1 + // +kubebuilder:validation:XValidation:message="BackendRefs only supports Service kind.",rule="self.all(f, f.kind == 'Service')" + BackendRefs []BackendRef `json:"backendRefs"` // LogName defines the friendly name of the access log to be returned in // StreamAccessLogsMessage.Identifier. This allows the access log server // to differentiate between different access logs coming from the same Envoy. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3de1bfb0f464..1b0a97fa77a4 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -22,7 +22,13 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ALSEnvoyProxyAccessLog) DeepCopyInto(out *ALSEnvoyProxyAccessLog) { *out = *in - in.BackendRef.DeepCopyInto(&out.BackendRef) + if in.BackendRefs != nil { + in, out := &in.BackendRefs, &out.BackendRefs + *out = make([]BackendRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.LogName != nil { in, out := &in.LogName, &out.LogName *out = new(string) diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml index 3413f385e647..76d96eccaeab 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -5890,84 +5890,95 @@ spec: description: ALS defines the gRPC Access Log Service (ALS) sink. properties: - backendRef: + backendRefs: description: |- - BackendRef references a Kubernetes object that represents the gRPC service to which + BackendRefs references a Kubernetes object that represents the gRPC service to which the access logs will be sent. Currently only Service is supported. - properties: - group: - default: "" - description: |- - Group is the group of the referent. For example, "gateway.networking.k8s.io". - When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: |- - Kind is the Kubernetes resource kind of the referent. For example - "Service". + items: + description: BackendRef defines how an ObjectReference + that is specific to BackendRef. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". - Defaults to "Service" when not specified. + Defaults to "Service" when not specified. - ExternalName services can refer to CNAME DNS records that may live - outside of the cluster and as such are difficult to reason about in - terms of conformance. They also may not be safe to forward to (see - CVE-2021-25740 for more information). Implementations SHOULD NOT - support ExternalName Services. + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. - Support: Core (Services with a type other than ExternalName) + Support: Core (Services with a type other than ExternalName) - Support: Implementation-specific (Services with type ExternalName) - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: |- - Namespace is the namespace of the backend. When unspecified, the local - namespace is inferred. + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the + referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. - Note that when a namespace different than the local namespace is specified, - a ReferenceGrant object is required in the referent namespace to allow that - namespace's owner to accept the reference. See the ReferenceGrant - documentation for details. + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. - Support: Core - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: |- - Port specifies the destination port number to use for this resource. - Port is required when the referent is a Kubernetes Service. In this - case, the port number is the service port number, not the target port. - For other resources, destination port might be derived from the referent - resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + maxItems: 1 + minItems: 1 + type: array x-kubernetes-validations: - - message: Must have port for Service reference - rule: '(size(self.group) == 0 && self.kind - == ''Service'') ? has(self.port) : true' + - message: BackendRefs only supports Service + kind. + rule: self.all(f, f.kind == 'Service') http: description: HTTP defines additional configuration specific to HTTP access logs. @@ -6009,13 +6020,10 @@ spec: - TCP type: string required: - - backendRef + - backendRefs - type type: object x-kubernetes-validations: - - message: BackendRef only supports Service Kind. - rule: '!has(self.backendRef.kind) || self.backendRef.kind - == ''Service''' - message: The http field may only be set when type is HTTP. rule: self.type == 'HTTP' || !has(self.http) diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 02678a32ddec..e5b4dae59af6 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -59,7 +59,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | -| `backendRef` | _[BackendObjectReference](#backendobjectreference)_ | true | BackendRef references a Kubernetes object that represents the gRPC service to which
the access logs will be sent. Currently only Service is supported. | +| `backendRefs` | _[BackendRef](#backendref) array_ | true | BackendRefs references a Kubernetes object that represents the gRPC service to which
the access logs will be sent. Currently only Service is supported. | | `logName` | _string_ | false | LogName defines the friendly name of the access log to be returned in
StreamAccessLogsMessage.Identifier. This allows the access log server
to differentiate between different access logs coming from the same Envoy. | | `type` | _[ALSEnvoyProxyAccessLogType](#alsenvoyproxyaccesslogtype)_ | true | Type defines the type of accesslog. Supported types are "HTTP" and "TCP". | | `http` | _[ALSEnvoyProxyHTTPAccessLogConfig](#alsenvoyproxyhttpaccesslogconfig)_ | false | HTTP defines additional configuration specific to HTTP access logs. | @@ -174,6 +174,7 @@ _Appears in:_ BackendRef defines how an ObjectReference that is specific to BackendRef. _Appears in:_ +- [ALSEnvoyProxyAccessLog](#alsenvoyproxyaccesslog) - [OpenTelemetryEnvoyProxyAccessLog](#opentelemetryenvoyproxyaccesslog) - [ProxyOpenTelemetrySink](#proxyopentelemetrysink) - [TracingProvider](#tracingprovider) diff --git a/test/cel-validation/envoyproxy_test.go b/test/cel-validation/envoyproxy_test.go index 606adcc7550b..3b2785f713da 100644 --- a/test/cel-validation/envoyproxy_test.go +++ b/test/cel-validation/envoyproxy_test.go @@ -445,9 +445,13 @@ func TestEnvoyProxyProvider(t *testing.T) { { Type: egv1a1.ProxyAccessLogSinkTypeALS, ALS: &egv1a1.ALSEnvoyProxyAccessLog{ - BackendRef: gwapiv1.BackendObjectReference{ - Name: "fake-service", - Port: ptr.To(gwapiv1.PortNumber(9000)), + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Name: "fake-service", + Port: ptr.To(gwapiv1.PortNumber(9000)), + }, + }, }, Type: egv1a1.ALSEnvoyProxyAccessLogTypeHTTP, }, @@ -476,9 +480,13 @@ func TestEnvoyProxyProvider(t *testing.T) { { Type: egv1a1.ProxyAccessLogSinkTypeALS, ALS: &egv1a1.ALSEnvoyProxyAccessLog{ - BackendRef: gwapiv1.BackendObjectReference{ - Name: "fake-service", - Port: ptr.To(gwapiv1.PortNumber(9000)), + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Name: "fake-service", + Port: ptr.To(gwapiv1.PortNumber(9000)), + }, + }, }, Type: egv1a1.ALSEnvoyProxyAccessLogTypeTCP, HTTP: &egv1a1.ALSEnvoyProxyHTTPAccessLogConfig{}, @@ -494,7 +502,7 @@ func TestEnvoyProxyProvider(t *testing.T) { wantErrors: []string{"The http field may only be set when type is HTTP."}, }, { - desc: "invalid-accesslog-ALS-backendref", + desc: "invalid-accesslog-ALS-backendrefs", mutate: func(envoy *egv1a1.EnvoyProxy) { envoy.Spec = egv1a1.EnvoyProxySpec{ Telemetry: &egv1a1.ProxyTelemetry{ @@ -509,10 +517,114 @@ func TestEnvoyProxyProvider(t *testing.T) { { Type: egv1a1.ProxyAccessLogSinkTypeALS, ALS: &egv1a1.ALSEnvoyProxyAccessLog{ - BackendRef: gwapiv1.BackendObjectReference{ - Name: "fake-service", - Kind: ptr.To(gwapiv1.Kind("foo")), + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Name: "fake-service", + Kind: ptr.To(gwapiv1.Kind("foo")), + }, + }, + }, + Type: egv1a1.ALSEnvoyProxyAccessLogTypeHTTP, + }, + }, + }, + }, + }, + }, + }, + } + }, + wantErrors: []string{"BackendRefs only supports Service Kind."}, + }, + { + desc: "invalid-accesslog-ALS-no-backendrefs", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + Telemetry: &egv1a1.ProxyTelemetry{ + AccessLog: &egv1a1.ProxyAccessLog{ + Settings: []egv1a1.ProxyAccessLogSetting{ + { + Format: egv1a1.ProxyAccessLogFormat{ + Type: "Text", + Text: ptr.To("[%START_TIME%]"), + }, + Sinks: []egv1a1.ProxyAccessLogSink{ + { + Type: egv1a1.ProxyAccessLogSinkTypeALS, + ALS: &egv1a1.ALSEnvoyProxyAccessLog{ + Type: egv1a1.ALSEnvoyProxyAccessLogTypeHTTP, + }, + }, + }, + }, + }, + }, + }, + } + }, + wantErrors: []string{"Invalid value: \"null\""}, + }, + { + desc: "invalid-accesslog-ALS-empty-backendrefs", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + Telemetry: &egv1a1.ProxyTelemetry{ + AccessLog: &egv1a1.ProxyAccessLog{ + Settings: []egv1a1.ProxyAccessLogSetting{ + { + Format: egv1a1.ProxyAccessLogFormat{ + Type: "Text", + Text: ptr.To("[%START_TIME%]"), + }, + Sinks: []egv1a1.ProxyAccessLogSink{ + { + Type: egv1a1.ProxyAccessLogSinkTypeALS, + ALS: &egv1a1.ALSEnvoyProxyAccessLog{ + BackendRefs: []egv1a1.BackendRef{}, + Type: egv1a1.ALSEnvoyProxyAccessLogTypeHTTP, + }, + }, + }, + }, + }, + }, + }, + } + }, + wantErrors: []string{"should have at least 1 items"}, + }, + { + desc: "invalid-accesslog-ALS-multi-backendrefs", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + Telemetry: &egv1a1.ProxyTelemetry{ + AccessLog: &egv1a1.ProxyAccessLog{ + Settings: []egv1a1.ProxyAccessLogSetting{ + { + Format: egv1a1.ProxyAccessLogFormat{ + Type: "Text", + Text: ptr.To("[%START_TIME%]"), + }, + Sinks: []egv1a1.ProxyAccessLogSink{ + { + Type: egv1a1.ProxyAccessLogSinkTypeALS, + ALS: &egv1a1.ALSEnvoyProxyAccessLog{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Name: "fake-service", + Port: ptr.To(gwapiv1.PortNumber(8080)), + }, + }, + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Name: "fake-service", + Port: ptr.To(gwapiv1.PortNumber(8080)), + }, + }, }, + Type: egv1a1.ALSEnvoyProxyAccessLogTypeHTTP, }, }, }, @@ -522,7 +634,7 @@ func TestEnvoyProxyProvider(t *testing.T) { }, } }, - wantErrors: []string{"BackendRef only supports Service Kind."}, + wantErrors: []string{"must have at most 1 items"}, }, { desc: "accesslog-OpenTelemetry",