From 5b9ebea5bc513d14f1b48a4abf72a086d957d95b Mon Sep 17 00:00:00 2001 From: David Alger Date: Mon, 1 Apr 2024 09:28:50 -0500 Subject: [PATCH 1/6] api: gRPC Access Log Service (ALS) logging sink Signed-off-by: David Alger --- api/v1alpha1/accesslogging_types.go | 66 ++++++++- api/v1alpha1/zz_generated.deepcopy.go | 56 +++++++ .../gateway.envoyproxy.io_envoyproxies.yaml | 137 ++++++++++++++++++ site/content/en/latest/api/extension_types.md | 55 +++++++ 4 files changed, 313 insertions(+), 1 deletion(-) diff --git a/api/v1alpha1/accesslogging_types.go b/api/v1alpha1/accesslogging_types.go index 483a224fab4..64b6ea391d5 100644 --- a/api/v1alpha1/accesslogging_types.go +++ b/api/v1alpha1/accesslogging_types.go @@ -5,6 +5,8 @@ package v1alpha1 +import gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + type ProxyAccessLog struct { // Disable disables access logging for managed proxies if set to true. Disable bool `json:"disable,omitempty"` @@ -60,6 +62,10 @@ type ProxyAccessLogFormat struct { type ProxyAccessLogSinkType string const ( + // ProxyAccessLogSinkTypeALS defines the gRPC Access Log Service (ALS) sink. + // The service must implement the Envoy gRPC Access Log Service streaming API: + // https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/accesslog/v3/als.proto + ProxyAccessLogSinkTypeALS ProxyAccessLogSinkType = "ALS" // ProxyAccessLogSinkTypeFile defines the file accesslog sink. ProxyAccessLogSinkTypeFile ProxyAccessLogSinkType = "File" // ProxyAccessLogSinkTypeOpenTelemetry defines the OpenTelemetry accesslog sink. @@ -71,13 +77,17 @@ const ( // ProxyAccessLogSink defines the sink of accesslog. // +union // +// +kubebuilder:validation:XValidation:rule="self.type == 'ALS' ? has(self.als) : !has(self.als)",message="If AccessLogSink type is ALS, als field needs to be set." // +kubebuilder:validation:XValidation:rule="self.type == 'File' ? has(self.file) : !has(self.file)",message="If AccessLogSink type is File, file field needs to be set." // +kubebuilder:validation:XValidation:rule="self.type == 'OpenTelemetry' ? has(self.openTelemetry) : !has(self.openTelemetry)",message="If AccessLogSink type is OpenTelemetry, openTelemetry field needs to be set." type ProxyAccessLogSink struct { // Type defines the type of accesslog sink. - // +kubebuilder:validation:Enum=File;OpenTelemetry + // +kubebuilder:validation:Enum=ALS;File;OpenTelemetry // +unionDiscriminator Type ProxyAccessLogSinkType `json:"type,omitempty"` + // ALS defines the gRPC Access Log Service (ALS) sink. + // +optional + ALS *ALSEnvoyProxyAccessLog `json:"als,omitempty"` // File defines the file accesslog sink. // +optional File *FileEnvoyProxyAccessLog `json:"file,omitempty"` @@ -86,6 +96,60 @@ type ProxyAccessLogSink struct { OpenTelemetry *OpenTelemetryEnvoyProxyAccessLog `json:"openTelemetry,omitempty"` } +type ALSEnvoyProxyAccessLogType string + +const ( + // ALSEnvoyProxyAccessLogTypeHTTP defines the HTTP access log type and will populate StreamAccessLogsMessage.http_logs. + ALSEnvoyProxyAccessLogTypeHTTP ALSEnvoyProxyAccessLogType = "HTTP" + // ALSEnvoyProxyAccessLogTypeTCP defines the TCP access log type and will populate StreamAccessLogsMessage.tcp_logs. + ALSEnvoyProxyAccessLogTypeTCP ALSEnvoyProxyAccessLogType = "TCP" +) + +// ALSEnvoyProxyAccessLog defines the gRPC Access Log Service (ALS) sink. +// The service must implement the Envoy gRPC Access Log Service streaming API: +// https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/accesslog/v3/als.proto +// Access log format information is passed in the form of gRPC metadata when the +// stream is established. Specifically, the following metadata is passed: +// +// - `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 + // the access logs will be sent. Currently only Service is supported. + BackendRef gwapiv1.BackendObjectReference `json:"backendRef,omitempty"` + // 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. + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:default="accesslog" + LogName string `json:"logName,omitempty"` + // Type defines the type of accesslog. Supported types are "HTTP" and "TCP". Defaults to "HTTP" when not specified. + // +kubebuilder:validation:Enum=HTTP;TCP + // +unionDiscriminator + // +kubebuilder:default="HTTP" + Type ALSEnvoyProxyAccessLogType `json:"type,omitempty"` + // HTTP defines additional configuration specific to HTTP access logs. + // +optional + HTTP *ALSEnvoyProxyHTTPAccessLogConfig `json:"http,omitempty"` +} + +type ALSEnvoyProxyHTTPAccessLogConfig struct { + // RequestHeaders defines request headers to include in log entries sent to the access log service. + // +optional + RequestHeaders []string `json:"requestHeaders,omitempty" yaml:"requestHeaders,omitempty"` + // ResponseHeaders defines response headers to include in log entries sent to the access log service. + // +optional + ResponseHeaders []string `json:"responseHeaders,omitempty" yaml:"responseHeaders,omitempty"` + // ResponseTrailers defines response trailers to include in log entries sent to the access log service. + // +optional + ResponseTrailers []string `json:"responseTrailers,omitempty" yaml:"responseTrailers,omitempty"` +} + type FileEnvoyProxyAccessLog struct { // Path defines the file path used to expose envoy access log(e.g. /dev/stdout). // +kubebuilder:validation:MinLength=1 diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index d170dcf1f43..0d532b2be9f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -19,6 +19,57 @@ import ( "sigs.k8s.io/gateway-api/apis/v1" ) +// 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.HTTP != nil { + in, out := &in.HTTP, &out.HTTP + *out = new(ALSEnvoyProxyHTTPAccessLogConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ALSEnvoyProxyAccessLog. +func (in *ALSEnvoyProxyAccessLog) DeepCopy() *ALSEnvoyProxyAccessLog { + if in == nil { + return nil + } + out := new(ALSEnvoyProxyAccessLog) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ALSEnvoyProxyHTTPAccessLogConfig) DeepCopyInto(out *ALSEnvoyProxyHTTPAccessLogConfig) { + *out = *in + if in.RequestHeaders != nil { + in, out := &in.RequestHeaders, &out.RequestHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ResponseHeaders != nil { + in, out := &in.ResponseHeaders, &out.ResponseHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ResponseTrailers != nil { + in, out := &in.ResponseTrailers, &out.ResponseTrailers + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ALSEnvoyProxyHTTPAccessLogConfig. +func (in *ALSEnvoyProxyHTTPAccessLogConfig) DeepCopy() *ALSEnvoyProxyHTTPAccessLogConfig { + if in == nil { + return nil + } + out := new(ALSEnvoyProxyHTTPAccessLogConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ActiveHealthCheck) DeepCopyInto(out *ActiveHealthCheck) { *out = *in @@ -2804,6 +2855,11 @@ func (in *ProxyAccessLogSetting) DeepCopy() *ProxyAccessLogSetting { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProxyAccessLogSink) DeepCopyInto(out *ProxyAccessLogSink) { *out = *in + if in.ALS != nil { + in, out := &in.ALS, &out.ALS + *out = new(ALSEnvoyProxyAccessLog) + (*in).DeepCopyInto(*out) + } if in.File != nil { in, out := &in.File, &out.File *out = new(FileEnvoyProxyAccessLog) 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 4e092a8e739..98c2fcf0d68 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -5886,6 +5886,139 @@ spec: description: ProxyAccessLogSink defines the sink of accesslog. properties: + als: + description: ALS defines the gRPC Access Log Service + (ALS) sink. + properties: + backendRef: + description: |- + BackendRef 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". + + + 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. + + + 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. + + + 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 + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + http: + description: HTTP defines additional configuration + specific to HTTP access logs. + properties: + requestHeaders: + description: RequestHeaders defines request + headers to include in log entries sent + to the access log service. + items: + type: string + type: array + responseHeaders: + description: ResponseHeaders defines response + headers to include in log entries sent + to the access log service. + items: + type: string + type: array + responseTrailers: + description: ResponseTrailers defines + response trailers to include in log + entries sent to the access log service. + items: + type: string + type: array + type: object + logName: + default: accesslog + description: |- + 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. + minLength: 1 + type: string + type: + default: HTTP + description: Type defines the type of accesslog. + Supported types are "HTTP" and "TCP". Defaults + to "HTTP" when not specified. + enum: + - HTTP + - TCP + type: string + 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) file: description: File defines the file accesslog sink. properties: @@ -5924,11 +6057,15 @@ spec: description: Type defines the type of accesslog sink. enum: + - ALS - File - OpenTelemetry type: string type: object x-kubernetes-validations: + - message: If AccessLogSink type is ALS, als field + needs to be set. + rule: 'self.type == ''ALS'' ? has(self.als) : !has(self.als)' - message: If AccessLogSink type is File, file field needs to be set. rule: 'self.type == ''File'' ? has(self.file) : diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index a289a7530b8..6f69cfc2a89 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -40,6 +40,60 @@ _Appears in:_ +#### ALSEnvoyProxyAccessLog + + + +ALSEnvoyProxyAccessLog defines the gRPC Access Log Service (ALS) sink. +The service must implement the Envoy gRPC Access Log Service streaming API: +https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/accesslog/v3/als.proto +Access log format information is passed in the form of gRPC metadata when the +stream is established. Specifically, the following metadata is passed: + + +- `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. + +_Appears in:_ +- [ProxyAccessLogSink](#proxyaccesslogsink) + +| 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. | +| `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". Defaults to "HTTP" when not specified. | +| `http` | _[ALSEnvoyProxyHTTPAccessLogConfig](#alsenvoyproxyhttpaccesslogconfig)_ | false | HTTP defines additional configuration specific to HTTP access logs. | + + +#### ALSEnvoyProxyAccessLogType + +_Underlying type:_ _string_ + + + +_Appears in:_ +- [ALSEnvoyProxyAccessLog](#alsenvoyproxyaccesslog) + + + +#### ALSEnvoyProxyHTTPAccessLogConfig + + + + + +_Appears in:_ +- [ALSEnvoyProxyAccessLog](#alsenvoyproxyaccesslog) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `requestHeaders` | _string array_ | false | RequestHeaders defines request headers to include in log entries sent to the access log service. | +| `responseHeaders` | _string array_ | false | ResponseHeaders defines response headers to include in log entries sent to the access log service. | +| `responseTrailers` | _string array_ | false | ResponseTrailers defines response trailers to include in log entries sent to the access log service. | + + #### ActiveHealthCheck @@ -1974,6 +2028,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | | `type` | _[ProxyAccessLogSinkType](#proxyaccesslogsinktype)_ | true | Type defines the type of accesslog sink. | +| `als` | _[ALSEnvoyProxyAccessLog](#alsenvoyproxyaccesslog)_ | false | ALS defines the gRPC Access Log Service (ALS) sink. | | `file` | _[FileEnvoyProxyAccessLog](#fileenvoyproxyaccesslog)_ | false | File defines the file accesslog sink. | | `openTelemetry` | _[OpenTelemetryEnvoyProxyAccessLog](#opentelemetryenvoyproxyaccesslog)_ | false | OpenTelemetry defines the OpenTelemetry accesslog sink. | From f5c13a382b44050b2ff05f94569df4a836f4bc60 Mon Sep 17 00:00:00 2001 From: David Alger Date: Tue, 2 Apr 2024 09:25:45 -0500 Subject: [PATCH 2/6] Make type mandatory and remove kubebuilder default for LogName Signed-off-by: David Alger --- api/v1alpha1/accesslogging_types.go | 5 +---- api/v1alpha1/zz_generated.deepcopy.go | 5 +++++ .../crds/generated/gateway.envoyproxy.io_envoyproxies.yaml | 2 -- site/content/en/latest/api/extension_types.md | 2 -- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/api/v1alpha1/accesslogging_types.go b/api/v1alpha1/accesslogging_types.go index 3311c2f5eab..8d6690c02c6 100644 --- a/api/v1alpha1/accesslogging_types.go +++ b/api/v1alpha1/accesslogging_types.go @@ -112,7 +112,6 @@ const ( // stream is established. Specifically, the following metadata is passed: // // - `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'" @@ -126,12 +125,10 @@ type ALSEnvoyProxyAccessLog struct { // to differentiate between different access logs coming from the same Envoy. // +optional // +kubebuilder:validation:MinLength=1 - // +kubebuilder:default="accesslog" - LogName string `json:"logName,omitempty"` + LogName *string `json:"logName,omitempty"` // Type defines the type of accesslog. Supported types are "HTTP" and "TCP". Defaults to "HTTP" when not specified. // +kubebuilder:validation:Enum=HTTP;TCP // +unionDiscriminator - // +kubebuilder:default="HTTP" Type ALSEnvoyProxyAccessLogType `json:"type,omitempty"` // HTTP defines additional configuration specific to HTTP access logs. // +optional diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index f9a1f32ae57..445222b93fd 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -23,6 +23,11 @@ import ( func (in *ALSEnvoyProxyAccessLog) DeepCopyInto(out *ALSEnvoyProxyAccessLog) { *out = *in in.BackendRef.DeepCopyInto(&out.BackendRef) + if in.LogName != nil { + in, out := &in.LogName, &out.LogName + *out = new(string) + **out = **in + } if in.HTTP != nil { in, out := &in.HTTP, &out.HTTP *out = new(ALSEnvoyProxyHTTPAccessLogConfig) 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 20c439f0102..80ac072f6be 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -5995,7 +5995,6 @@ spec: type: array type: object logName: - default: accesslog description: |- LogName defines the friendly name of the access log to be returned in StreamAccessLogsMessage.Identifier. This allows the access log server @@ -6003,7 +6002,6 @@ spec: minLength: 1 type: string type: - default: HTTP description: Type defines the type of accesslog. Supported types are "HTTP" and "TCP". Defaults to "HTTP" when not specified. diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 74742c009c0..1a64a043138 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -52,8 +52,6 @@ stream is established. Specifically, the following metadata is passed: - `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. _Appears in:_ From 64890e9ec8c6e3edc8bc53119e4df656c638a8bb Mon Sep 17 00:00:00 2001 From: David Alger Date: Tue, 2 Apr 2024 11:02:21 -0500 Subject: [PATCH 3/6] Add CEL validations Signed-off-by: David Alger --- api/v1alpha1/accesslogging_types.go | 5 +- .../gateway.envoyproxy.io_envoyproxies.yaml | 3 + test/cel-validation/envoyproxy_test.go | 124 ++++++++++++++++++ 3 files changed, 129 insertions(+), 3 deletions(-) diff --git a/api/v1alpha1/accesslogging_types.go b/api/v1alpha1/accesslogging_types.go index 8d6690c02c6..726dbc50d09 100644 --- a/api/v1alpha1/accesslogging_types.go +++ b/api/v1alpha1/accesslogging_types.go @@ -119,7 +119,7 @@ const ( type ALSEnvoyProxyAccessLog struct { // BackendRef 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,omitempty"` + BackendRef gwapiv1.BackendObjectReference `json:"backendRef"` // 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. @@ -128,8 +128,7 @@ type ALSEnvoyProxyAccessLog struct { LogName *string `json:"logName,omitempty"` // Type defines the type of accesslog. Supported types are "HTTP" and "TCP". Defaults to "HTTP" when not specified. // +kubebuilder:validation:Enum=HTTP;TCP - // +unionDiscriminator - Type ALSEnvoyProxyAccessLogType `json:"type,omitempty"` + Type ALSEnvoyProxyAccessLogType `json:"type"` // HTTP defines additional configuration specific to HTTP access logs. // +optional HTTP *ALSEnvoyProxyHTTPAccessLogConfig `json:"http,omitempty"` 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 80ac072f6be..2ca621f1f64 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -6009,6 +6009,9 @@ spec: - HTTP - TCP type: string + required: + - backendRef + - type type: object x-kubernetes-validations: - message: BackendRef only supports Service Kind. diff --git a/test/cel-validation/envoyproxy_test.go b/test/cel-validation/envoyproxy_test.go index b7850a084ad..416fe2fe6b6 100644 --- a/test/cel-validation/envoyproxy_test.go +++ b/test/cel-validation/envoyproxy_test.go @@ -321,6 +321,33 @@ func TestEnvoyProxyProvider(t *testing.T) { }, wantErrors: []string{"If AccessLogFormat type is JSON, json field needs to be set"}, }, + { + desc: "ProxyAccessLogSink-with-TypeALS-but-no-als", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + Telemetry: &egv1a1.ProxyTelemetry{ + AccessLog: &egv1a1.ProxyAccessLog{ + Settings: []egv1a1.ProxyAccessLogSetting{ + { + Format: egv1a1.ProxyAccessLogFormat{ + Type: egv1a1.ProxyAccessLogFormatTypeJSON, + JSON: map[string]string{ + "foo": "bar", + }, + }, + Sinks: []egv1a1.ProxyAccessLogSink{ + { + Type: egv1a1.ProxyAccessLogSinkTypeALS, + }, + }, + }, + }, + }, + }, + } + }, + wantErrors: []string{"If AccessLogSink type is ALS, als field needs to be set"}, + }, { desc: "ProxyAccessLogSink-with-TypeFile-but-no-file", mutate: func(envoy *egv1a1.EnvoyProxy) { @@ -399,6 +426,103 @@ func TestEnvoyProxyProvider(t *testing.T) { }, wantErrors: []string{}, }, + { + desc: "accesslog-ALS", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + Telemetry: &egv1a1.ProxyTelemetry{ + AccessLog: &egv1a1.ProxyAccessLog{ + Settings: []egv1a1.ProxyAccessLogSetting{ + { + Format: egv1a1.ProxyAccessLogFormat{ + Type: egv1a1.ProxyAccessLogFormatTypeJSON, + JSON: map[string]string{ + "attr1": "value1", + "attr2": "value2", + }, + }, + Sinks: []egv1a1.ProxyAccessLogSink{ + { + Type: egv1a1.ProxyAccessLogSinkTypeALS, + ALS: &egv1a1.ALSEnvoyProxyAccessLog{ + BackendRef: gwapiv1.BackendObjectReference{ + Name: "fake-service", + Port: ptr.To(gwapiv1.PortNumber(9000)), + }, + }, + }, + }, + }, + }, + }, + }, + } + }, + }, + { + desc: "invalid-accesslog-ALS-type", + 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{ + BackendRef: gwapiv1.BackendObjectReference{ + Name: "fake-service", + Port: ptr.To(gwapiv1.PortNumber(9000)), + }, + Type: egv1a1.ALSEnvoyProxyAccessLogTypeTCP, + HTTP: &egv1a1.ALSEnvoyProxyHTTPAccessLogConfig{}, + }, + }, + }, + }, + }, + }, + }, + } + }, + wantErrors: []string{"The http field may only be set when type is HTTP."}, + }, + { + desc: "invalid-accesslog-ALS-backendref", + 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{ + BackendRef: gwapiv1.BackendObjectReference{ + Name: "fake-service", + Kind: ptr.To(gwapiv1.Kind("foo")), + }, + }, + }, + }, + }, + }, + }, + }, + } + }, + wantErrors: []string{"BackendRef only supports Service Kind."}, + }, { desc: "accesslog-OpenTelemetry", mutate: func(envoy *egv1a1.EnvoyProxy) { From a32679ee15e6c94c4e7ade4f6a8a27171d4fe9ea Mon Sep 17 00:00:00 2001 From: David Alger Date: Tue, 2 Apr 2024 11:11:46 -0500 Subject: [PATCH 4/6] Cleanup Signed-off-by: David Alger --- api/v1alpha1/accesslogging_types.go | 8 ++++---- .../generated/gateway.envoyproxy.io_envoyproxies.yaml | 3 +-- site/content/en/latest/api/extension_types.md | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/api/v1alpha1/accesslogging_types.go b/api/v1alpha1/accesslogging_types.go index 726dbc50d09..97acad15568 100644 --- a/api/v1alpha1/accesslogging_types.go +++ b/api/v1alpha1/accesslogging_types.go @@ -126,7 +126,7 @@ type ALSEnvoyProxyAccessLog struct { // +optional // +kubebuilder:validation:MinLength=1 LogName *string `json:"logName,omitempty"` - // Type defines the type of accesslog. Supported types are "HTTP" and "TCP". Defaults to "HTTP" when not specified. + // Type defines the type of accesslog. Supported types are "HTTP" and "TCP". // +kubebuilder:validation:Enum=HTTP;TCP Type ALSEnvoyProxyAccessLogType `json:"type"` // HTTP defines additional configuration specific to HTTP access logs. @@ -137,13 +137,13 @@ type ALSEnvoyProxyAccessLog struct { type ALSEnvoyProxyHTTPAccessLogConfig struct { // RequestHeaders defines request headers to include in log entries sent to the access log service. // +optional - RequestHeaders []string `json:"requestHeaders,omitempty" yaml:"requestHeaders,omitempty"` + RequestHeaders []string `json:"requestHeaders,omitempty"` // ResponseHeaders defines response headers to include in log entries sent to the access log service. // +optional - ResponseHeaders []string `json:"responseHeaders,omitempty" yaml:"responseHeaders,omitempty"` + ResponseHeaders []string `json:"responseHeaders,omitempty"` // ResponseTrailers defines response trailers to include in log entries sent to the access log service. // +optional - ResponseTrailers []string `json:"responseTrailers,omitempty" yaml:"responseTrailers,omitempty"` + ResponseTrailers []string `json:"responseTrailers,omitempty"` } type FileEnvoyProxyAccessLog struct { 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 2ca621f1f64..50220f93acd 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -6003,8 +6003,7 @@ spec: type: string type: description: Type defines the type of accesslog. - Supported types are "HTTP" and "TCP". Defaults - to "HTTP" when not specified. + Supported types are "HTTP" and "TCP". enum: - HTTP - TCP diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 1a64a043138..9caa9b9aa85 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -61,7 +61,7 @@ _Appears in:_ | --- | --- | --- | --- | | `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. | | `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". Defaults to "HTTP" when not specified. | +| `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. | From 21ae2a5290b629297963eb5d1aaa31b6b79a31de Mon Sep 17 00:00:00 2001 From: David Alger Date: Tue, 2 Apr 2024 11:14:11 -0500 Subject: [PATCH 5/6] Fix missing type in validation Signed-off-by: David Alger --- test/cel-validation/envoyproxy_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test/cel-validation/envoyproxy_test.go b/test/cel-validation/envoyproxy_test.go index 416fe2fe6b6..5c0f82c359f 100644 --- a/test/cel-validation/envoyproxy_test.go +++ b/test/cel-validation/envoyproxy_test.go @@ -449,6 +449,7 @@ func TestEnvoyProxyProvider(t *testing.T) { Name: "fake-service", Port: ptr.To(gwapiv1.PortNumber(9000)), }, + Type: egv1a1.ALSEnvoyProxyAccessLogTypeHTTP, }, }, }, From dd46082de63ba25a92451c572fec106bc5486a1c Mon Sep 17 00:00:00 2001 From: David Alger Date: Thu, 11 Apr 2024 09:09:51 -0500 Subject: [PATCH 6/6] Pluralize backendRefs Signed-off-by: David Alger --- 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 12e9dba405f..e1311566eeb 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 3de1bfb0f46..1b0a97fa77a 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 3413f385e64..76d96eccaea 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 02678a32dde..e5b4dae59af 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 606adcc7550..3b2785f713d 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",