From fc0fac8543457a71e7b8b9f663bcc6f76c34296f Mon Sep 17 00:00:00 2001 From: Nelson Isioma Date: Thu, 21 Aug 2025 10:40:06 +0100 Subject: [PATCH 001/134] Add passive health checks --- .../dynamic-configuration/docker-labels.yml | 2 + .../reference/dynamic-configuration/file.toml | 3 + .../reference/dynamic-configuration/file.yaml | 3 + .../kubernetes-crd-definition-v1.yml | 95 ++++++++++ .../reference/dynamic-configuration/kv-ref.md | 2 + .../traefik.io_ingressroutes.yaml | 19 ++ .../traefik.io_middlewares.yaml | 19 ++ .../traefik.io_traefikservices.yaml | 57 ++++++ .../http/load-balancing/service.md | 34 +++- .../fixtures/healthcheck/simple_passive.toml | 31 ++++ integration/fixtures/k8s/01-traefik-crd.yml | 95 ++++++++++ integration/healthcheck_test.go | 47 +++++ pkg/config/dynamic/http_config.go | 24 ++- pkg/config/dynamic/zz_generated.deepcopy.go | 21 +++ pkg/healthcheck/healthcheck.go | 162 ++++++++++++++++++ .../kubernetes/crd/kubernetes_http.go | 15 ++ .../crd/traefikio/v1alpha1/ingressroute.go | 9 + .../v1alpha1/zz_generated.deepcopy.go | 31 ++++ pkg/server/service/service.go | 18 +- pkg/server/service/service_test.go | 15 ++ 20 files changed, 696 insertions(+), 6 deletions(-) create mode 100644 integration/fixtures/healthcheck/simple_passive.toml diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index 3399c2762..42bbed1da 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -213,6 +213,8 @@ - "traefik.http.services.service02.loadbalancer.healthcheck.timeout=42s" - "traefik.http.services.service02.loadbalancer.healthcheck.unhealthyinterval=42s" - "traefik.http.services.service02.loadbalancer.passhostheader=true" +- "traefik.http.services.service02.loadbalancer.passivehealthcheck.failurewindow=42s" +- "traefik.http.services.service02.loadbalancer.passivehealthcheck.maxfailedattempts=42" - "traefik.http.services.service02.loadbalancer.responseforwarding.flushinterval=42s" - "traefik.http.services.service02.loadbalancer.serverstransport=foobar" - "traefik.http.services.service02.loadbalancer.sticky=true" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index b7f30c649..14d9753ce 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -93,6 +93,9 @@ [http.services.Service02.loadBalancer.healthCheck.headers] name0 = "foobar" name1 = "foobar" + [http.services.Service02.loadBalancer.passiveHealthCheck] + failureWindow = "42s" + maxFailedAttempts = 42 [http.services.Service02.loadBalancer.responseForwarding] flushInterval = "42s" [http.services.Service03] diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index 2ac73fd85..99a79e291 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -98,6 +98,9 @@ http: headers: name0: foobar name1: foobar + passiveHealthCheck: + failureWindow: 42s + maxFailedAttempts: 42 passHostHeader: true responseForwarding: flushInterval: 42s diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index c4f6dc076..bc4689191 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -227,6 +227,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health + checks for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window + during which the failed attempts must occur for + the server to be marked as unhealthy. It also defines + for how long the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window + before marking the server as unhealthy. + type: integer + type: object port: anyOf: - type: integer @@ -1169,6 +1188,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server + to be marked as unhealthy. It also defines for how long + the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window before + marking the server as unhealthy. + type: integer + type: object port: anyOf: - type: integer @@ -2944,6 +2982,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server + to be marked as unhealthy. It also defines for how + long the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window + before marking the server as unhealthy. + type: integer + type: object percent: description: |- Percent defines the part of the traffic to mirror. @@ -3078,6 +3135,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server to be + marked as unhealthy. It also defines for how long the server + will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window before + marking the server as unhealthy. + type: integer + type: object port: anyOf: - type: integer @@ -3290,6 +3366,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server + to be marked as unhealthy. It also defines for how + long the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window + before marking the server as unhealthy. + type: integer + type: object port: anyOf: - type: integer diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index a7c1b217f..8c0737abb 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -288,6 +288,8 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/services/Service02/loadBalancer/healthCheck/timeout` | `42s` | | `traefik/http/services/Service02/loadBalancer/healthCheck/unhealthyInterval` | `42s` | | `traefik/http/services/Service02/loadBalancer/passHostHeader` | `true` | +| `traefik/http/services/Service02/loadBalancer/passiveHealthCheck/failureWindow` | `42s` | +| `traefik/http/services/Service02/loadBalancer/passiveHealthCheck/maxFailedAttempts` | `42` | | `traefik/http/services/Service02/loadBalancer/responseForwarding/flushInterval` | `42s` | | `traefik/http/services/Service02/loadBalancer/servers/0/preservePath` | `true` | | `traefik/http/services/Service02/loadBalancer/servers/0/url` | `foobar` | diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml index 46cc78976..0ffc11dfb 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml @@ -227,6 +227,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health + checks for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window + during which the failed attempts must occur for + the server to be marked as unhealthy. It also defines + for how long the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window + before marking the server as unhealthy. + type: integer + type: object port: anyOf: - type: integer diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index d658c6602..97135b70d 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -381,6 +381,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server + to be marked as unhealthy. It also defines for how long + the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window before + marking the server as unhealthy. + type: integer + type: object port: anyOf: - type: integer diff --git a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml index 1e6fec094..1ed24d021 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml @@ -245,6 +245,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server + to be marked as unhealthy. It also defines for how + long the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window + before marking the server as unhealthy. + type: integer + type: object percent: description: |- Percent defines the part of the traffic to mirror. @@ -379,6 +398,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server to be + marked as unhealthy. It also defines for how long the server + will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window before + marking the server as unhealthy. + type: integer + type: object port: anyOf: - type: integer @@ -591,6 +629,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server + to be marked as unhealthy. It also defines for how + long the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window + before marking the server as unhealthy. + type: integer + type: object port: anyOf: - type: integer diff --git a/docs/content/reference/routing-configuration/http/load-balancing/service.md b/docs/content/reference/routing-configuration/http/load-balancing/service.md index b99a5b3c3..fb5e0173d 100644 --- a/docs/content/reference/routing-configuration/http/load-balancing/service.md +++ b/docs/content/reference/routing-configuration/http/load-balancing/service.md @@ -27,6 +27,9 @@ http: path: "/health" interval: "10s" timeout: "3s" + passiveHealthcheck: + failureWindow: "3s" + maxFailedAttempts: "3" passHostHeader: true serversTransport: "customTransport@file" responseForwarding: @@ -46,6 +49,10 @@ http: path = "/health" interval = "10s" timeout = "3s" + + [http.services.my-service.loadBalancer.passiveHealthcheck] + failureWindow = "3s" + maxFailedAttempts = "3" passHostHeader = true serversTransport = "customTransport@file" @@ -63,6 +70,8 @@ labels: - "traefik.http.services.my-service.loadBalancer.healthcheck.path=/health" - "traefik.http.services.my-service.loadBalancer.healthcheck.interval=10s" - "traefik.http.services.my-service.loadBalancer.healthcheck.timeout=3s" + - "traefik.http.services.my-service.loadBalancer.passiveHealthcheck.failureWindow=3s" + - "traefik.http.services.my-service.loadBalancer.passiveHealthcheck.maxFailedAttempts=3" - "traefik.http.services.my-service.loadBalancer.passHostHeader=true" - "traefik.http.services.my-service.loadBalancer.serversTransport=customTransport@file" - "traefik.http.services.my-service.loadBalancer.responseForwarding.flushInterval=150ms" @@ -78,6 +87,8 @@ labels: "traefik.http.services.my-service.loadBalancer.healthcheck.path=/health", "traefik.http.services.my-service.loadBalancer.healthcheck.interval=10s", "traefik.http.services.my-service.loadBalancer.healthcheck.timeout=3s", + "traefik.http.services.my-service.loadBalancer.passiveHealthcheck.failureWindow=3s", + "traefik.http.services.my-service.loadBalancer.passiveHealthcheck.maxFailedAttempts=3", "traefik.http.services.my-service.loadBalancer.passHostHeader=true", "traefik.http.services.my-service.loadBalancer.serversTransport=customTransport@file", "traefik.http.services.my-service.loadBalancer.responseForwarding.flushInterval=150ms" @@ -92,6 +103,7 @@ labels: | `servers` | Represents individual backend instances for your service | Yes | | `sticky` | Defines a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response. | No | | `healthcheck` | Configures health check to remove unhealthy servers from the load balancing rotation. | No | +| `passiveHealthcheck` | Configures the passive health check to remove unhealthy servers from the load balancing rotation. | No | | `passHostHeader` | Allows forwarding of the client Host header to server. By default, `passHostHeader` is true. | No | | `serversTransport` | Allows to reference an [HTTP ServersTransport](./serverstransport.md) configuration for the communication between Traefik and your servers. If no `serversTransport` is specified, the `default@internal` will be used. | No | | `responseForwarding` | Configures how Traefik forwards the response from the backend server to the client. | No | @@ -111,7 +123,9 @@ Servers represent individual backend instances for your service. The [service lo #### Health Check -The `healthcheck` option configures health check to remove unhealthy servers from the load balancing rotation. Traefik will consider HTTP(s) servers healthy as long as they return a status code to the health check request (carried out every interval) between `2XX` and `3XX`, or matching the configured status. For gRPC servers, Traefik will consider them healthy as long as they return SERVING to [gRPC health check v1 requests](https://github.com/grpc/grpc/blob/master/doc/health-checking.md). +The `healthcheck` option configures health check to remove unhealthy servers from the load balancing rotation. +Traefik will consider HTTP(s) servers healthy as long as they return a status code to the health check request (carried out every interval) between `2XX` and `3XX`, or matching the configured status. +For gRPC servers, Traefik will consider them healthy as long as they return SERVING to [gRPC health check v1 requests](https://github.com/grpc/grpc/blob/master/doc/health-checking.md). To propagate status changes (e.g. all servers of this service are down) upwards, HealthCheck must also be enabled on the parent(s) of this service. @@ -133,6 +147,24 @@ Below are the available options for the health check mechanism: | `method` | Defines the HTTP method that will be used while connecting to the endpoint. | GET | No | | `status` | Defines the expected HTTP status code of the response to the health check request. | | No | +#### Passive Health Check + +The `passiveHealthcheck` option configures passive health check to remove unhealthy servers from the load balancing rotation. + +Passive health checks rely on real traffic to assess server health. +Traefik forwards requests as usual and evaluates each response or timeout, +incrementing a failure counter whenever a request fails. +If the number of successive failures within a specified time window exceeds the configured threshold, +Traefik will automatically stop routing traffic to that server until it recovers. +A server will be considered healthy again after the configured failure window has passed. + +Below are the available options for the passive health check mechanism: + +| Field | Description | Default | Required | +|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|----------| +| `failureWindow` | Defines the time window during which the failed attempts must occur for the server to be marked as unhealthy. It also defines for how long the server will be considered unhealthy. | 10s | No | +| `maxFailedAttempts` | Defines the number of consecutive failed attempts allowed within the failure window before marking the server as unhealthy. | 1 | No | + ## Weighted Round Robin (WRR) The WRR is able to load balance the requests between multiple services based on weights. diff --git a/integration/fixtures/healthcheck/simple_passive.toml b/integration/fixtures/healthcheck/simple_passive.toml new file mode 100644 index 000000000..02fb698b4 --- /dev/null +++ b/integration/fixtures/healthcheck/simple_passive.toml @@ -0,0 +1,31 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + noColor = true + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[api] + insecure = true + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + [http.routers.router1] + service = "service1" + rule = "Host(`test.localhost`)" + +[http.services] + [http.services.service1.loadBalancer] + [http.services.service1.loadBalancer.passiveHealthCheck] + failureWindow = "2s" + [[http.services.service1.loadBalancer.servers]] + url = "http://{{.Server1}}:80" diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index c4f6dc076..bc4689191 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -227,6 +227,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health + checks for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window + during which the failed attempts must occur for + the server to be marked as unhealthy. It also defines + for how long the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window + before marking the server as unhealthy. + type: integer + type: object port: anyOf: - type: integer @@ -1169,6 +1188,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server + to be marked as unhealthy. It also defines for how long + the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window before + marking the server as unhealthy. + type: integer + type: object port: anyOf: - type: integer @@ -2944,6 +2982,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server + to be marked as unhealthy. It also defines for how + long the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window + before marking the server as unhealthy. + type: integer + type: object percent: description: |- Percent defines the part of the traffic to mirror. @@ -3078,6 +3135,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server to be + marked as unhealthy. It also defines for how long the server + will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window before + marking the server as unhealthy. + type: integer + type: object port: anyOf: - type: integer @@ -3290,6 +3366,25 @@ spec: PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. By default, passHostHeader is true. type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server + to be marked as unhealthy. It also defines for how + long the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window + before marking the server as unhealthy. + type: integer + type: object port: anyOf: - type: integer diff --git a/integration/healthcheck_test.go b/integration/healthcheck_test.go index 1276f09dd..6ab2ac618 100644 --- a/integration/healthcheck_test.go +++ b/integration/healthcheck_test.go @@ -108,6 +108,53 @@ func (s *HealthCheckSuite) TestSimpleConfiguration() { assert.Equal(s.T(), http.StatusNotFound, resp.StatusCode) } +func (s *HealthCheckSuite) TestSimpleConfiguration_Passive() { + file := s.adaptFile("fixtures/healthcheck/simple_passive.toml", struct { + Server1 string + }{s.whoami1IP}) + + s.traefikCmd(withConfigFile(file)) + + // wait for traefik + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) + require.NoError(s.T(), err) + + frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) + require.NoError(s.T(), err) + frontendHealthReq.Host = "test.localhost" + + err = try.Request(frontendHealthReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + + // Fix all whoami health to 500 + client := &http.Client{} + whoamiHosts := []string{s.whoami1IP, s.whoami2IP} + for _, whoami := range whoamiHosts { + statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500")) + require.NoError(s.T(), err) + _, err = client.Do(statusInternalServerErrorReq) + require.NoError(s.T(), err) + } + + // First call, the passive health check is not yet triggered, so we expect a 500. + err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusInternalServerError)) + require.NoError(s.T(), err) + + // Verify no backend service is available due to failing health checks + err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) + require.NoError(s.T(), err) + + // Change one whoami health to 200 + statusOKReq1, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200")) + require.NoError(s.T(), err) + _, err = client.Do(statusOKReq1) + require.NoError(s.T(), err) + + // Verify frontend health : after + err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) +} + func (s *HealthCheckSuite) TestMultipleEntrypoints() { file := s.adaptFile("fixtures/healthcheck/multiple-entrypoints.toml", struct { Server1 string diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index ef6a896f1..a60f59713 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -244,10 +244,12 @@ type ServersLoadBalancer struct { // children servers of this load-balancer. To propagate status changes (e.g. all // servers of this service are down) upwards, HealthCheck must also be enabled on // the parent(s) of this service. - HealthCheck *ServerHealthCheck `json:"healthCheck,omitempty" toml:"healthCheck,omitempty" yaml:"healthCheck,omitempty" export:"true"` - PassHostHeader *bool `json:"passHostHeader" toml:"passHostHeader" yaml:"passHostHeader" export:"true"` - ResponseForwarding *ResponseForwarding `json:"responseForwarding,omitempty" toml:"responseForwarding,omitempty" yaml:"responseForwarding,omitempty" export:"true"` - ServersTransport string `json:"serversTransport,omitempty" toml:"serversTransport,omitempty" yaml:"serversTransport,omitempty" export:"true"` + HealthCheck *ServerHealthCheck `json:"healthCheck,omitempty" toml:"healthCheck,omitempty" yaml:"healthCheck,omitempty" export:"true"` + // PassiveHealthCheck enables passive health checks for children servers of this load-balancer. + PassiveHealthCheck *PassiveServerHealthCheck `json:"passiveHealthCheck,omitempty" toml:"passiveHealthCheck,omitempty" yaml:"passiveHealthCheck,omitempty" export:"true"` + PassHostHeader *bool `json:"passHostHeader" toml:"passHostHeader" yaml:"passHostHeader" export:"true"` + ResponseForwarding *ResponseForwarding `json:"responseForwarding,omitempty" toml:"responseForwarding,omitempty" yaml:"responseForwarding,omitempty" export:"true"` + ServersTransport string `json:"serversTransport,omitempty" toml:"serversTransport,omitempty" yaml:"serversTransport,omitempty" export:"true"` } // Mergeable tells if the given service is mergeable. @@ -336,6 +338,20 @@ func (h *ServerHealthCheck) SetDefaults() { // +k8s:deepcopy-gen=true +type PassiveServerHealthCheck struct { + // FailureWindow defines the time window during which the failed attempts must occur for the server to be marked as unhealthy. It also defines for how long the server will be considered unhealthy. + FailureWindow ptypes.Duration `json:"failureWindow,omitempty" toml:"failureWindow,omitempty" yaml:"failureWindow,omitempty" export:"true"` + // MaxFailedAttempts is the number of consecutive failed attempts allowed within the failure window before marking the server as unhealthy. + MaxFailedAttempts int `json:"maxFailedAttempts,omitempty" toml:"maxFailedAttempts,omitempty" yaml:"maxFailedAttempts,omitempty" export:"true"` +} + +func (p *PassiveServerHealthCheck) SetDefaults() { + p.FailureWindow = ptypes.Duration(10 * time.Second) + p.MaxFailedAttempts = 1 +} + +// +k8s:deepcopy-gen=true + // HealthCheck controls healthcheck awareness and propagation at the services level. type HealthCheck struct{} diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index 97f8907e0..94d0f11a8 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -1071,6 +1071,22 @@ func (in *PassTLSClientCert) DeepCopy() *PassTLSClientCert { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PassiveServerHealthCheck) DeepCopyInto(out *PassiveServerHealthCheck) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PassiveServerHealthCheck. +func (in *PassiveServerHealthCheck) DeepCopy() *PassiveServerHealthCheck { + if in == nil { + return nil + } + out := new(PassiveServerHealthCheck) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProxyProtocol) DeepCopyInto(out *ProxyProtocol) { *out = *in @@ -1478,6 +1494,11 @@ func (in *ServersLoadBalancer) DeepCopyInto(out *ServersLoadBalancer) { *out = new(ServerHealthCheck) (*in).DeepCopyInto(*out) } + if in.PassiveHealthCheck != nil { + in, out := &in.PassiveHealthCheck, &out.PassiveHealthCheck + *out = new(PassiveServerHealthCheck) + **out = **in + } if in.PassHostHeader != nil { in, out := &in.PassHostHeader, &out.PassHostHeader *out = new(bool) diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go index e2679d73c..e1be3343e 100644 --- a/pkg/healthcheck/healthcheck.go +++ b/pkg/healthcheck/healthcheck.go @@ -1,19 +1,24 @@ package healthcheck import ( + "bufio" "context" "errors" "fmt" "net" "net/http" + "net/http/httptrace" "net/url" "strconv" + "sync" "time" gokitmetrics "github.com/go-kit/kit/metrics" "github.com/rs/zerolog/log" + ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" + "golang.org/x/sync/singleflight" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" @@ -322,3 +327,160 @@ func (shc *ServiceHealthChecker) checkHealthGRPC(ctx context.Context, serverURL return nil } + +type PassiveServiceHealthChecker struct { + serviceName string + balancer StatusSetter + metrics metricsHealthCheck + + maxFailedAttempts int + failureWindow ptypes.Duration + hasActiveHealthCheck bool + + failuresMu sync.RWMutex + failures map[string][]time.Time + + timersGroup singleflight.Group + timers sync.Map +} + +func NewPassiveHealthChecker(serviceName string, balancer StatusSetter, maxFailedAttempts int, failureWindow ptypes.Duration, hasActiveHealthCheck bool, metrics metricsHealthCheck) *PassiveServiceHealthChecker { + return &PassiveServiceHealthChecker{ + serviceName: serviceName, + balancer: balancer, + failures: make(map[string][]time.Time), + maxFailedAttempts: maxFailedAttempts, + failureWindow: failureWindow, + hasActiveHealthCheck: hasActiveHealthCheck, + metrics: metrics, + } +} + +func (p *PassiveServiceHealthChecker) WrapHandler(ctx context.Context, next http.Handler, targetURL string) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + var backendCalled bool + trace := &httptrace.ClientTrace{ + WroteHeaders: func() { + backendCalled = true + }, + WroteRequest: func(httptrace.WroteRequestInfo) { + backendCalled = true + }, + } + clientTraceCtx := httptrace.WithClientTrace(req.Context(), trace) + + codeCatcher := &codeCatcher{ + ResponseWriter: rw, + } + + next.ServeHTTP(codeCatcher, req.WithContext(clientTraceCtx)) + + if backendCalled && codeCatcher.statusCode < http.StatusInternalServerError { + p.failuresMu.Lock() + p.failures[targetURL] = nil + p.failuresMu.Unlock() + return + } + + p.failuresMu.Lock() + p.failures[targetURL] = append(p.failures[targetURL], time.Now()) + p.failuresMu.Unlock() + + if p.healthy(targetURL) { + return + } + + // We need to guarantee that only one goroutine (request) will update the status and create a timer for the target. + _, _, _ = p.timersGroup.Do(targetURL, func() (interface{}, error) { + // A timer is already running for this target; + // it means that the target is already considered unhealthy. + if _, ok := p.timers.Load(targetURL); ok { + return nil, nil + } + + p.balancer.SetStatus(ctx, targetURL, false) + p.metrics.ServiceServerUpGauge().With("service", p.serviceName, "url", targetURL).Set(0) + + // If the service has an active health check, the passive health checker should not reset the status. + // The active health check will handle the status updates. + if p.hasActiveHealthCheck { + return nil, nil + } + + go func() { + timer := time.NewTimer(time.Duration(p.failureWindow)) + defer timer.Stop() + + p.timers.Store(targetURL, timer) + + select { + case <-ctx.Done(): + case <-timer.C: + p.timers.Delete(targetURL) + + p.balancer.SetStatus(ctx, targetURL, true) + p.metrics.ServiceServerUpGauge().With("service", p.serviceName, "url", targetURL).Set(1) + } + }() + + return nil, nil + }) + }) +} + +func (p *PassiveServiceHealthChecker) healthy(targetURL string) bool { + windowStart := time.Now().Add(-time.Duration(p.failureWindow)) + + p.failuresMu.Lock() + defer p.failuresMu.Unlock() + + // Filter failures within the sliding window. + failures := p.failures[targetURL] + for i, t := range failures { + if t.After(windowStart) { + p.failures[targetURL] = failures[i:] + break + } + } + + // Check if failures exceed maxFailedAttempts. + return len(p.failures[targetURL]) < p.maxFailedAttempts +} + +type codeCatcher struct { + http.ResponseWriter + + statusCode int +} + +func (c *codeCatcher) WriteHeader(statusCode int) { + // Here we allow the overriding of the status code, + // for the health check we care about the last status code written. + c.statusCode = statusCode + c.ResponseWriter.WriteHeader(statusCode) +} + +func (c *codeCatcher) Write(bytes []byte) (int, error) { + // At the time of writing, if the status code is not set, + // or set to an informational status code (1xx), + // we set it to http.StatusOK (200). + if c.statusCode < http.StatusOK { + c.statusCode = http.StatusOK + } + + return c.ResponseWriter.Write(bytes) +} + +func (c *codeCatcher) Flush() { + if flusher, ok := c.ResponseWriter.(http.Flusher); ok { + flusher.Flush() + } +} + +func (c *codeCatcher) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if h, ok := c.ResponseWriter.(http.Hijacker); ok { + return h.Hijack() + } + + return nil, nil, fmt.Errorf("not a hijacker: %T", c.ResponseWriter) +} diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 59dfbd4a5..09c206a74 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -392,6 +392,21 @@ func (c configBuilder) buildServersLB(namespace string, svc traefikv1alpha1.Load } } + if svc.PassiveHealthCheck != nil { + lb.PassiveHealthCheck = &dynamic.PassiveServerHealthCheck{} + lb.PassiveHealthCheck.SetDefaults() + + if svc.PassiveHealthCheck.MaxFailedAttempts != nil { + lb.PassiveHealthCheck.MaxFailedAttempts = *svc.PassiveHealthCheck.MaxFailedAttempts + } + + if svc.PassiveHealthCheck.FailureWindow != nil { + if err := lb.PassiveHealthCheck.FailureWindow.Set(svc.PassiveHealthCheck.FailureWindow.String()); err != nil { + return nil, err + } + } + } + conf := svc lb.PassHostHeader = conf.PassHostHeader if lb.PassHostHeader == nil { diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go index a717d6cdd..fb5513e4c 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go @@ -144,6 +144,8 @@ type LoadBalancerSpec struct { NodePortLB bool `json:"nodePortLB,omitempty"` // Healthcheck defines health checks for ExternalName services. HealthCheck *ServerHealthCheck `json:"healthCheck,omitempty"` + // PassiveHealthCheck defines passive health checks for ExternalName services. + PassiveHealthCheck *PassiveServerHealthCheck `json:"passiveHealthCheck,omitempty"` } type ResponseForwarding struct { @@ -189,6 +191,13 @@ type ServerHealthCheck struct { Headers map[string]string `json:"headers,omitempty"` } +type PassiveServerHealthCheck struct { + // FailureWindow defines the time window during which the failed attempts must occur for the server to be marked as unhealthy. It also defines for how long the server will be considered unhealthy. + FailureWindow *intstr.IntOrString `json:"failureWindow,omitempty"` + // MaxFailedAttempts is the number of consecutive failed attempts allowed within the failure window before marking the server as unhealthy. + MaxFailedAttempts *int `json:"maxFailedAttempts,omitempty"` +} + // Service defines an upstream HTTP service to proxy traffic to. type Service struct { LoadBalancerSpec `json:",inline"` diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go index 3e53cfae5..6b99e8fa7 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go @@ -657,6 +657,11 @@ func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) { *out = new(ServerHealthCheck) (*in).DeepCopyInto(*out) } + if in.PassiveHealthCheck != nil { + in, out := &in.PassiveHealthCheck, &out.PassiveHealthCheck + *out = new(PassiveServerHealthCheck) + (*in).DeepCopyInto(*out) + } return } @@ -1047,6 +1052,32 @@ func (in *ObjectReference) DeepCopy() *ObjectReference { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PassiveServerHealthCheck) DeepCopyInto(out *PassiveServerHealthCheck) { + *out = *in + if in.FailureWindow != nil { + in, out := &in.FailureWindow, &out.FailureWindow + *out = new(intstr.IntOrString) + **out = **in + } + if in.MaxFailedAttempts != nil { + in, out := &in.MaxFailedAttempts, &out.MaxFailedAttempts + *out = new(int) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PassiveServerHealthCheck. +func (in *PassiveServerHealthCheck) DeepCopy() *PassiveServerHealthCheck { + if in == nil { + return nil + } + out := new(PassiveServerHealthCheck) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RateLimit) DeepCopyInto(out *RateLimit) { *out = *in diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index b1bc6d8e9..6967f6e7b 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -341,7 +341,7 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName var lb serverBalancer switch service.Strategy { // Here we are handling the empty value to comply with providers that are not applying defaults (e.g. REST provider) - // TODO: remove this when all providers apply default values. + // TODO: remove this empty check when all providers apply default values. case dynamic.BalancerStrategyWRR, "": lb = wrr.New(service.Sticky, service.HealthCheck != nil) case dynamic.BalancerStrategyP2C: @@ -350,6 +350,17 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName return nil, fmt.Errorf("unsupported load-balancer strategy %q", service.Strategy) } + var passiveHealthChecker *healthcheck.PassiveServiceHealthChecker + if service.PassiveHealthCheck != nil { + passiveHealthChecker = healthcheck.NewPassiveHealthChecker( + serviceName, + lb, + service.PassiveHealthCheck.MaxFailedAttempts, + service.PassiveHealthCheck.FailureWindow, + service.HealthCheck != nil, + m.observabilityMgr.MetricsRegistry()) + } + healthCheckTargets := make(map[string]*url.URL) for i, server := range shuffle(service.Servers, m.rand) { @@ -368,6 +379,11 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err) } + if passiveHealthChecker != nil { + // If passive health check is enabled, we wrap the proxy with the passive health checker. + proxy = passiveHealthChecker.WrapHandler(ctx, proxy, target.String()) + } + // The retry wrapping must be done just before the proxy handler, // to make sure that the retry will not be triggered/disabled by // middlewares in the chain. diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go index 7d00a69fb..4952dde12 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -10,9 +10,11 @@ import ( "net/textproto" "strings" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/proxy/httputil" @@ -67,6 +69,19 @@ func TestGetLoadBalancer(t *testing.T) { fwd: &forwarderMock{}, expectError: false, }, + { + desc: "Succeeds when passive health checker is set", + serviceName: "test", + service: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + PassiveHealthCheck: &dynamic.PassiveServerHealthCheck{ + FailureWindow: ptypes.Duration(30 * time.Second), + MaxFailedAttempts: 3, + }, + }, + fwd: &forwarderMock{}, + expectError: false, + }, } for _, test := range testCases { From 9b42b5b9306d333ed3a5300e76040ca0b9ae68b1 Mon Sep 17 00:00:00 2001 From: 0slb <32650698+0slb@users.noreply.github.com> Date: Wed, 27 Aug 2025 15:20:04 +0200 Subject: [PATCH 002/134] Fix broken link to migration guide on readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fae31b955..923943300 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Pointing Traefik at your orchestrator should be the _only_ configuration step yo --- -:warning: When migrating to a new major version of Traefik, please refer to the [migration guide](https://doc.traefik.io/traefik/migration/v2-to-v3/) to ensure a smooth transition and to be aware of any breaking changes. +:warning: When migrating to a new major version of Traefik, please refer to the [migration guide](https://doc.traefik.io/traefik/migrate/v2-to-v3/) to ensure a smooth transition and to be aware of any breaking changes. ## Overview From 02443545e743f00821098def5f7e0f30d24e3e21 Mon Sep 17 00:00:00 2001 From: mathieuHa Date: Mon, 8 Sep 2025 12:00:42 +0200 Subject: [PATCH 003/134] Add HighestRandomWeight Loadbalancing Algorithm --- .../dynamic-configuration/docker-labels.yml | 66 ++-- .../reference/dynamic-configuration/file.toml | 53 +-- .../reference/dynamic-configuration/file.yaml | 12 +- .../reference/dynamic-configuration/kv-ref.md | 109 ++++--- docs/content/routing/services/index.md | 170 ++++++++++ pkg/config/dynamic/http_config.go | 38 ++- pkg/config/dynamic/zz_generated.deepcopy.go | 54 ++++ pkg/server/service/loadbalancer/hrw/hrw.go | 200 ++++++++++++ .../service/loadbalancer/hrw/hrw_test.go | 302 ++++++++++++++++++ pkg/server/service/service.go | 44 +++ 10 files changed, 936 insertions(+), 112 deletions(-) create mode 100644 pkg/server/service/loadbalancer/hrw/hrw.go create mode 100644 pkg/server/service/loadbalancer/hrw/hrw_test.go diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index 42bbed1da..b2881a594 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -199,39 +199,39 @@ - "traefik.http.routers.router1.tls.domains[1].main=foobar" - "traefik.http.routers.router1.tls.domains[1].sans=foobar, foobar" - "traefik.http.routers.router1.tls.options=foobar" -- "traefik.http.services.service02.loadbalancer.healthcheck.followredirects=true" -- "traefik.http.services.service02.loadbalancer.healthcheck.headers.name0=foobar" -- "traefik.http.services.service02.loadbalancer.healthcheck.headers.name1=foobar" -- "traefik.http.services.service02.loadbalancer.healthcheck.hostname=foobar" -- "traefik.http.services.service02.loadbalancer.healthcheck.interval=42s" -- "traefik.http.services.service02.loadbalancer.healthcheck.method=foobar" -- "traefik.http.services.service02.loadbalancer.healthcheck.mode=foobar" -- "traefik.http.services.service02.loadbalancer.healthcheck.path=foobar" -- "traefik.http.services.service02.loadbalancer.healthcheck.port=42" -- "traefik.http.services.service02.loadbalancer.healthcheck.scheme=foobar" -- "traefik.http.services.service02.loadbalancer.healthcheck.status=42" -- "traefik.http.services.service02.loadbalancer.healthcheck.timeout=42s" -- "traefik.http.services.service02.loadbalancer.healthcheck.unhealthyinterval=42s" -- "traefik.http.services.service02.loadbalancer.passhostheader=true" -- "traefik.http.services.service02.loadbalancer.passivehealthcheck.failurewindow=42s" -- "traefik.http.services.service02.loadbalancer.passivehealthcheck.maxfailedattempts=42" -- "traefik.http.services.service02.loadbalancer.responseforwarding.flushinterval=42s" -- "traefik.http.services.service02.loadbalancer.serverstransport=foobar" -- "traefik.http.services.service02.loadbalancer.sticky=true" -- "traefik.http.services.service02.loadbalancer.sticky.cookie=true" -- "traefik.http.services.service02.loadbalancer.sticky.cookie.domain=foobar" -- "traefik.http.services.service02.loadbalancer.sticky.cookie.httponly=true" -- "traefik.http.services.service02.loadbalancer.sticky.cookie.maxage=42" -- "traefik.http.services.service02.loadbalancer.sticky.cookie.name=foobar" -- "traefik.http.services.service02.loadbalancer.sticky.cookie.path=foobar" -- "traefik.http.services.service02.loadbalancer.sticky.cookie.samesite=foobar" -- "traefik.http.services.service02.loadbalancer.sticky.cookie.secure=true" -- "traefik.http.services.service02.loadbalancer.strategy=foobar" -- "traefik.http.services.service02.loadbalancer.server.port=foobar" -- "traefik.http.services.service02.loadbalancer.server.preservepath=true" -- "traefik.http.services.service02.loadbalancer.server.scheme=foobar" -- "traefik.http.services.service02.loadbalancer.server.url=foobar" -- "traefik.http.services.service02.loadbalancer.server.weight=42" +- "traefik.http.services.service03.loadbalancer.healthcheck.followredirects=true" +- "traefik.http.services.service03.loadbalancer.healthcheck.headers.name0=foobar" +- "traefik.http.services.service03.loadbalancer.healthcheck.headers.name1=foobar" +- "traefik.http.services.service03.loadbalancer.healthcheck.hostname=foobar" +- "traefik.http.services.service03.loadbalancer.healthcheck.interval=42s" +- "traefik.http.services.service03.loadbalancer.healthcheck.method=foobar" +- "traefik.http.services.service03.loadbalancer.healthcheck.mode=foobar" +- "traefik.http.services.service03.loadbalancer.healthcheck.path=foobar" +- "traefik.http.services.service03.loadbalancer.healthcheck.port=42" +- "traefik.http.services.service03.loadbalancer.healthcheck.scheme=foobar" +- "traefik.http.services.service03.loadbalancer.healthcheck.status=42" +- "traefik.http.services.service03.loadbalancer.healthcheck.timeout=42s" +- "traefik.http.services.service03.loadbalancer.healthcheck.unhealthyinterval=42s" +- "traefik.http.services.service03.loadbalancer.passhostheader=true" +- "traefik.http.services.service03.loadbalancer.passivehealthcheck.failurewindow=42s" +- "traefik.http.services.service03.loadbalancer.passivehealthcheck.maxfailedattempts=42" +- "traefik.http.services.service03.loadbalancer.responseforwarding.flushinterval=42s" +- "traefik.http.services.service03.loadbalancer.serverstransport=foobar" +- "traefik.http.services.service03.loadbalancer.sticky=true" +- "traefik.http.services.service03.loadbalancer.sticky.cookie=true" +- "traefik.http.services.service03.loadbalancer.sticky.cookie.domain=foobar" +- "traefik.http.services.service03.loadbalancer.sticky.cookie.httponly=true" +- "traefik.http.services.service03.loadbalancer.sticky.cookie.maxage=42" +- "traefik.http.services.service03.loadbalancer.sticky.cookie.name=foobar" +- "traefik.http.services.service03.loadbalancer.sticky.cookie.path=foobar" +- "traefik.http.services.service03.loadbalancer.sticky.cookie.samesite=foobar" +- "traefik.http.services.service03.loadbalancer.sticky.cookie.secure=true" +- "traefik.http.services.service03.loadbalancer.strategy=foobar" +- "traefik.http.services.service03.loadbalancer.server.port=foobar" +- "traefik.http.services.service03.loadbalancer.server.preservepath=true" +- "traefik.http.services.service03.loadbalancer.server.scheme=foobar" +- "traefik.http.services.service03.loadbalancer.server.url=foobar" +- "traefik.http.services.service03.loadbalancer.server.weight=42" - "traefik.tcp.middlewares.tcpmiddleware01.ipallowlist.sourcerange=foobar, foobar" - "traefik.tcp.middlewares.tcpmiddleware02.ipwhitelist.sourcerange=foobar, foobar" - "traefik.tcp.middlewares.tcpmiddleware03.inflightconn.amount=42" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 14d9753ce..48ad2a9d2 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -55,12 +55,23 @@ fallback = "foobar" [http.services.Service01.failover.healthCheck] [http.services.Service02] - [http.services.Service02.loadBalancer] + [http.services.Service02.highestRandomWeight] + + [[http.services.Service02.highestRandomWeight.services]] + name = "foobar" + weight = 42 + + [[http.services.Service02.highestRandomWeight.services]] + name = "foobar" + weight = 42 + [http.services.Service02.highestRandomWeight.healthCheck] + [http.services.Service03] + [http.services.Service03.loadBalancer] strategy = "foobar" passHostHeader = true serversTransport = "foobar" - [http.services.Service02.loadBalancer.sticky] - [http.services.Service02.loadBalancer.sticky.cookie] + [http.services.Service03.loadBalancer.sticky] + [http.services.Service03.loadBalancer.sticky.cookie] name = "foobar" secure = true httpOnly = true @@ -69,16 +80,16 @@ path = "foobar" domain = "foobar" - [[http.services.Service02.loadBalancer.servers]] + [[http.services.Service03.loadBalancer.servers]] url = "foobar" weight = 42 preservePath = true - [[http.services.Service02.loadBalancer.servers]] + [[http.services.Service03.loadBalancer.servers]] url = "foobar" weight = 42 preservePath = true - [http.services.Service02.loadBalancer.healthCheck] + [http.services.Service03.loadBalancer.healthCheck] scheme = "foobar" mode = "foobar" path = "foobar" @@ -90,40 +101,40 @@ timeout = "42s" hostname = "foobar" followRedirects = true - [http.services.Service02.loadBalancer.healthCheck.headers] + [http.services.Service03.loadBalancer.healthCheck.headers] name0 = "foobar" name1 = "foobar" - [http.services.Service02.loadBalancer.passiveHealthCheck] + [http.services.Service03.loadBalancer.passiveHealthCheck] failureWindow = "42s" maxFailedAttempts = 42 - [http.services.Service02.loadBalancer.responseForwarding] + [http.services.Service03.loadBalancer.responseForwarding] flushInterval = "42s" - [http.services.Service03] - [http.services.Service03.mirroring] + [http.services.Service04] + [http.services.Service04.mirroring] service = "foobar" mirrorBody = true maxBodySize = 42 - [[http.services.Service03.mirroring.mirrors]] + [[http.services.Service04.mirroring.mirrors]] name = "foobar" percent = 42 - [[http.services.Service03.mirroring.mirrors]] + [[http.services.Service04.mirroring.mirrors]] name = "foobar" percent = 42 - [http.services.Service03.mirroring.healthCheck] - [http.services.Service04] - [http.services.Service04.weighted] + [http.services.Service04.mirroring.healthCheck] + [http.services.Service05] + [http.services.Service05.weighted] - [[http.services.Service04.weighted.services]] + [[http.services.Service05.weighted.services]] name = "foobar" weight = 42 - [[http.services.Service04.weighted.services]] + [[http.services.Service05.weighted.services]] name = "foobar" weight = 42 - [http.services.Service04.weighted.sticky] - [http.services.Service04.weighted.sticky.cookie] + [http.services.Service05.weighted.sticky] + [http.services.Service05.weighted.sticky.cookie] name = "foobar" secure = true httpOnly = true @@ -131,7 +142,7 @@ maxAge = 42 path = "foobar" domain = "foobar" - [http.services.Service04.weighted.healthCheck] + [http.services.Service05.weighted.healthCheck] [http.middlewares] [http.middlewares.Middleware01] [http.middlewares.Middleware01.addPrefix] diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index 99a79e291..ab2574c50 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -65,6 +65,14 @@ http: fallback: foobar healthCheck: {} Service02: + highestRandomWeight: + services: + - name: foobar + weight: 42 + - name: foobar + weight: 42 + healthCheck: {} + Service03: loadBalancer: sticky: cookie: @@ -105,7 +113,7 @@ http: responseForwarding: flushInterval: 42s serversTransport: foobar - Service03: + Service04: mirroring: service: foobar mirrorBody: true @@ -116,7 +124,7 @@ http: - name: foobar percent: 42 healthCheck: {} - Service04: + Service05: weighted: services: - name: foobar diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index 8c0737abb..a6fad6ee9 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -274,58 +274,63 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/services/Service01/failover/fallback` | `foobar` | | `traefik/http/services/Service01/failover/healthCheck` | `` | | `traefik/http/services/Service01/failover/service` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/followRedirects` | `true` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/headers/name0` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/headers/name1` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/hostname` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/interval` | `42s` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/method` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/mode` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/path` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/port` | `42` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/scheme` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/status` | `42` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/timeout` | `42s` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/unhealthyInterval` | `42s` | -| `traefik/http/services/Service02/loadBalancer/passHostHeader` | `true` | -| `traefik/http/services/Service02/loadBalancer/passiveHealthCheck/failureWindow` | `42s` | -| `traefik/http/services/Service02/loadBalancer/passiveHealthCheck/maxFailedAttempts` | `42` | -| `traefik/http/services/Service02/loadBalancer/responseForwarding/flushInterval` | `42s` | -| `traefik/http/services/Service02/loadBalancer/servers/0/preservePath` | `true` | -| `traefik/http/services/Service02/loadBalancer/servers/0/url` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/servers/0/weight` | `42` | -| `traefik/http/services/Service02/loadBalancer/servers/1/preservePath` | `true` | -| `traefik/http/services/Service02/loadBalancer/servers/1/url` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/servers/1/weight` | `42` | -| `traefik/http/services/Service02/loadBalancer/serversTransport` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/domain` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/httpOnly` | `true` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/maxAge` | `42` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/name` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/path` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/sameSite` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/secure` | `true` | -| `traefik/http/services/Service02/loadBalancer/strategy` | `foobar` | -| `traefik/http/services/Service03/mirroring/healthCheck` | `` | -| `traefik/http/services/Service03/mirroring/maxBodySize` | `42` | -| `traefik/http/services/Service03/mirroring/mirrorBody` | `true` | -| `traefik/http/services/Service03/mirroring/mirrors/0/name` | `foobar` | -| `traefik/http/services/Service03/mirroring/mirrors/0/percent` | `42` | -| `traefik/http/services/Service03/mirroring/mirrors/1/name` | `foobar` | -| `traefik/http/services/Service03/mirroring/mirrors/1/percent` | `42` | -| `traefik/http/services/Service03/mirroring/service` | `foobar` | -| `traefik/http/services/Service04/weighted/healthCheck` | `` | -| `traefik/http/services/Service04/weighted/services/0/name` | `foobar` | -| `traefik/http/services/Service04/weighted/services/0/weight` | `42` | -| `traefik/http/services/Service04/weighted/services/1/name` | `foobar` | -| `traefik/http/services/Service04/weighted/services/1/weight` | `42` | -| `traefik/http/services/Service04/weighted/sticky/cookie/domain` | `foobar` | -| `traefik/http/services/Service04/weighted/sticky/cookie/httpOnly` | `true` | -| `traefik/http/services/Service04/weighted/sticky/cookie/maxAge` | `42` | -| `traefik/http/services/Service04/weighted/sticky/cookie/name` | `foobar` | -| `traefik/http/services/Service04/weighted/sticky/cookie/path` | `foobar` | -| `traefik/http/services/Service04/weighted/sticky/cookie/sameSite` | `foobar` | -| `traefik/http/services/Service04/weighted/sticky/cookie/secure` | `true` | +| `traefik/http/services/Service02/highestRandomWeight/healthCheck` | `` | +| `traefik/http/services/Service02/highestRandomWeight/services/0/name` | `foobar` | +| `traefik/http/services/Service02/highestRandomWeight/services/0/weight` | `42` | +| `traefik/http/services/Service02/highestRandomWeight/services/1/name` | `foobar` | +| `traefik/http/services/Service02/highestRandomWeight/services/1/weight` | `42` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/followRedirects` | `true` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/headers/name0` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/headers/name1` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/hostname` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/interval` | `42s` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/method` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/mode` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/path` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/port` | `42` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/scheme` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/status` | `42` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/timeout` | `42s` | +| `traefik/http/services/Service03/loadBalancer/healthCheck/unhealthyInterval` | `42s` | +| `traefik/http/services/Service03/loadBalancer/passHostHeader` | `true` | +| `traefik/http/services/Service03/loadBalancer/passiveHealthCheck/failureWindow` | `42s` | +| `traefik/http/services/Service03/loadBalancer/passiveHealthCheck/maxFailedAttempts` | `42` | +| `traefik/http/services/Service03/loadBalancer/responseForwarding/flushInterval` | `42s` | +| `traefik/http/services/Service03/loadBalancer/servers/0/preservePath` | `true` | +| `traefik/http/services/Service03/loadBalancer/servers/0/url` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/servers/0/weight` | `42` | +| `traefik/http/services/Service03/loadBalancer/servers/1/preservePath` | `true` | +| `traefik/http/services/Service03/loadBalancer/servers/1/url` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/servers/1/weight` | `42` | +| `traefik/http/services/Service03/loadBalancer/serversTransport` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/sticky/cookie/domain` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/sticky/cookie/httpOnly` | `true` | +| `traefik/http/services/Service03/loadBalancer/sticky/cookie/maxAge` | `42` | +| `traefik/http/services/Service03/loadBalancer/sticky/cookie/name` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/sticky/cookie/path` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/sticky/cookie/sameSite` | `foobar` | +| `traefik/http/services/Service03/loadBalancer/sticky/cookie/secure` | `true` | +| `traefik/http/services/Service03/loadBalancer/strategy` | `foobar` | +| `traefik/http/services/Service04/mirroring/healthCheck` | `` | +| `traefik/http/services/Service04/mirroring/maxBodySize` | `42` | +| `traefik/http/services/Service04/mirroring/mirrorBody` | `true` | +| `traefik/http/services/Service04/mirroring/mirrors/0/name` | `foobar` | +| `traefik/http/services/Service04/mirroring/mirrors/0/percent` | `42` | +| `traefik/http/services/Service04/mirroring/mirrors/1/name` | `foobar` | +| `traefik/http/services/Service04/mirroring/mirrors/1/percent` | `42` | +| `traefik/http/services/Service04/mirroring/service` | `foobar` | +| `traefik/http/services/Service05/weighted/healthCheck` | `` | +| `traefik/http/services/Service05/weighted/services/0/name` | `foobar` | +| `traefik/http/services/Service05/weighted/services/0/weight` | `42` | +| `traefik/http/services/Service05/weighted/services/1/name` | `foobar` | +| `traefik/http/services/Service05/weighted/services/1/weight` | `42` | +| `traefik/http/services/Service05/weighted/sticky/cookie/domain` | `foobar` | +| `traefik/http/services/Service05/weighted/sticky/cookie/httpOnly` | `true` | +| `traefik/http/services/Service05/weighted/sticky/cookie/maxAge` | `42` | +| `traefik/http/services/Service05/weighted/sticky/cookie/name` | `foobar` | +| `traefik/http/services/Service05/weighted/sticky/cookie/path` | `foobar` | +| `traefik/http/services/Service05/weighted/sticky/cookie/sameSite` | `foobar` | +| `traefik/http/services/Service05/weighted/sticky/cookie/secure` | `true` | | `traefik/tcp/middlewares/TCPMiddleware01/ipAllowList/sourceRange/0` | `foobar` | | `traefik/tcp/middlewares/TCPMiddleware01/ipAllowList/sourceRange/1` | `foobar` | | `traefik/tcp/middlewares/TCPMiddleware02/ipWhiteList/sourceRange/0` | `foobar` | diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md index fddf2c953..4afcd9e46 100644 --- a/docs/content/routing/services/index.md +++ b/docs/content/routing/services/index.md @@ -175,6 +175,7 @@ Two load balancing algorithms are supported: - Weighed round-robin (wrr) - Power of two choices (p2c) +- Highest Random Weight (hrw) ##### WRR @@ -242,6 +243,34 @@ Power of two choices algorithm is a load balancing strategy that selects two ser url = "http://private-ip-server-3/" ``` +##### HRW + +HighestRandomWeight, also called RendezVous hashing allows to loadbalance clients in a pool of services or servers. + +??? example "Load Balancing HRW with-- Using the [File Provider](../../providers/file.md)" + + ```yaml tab="YAML" + ## Dynamic configuration + http: + services: + my-service: + loadBalancer: + type: hrw + servers: + - url: "http://private-ip-server-1/" + - url: "http://private-ip-server-2/" + ``` + + ```toml tab="TOML" + ## Dynamic configuration + [http.services] + [http.services.my-service.loadBalancer] + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-1/" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-2/" + ``` + #### Sticky sessions When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response. @@ -1253,6 +1282,147 @@ http: url = "http://private-ip-server-2/" ``` +### Highest Random Weight (service) + +The HRW is able to load balance the requests between multiple services based on weights. + +This strategy is only available to load balance between [services](./index.md) and not between [servers](./index.md#servers). + +!!! info "Supported Providers" + + This strategy can be defined currently with the [File](../../providers/file.md) or [IngressRoute](../../providers/kubernetes-crd.md) providers. + +```yaml tab="YAML" +## Dynamic configuration +http: + services: + app: + highestRandomWeight: + services: + - name: appv1 + weight: 3 + - name: appv2 + weight: 1 + + appv1: + loadBalancer: + type: hrw + servers: + - url: "http://private-ip-server-1/" + + appv2: + loadBalancer: + type: hrw + servers: + - url: "http://private-ip-server-2/" +``` + +```toml tab="TOML" +## Dynamic configuration +[http.services] + [http.services.app] + [[http.services.app.highestRandomWeight.services]] + name = "appv1" + weight = 3 + [[http.services.app.highestRandomWeight.services]] + name = "appv2" + weight = 1 + + [http.services.appv1] + [http.services.appv1.loadBalancer] + type = "hrw" + [[http.services.appv1.loadBalancer.servers]] + url = "http://private-ip-server-1/" + + [http.services.appv2] + [http.services.appv2.loadBalancer] + type = "hrw" + [[http.services.appv2.loadBalancer.servers]] + url = "http://private-ip-server-2/" +``` + +#### Health Check + +HealthCheck enables automatic self-healthcheck for this service, i.e. whenever +one of its children is reported as down, this service becomes aware of it, and +takes it into account (i.e. it ignores the down child) when running the +load-balancing algorithm. In addition, if the parent of this service also has +HealthCheck enabled, this service reports to its parent any status change. + +!!! info "All or nothing" + + If HealthCheck is enabled for a given service, but any of its descendants does + not have it enabled, the creation of the service will fail. + + HealthCheck on Weighted services can be defined currently only with the [File](../../providers/file.md) provider. + +```yaml tab="YAML" +## Dynamic configuration +http: + services: + app: + highestRandomWeight: + healthCheck: {} + services: + - name: appv1 + weight: 3 + - name: appv2 + weight: 1 + + appv1: + loadBalancer: + type: hrw + healthCheck: + path: /status + interval: 10s + timeout: 3s + servers: + - url: "http://private-ip-server-1/" + + appv2: + loadBalancer: + type: hrw + healthCheck: + path: /status + interval: 10s + timeout: 3s + servers: + - url: "http://private-ip-server-2/" +``` + +```toml tab="TOML" +## Dynamic configuration +[http.services] + [http.services.app] + [http.services.app.highestRandomWeight.healthCheck] + [[http.services.app.highestRandomWeight.services]] + name = "appv1" + weight = 3 + [[http.services.app.highestRandomWeight.services]] + name = "appv2" + weight = 1 + + [http.services.appv1] + [http.services.appv1.loadBalancer] + type="hrw" + [http.services.appv1.loadBalancer.healthCheck] + path = "/health" + interval = "10s" + timeout = "3s" + [[http.services.appv1.loadBalancer.servers]] + url = "http://private-ip-server-1/" + + [http.services.appv2] + [http.services.appv2.loadBalancer] + type="hrw" + [http.services.appv2.loadBalancer.healthCheck] + path = "/health" + interval = "10s" + timeout = "3s" + [[http.services.appv2.loadBalancer.servers]] + url = "http://private-ip-server-2/" +``` + ### Mirroring (service) The mirroring is able to mirror requests sent to a service to other services. diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index a60f59713..4fbf06d58 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -53,10 +53,11 @@ type Model struct { // Service holds a service configuration (can only be of one type at the same time). type Service struct { - LoadBalancer *ServersLoadBalancer `json:"loadBalancer,omitempty" toml:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty" export:"true"` - Weighted *WeightedRoundRobin `json:"weighted,omitempty" toml:"weighted,omitempty" yaml:"weighted,omitempty" label:"-" export:"true"` - Mirroring *Mirroring `json:"mirroring,omitempty" toml:"mirroring,omitempty" yaml:"mirroring,omitempty" label:"-" export:"true"` - Failover *Failover `json:"failover,omitempty" toml:"failover,omitempty" yaml:"failover,omitempty" label:"-" export:"true"` + LoadBalancer *ServersLoadBalancer `json:"loadBalancer,omitempty" toml:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty" export:"true"` + HighestRandomWeight *HighestRandomWeight `json:"highestRandomWeight,omitempty" toml:"highestRandomWeight,omitempty" yaml:"highestRandomWeight,omitempty" label:"-" export:"true"` + Weighted *WeightedRoundRobin `json:"weighted,omitempty" toml:"weighted,omitempty" yaml:"weighted,omitempty" label:"-" export:"true"` + Mirroring *Mirroring `json:"mirroring,omitempty" toml:"mirroring,omitempty" yaml:"mirroring,omitempty" label:"-" export:"true"` + Failover *Failover `json:"failover,omitempty" toml:"failover,omitempty" yaml:"failover,omitempty" label:"-" export:"true"` } // +k8s:deepcopy-gen=true @@ -157,6 +158,19 @@ type WeightedRoundRobin struct { // +k8s:deepcopy-gen=true +// HighestRandomWeight is a weighted sticky load-balancer of services. +type HighestRandomWeight struct { + Services []HRWService `json:"services,omitempty" toml:"services,omitempty" yaml:"services,omitempty" export:"true"` + // HealthCheck enables automatic self-healthcheck for this service, i.e. + // whenever one of its children is reported as down, this service becomes aware of it, + // and takes it into account (i.e. it ignores the down child) when running the + // load-balancing algorithm. In addition, if the parent of this service also has + // HealthCheck enabled, this service reports to its parent any status change. + HealthCheck *HealthCheck `json:"healthCheck,omitempty" toml:"healthCheck,omitempty" yaml:"healthCheck,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` +} + +// +k8s:deepcopy-gen=true + // WRRService is a reference to a service load-balanced with weighted round-robin. type WRRService struct { Name string `json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty" export:"true"` @@ -170,6 +184,20 @@ type WRRService struct { GRPCStatus *GRPCStatus `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` } +// +k8s:deepcopy-gen=true + +// HRWService is a reference to a service load-balanced with highest random weight. +type HRWService struct { + Name string `json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty" export:"true"` + Weight *int `json:"weight,omitempty" toml:"weight,omitempty" yaml:"weight,omitempty" export:"true"` +} + +// SetDefaults Default values for a HRWService. +func (w *HRWService) SetDefaults() { + defaultWeight := 1 + w.Weight = &defaultWeight +} + // SetDefaults Default values for a WRRService. func (w *WRRService) SetDefaults() { defaultWeight := 1 @@ -231,6 +259,8 @@ const ( BalancerStrategyWRR BalancerStrategy = "wrr" // BalancerStrategyP2C is the power of two choices strategy. BalancerStrategyP2C BalancerStrategy = "p2c" + // BalancerStrategyHRW is the power of two choices strategy. + BalancerStrategyHRW BalancerStrategy = "hrw" ) // +k8s:deepcopy-gen=true diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index 94d0f11a8..65425d028 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -449,6 +449,27 @@ func (in *GrpcWeb) DeepCopy() *GrpcWeb { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HRWService) DeepCopyInto(out *HRWService) { + *out = *in + if in.Weight != nil { + in, out := &in.Weight, &out.Weight + *out = new(int) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HRWService. +func (in *HRWService) DeepCopy() *HRWService { + if in == nil { + return nil + } + out := new(HRWService) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPConfiguration) DeepCopyInto(out *HTTPConfiguration) { *out = *in @@ -688,6 +709,34 @@ func (in *HealthCheck) DeepCopy() *HealthCheck { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HighestRandomWeight) DeepCopyInto(out *HighestRandomWeight) { + *out = *in + if in.Services != nil { + in, out := &in.Services, &out.Services + *out = make([]HRWService, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.HealthCheck != nil { + in, out := &in.HealthCheck, &out.HealthCheck + *out = new(HealthCheck) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HighestRandomWeight. +func (in *HighestRandomWeight) DeepCopy() *HighestRandomWeight { + if in == nil { + return nil + } + out := new(HighestRandomWeight) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPAllowList) DeepCopyInto(out *IPAllowList) { *out = *in @@ -1566,6 +1615,11 @@ func (in *Service) DeepCopyInto(out *Service) { *out = new(ServersLoadBalancer) (*in).DeepCopyInto(*out) } + if in.HighestRandomWeight != nil { + in, out := &in.HighestRandomWeight, &out.HighestRandomWeight + *out = new(HighestRandomWeight) + (*in).DeepCopyInto(*out) + } if in.Weighted != nil { in, out := &in.Weighted, &out.Weighted *out = new(WeightedRoundRobin) diff --git a/pkg/server/service/loadbalancer/hrw/hrw.go b/pkg/server/service/loadbalancer/hrw/hrw.go new file mode 100644 index 000000000..475873ece --- /dev/null +++ b/pkg/server/service/loadbalancer/hrw/hrw.go @@ -0,0 +1,200 @@ +package hrw + +import ( + "context" + "errors" + "hash/fnv" + "math" + "net/http" + "sync" + + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/ip" +) + +type namedHandler struct { + http.Handler + name string + weight float64 +} + +// Balancer implements the Rendezvous Hashing algorithm for load balancing. +// The idea is to compute a score for each available backend using a hash of the client's +// source (for example, IP) combined with the backend's identifier, and assign the client +// to the backend with the highest score. This ensures that each client consistently +// connects to the same backend while distributing load evenly across all backends. +type Balancer struct { + wantsHealthCheck bool + + strategy ip.RemoteAddrStrategy + + handlersMu sync.RWMutex + // References all the handlers by name and also by the hashed value of the name. + handlers []*namedHandler + // status is a record of which child services of the Balancer are healthy, keyed + // by name of child service. A service is initially added to the map when it is + // created via Add, and it is later removed or added to the map as needed, + // through the SetStatus method. + status map[string]struct{} + // updaters is the list of hooks that are run (to update the Balancer + // parent(s)), whenever the Balancer status changes. + updaters []func(bool) + // fenced is the list of terminating yet still serving child services. + fenced map[string]struct{} +} + +// New creates a new load balancer. +func New(wantHealthCheck bool) *Balancer { + balancer := &Balancer{ + status: make(map[string]struct{}), + fenced: make(map[string]struct{}), + wantsHealthCheck: wantHealthCheck, + strategy: ip.RemoteAddrStrategy{}, + } + + return balancer +} + +// getNodeScore calculates the score of the couple of src and handler name. +func getNodeScore(handler *namedHandler, src string) float64 { + h := fnv.New64a() + h.Write([]byte(src + handler.name)) + sum := h.Sum64() + score := float64(sum) / math.Pow(2, 64) + logScore := 1.0 / -math.Log(score) + + return logScore * handler.weight +} + +// SetStatus sets on the balancer that its given child is now of the given +// status. balancerName is only needed for logging purposes. +func (b *Balancer) SetStatus(ctx context.Context, childName string, up bool) { + b.handlersMu.Lock() + defer b.handlersMu.Unlock() + + upBefore := len(b.status) > 0 + + status := "DOWN" + if up { + status = "UP" + } + + log.Ctx(ctx).Debug().Msgf("Setting status of %s to %v", childName, status) + + if up { + b.status[childName] = struct{}{} + } else { + delete(b.status, childName) + } + + upAfter := len(b.status) > 0 + status = "DOWN" + if upAfter { + status = "UP" + } + + // No Status Change + if upBefore == upAfter { + // We're still with the same status, no need to propagate + log.Ctx(ctx).Debug().Msgf("Still %s, no need to propagate", status) + return + } + + // Status Change + log.Ctx(ctx).Debug().Msgf("Propagating new %s status", status) + for _, fn := range b.updaters { + fn(upAfter) + } +} + +// RegisterStatusUpdater adds fn to the list of hooks that are run when the +// status of the Balancer changes. +// Not thread safe. +func (b *Balancer) RegisterStatusUpdater(fn func(up bool)) error { + if !b.wantsHealthCheck { + return errors.New("healthCheck not enabled in config for this weighted service") + } + b.updaters = append(b.updaters, fn) + + return nil +} + +var errNoAvailableServer = errors.New("no available server") + +func (b *Balancer) nextServer(ip string) (*namedHandler, error) { + b.handlersMu.RLock() + var healthy []*namedHandler + for _, h := range b.handlers { + if _, ok := b.status[h.name]; ok { + if _, fenced := b.fenced[h.name]; !fenced { + healthy = append(healthy, h) + } + } + } + b.handlersMu.RUnlock() + + if len(healthy) == 0 { + return nil, errNoAvailableServer + } + + var handler *namedHandler + score := 0.0 + for _, h := range healthy { + s := getNodeScore(h, ip) + if s > score { + handler = h + score = s + } + } + + log.Debug().Msgf("Service selected by HRW: %s", handler.name) + + return handler, nil +} + +func (b *Balancer) ServeHTTP(w http.ResponseWriter, req *http.Request) { + // give ip fetched to b.nextServer + clientIP := b.strategy.GetIP(req) + log.Debug().Msgf("ServeHTTP() clientIP=%s", clientIP) + + server, err := b.nextServer(clientIP) + if err != nil { + if errors.Is(err, errNoAvailableServer) { + http.Error(w, errNoAvailableServer.Error(), http.StatusServiceUnavailable) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + return + } + + server.ServeHTTP(w, req) +} + +// AddServer adds a handler with a server. +func (b *Balancer) AddServer(name string, handler http.Handler, server dynamic.Server) { + b.Add(name, handler, server.Weight, server.Fenced) +} + +// Add adds a handler. +// A handler with a non-positive weight is ignored. +func (b *Balancer) Add(name string, handler http.Handler, weight *int, fenced bool) { + w := 1 + if weight != nil { + w = *weight + } + + if w <= 0 { // non-positive weight is meaningless + return + } + + h := &namedHandler{Handler: handler, name: name, weight: float64(w)} + + b.handlersMu.Lock() + b.handlers = append(b.handlers, h) + b.status[name] = struct{}{} + if fenced { + b.fenced[name] = struct{}{} + } + b.handlersMu.Unlock() +} diff --git a/pkg/server/service/loadbalancer/hrw/hrw_test.go b/pkg/server/service/loadbalancer/hrw/hrw_test.go new file mode 100644 index 000000000..d095bc361 --- /dev/null +++ b/pkg/server/service/loadbalancer/hrw/hrw_test.go @@ -0,0 +1,302 @@ +package hrw + +import ( + "context" + "encoding/binary" + "math/rand" + "net" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +// genIPAddress generate randomly an IP address as a string. +func genIPAddress() string { + buf := make([]byte, 4) + + ip := rand.Uint32() + + binary.LittleEndian.PutUint32(buf, ip) + ipStr := net.IP(buf) + return ipStr.String() +} + +// initStatusArray initialize an array filled with status value for test assertions. +func initStatusArray(size int, value int) []int { + status := make([]int, 0, size) + for i := 1; i <= size; i++ { + status = append(status, value) + } + return status +} + +// Tests evaluate load balancing of single and multiple clients. +// Due to the randomness of IP Adresses, repartition between services is not perfect +// The tests validate repartition using a margin of 10% of the number of requests + +func TestBalancer(t *testing.T) { + balancer := New(false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), Int(4), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "second") + rw.WriteHeader(http.StatusOK) + }), Int(1), false) + + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + req := httptest.NewRequest(http.MethodGet, "/", nil) + for range 100 { + req.RemoteAddr = genIPAddress() + balancer.ServeHTTP(recorder, req) + } + assert.InDelta(t, 80, recorder.save["first"], 10) + assert.InDelta(t, 20, recorder.save["second"], 10) +} + +func TestBalancerNoService(t *testing.T) { + balancer := New(false) + + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + + assert.Equal(t, http.StatusServiceUnavailable, recorder.Result().StatusCode) +} + +func TestBalancerOneServerZeroWeight(t *testing.T) { + balancer := New(false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), Int(1), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0), false) + + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 3 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + assert.Equal(t, 3, recorder.save["first"]) +} + +type key string + +const serviceName key = "serviceName" + +func TestBalancerNoServiceUp(t *testing.T) { + balancer := New(false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusInternalServerError) + }), Int(1), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusInternalServerError) + }), Int(1), false) + + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "first", false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", false) + + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + + assert.Equal(t, http.StatusServiceUnavailable, recorder.Result().StatusCode) +} + +func TestBalancerOneServerDown(t *testing.T) { + balancer := New(false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), Int(1), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusInternalServerError) + }), Int(1), false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", false) + + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 3 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + assert.Equal(t, 3, recorder.save["first"]) +} + +func TestBalancerDownThenUp(t *testing.T) { + balancer := New(false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), Int(1), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "second") + rw.WriteHeader(http.StatusOK) + }), Int(1), false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", false) + + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 3 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + assert.Equal(t, 3, recorder.save["first"]) + + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", true) + recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + req := httptest.NewRequest(http.MethodGet, "/", nil) + for range 100 { + req.RemoteAddr = genIPAddress() + balancer.ServeHTTP(recorder, req) + } + assert.InDelta(t, 50, recorder.save["first"], 10) + assert.InDelta(t, 50, recorder.save["second"], 10) +} + +func TestBalancerPropagate(t *testing.T) { + balancer1 := New(true) + + balancer1.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), Int(1), false) + balancer1.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "second") + rw.WriteHeader(http.StatusOK) + }), Int(1), false) + + balancer2 := New(true) + balancer2.Add("third", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "third") + rw.WriteHeader(http.StatusOK) + }), Int(1), false) + balancer2.Add("fourth", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "fourth") + rw.WriteHeader(http.StatusOK) + }), Int(1), false) + + topBalancer := New(true) + topBalancer.Add("balancer1", balancer1, Int(1), false) + _ = balancer1.RegisterStatusUpdater(func(up bool) { + topBalancer.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "balancer1", up) + // TODO(mpl): if test gets flaky, add channel or something here to signal that + // propagation is done, and wait on it before sending request. + }) + topBalancer.Add("balancer2", balancer2, Int(1), false) + _ = balancer2.RegisterStatusUpdater(func(up bool) { + topBalancer.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "balancer2", up) + }) + + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + req := httptest.NewRequest(http.MethodGet, "/", nil) + for range 100 { + req.RemoteAddr = genIPAddress() + topBalancer.ServeHTTP(recorder, req) + } + assert.InDelta(t, 25, recorder.save["first"], 10) + assert.InDelta(t, 25, recorder.save["second"], 10) + assert.InDelta(t, 25, recorder.save["third"], 10) + assert.InDelta(t, 25, recorder.save["fourth"], 10) + wantStatus := initStatusArray(100, 200) + assert.Equal(t, wantStatus, recorder.status) + + // fourth gets downed, but balancer2 still up since third is still up. + balancer2.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "fourth", false) + recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + req = httptest.NewRequest(http.MethodGet, "/", nil) + for range 100 { + req.RemoteAddr = genIPAddress() + topBalancer.ServeHTTP(recorder, req) + } + assert.InDelta(t, 25, recorder.save["first"], 10) + assert.InDelta(t, 25, recorder.save["second"], 10) + assert.InDelta(t, 50, recorder.save["third"], 10) + assert.InDelta(t, 0, recorder.save["fourth"], 0) + wantStatus = initStatusArray(100, 200) + assert.Equal(t, wantStatus, recorder.status) + + // third gets downed, and the propagation triggers balancer2 to be marked as + // down as well for topBalancer. + balancer2.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "third", false) + recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + req = httptest.NewRequest(http.MethodGet, "/", nil) + for range 100 { + req.RemoteAddr = genIPAddress() + topBalancer.ServeHTTP(recorder, req) + } + assert.InDelta(t, 50, recorder.save["first"], 10) + assert.InDelta(t, 50, recorder.save["second"], 10) + assert.InDelta(t, 0, recorder.save["third"], 0) + assert.InDelta(t, 0, recorder.save["fourth"], 0) + wantStatus = initStatusArray(100, 200) + assert.Equal(t, wantStatus, recorder.status) +} + +func TestBalancerAllServersZeroWeight(t *testing.T) { + balancer := New(false) + + balancer.Add("test", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0), false) + balancer.Add("test2", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0), false) + + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + + assert.Equal(t, http.StatusServiceUnavailable, recorder.Result().StatusCode) +} + +func TestSticky(t *testing.T) { + balancer := New(false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), Int(1), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "second") + rw.WriteHeader(http.StatusOK) + }), Int(2), false) + + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.RemoteAddr = genIPAddress() + for range 10 { + for _, cookie := range recorder.Result().Cookies() { + req.AddCookie(cookie) + } + recorder.ResponseRecorder = httptest.NewRecorder() + + balancer.ServeHTTP(recorder, req) + } + + assert.True(t, recorder.save["first"] == 0 || recorder.save["first"] == 10) + assert.True(t, recorder.save["second"] == 0 || recorder.save["second"] == 10) + // from one IP, the choice between server must be the same for the 10 requests + // weight does not impose what would be chosen from 1 client +} + +func Int(v int) *int { return &v } + +type responseRecorder struct { + *httptest.ResponseRecorder + save map[string]int + sequence []string + status []int +} + +func (r *responseRecorder) WriteHeader(statusCode int) { + r.save[r.Header().Get("server")]++ + r.sequence = append(r.sequence, r.Header().Get("server")) + r.status = append(r.status, statusCode) + r.ResponseRecorder.WriteHeader(statusCode) +} diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 6967f6e7b..f5e3c8cbf 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -28,6 +28,7 @@ import ( "github.com/traefik/traefik/v3/pkg/server/middleware" "github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/failover" + "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/hrw" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/mirror" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/p2c" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/wrr" @@ -137,6 +138,13 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H conf.AddError(err, true) return nil, err } + case conf.HighestRandomWeight != nil: + var err error + lb, err = m.getHRWServiceHandler(ctx, serviceName, conf.HighestRandomWeight) + if err != nil { + conf.AddError(err, true) + return nil, err + } case conf.Mirroring != nil: var err error lb, err = m.getMirrorServiceHandler(ctx, conf.Mirroring) @@ -305,6 +313,40 @@ func (m *Manager) getServiceHandler(ctx context.Context, service dynamic.WRRServ } } +func (m *Manager) getHRWServiceHandler(ctx context.Context, serviceName string, config *dynamic.HighestRandomWeight) (http.Handler, error) { + // TODO Handle accesslog and metrics with multiple service name + balancer := hrw.New(config.HealthCheck != nil) + for _, service := range shuffle(config.Services, m.rand) { + serviceHandler, err := m.BuildHTTP(ctx, service.Name) + if err != nil { + return nil, err + } + + balancer.Add(service.Name, serviceHandler, service.Weight, false) + + if config.HealthCheck == nil { + continue + } + + childName := service.Name + updater, ok := serviceHandler.(healthcheck.StatusUpdater) + if !ok { + return nil, fmt.Errorf("child service %v of %v not a healthcheck.StatusUpdater (%T)", childName, serviceName, serviceHandler) + } + + if err := updater.RegisterStatusUpdater(func(up bool) { + balancer.SetStatus(ctx, childName, up) + }); err != nil { + return nil, fmt.Errorf("cannot register %v as updater for %v: %w", childName, serviceName, err) + } + + log.Ctx(ctx).Debug().Str("parent", serviceName).Str("child", childName). + Msg("Child service will update parent on status change") + } + + return balancer, nil +} + type serverBalancer interface { http.Handler healthcheck.StatusSetter @@ -346,6 +388,8 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName lb = wrr.New(service.Sticky, service.HealthCheck != nil) case dynamic.BalancerStrategyP2C: lb = p2c.New(service.Sticky, service.HealthCheck != nil) + case dynamic.BalancerStrategyHRW: + lb = hrw.New(service.HealthCheck != nil) default: return nil, fmt.Errorf("unsupported load-balancer strategy %q", service.Strategy) } From a0904528073b0c2402598816c5fdcc881ec77e7e Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 9 Sep 2025 17:36:05 +0200 Subject: [PATCH 004/134] Add new certificatesresolvers options --- docs/content/https/acme.md | 65 +++++++++++++++++++ .../tls/certificate-resolvers/acme.md | 50 +++++++------- .../reference/static-configuration/cli-ref.md | 8 ++- .../reference/static-configuration/env-ref.md | 8 ++- .../reference/static-configuration/file.toml | 4 ++ .../reference/static-configuration/file.yaml | 8 ++- pkg/provider/acme/provider.go | 9 ++- 7 files changed, 122 insertions(+), 30 deletions(-) diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index 24cbbd21b..1769d15e5 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -201,6 +201,36 @@ when using the `TLS-ALPN-01` challenge, Traefik must be reachable by Let's Encry --certificatesresolvers.myresolver.acme.tlschallenge=true ``` +#### `Delay` + +_Optional, Default=0_ + +The delay between the creation of the challenge and the validation. +A value lower than or equal to zero means no delay. + +```yaml tab="File (YAML)" +certificatesResolvers: + myresolver: + acme: + # ... + tlsChallenge: + # ... + delay: 12 +``` + +```toml tab="File (TOML)" +[certificatesResolvers.myresolver.acme] + # ... + [certificatesResolvers.myresolver.acme.tlsChallenge] + # ... + delay = 12 +``` + +```bash tab="CLI" +# ... +--certificatesresolvers.myresolver.acme.tlschallenge.delay=12 +``` + ### `httpChallenge` Use the `HTTP-01` challenge to generate and renew ACME certificates by provisioning an HTTP resource under a well-known URI. @@ -252,6 +282,8 @@ when using the `HTTP-01` challenge, `certificatesresolvers.myresolver.acme.httpc #### `Delay` +_Optional, Default=0_ + The delay between the creation of the challenge and the validation. A value lower than or equal to zero means no delay. @@ -998,6 +1030,39 @@ certificatesResolvers: # ... ``` +### `disableCommonName` + +_Optional, Default=false_ + +Disable common name inside CSR and certificates. + +It's recommended to disable the common name and required to get a certificate for IP. + +- https://letsencrypt.org/docs/profiles/#certificate-common-name +- https://community.letsencrypt.org/t/ip-san-error-csr-contains-ip-address-in-common-name/239012/7 + +```yaml tab="File (YAML)" +certificatesResolvers: + myresolver: + acme: + # ... + disableCommonName: true + # ... +``` + +```toml tab="File (TOML)" +[certificatesResolvers.myresolver.acme] + # ... + disableCommonName = true + # ... +``` + +```bash tab="CLI" +# ... +--certificatesresolvers.myresolver.acme.disableCommonName=true +# ... +``` + ### `keyType` _Optional, Default="RSA4096"_ diff --git a/docs/content/reference/install-configuration/tls/certificate-resolvers/acme.md b/docs/content/reference/install-configuration/tls/certificate-resolvers/acme.md index 3a14f3493..5a8c448f2 100644 --- a/docs/content/reference/install-configuration/tls/certificate-resolvers/acme.md +++ b/docs/content/reference/install-configuration/tls/certificate-resolvers/acme.md @@ -73,30 +73,32 @@ certificatesResolvers: ACME certificate resolvers have the following configuration options: -| Field | Description | Default | Required | -|:--------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------|:---------| -| `acme.email` | Email address used for registration. | "" | Yes | -| `acme.caServer` | CA server to use. | https://acme-v02.api.letsencrypt.org/directory | No | -| `acme.preferredChain` | Preferred chain to use. If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. | "" | No | -| `acme.keyType` | KeyType to use. | "RSA4096" | No | -| `acme.eab` | Enable external account binding. | | No | -| `acme.eab.kid` | Key identifier from External CA. | "" | No | -| `acme.eab.hmacEncoded` | HMAC key from External CA, should be in Base64 URL Encoding without padding format. | "" | No | -| `acme.certificatesDuration` | The certificates' duration in hours, exclusively used to determine renewal dates. | 2160 | No | -| `acme.clientTimeout` | Timeout for HTTP Client used to communicate with the ACME server. | 2m | No | -| `acme.clientResponseHeaderTimeout` | Timeout for response headers for HTTP Client used to communicate with the ACME server. | 30s | No | -| `acme.dnsChallenge` | Enable DNS-01 challenge. More information [here](#dnschallenge). | - | No | -| `acme.dnsChallenge.provider` | DNS provider to use. | "" | No | -| `acme.dnsChallenge.resolvers` | DNS servers to resolve the FQDN authority. | [] | No | -| `acme.dnsChallenge.propagation.delayBeforeChecks` | By default, the provider will verify the TXT DNS challenge record before letting ACME verify. If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds. This is Useful if internal networks block external DNS queries. | 0s | No | -| `acme.dnsChallenge.propagation.disableChecks` | Disables the challenge TXT record propagation checks, before notifying ACME that the DNS challenge is ready. Please note that disabling checks can prevent the challenge from succeeding. | false | No | -| `acme.dnsChallenge.propagation.requireAllRNS` | Enables the challenge TXT record to be propagated to all recursive nameservers. If you have disabled authoritative nameservers checks (with `propagation.disableANSChecks`), it is recommended to check all recursive nameservers instead. | false | No | -| `acme.dnsChallenge.propagation.disableANSChecks` | Disables the challenge TXT record propagation checks against authoritative nameservers. This option will skip the propagation check against the nameservers of the authority (SOA). It should be used only if the nameservers of the authority are not reachable. | false | No | -| `acme.httpChallenge` | Enable HTTP-01 challenge. More information [here](#httpchallenge). | | No | -| `acme.httpChallenge.entryPoint` | EntryPoint to use for the HTTP-01 challenges. Must be reachable by Let's Encrypt through port 80 | "" | Yes | -| `acme.httpChallenge.delay` | The delay between the creation of the challenge and the validation. A value lower than or equal to zero means no delay. | 0 | No | -| `acme.tlsChallenge` | Enable TLS-ALPN-01 challenge. Traefik must be reachable by Let's Encrypt through port 443. More information [here](#tlschallenge). | - | No | -| `acme.storage` | File path used for certificates storage. | "acme.json" | Yes | +| Field | Description | Default | Required | +|:--------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------|:---------| +| `acme.email` | Email address used for registration. | "" | Yes | +| `acme.caServer` | CA server to use. | https://acme-v02.api.letsencrypt.org/directory | No | +| `acme.preferredChain` | Preferred chain to use. If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. | "" | No | +| `acme.keyType` | KeyType to use. | "RSA4096" | No | +| `acme.disableCommonName` | Disable common name inside CSR and certificates. | false | No | +| `acme.eab` | Enable external account binding. | | No | +| `acme.eab.kid` | Key identifier from External CA. | "" | No | +| `acme.eab.hmacEncoded` | HMAC key from External CA, should be in Base64 URL Encoding without padding format. | "" | No | +| `acme.certificatesDuration` | The certificates' duration in hours, exclusively used to determine renewal dates. | 2160 | No | +| `acme.clientTimeout` | Timeout for HTTP Client used to communicate with the ACME server. | 2m | No | +| `acme.clientResponseHeaderTimeout` | Timeout for response headers for HTTP Client used to communicate with the ACME server. | 30s | No | +| `acme.dnsChallenge` | Enable DNS-01 challenge. More information [here](#dnschallenge). | - | No | +| `acme.dnsChallenge.provider` | DNS provider to use. | "" | No | +| `acme.dnsChallenge.resolvers` | DNS servers to resolve the FQDN authority. | [] | No | +| `acme.dnsChallenge.propagation.delayBeforeChecks` | By default, the provider will verify the TXT DNS challenge record before letting ACME verify. If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds. This is Useful if internal networks block external DNS queries. | 0s | No | +| `acme.dnsChallenge.propagation.disableChecks` | Disables the challenge TXT record propagation checks, before notifying ACME that the DNS challenge is ready. Please note that disabling checks can prevent the challenge from succeeding. | false | No | +| `acme.dnsChallenge.propagation.requireAllRNS` | Enables the challenge TXT record to be propagated to all recursive nameservers. If you have disabled authoritative nameservers checks (with `propagation.disableANSChecks`), it is recommended to check all recursive nameservers instead. | false | No | +| `acme.dnsChallenge.propagation.disableANSChecks` | Disables the challenge TXT record propagation checks against authoritative nameservers. This option will skip the propagation check against the nameservers of the authority (SOA). It should be used only if the nameservers of the authority are not reachable. | false | No | +| `acme.httpChallenge` | Enable HTTP-01 challenge. More information [here](#httpchallenge). | | No | +| `acme.httpChallenge.entryPoint` | EntryPoint to use for the HTTP-01 challenges. Must be reachable by Let's Encrypt through port 80 | "" | Yes | +| `acme.httpChallenge.delay` | The delay between the creation of the challenge and the validation. A value lower than or equal to zero means no delay. | 0 | No | +| `acme.tlsChallenge` | Enable TLS-ALPN-01 challenge. Traefik must be reachable by Let's Encrypt through port 443. More information [here](#tlschallenge). | - | No | +| `acme.tlschallenge.delay` | The delay between the creation of the challenge and the validation. A value lower than or equal to zero means no delay. | 0 | No | +| `acme.storage` | File path used for certificates storage. | "acme.json" | Yes | ## Automatic Certificate Renewal diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 81cfb6e07..6c9a2657a 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -135,6 +135,9 @@ Timeout for receiving the response headers when communicating with the ACME serv `--certificatesresolvers..acme.clienttimeout`: Timeout for a complete HTTP transaction with the ACME server. (Default: ```120```) +`--certificatesresolvers..acme.disablecommonname`: +Disable the common name in the CSR. (Default: ```false```) + `--certificatesresolvers..acme.dnschallenge`: Activate DNS-01 Challenge. (Default: ```false```) @@ -199,7 +202,10 @@ Certificate profile to use. Storage to use. (Default: ```acme.json```) `--certificatesresolvers..acme.tlschallenge`: -Activate TLS-ALPN-01 Challenge. (Default: ```true```) +Activate TLS-ALPN-01 Challenge. (Default: ```false```) + +`--certificatesresolvers..acme.tlschallenge.delay`: +Delay between the creation of the challenge and the validation. (Default: ```0```) `--certificatesresolvers..tailscale`: Enables Tailscale certificate resolution. (Default: ```true```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index eec38608e..fdf963498 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -135,6 +135,9 @@ Timeout for receiving the response headers when communicating with the ACME serv `TRAEFIK_CERTIFICATESRESOLVERS__ACME_CLIENTTIMEOUT`: Timeout for a complete HTTP transaction with the ACME server. (Default: ```120```) +`TRAEFIK_CERTIFICATESRESOLVERS__ACME_DISABLECOMMONNAME`: +Disable the common name in the CSR. (Default: ```false```) + `TRAEFIK_CERTIFICATESRESOLVERS__ACME_DNSCHALLENGE`: Activate DNS-01 Challenge. (Default: ```false```) @@ -199,7 +202,10 @@ Certificate profile to use. Storage to use. (Default: ```acme.json```) `TRAEFIK_CERTIFICATESRESOLVERS__ACME_TLSCHALLENGE`: -Activate TLS-ALPN-01 Challenge. (Default: ```true```) +Activate TLS-ALPN-01 Challenge. (Default: ```false```) + +`TRAEFIK_CERTIFICATESRESOLVERS__ACME_TLSCHALLENGE_DELAY`: +Delay between the creation of the challenge and the validation. (Default: ```0```) `TRAEFIK_CERTIFICATESRESOLVERS__TAILSCALE`: Enables Tailscale certificate resolution. (Default: ```true```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index de4fc3601..10821e0c5 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -528,6 +528,7 @@ preferredChain = "foobar" profile = "foobar" emailAddresses = ["foobar", "foobar"] + disableCommonName = true storage = "foobar" keyType = "foobar" certificatesDuration = 42 @@ -553,6 +554,7 @@ entryPoint = "foobar" delay = "42s" [certificatesResolvers.CertificateResolver0.acme.tlsChallenge] + delay = "42s" [certificatesResolvers.CertificateResolver0.tailscale] [certificatesResolvers.CertificateResolver1] [certificatesResolvers.CertificateResolver1.acme] @@ -561,6 +563,7 @@ preferredChain = "foobar" profile = "foobar" emailAddresses = ["foobar", "foobar"] + disableCommonName = true storage = "foobar" keyType = "foobar" certificatesDuration = 42 @@ -586,6 +589,7 @@ entryPoint = "foobar" delay = "42s" [certificatesResolvers.CertificateResolver1.acme.tlsChallenge] + delay = "42s" [certificatesResolvers.CertificateResolver1.tailscale] [experimental] diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index f1612dde6..21f36966f 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -573,6 +573,7 @@ certificatesResolvers: emailAddresses: - foobar - foobar + disableCommonName: true storage: foobar keyType: foobar eab: @@ -601,7 +602,8 @@ certificatesResolvers: httpChallenge: entryPoint: foobar delay: 42s - tlsChallenge: {} + tlsChallenge: + delay: 42s tailscale: {} CertificateResolver1: acme: @@ -612,6 +614,7 @@ certificatesResolvers: emailAddresses: - foobar - foobar + disableCommonName: true storage: foobar keyType: foobar eab: @@ -640,7 +643,8 @@ certificatesResolvers: httpChallenge: entryPoint: foobar delay: 42s - tlsChallenge: {} + tlsChallenge: + delay: 42s tailscale: {} experimental: plugins: diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go index 35ea148f8..4f312b5f9 100644 --- a/pkg/provider/acme/provider.go +++ b/pkg/provider/acme/provider.go @@ -21,6 +21,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/challenge/http01" + "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/providers/dns" "github.com/go-acme/lego/v4/registration" @@ -45,6 +46,7 @@ type Configuration struct { PreferredChain string `description:"Preferred chain to use." json:"preferredChain,omitempty" toml:"preferredChain,omitempty" yaml:"preferredChain,omitempty" export:"true"` Profile string `description:"Certificate profile to use." json:"profile,omitempty" toml:"profile,omitempty" yaml:"profile,omitempty" export:"true"` EmailAddresses []string `description:"CSR email addresses to use." json:"emailAddresses,omitempty" toml:"emailAddresses,omitempty" yaml:"emailAddresses,omitempty"` + DisableCommonName bool `description:"Disable the common name in the CSR." json:"disableCommonName,omitempty" toml:"disableCommonName,omitempty" yaml:"disableCommonName,omitempty" export:"true"` Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty" export:"true"` KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'." json:"keyType,omitempty" toml:"keyType,omitempty" yaml:"keyType,omitempty" export:"true"` EAB *EAB `description:"External Account Binding to use." json:"eab,omitempty" toml:"eab,omitempty" yaml:"eab,omitempty"` @@ -117,7 +119,9 @@ type HTTPChallenge struct { } // TLSChallenge contains TLS challenge configuration. -type TLSChallenge struct{} +type TLSChallenge struct { + Delay ptypes.Duration `description:"Delay between the creation of the challenge and the validation." json:"delay,omitempty" toml:"delay,omitempty" yaml:"delay,omitempty" export:"true"` +} // Provider holds configurations of the provider. type Provider struct { @@ -292,6 +296,7 @@ func (p *Provider) getClient() (*lego.Client, error) { config.CADirURL = caServer config.Certificate.KeyType = GetKeyType(ctx, p.KeyType) config.UserAgent = fmt.Sprintf("containous-traefik/%s", version.Version) + config.Certificate.DisableCommonName = p.DisableCommonName config.HTTPClient, err = p.createHTTPClient() if err != nil { @@ -371,7 +376,7 @@ func (p *Provider) getClient() (*lego.Client, error) { if p.TLSChallenge != nil { logger.Debug().Msg("Using TLS Challenge provider.") - err = client.Challenge.SetTLSALPN01Provider(p.TLSChallengeProvider) + err = client.Challenge.SetTLSALPN01Provider(p.TLSChallengeProvider, tlsalpn01.SetDelay(time.Duration(p.TLSChallenge.Delay))) if err != nil { return nil, err } From 634f892370bc9430cf8fd5087a84a2ff0ff41e41 Mon Sep 17 00:00:00 2001 From: Landry Benguigui Date: Fri, 19 Sep 2025 10:18:04 +0200 Subject: [PATCH 005/134] feat: add highest random weight in kubernetes CRD --- .../kubernetes-crd-definition-v1.yml | 252 +++++++++++++++++- .../traefik.io_ingressroutes.yaml | 3 +- .../traefik.io_middlewares.yaml | 3 +- .../traefik.io_traefikservices.yaml | 246 ++++++++++++++++- .../kubernetes/crd/http/traefikservice.md | 140 +++++++++- .../fixtures/highest_random_weight.toml | 48 ++++ integration/fixtures/k8s/01-traefik-crd.yml | 252 +++++++++++++++++- .../k8s/06-ingressroute-traefikservices.yml | 33 +++ integration/simple_test.go | 68 +++++ integration/testdata/rawdata-crd.json | 36 +++ pkg/config/dynamic/http_config.go | 2 +- .../fixtures/with_highest_random_weight.yml | 107 ++++++++ .../kubernetes/crd/kubernetes_http.go | 46 +++- .../kubernetes/crd/kubernetes_test.go | 73 +++++ .../crd/traefikio/v1alpha1/ingressroute.go | 4 +- .../crd/traefikio/v1alpha1/service.go | 11 + .../v1alpha1/zz_generated.deepcopy.go | 28 ++ 17 files changed, 1328 insertions(+), 24 deletions(-) create mode 100644 integration/fixtures/highest_random_weight.toml create mode 100644 pkg/provider/kubernetes/crd/fixtures/with_highest_random_weight.yml diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index 46d70ef9f..afc32d9b9 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -331,11 +331,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: @@ -1292,11 +1293,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: @@ -2791,6 +2793,243 @@ spec: spec: description: TraefikServiceSpec defines the desired state of a TraefikService. properties: + highestRandomWeight: + description: HighestRandomWeight defines the highest random weight + service configuration. + properties: + services: + description: Services defines the list of Kubernetes Service and/or + TraefikService to load-balance, with weight. + items: + description: Service defines an upstream HTTP service to proxy + traffic to. + properties: + healthCheck: + description: Healthcheck defines health checks for ExternalName + services. + properties: + followRedirects: + description: |- + FollowRedirects defines whether redirects should be followed during the health check calls. + Default: true + type: boolean + headers: + additionalProperties: + type: string + description: Headers defines custom headers to be sent + to the health check endpoint. + type: object + hostname: + description: Hostname defines the value of hostname + in the Host header of the health check request. + type: string + interval: + anyOf: + - type: integer + - type: string + description: |- + Interval defines the frequency of the health check calls for healthy targets. + Default: 30s + x-kubernetes-int-or-string: true + method: + description: Method defines the healthcheck method. + type: string + mode: + description: |- + Mode defines the health check mode. + If defined to grpc, will use the gRPC health check protocol to probe the server. + Default: http + type: string + path: + description: Path defines the server URL path for the + health check endpoint. + type: string + port: + description: Port defines the server URL port for the + health check endpoint. + type: integer + scheme: + description: Scheme replaces the server URL scheme for + the health check endpoint. + type: string + status: + description: Status defines the expected HTTP status + code of the response to the health check request. + type: integer + timeout: + anyOf: + - type: integer + - type: string + description: |- + Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. + Default: 5s + x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true + type: object + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + name: + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + nativeLB: + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. + type: boolean + nodePortLB: + description: |- + NodePortLB controls, when creating the load-balancer, + whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. + It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. + By default, NodePortLB is false. + type: boolean + passHostHeader: + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. + type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server + to be marked as unhealthy. It also defines for how + long the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window + before marking the server as unhealthy. + type: integer + type: object + port: + anyOf: + - type: integer + - type: string + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards + the response from the upstream Kubernetes Service to the + client. + properties: + flushInterval: + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms + type: string + type: object + scheme: + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. + type: string + sticky: + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.5/routing/services/#sticky-sessions + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + domain: + description: |- + Domain defines the host to which the cookie will be sent. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#domaindomain-value + type: string + httpOnly: + description: HTTPOnly defines whether the cookie + can be accessed by client-side APIs, such as JavaScript. + type: boolean + maxAge: + description: |- + MaxAge defines the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. + type: integer + name: + description: Name defines the Cookie name. + type: string + path: + description: |- + Path defines the path that must exist in the requested URL for the browser to send the Cookie header. + When not provided the cookie will be sent on every request to the domain. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value + type: string + sameSite: + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite + enum: + - none + - lax + - strict + type: string + secure: + description: Secure defines whether the cookie can + only be transmitted over an encrypted connection + (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: |- + Strategy defines the load balancing strategy between the servers. + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + RoundRobin value is deprecated and supported for backward compatibility. + enum: + - wrr + - p2c + - hrw + - RoundRobin + type: string + weight: + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). + minimum: 0 + type: integer + required: + - name + type: object + type: array + type: object mirroring: description: Mirroring defines the Mirroring service configuration. properties: @@ -3100,11 +3339,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: @@ -3246,11 +3486,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: @@ -3479,11 +3720,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml index 0ffc11dfb..99d510b4b 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml @@ -331,11 +331,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index 97135b70d..3c976793f 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -484,11 +484,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml index 1ed24d021..e7bf10673 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml @@ -44,6 +44,243 @@ spec: spec: description: TraefikServiceSpec defines the desired state of a TraefikService. properties: + highestRandomWeight: + description: HighestRandomWeight defines the highest random weight + service configuration. + properties: + services: + description: Services defines the list of Kubernetes Service and/or + TraefikService to load-balance, with weight. + items: + description: Service defines an upstream HTTP service to proxy + traffic to. + properties: + healthCheck: + description: Healthcheck defines health checks for ExternalName + services. + properties: + followRedirects: + description: |- + FollowRedirects defines whether redirects should be followed during the health check calls. + Default: true + type: boolean + headers: + additionalProperties: + type: string + description: Headers defines custom headers to be sent + to the health check endpoint. + type: object + hostname: + description: Hostname defines the value of hostname + in the Host header of the health check request. + type: string + interval: + anyOf: + - type: integer + - type: string + description: |- + Interval defines the frequency of the health check calls for healthy targets. + Default: 30s + x-kubernetes-int-or-string: true + method: + description: Method defines the healthcheck method. + type: string + mode: + description: |- + Mode defines the health check mode. + If defined to grpc, will use the gRPC health check protocol to probe the server. + Default: http + type: string + path: + description: Path defines the server URL path for the + health check endpoint. + type: string + port: + description: Port defines the server URL port for the + health check endpoint. + type: integer + scheme: + description: Scheme replaces the server URL scheme for + the health check endpoint. + type: string + status: + description: Status defines the expected HTTP status + code of the response to the health check request. + type: integer + timeout: + anyOf: + - type: integer + - type: string + description: |- + Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. + Default: 5s + x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true + type: object + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + name: + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + nativeLB: + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. + type: boolean + nodePortLB: + description: |- + NodePortLB controls, when creating the load-balancer, + whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. + It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. + By default, NodePortLB is false. + type: boolean + passHostHeader: + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. + type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server + to be marked as unhealthy. It also defines for how + long the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window + before marking the server as unhealthy. + type: integer + type: object + port: + anyOf: + - type: integer + - type: string + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards + the response from the upstream Kubernetes Service to the + client. + properties: + flushInterval: + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms + type: string + type: object + scheme: + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. + type: string + sticky: + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.5/routing/services/#sticky-sessions + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + domain: + description: |- + Domain defines the host to which the cookie will be sent. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#domaindomain-value + type: string + httpOnly: + description: HTTPOnly defines whether the cookie + can be accessed by client-side APIs, such as JavaScript. + type: boolean + maxAge: + description: |- + MaxAge defines the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. + type: integer + name: + description: Name defines the Cookie name. + type: string + path: + description: |- + Path defines the path that must exist in the requested URL for the browser to send the Cookie header. + When not provided the cookie will be sent on every request to the domain. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value + type: string + sameSite: + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite + enum: + - none + - lax + - strict + type: string + secure: + description: Secure defines whether the cookie can + only be transmitted over an encrypted connection + (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: |- + Strategy defines the load balancing strategy between the servers. + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + RoundRobin value is deprecated and supported for backward compatibility. + enum: + - wrr + - p2c + - hrw + - RoundRobin + type: string + weight: + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). + minimum: 0 + type: integer + required: + - name + type: object + type: array + type: object mirroring: description: Mirroring defines the Mirroring service configuration. properties: @@ -353,11 +590,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: @@ -499,11 +737,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: @@ -732,11 +971,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/http/traefikservice.md b/docs/content/reference/routing-configuration/kubernetes/crd/http/traefikservice.md index 2cdf94c9c..8b3f18f3f 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/http/traefikservice.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/http/traefikservice.md @@ -3,11 +3,12 @@ title: "Traefik Kubernetes Services Documentation" description: "Learn how to configure routing and load balancing in Traefik Proxy to reach Services, which handle incoming requests. Read the technical documentation." --- -A `TraefikService` is a custom resource that sits on top of the Kubernetes Services. It enables advanced load-balancing features such as a [Weighted Round Robin](#weighted-round-robin) load balancing or a [Mirroring](#mirroring) between your Kubernetes Services. +A `TraefikService` is a custom resource that sits on top of the Kubernetes Services. It enables advanced load-balancing features such as a [Weighted Round Robin](#weighted-round-robin) load balancing, a [Highest Random Weight](#highest-random-weight) load balancing, or a [Mirroring](#mirroring) between your Kubernetes Services. Services configure how to reach the actual endpoints that will eventually handle incoming requests. In Traefik, the target service can be either a standard [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/)—which exposes a pod—or a TraefikService. The latter allows you to combine advanced load-balancing options like: - [Weighted Round Robin load balancing](#weighted-round-robin). +- [Highest Random Weight load balancing](#highest-random-weight). - [Mirroring](#mirroring). ## Weighted Round Robin @@ -255,6 +256,143 @@ In the example above, to keep a session open with the same server, the client wo curl -H Host:example.com -b "lvl1=default-whoami1-80; lvl2=http://10.42.0.6:80" http://localhost:8000/foo ``` +## Highest Random Weight + +The HRW (Highest Random Weight) load balancer uses consistent hashing to ensure that requests from the same client IP are always routed to the same service. Unlike weighted round-robin which distributes requests based on weights, HRW provides consistent routing based on the client's remote address. + +This is particularly useful for maintaining session affinity without requiring sticky cookies, as clients will consistently reach the same backend service based on their IP address. + +### Configuration Example + +```yaml tab="IngressRoute" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: test-hrw + namespace: apps + +spec: + entryPoints: + - websecure + routes: + - match: Host(`example.com`) && PathPrefix(`/app`) + kind: Rule + services: + # Set an HRW TraefikService + - name: hrw1 + namespace: apps + kind: TraefikService + tls: + secretName: supersecret +``` + +```yaml tab="TraefikService HRW" +apiVersion: traefik.io/v1alpha1 +kind: TraefikService +metadata: + name: hrw1 + namespace: apps + +spec: + highestRandomWeight: + services: + # Kubernetes Service with weight 10 + - name: svc1 + namespace: apps + port: 80 + weight: 10 + # Kubernetes Service with weight 20 + - name: svc2 + namespace: apps + port: 80 + weight: 20 + # Another TraefikService + - name: wrr1 + namespace: apps + kind: TraefikService + weight: 15 +``` + +```yaml tab="Kubernetes Services" +apiVersion: v1 +kind: Service +metadata: + name: svc1 + namespace: apps + +spec: + ports: + - name: http + port: 80 + selector: + app: traefiklabs + task: app1 +--- +apiVersion: v1 +kind: Service +metadata: + name: svc2 + namespace: apps + +spec: + ports: + - name: http + port: 80 + selector: + app: traefiklabs + task: app2 +``` + +### Configuration Options + +| Field | Description | Default | Required | +|:---------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------|:---------| +| `services` | List of any combination of TraefikService and [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). Each service must have a weight assigned. | | Yes | +| `services[m].`
`kind`
| Kind of the service targeted.
Two values allowed:
- **Service**: Kubernetes Service
- **TraefikService**: Traefik Service. | "" | No | +| `services[m].`
`name`
| Service name.
The character `@` is not authorized. | "" | Yes | +| `services[m].`
`namespace`
| Service namespace. | "" | No | +| `services[m].`
`port`
| Service port (number or port name).
Evaluated only if the kind is **Service**. | "" | No | +| `services[m].`
`weight`
| Service weight used in the HRW algorithm. Higher weights increase the probability of selection for a given client IP. | 1 | No | +| `services[m].`
`responseForwarding.`
`flushInterval`
| Interval, in milliseconds, in between flushes to the client while copying the response body.
A negative value means to flush immediately after each write to the client.
This configuration is ignored when a response is a streaming response; for such responses, writes are flushed to the client immediately.
Evaluated only if the kind is **Service**. | 100ms | No | +| `services[m].`
`scheme`
| Scheme to use for the request to the upstream Kubernetes Service.
Evaluated only if the kind is **Service**. | "http"
"https" if `port` is 443 or contains the string *https*. | No | +| `services[m].`
`serversTransport`
| Name of ServersTransport resource to use to configure the transport between Traefik and your servers.
Evaluated only if the kind is **Service**. | "" | No | +| `services[m].`
`passHostHeader`
| Forward client Host header to server.
Evaluated only if the kind is **Service**. | true | No | +| `services[m].`
`healthCheck.scheme`
| Server URL scheme for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "" | No | +| `services[m].`
`healthCheck.mode`
| Health check mode.
If defined to grpc, will use the gRPC health check protocol to probe the server.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "http" | No | +| `services[m].`
`healthCheck.path`
| Server URL path for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "" | No | +| `services[m].`
`healthCheck.interval`
| Frequency of the health check calls for healthy targets.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "100ms" | No | +| `services[m].`
`healthCheck.unhealthyInterval`
| Frequency of the health check calls for unhealthy targets.
When not defined, it defaults to the `interval` value.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "100ms" | No | +| `services[m].`
`healthCheck.method`
| HTTP method for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "GET" | No | +| `services[m].`
`healthCheck.status`
| Expected HTTP status code of the response to the health check request.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName.
If not set, expect a status between 200 and 399.
Evaluated only if the kind is **Service**. | | No | +| `services[m].`
`healthCheck.port`
| URL port for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | | No | +| `services[m].`
`healthCheck.timeout`
| Maximum duration to wait before considering the server unhealthy.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "5s" | No | +| `services[m].`
`healthCheck.hostname`
| Value in the Host header of the health check request.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "" | No | +| `services[m].`
`healthCheck.`
`followRedirect`
| Follow the redirections during the healtchcheck.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | true | No | +| `services[m].`
`healthCheck.headers`
| Map of header to send to the health check endpoint
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | | No | +| `services[m].`
`sticky.`
`cookie.name`
| Name of the cookie used for the stickiness.
Evaluated only if the kind is **Service**. | Abbreviation of a sha1
(ex: `_1d52e`). | No | +| `services[m].`
`sticky.`
`cookie.httpOnly`
| Allow the cookie can be accessed by client-side APIs, such as JavaScript.
Evaluated only if the kind is **Service**. | false | No | +| `services[m].`
`sticky.`
`cookie.secure`
| Allow the cookie can only be transmitted over an encrypted connection (i.e. HTTPS).
Evaluated only if the kind is **Service**. | false | No | +| `services[m].`
`sticky.`
`cookie.sameSite`
| [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy.
Allowed values:
-`none`
-`lax`
`strict`
Evaluated only if the kind is **Service**. | "" | No | +| `services[m].`
`sticky.`
`cookie.maxAge`
| Number of seconds until the cookie expires.
Negative number, the cookie expires immediately.
0, the cookie never expires.
Evaluated only if the kind is **Service**. | 0 | No | +| `services[m].`
`strategy`
| Load balancing strategy between the servers.
RoundRobin is the only supported value yet.
Evaluated only if the kind is **Service**. | "RoundRobin" | No | +| `services[m].`
`nativeLB`
| Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik.
Evaluated only if the kind is **Service**. | false | No | +| `services[m].`
`nodePortLB`
| Use the nodePort IP address when the service type is NodePort.
It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
Evaluated only if the kind is **Service**. | false | No | + +### How HRW Works + +The Highest Random Weight algorithm combines consistent hashing with weighted load balancing: + +1. **Consistent Hashing**: For each incoming request, the client's remote address is hashed to ensure consistent routing. +2. **Weighted Selection**: Each service is assigned a random value based on both the hash of the client IP and the service's weight. +3. **Highest Selection**: The service with the highest calculated value receives the request. + +This approach provides several benefits: + +- **Session Affinity**: Clients consistently reach the same backend service +- **Weighted Distribution**: Services with higher weights are more likely to be selected +- **No State Required**: Unlike sticky cookies, no client-side or server-side state is needed +- **Fault Tolerance**: If a service becomes unavailable, requests are redistributed consistently among remaining services + ## Mirroring The mirroring is able to mirror requests sent to a service to other services. diff --git a/integration/fixtures/highest_random_weight.toml b/integration/fixtures/highest_random_weight.toml new file mode 100644 index 000000000..de6bd1d4a --- /dev/null +++ b/integration/fixtures/highest_random_weight.toml @@ -0,0 +1,48 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[api] + insecure = true + +[log] + level = "DEBUG" + noColor = true + +[entryPoints] + + [entryPoints.web] + address = ":8000" + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + [http.routers.router] + service = "hrw" + rule = "Path(`/whoami`)" + + +[http.services] + [http.services.hrw.highestRandomWeight] + [[http.services.hrw.highestRandomWeight.services]] + name = "service1" + weight = 10 + [[http.services.hrw.highestRandomWeight.services]] + name = "service2" + weight = 20 + [[http.services.hrw.highestRandomWeight.services]] + name = "service3" + weight = 30 + + [http.services.service1.loadBalancer] + [[http.services.service1.loadBalancer.servers]] + url = "{{ .Service1Server }}" + [http.services.service2.loadBalancer] + [[http.services.service2.loadBalancer.servers]] + url = "{{ .Service2Server }}" + [http.services.service3.loadBalancer] + [[http.services.service3.loadBalancer.servers]] + url = "{{ .Service3Server }}" \ No newline at end of file diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 46d70ef9f..afc32d9b9 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -331,11 +331,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: @@ -1292,11 +1293,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: @@ -2791,6 +2793,243 @@ spec: spec: description: TraefikServiceSpec defines the desired state of a TraefikService. properties: + highestRandomWeight: + description: HighestRandomWeight defines the highest random weight + service configuration. + properties: + services: + description: Services defines the list of Kubernetes Service and/or + TraefikService to load-balance, with weight. + items: + description: Service defines an upstream HTTP service to proxy + traffic to. + properties: + healthCheck: + description: Healthcheck defines health checks for ExternalName + services. + properties: + followRedirects: + description: |- + FollowRedirects defines whether redirects should be followed during the health check calls. + Default: true + type: boolean + headers: + additionalProperties: + type: string + description: Headers defines custom headers to be sent + to the health check endpoint. + type: object + hostname: + description: Hostname defines the value of hostname + in the Host header of the health check request. + type: string + interval: + anyOf: + - type: integer + - type: string + description: |- + Interval defines the frequency of the health check calls for healthy targets. + Default: 30s + x-kubernetes-int-or-string: true + method: + description: Method defines the healthcheck method. + type: string + mode: + description: |- + Mode defines the health check mode. + If defined to grpc, will use the gRPC health check protocol to probe the server. + Default: http + type: string + path: + description: Path defines the server URL path for the + health check endpoint. + type: string + port: + description: Port defines the server URL port for the + health check endpoint. + type: integer + scheme: + description: Scheme replaces the server URL scheme for + the health check endpoint. + type: string + status: + description: Status defines the expected HTTP status + code of the response to the health check request. + type: integer + timeout: + anyOf: + - type: integer + - type: string + description: |- + Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. + Default: 5s + x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true + type: object + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + name: + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + nativeLB: + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. + type: boolean + nodePortLB: + description: |- + NodePortLB controls, when creating the load-balancer, + whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. + It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. + By default, NodePortLB is false. + type: boolean + passHostHeader: + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. + type: boolean + passiveHealthCheck: + description: PassiveHealthCheck defines passive health checks + for ExternalName services. + properties: + failureWindow: + anyOf: + - type: integer + - type: string + description: FailureWindow defines the time window during + which the failed attempts must occur for the server + to be marked as unhealthy. It also defines for how + long the server will be considered unhealthy. + x-kubernetes-int-or-string: true + maxFailedAttempts: + description: MaxFailedAttempts is the number of consecutive + failed attempts allowed within the failure window + before marking the server as unhealthy. + type: integer + type: object + port: + anyOf: + - type: integer + - type: string + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards + the response from the upstream Kubernetes Service to the + client. + properties: + flushInterval: + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms + type: string + type: object + scheme: + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. + type: string + sticky: + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.5/routing/services/#sticky-sessions + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + domain: + description: |- + Domain defines the host to which the cookie will be sent. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#domaindomain-value + type: string + httpOnly: + description: HTTPOnly defines whether the cookie + can be accessed by client-side APIs, such as JavaScript. + type: boolean + maxAge: + description: |- + MaxAge defines the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. + type: integer + name: + description: Name defines the Cookie name. + type: string + path: + description: |- + Path defines the path that must exist in the requested URL for the browser to send the Cookie header. + When not provided the cookie will be sent on every request to the domain. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value + type: string + sameSite: + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite + enum: + - none + - lax + - strict + type: string + secure: + description: Secure defines whether the cookie can + only be transmitted over an encrypted connection + (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: |- + Strategy defines the load balancing strategy between the servers. + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + RoundRobin value is deprecated and supported for backward compatibility. + enum: + - wrr + - p2c + - hrw + - RoundRobin + type: string + weight: + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). + minimum: 0 + type: integer + required: + - name + type: object + type: array + type: object mirroring: description: Mirroring defines the Mirroring service configuration. properties: @@ -3100,11 +3339,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: @@ -3246,11 +3486,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: @@ -3479,11 +3720,12 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c + - hrw - RoundRobin type: string weight: diff --git a/integration/fixtures/k8s/06-ingressroute-traefikservices.yml b/integration/fixtures/k8s/06-ingressroute-traefikservices.yml index d979bc425..b1ce5660b 100644 --- a/integration/fixtures/k8s/06-ingressroute-traefikservices.yml +++ b/integration/fixtures/k8s/06-ingressroute-traefikservices.yml @@ -28,6 +28,23 @@ spec: - name: whoami port: 80 +--- +apiVersion: traefik.io/v1alpha1 +kind: TraefikService +metadata: + name: hrw1 + namespace: default + +spec: + highestRandomWeight: + services: + - name: whoami + port: 80 + weight: 10 + - name: whoami + port: 80 + weight: 20 + --- apiVersion: traefik.io/v1alpha1 kind: IngressRoute @@ -47,6 +64,22 @@ spec: --- apiVersion: traefik.io/v1alpha1 kind: IngressRoute +metadata: + name: test4.route + namespace: default + +spec: + entryPoints: + - web + routes: + - match: Host(`foo.com`) && PathPrefix(`/hrw1`) + kind: Rule + services: + - name: hrw1 + kind: TraefikService +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute metadata: name: api.route namespace: default diff --git a/integration/simple_test.go b/integration/simple_test.go index 675e21c3d..2be9f7f06 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -1217,6 +1217,74 @@ func (s *SimpleSuite) TestMirrorCanceled() { assert.Equal(s.T(), int32(0), val2) } +func (s *SimpleSuite) TestHighestRandomWeight() { + var count1, count2, count3 int32 + + service1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + atomic.AddInt32(&count1, 1) + rw.WriteHeader(http.StatusOK) + })) + + service2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + atomic.AddInt32(&count2, 1) + rw.WriteHeader(http.StatusOK) + })) + + service3 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + atomic.AddInt32(&count3, 1) + rw.WriteHeader(http.StatusOK) + })) + + service1Server := service1.URL + service2Server := service2.URL + service3Server := service3.URL + + file := s.adaptFile("fixtures/highest_random_weight.toml", struct { + Service1Server string + Service2Server string + Service3Server string + }{Service1Server: service1Server, Service2Server: service2Server, Service3Server: service3Server}) + + s.traefikCmd(withConfigFile(file)) + + err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 3*time.Second, try.BodyContains("service1", "service2", "service3", "hrw")) + require.NoError(s.T(), err) + + // Make 10 requests from the same client (127.0.0.1) - should all go to the same service + client := &http.Client{} + for range 10 { + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) + require.NoError(s.T(), err) + + response, err := client.Do(req) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) + response.Body.Close() + } + + // Check if all requests went to the same service + val1 := atomic.LoadInt32(&count1) + val2 := atomic.LoadInt32(&count2) + val3 := atomic.LoadInt32(&count3) + + // All requests should have been handled (total should be 10) + assert.Equal(s.T(), int32(10), val1+val2+val3) + + // All requests from same remoteAddr (127.0.0.1) should go to exactly one service + servicesUsed := 0 + if val1 > 0 { + servicesUsed++ + } + if val2 > 0 { + servicesUsed++ + } + if val3 > 0 { + servicesUsed++ + } + + assert.Equal(s.T(), 1, servicesUsed, "All requests from same remoteAddr should go to exactly one service") +} + func (s *SimpleSuite) TestSecureAPI() { s.createComposeProject("base") diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index 9f1b2d33c..4f9c63322 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -78,6 +78,24 @@ "web" ] }, + "default-test4-route-466d8d3a547de55dfe8a@kubernetescrd": { + "entryPoints": [ + "web" + ], + "service": "default-hrw1", + "rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/hrw1`)", + "priority": 38, + "observability": { + "accessLogs": true, + "metrics": true, + "tracing": true, + "traceVerbosity": "minimal" + }, + "status": "enabled", + "using": [ + "web" + ] + }, "default-testst-route-60ad45fcb5fc1f5f3629@kubernetescrd": { "entryPoints": [ "web" @@ -157,6 +175,24 @@ "dashboard@internal": { "status": "enabled" }, + "default-hrw1@kubernetescrd": { + "highestRandomWeight": { + "services": [ + { + "name": "default-whoami-80", + "weight": 10 + }, + { + "name": "default-whoami-80", + "weight": 20 + } + ] + }, + "status": "enabled", + "usedBy": [ + "default-test4-route-466d8d3a547de55dfe8a@kubernetescrd" + ] + }, "default-mirror1@kubernetescrd": { "mirroring": { "service": "default-whoami-80", diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index 4fbf06d58..b64c444f1 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -259,7 +259,7 @@ const ( BalancerStrategyWRR BalancerStrategy = "wrr" // BalancerStrategyP2C is the power of two choices strategy. BalancerStrategyP2C BalancerStrategy = "p2c" - // BalancerStrategyHRW is the power of two choices strategy. + // BalancerStrategyHRW is the highest random weight strategy. BalancerStrategyHRW BalancerStrategy = "hrw" ) diff --git a/pkg/provider/kubernetes/crd/fixtures/with_highest_random_weight.yml b/pkg/provider/kubernetes/crd/fixtures/with_highest_random_weight.yml new file mode 100644 index 000000000..f6684c03e --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_highest_random_weight.yml @@ -0,0 +1,107 @@ +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: whoami1-abc + namespace: default + labels: + kubernetes.io/service-name: whoami1 + +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: + - addresses: + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami1 + namespace: default + +spec: + ports: + - name: web + port: 8080 + selector: + app: traefiklabs + task: whoami1 + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: whoami2-abc + namespace: default + labels: + kubernetes.io/service-name: whoami2 + +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: + - addresses: + - 10.10.0.3 + - 10.10.0.4 + conditions: + ready: true + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami2 + namespace: default + +spec: + ports: + - name: web + port: 8080 + selector: + app: traefiklabs + task: whoami2 + +--- +apiVersion: traefik.io/v1alpha1 +kind: TraefikService +metadata: + name: hrw1 + namespace: default + +spec: + highestRandomWeight: + services: + - name: whoami1 + kind: Service + port: 8080 + weight: 10 + - name: whoami2 + kind: Service + port: 8080 + weight: 20 + +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - web + + routes: + - match: Host(`foo.com`) && PathPrefix(`/foo`) + kind: Rule + priority: 12 + services: + - name: hrw1 + kind: TraefikService \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 09c206a74..e6b4df448 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -216,13 +216,17 @@ type configBuilder struct { func (c configBuilder) buildTraefikService(ctx context.Context, tService *traefikv1alpha1.TraefikService, conf map[string]*dynamic.Service) error { id := provider.Normalize(makeID(tService.Namespace, tService.Name)) - if tService.Spec.Weighted != nil { + switch { + case tService.Spec.Weighted != nil: return c.buildServicesLB(ctx, tService.Namespace, tService.Spec, id, conf) - } else if tService.Spec.Mirroring != nil { + case tService.Spec.Mirroring != nil: return c.buildMirroring(ctx, tService, id, conf) - } + case tService.Spec.HighestRandomWeight != nil: + return c.buildHRW(ctx, tService, id, conf) + default: - return errors.New("unspecified service type") + return errors.New("unspecified service type") + } } // buildServicesLB creates the configuration for the load-balancer of services named id, and defined in tService. @@ -329,7 +333,7 @@ func (c configBuilder) buildServersLB(namespace string, svc traefikv1alpha1.Load // TODO: remove this when the fake client apply default values. if svc.Strategy != "" { switch svc.Strategy { - case dynamic.BalancerStrategyWRR, dynamic.BalancerStrategyP2C: + case dynamic.BalancerStrategyWRR, dynamic.BalancerStrategyP2C, dynamic.BalancerStrategyHRW: lb.Strategy = svc.Strategy // Here we are just logging a warning as the default value is already applied. @@ -644,6 +648,38 @@ func (c configBuilder) nameAndService(ctx context.Context, parentNamespace strin } } +func (c configBuilder) buildHRW(ctx context.Context, tService *traefikv1alpha1.TraefikService, id string, conf map[string]*dynamic.Service) error { + var hrwServices []dynamic.HRWService + for _, hrwService := range tService.Spec.HighestRandomWeight.Services { + hrwServiceName, k8sService, err := c.nameAndService(ctx, tService.Namespace, hrwService.LoadBalancerSpec) + if err != nil { + return err + } + + if k8sService != nil { + conf[hrwServiceName] = k8sService + } + + weight := hrwService.Weight + if weight == nil { + weight = func(i int) *int { return &i }(1) + } + + hrwServices = append(hrwServices, dynamic.HRWService{ + Name: hrwServiceName, + Weight: weight, + }) + } + + conf[id] = &dynamic.Service{ + HighestRandomWeight: &dynamic.HighestRandomWeight{ + Services: hrwServices, + }, + } + + return nil +} + func splitSvcNameProvider(name string) (string, string) { parts := strings.Split(name, providerNamespaceSeparator) diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index ec6651091..ac6b2cbac 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -3150,6 +3150,79 @@ func TestLoadIngressRoutes(t *testing.T) { }, }, }, + { + desc: "one kube services in a highest random weight", + paths: []string{"with_highest_random_weight.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TLS: &dynamic.TLSConfiguration{}, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-77c62dfe9517144aeeaa": { + EntryPoints: []string{"web"}, + Service: "default-hrw1", + Rule: "Host(`foo.com`) && PathPrefix(`/foo`)", + Priority: 12, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-hrw1": { + HighestRandomWeight: &dynamic.HighestRandomWeight{ + Services: []dynamic.HRWService{ + {Name: "default-whoami1-8080", Weight: pointer(10)}, + {Name: "default-whoami2-8080", Weight: pointer(20)}, + }, + }, + }, + "default-whoami1-8080": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + { + URL: "http://10.10.0.2:8080", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + "default-whoami2-8080": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.3:8080", + }, + { + URL: "http://10.10.0.4:8080", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, { desc: "One ingress Route with two different services, with weights", paths: []string{"services.yml", "with_two_services_weight.yml"}, diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go index fb5513e4c..16088fd8b 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go @@ -114,10 +114,10 @@ type LoadBalancerSpec struct { // It defaults to https when Kubernetes Service port is 443, http otherwise. Scheme string `json:"scheme,omitempty"` // Strategy defines the load balancing strategy between the servers. - // Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices). + // Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). // RoundRobin value is deprecated and supported for backward compatibility. // TODO: when the deprecated RoundRobin value will be removed, set the default value to wrr. - // +kubebuilder:validation:Enum=wrr;p2c;RoundRobin + // +kubebuilder:validation:Enum=wrr;p2c;hrw;RoundRobin Strategy dynamic.BalancerStrategy `json:"strategy,omitempty"` // PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. // By default, passHostHeader is true. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go index 385aa6acc..aececb9e1 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go @@ -44,6 +44,8 @@ type TraefikServiceSpec struct { Weighted *WeightedRoundRobin `json:"weighted,omitempty"` // Mirroring defines the Mirroring service configuration. Mirroring *Mirroring `json:"mirroring,omitempty"` + // HighestRandomWeight defines the highest random weight service configuration. + HighestRandomWeight *HighestRandomWeight `json:"highestRandomWeight,omitempty"` } // +k8s:deepcopy-gen=true @@ -86,3 +88,12 @@ type WeightedRoundRobin struct { // More info: https://doc.traefik.io/traefik/v3.5/routing/providers/kubernetes-crd/#stickiness-and-load-balancing Sticky *dynamic.Sticky `json:"sticky,omitempty"` } + +// +k8s:deepcopy-gen=true + +// HighestRandomWeight holds the highest random weight configuration. +// More info: https://doc.traefik.io/traefik/v3.5/routing/services/#highest-random-configuration +type HighestRandomWeight struct { + // Services defines the list of Kubernetes Service and/or TraefikService to load-balance, with weight. + Services []Service `json:"services,omitempty"` +} diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go index 62dd22d9e..be6159b65 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go @@ -349,6 +349,29 @@ func (in *ForwardingTimeouts) DeepCopy() *ForwardingTimeouts { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HighestRandomWeight) DeepCopyInto(out *HighestRandomWeight) { + *out = *in + if in.Services != nil { + in, out := &in.Services, &out.Services + *out = make([]Service, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HighestRandomWeight. +func (in *HighestRandomWeight) DeepCopy() *HighestRandomWeight { + if in == nil { + return nil + } + out := new(HighestRandomWeight) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressRoute) DeepCopyInto(out *IngressRoute) { *out = *in @@ -2028,6 +2051,11 @@ func (in *TraefikServiceSpec) DeepCopyInto(out *TraefikServiceSpec) { *out = new(Mirroring) (*in).DeepCopyInto(*out) } + if in.HighestRandomWeight != nil { + in, out := &in.HighestRandomWeight, &out.HighestRandomWeight + *out = new(HighestRandomWeight) + (*in).DeepCopyInto(*out) + } return } From 5d830477b73dbaebea511dab280bf851e50c472b Mon Sep 17 00:00:00 2001 From: Kian Eliasi Date: Fri, 3 Oct 2025 13:24:16 +0330 Subject: [PATCH 006/134] Add warning when maxBodySize is not set --- .../http/middlewares/forwardauth.md | 36 ++++++++++++++++++- pkg/middlewares/auth/forward.go | 2 ++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/routing-configuration/http/middlewares/forwardauth.md b/docs/content/reference/routing-configuration/http/middlewares/forwardauth.md index f5cd9131b..1f220c8f3 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/forwardauth.md +++ b/docs/content/reference/routing-configuration/http/middlewares/forwardauth.md @@ -62,7 +62,7 @@ spec: | `authRequestHeaders` | List of the headers to copy from the request to the authentication server.
It allows filtering headers that should not be passed to the authentication server.
If not set or empty, then all request headers are passed. | [] | No | | `addAuthCookiesToResponse` | List of cookies to copy from the authentication server to the response, replacing any existing conflicting cookie from the forwarded response.
Please note that all backend cookies matching the configured list will not be added to the response. | [] | No | | `forwardBody` | Sets the `forwardBody` option to `true` to send the Body. As body is read inside Traefik before forwarding, this breaks streaming. | false | No | -| `maxBodySize` | Set the `maxBodySize` to limit the body size in bytes. If body is bigger than this, it returns a 401 (unauthorized). | -1 | No | +| `maxBodySize` | Set the `maxBodySize` to limit the body size in bytes. If body is bigger than this, it returns a 401 (unauthorized). If left unset, the request body size is unrestricted which can have performance or security implications. < br/>More information [here](#maxbodysize).| -1 | No | | `headerField` | Defines a header field to store the authenticated user. | "" | No | | `preserveLocationHeader` | Defines whether to forward the Location header to the client as is or prefix it with the domain name of the authentication server. | false | No | | `PreserveRequestMethod` | Defines whether to preserve the original request method while forwarding the request to the authentication server. | false | No | @@ -81,6 +81,40 @@ The start of string (`^`) and end of string (`$`) anchors should be used to ensu Regular expressions and replacements can be tested using online tools such as [Go Playground](https://play.golang.org/p/mWU9p-wk2ru) or the [Regex101](https://regex101.com/r/58sIgx/2). +### maxBodySize + +The `maxBodySize` option controls the maximum size of request bodies that will be forwarded to the authentication server. + +**⚠️ Important Security Consideration** + +By default, `maxBodySize` is not set (value: -1), which means request body size is unlimited. This can have significant security and performance implications: + +- **Security Risk**: Attackers can send extremely large request bodies, potentially causing DoS attacks or memory exhaustion +- **Performance Impact**: Large request bodies consume memory and processing resources, affecting overall system performance +- **Resource Consumption**: Unlimited body size can lead to unexpected resource usage patterns + +**Recommended Configuration** + +It is strongly recommended to set an appropriate `maxBodySize` value for your use case: + +```yaml +# For most web applications (1MB limit) +maxBodySize: 1048576 # 1MB in bytes + +# For API endpoints expecting larger payloads (10MB limit) +maxBodySize: 10485760 # 10MB in bytes + +# For file upload authentication (100MB limit) +maxBodySize: 104857600 # 100MB in bytes +``` + +**Guidelines for Setting maxBodySize** + +- **Web Forms**: 1-5MB is typically sufficient for most form submissions +- **API Endpoints**: Consider your largest expected JSON/XML payload + buffer +- **File Uploads**: Set based on your maximum expected file size +- **High-Traffic Services**: Use smaller limits to prevent resource exhaustion + ## Forward-Request Headers The following request properties are provided to the forward-auth target endpoint as `X-Forwarded-` headers. diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go index d30d33e2a..c6fa07b60 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -88,6 +88,8 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu if config.MaxBodySize != nil { fa.maxBodySize = *config.MaxBodySize + } else if fa.forwardBody { + logger.Warn().Msgf("ForwardAuth 'maxBodySize' is not configured with 'forwardBody: true', allowing unlimited request body size which can lead to DoS attacks and memory exhaustion. Please set an appropriate limit.") } // Ensure our request client does not follow redirects From 0b7f0b40427ec7888208919416f8cf749c9cee2a Mon Sep 17 00:00:00 2001 From: GCHQDeveloper548 <176427199+GCHQDeveloper548@users.noreply.github.com> Date: Fri, 3 Oct 2025 13:24:04 +0100 Subject: [PATCH 007/134] Implement HTTP2 HPACK table size options --- .../configuration-options.md | 2 + .../install-configuration/entrypoints.md | 4 +- docs/content/routing/entrypoints.md | 52 +++++++++++++++++++ pkg/config/static/entrypoints.go | 8 ++- pkg/config/static/static_config_test.go | 16 ++++-- pkg/server/server_entrypoint_tcp.go | 10 +++- pkg/server/server_entrypoint_tcp_test.go | 31 +++++++++++ 7 files changed, 115 insertions(+), 8 deletions(-) diff --git a/docs/content/reference/install-configuration/configuration-options.md b/docs/content/reference/install-configuration/configuration-options.md index 2a0c4a4dc..7f79fe0ff 100644 --- a/docs/content/reference/install-configuration/configuration-options.md +++ b/docs/content/reference/install-configuration/configuration-options.md @@ -100,6 +100,8 @@ THIS FILE MUST NOT BE EDITED BY HAND | entrypoints._name_.http.tls.domains[0].sans | Subject alternative names. | | | entrypoints._name_.http.tls.options | Default TLS options for the routers linked to the entry point. | | | entrypoints._name_.http2.maxconcurrentstreams | Specifies the number of concurrent streams per connection that each client is allowed to initiate. | 250 | +| entrypoints._name_.http2.maxdecoderheadertablesize | Specifies the maximum size of the HTTP2 HPACK header table on the decoding (receiving from client) side. | 4096 | +| entrypoints._name_.http2.maxencoderheadertablesize | Specifies the maximum size of the HTTP2 HPACK header table on the encoding (sending to client) side. | 4096 | | entrypoints._name_.http3 | HTTP/3 configuration. | false | | entrypoints._name_.http3.advertisedport | UDP port to advertise, on which HTTP/3 is available. | 0 | | entrypoints._name_.observability.accesslogs | Enables access-logs for this entryPoint. | true | diff --git a/docs/content/reference/install-configuration/entrypoints.md b/docs/content/reference/install-configuration/entrypoints.md index e7badffbf..8915f2575 100644 --- a/docs/content/reference/install-configuration/entrypoints.md +++ b/docs/content/reference/install-configuration/entrypoints.md @@ -101,7 +101,9 @@ additionalArguments: | `http.tls.options` | Apply TLS options on every router attached to the `entryPoint`.
The TLS options can be overidden per router.
More information in the [dedicated section](../../routing/providers/kubernetes-crd.md#kind-tlsoption). | - | No | | `http.tls.certResolver` | Apply a certificate resolver on every router attached to the `entryPoint`.
The TLS options can be overidden per router.
More information in the [dedicated section](../install-configuration/tls/certificate-resolvers/overview.md). | - | No | | `http2.maxConcurrentStreams` | Set the number of concurrent streams per connection that each client is allowed to initiate.
The value must be greater than zero. | 250 | No | -| `http3` | Enable HTTP/3 protocol on the `entryPoint`.
HTTP/3 requires a TCP `entryPoint`. as HTTP/3 always starts as a TCP connection that then gets upgraded to UDP. In most scenarios, this `entryPoint` is the same as the one used for TLS traffic.
More information [here](#http3). | - | No | +| `http2.maxDecoderHeaderTableSize` | Set the maximum size of the decoder header compression table. This controls the maximum size of the header cache that the server is willing to maintain so the client does not need to repeatedly send the same header across requests in the same http2 connection.
This value is only a maximum, the other end of the connection can use a lower size. | 4096 | No | +| `http2.maxEncoderHeaderTableSize` | Set the maximum size of the encoder header compression table. This controls the maximum size of the header cache that the server is willing to maintain when sending headers to the client, allowing the server to reduce the amount of duplicate headers it is sending in responses.
This value is only a maximum, the other end of the connection can use a lower size. | 4096 | No | +| `http3` | Enable HTTP/3 protocol on the `entryPoint`.
HTTP/3 requires a TCP `entryPoint`. as HTTP/3 always starts as a TCP connection that then gets upgraded to UDP. In most scenarios, this `entryPoint` is the same as the one used for TLS traffic.
More information [here](#http3). | - | No | | `http3.advertisedPort` | Set the UDP port to advertise as the HTTP/3 authority.
It defaults to the entryPoint's address port.
It can be used to override the authority in the `alt-svc` header, for example if the public facing port is different from where Traefik is listening. | - | No | | `observability.accessLogs` | Defines whether a router attached to this EntryPoint produces access-logs by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | | `observability.metrics` | Defines whether a router attached to this EntryPoint produces metrics by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index 6911f43b9..8f1b7a10d 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -107,6 +107,8 @@ They can be defined by using a file (YAML or TOML) or CLI arguments. address: ":8888" # same as ":8888/tcp" http2: maxConcurrentStreams: 42 + maxDecoderHeaderTableSize: 42 + maxEncoderHeaderTableSize: 42 http3: advertisedPort: 8888 transport: @@ -136,6 +138,8 @@ They can be defined by using a file (YAML or TOML) or CLI arguments. address = ":8888" # same as ":8888/tcp" [entryPoints.name.http2] maxConcurrentStreams = 42 + maxDecoderHeaderTableSize = 42 + maxEncoderHeaderTableSize = 42 [entryPoints.name.http3] advertisedPort = 8888 [entryPoints.name.transport] @@ -158,6 +162,8 @@ They can be defined by using a file (YAML or TOML) or CLI arguments. ## Static configuration --entryPoints.name.address=:8888 # same as :8888/tcp --entryPoints.name.http2.maxConcurrentStreams=42 + --entryPoints.name.http2.maxDecoderHeaderTableSize=42 + --entryPoints.name.http2.maxEncoderHeaderTableSize=42 --entryPoints.name.http3.advertisedport=8888 --entryPoints.name.transport.lifeCycle.requestAcceptGraceTimeout=42 --entryPoints.name.transport.lifeCycle.graceTimeOut=42 @@ -408,6 +414,52 @@ entryPoints: --entryPoints.name.http2.maxConcurrentStreams=250 ``` +#### `maxDecoderHeaderTableSize` + +_Optional, Default=4096_ + +`maxDecoderHeaderTableSize` specifies the maximum size of the HTTP2 HPACK header table on the decoding (receiving from client) side. + +```yaml tab="File (YAML)" +entryPoints: + foo: + http2: + maxDecoderHeaderTableSize: 4096 +``` + +```toml tab="File (TOML)" +[entryPoints.foo] + [entryPoints.foo.http2] + maxDecoderHeaderTableSize = 4096 +``` + +```bash tab="CLI" +--entryPoints.name.http2.maxDecoderHeaderTableSize=4096 +``` + +#### `maxEncoderHeaderTableSize` + +_Optional, Default=4096_ + +`maxEncoderHeaderTableSize` specifies the maximum size of the HTTP2 HPACK header table on the encoding (sending to client) side. + +```yaml tab="File (YAML)" +entryPoints: + foo: + http2: + maxEncoderHeaderTableSize: 4096 +``` + +```toml tab="File (TOML)" +[entryPoints.foo] + [entryPoints.foo.http2] + maxEncoderHeaderTableSize = 4096 +``` + +```bash tab="CLI" +--entryPoints.name.http2.maxEncoderHeaderTableSize=4096 +``` + ### HTTP/3 #### `http3` diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 3ac50e048..3153ad717 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -81,12 +81,16 @@ func (c *HTTPConfig) SetDefaults() { // HTTP2Config is the HTTP2 configuration of an entry point. type HTTP2Config struct { - MaxConcurrentStreams int32 `description:"Specifies the number of concurrent streams per connection that each client is allowed to initiate." json:"maxConcurrentStreams,omitempty" toml:"maxConcurrentStreams,omitempty" yaml:"maxConcurrentStreams,omitempty" export:"true"` + MaxConcurrentStreams int32 `description:"Specifies the number of concurrent streams per connection that each client is allowed to initiate." json:"maxConcurrentStreams,omitempty" toml:"maxConcurrentStreams,omitempty" yaml:"maxConcurrentStreams,omitempty" export:"true"` + MaxDecoderHeaderTableSize int32 `description:"Specifies the maximum size of the HTTP2 HPACK header table on the decoding (receiving from client) side." json:"maxDecoderHeaderTableSize,omitempty" toml:"maxDecoderHeaderTableSize,omitempty" yaml:"maxDecoderHeaderTableSize,omitempty" export:"true"` + MaxEncoderHeaderTableSize int32 `description:"Specifies the maximum size of the HTTP2 HPACK header table on the encoding (sending to client) side." json:"maxEncoderHeaderTableSize,omitempty" toml:"maxEncoderHeaderTableSize,omitempty" yaml:"maxEncoderHeaderTableSize,omitempty" export:"true"` } // SetDefaults sets the default values. func (c *HTTP2Config) SetDefaults() { - c.MaxConcurrentStreams = 250 // https://cs.opensource.google/go/x/net/+/cd36cc07:http2/server.go;l=58 + c.MaxConcurrentStreams = 250 // https://cs.opensource.google/go/x/net/+/cd36cc07:http2/server.go;l=58 + c.MaxDecoderHeaderTableSize = 4096 // https://cs.opensource.google/go/x/net/+/0e478a2a:http2/server.go;l=105 + c.MaxEncoderHeaderTableSize = 4096 // https://cs.opensource.google/go/x/net/+/0e478a2a:http2/server.go;l=111 } // HTTP3Config is the HTTP3 configuration of an entry point. diff --git a/pkg/config/static/static_config_test.go b/pkg/config/static/static_config_test.go index a18c7c35e..b7ba5b729 100644 --- a/pkg/config/static/static_config_test.go +++ b/pkg/config/static/static_config_test.go @@ -74,7 +74,9 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) { MaxHeaderBytes: 1048576, }, HTTP2: &HTTP2Config{ - MaxConcurrentStreams: 250, + MaxConcurrentStreams: 250, + MaxDecoderHeaderTableSize: 4096, + MaxEncoderHeaderTableSize: 4096, }, HTTP3: nil, UDP: &UDPConfig{ @@ -120,7 +122,9 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) { MaxHeaderBytes: 1048576, }, HTTP2: &HTTP2Config{ - MaxConcurrentStreams: 250, + MaxConcurrentStreams: 250, + MaxDecoderHeaderTableSize: 4096, + MaxEncoderHeaderTableSize: 4096, }, HTTP3: nil, UDP: &UDPConfig{ @@ -177,7 +181,9 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) { MaxHeaderBytes: 1048576, }, HTTP2: &HTTP2Config{ - MaxConcurrentStreams: 250, + MaxConcurrentStreams: 250, + MaxDecoderHeaderTableSize: 4096, + MaxEncoderHeaderTableSize: 4096, }, HTTP3: nil, UDP: &UDPConfig{ @@ -238,7 +244,9 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) { MaxHeaderBytes: 1048576, }, HTTP2: &HTTP2Config{ - MaxConcurrentStreams: 250, + MaxConcurrentStreams: 250, + MaxDecoderHeaderTableSize: 4096, + MaxEncoderHeaderTableSize: 4096, }, HTTP3: nil, UDP: &UDPConfig{ diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index eb68714d2..c6e5e5857 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -631,6 +631,12 @@ func newHTTPServer(ctx context.Context, ln net.Listener, configuration *static.E if configuration.HTTP2.MaxConcurrentStreams < 0 { return nil, errors.New("max concurrent streams value must be greater than or equal to zero") } + if configuration.HTTP2.MaxDecoderHeaderTableSize < 0 { + return nil, errors.New("max decoder header table size value must be greater than or equal to zero") + } + if configuration.HTTP2.MaxEncoderHeaderTableSize < 0 { + return nil, errors.New("max encoder header table size value must be greater than or equal to zero") + } httpSwitcher := middlewares.NewHandlerSwitcher(http.NotFoundHandler()) @@ -688,7 +694,9 @@ func newHTTPServer(ctx context.Context, ln net.Listener, configuration *static.E IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), MaxHeaderBytes: configuration.HTTP.MaxHeaderBytes, HTTP2: &http.HTTP2Config{ - MaxConcurrentStreams: int(configuration.HTTP2.MaxConcurrentStreams), + MaxConcurrentStreams: int(configuration.HTTP2.MaxConcurrentStreams), + MaxDecoderHeaderTableSize: int(configuration.HTTP2.MaxDecoderHeaderTableSize), + MaxEncoderHeaderTableSize: int(configuration.HTTP2.MaxEncoderHeaderTableSize), }, } if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) { diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index 8170234a7..acb3de6de 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -648,3 +648,34 @@ func TestPathOperations(t *testing.T) { }) } } + +func TestHTTP2Config(t *testing.T) { + expectedMaxConcurrentStreams := 42 + expectedEncoderTableSize := 128 + expectedDecoderTableSize := 256 + + // Create a listener for the server. + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + t.Cleanup(func() { + _ = ln.Close() + }) + + // Define the server configuration. + configuration := &static.EntryPoint{} + configuration.SetDefaults() + configuration.HTTP2.MaxConcurrentStreams = int32(expectedMaxConcurrentStreams) + configuration.HTTP2.MaxEncoderHeaderTableSize = int32(expectedEncoderTableSize) + configuration.HTTP2.MaxDecoderHeaderTableSize = int32(expectedDecoderTableSize) + + // Create the HTTP server using newHTTPServer. + server, err := newHTTPServer(t.Context(), ln, configuration, false, requestdecorator.New(nil)) + require.NoError(t, err) + + // Get the underlying HTTP Server. + httpServer := server.Server.(*http.Server) + + assert.Equal(t, expectedMaxConcurrentStreams, httpServer.HTTP2.MaxConcurrentStreams) + assert.Equal(t, expectedEncoderTableSize, httpServer.HTTP2.MaxEncoderHeaderTableSize) + assert.Equal(t, expectedDecoderTableSize, httpServer.HTTP2.MaxDecoderHeaderTableSize) +} From 7cc8b099d2a33d2b3586cb4a786303fbe6fab814 Mon Sep 17 00:00:00 2001 From: shreealt Date: Fri, 3 Oct 2025 19:00:05 +0530 Subject: [PATCH 008/134] Log provider namespace during startup --- pkg/provider/aggregator/aggregator.go | 13 +++++- pkg/provider/aggregator/aggregator_test.go | 49 ++++++++++++++++++++ pkg/provider/consulcatalog/consul_catalog.go | 5 ++ pkg/provider/kv/consul/consul.go | 5 ++ pkg/provider/nomad/nomad.go | 5 ++ pkg/provider/provider.go | 11 +++++ 6 files changed, 86 insertions(+), 2 deletions(-) diff --git a/pkg/provider/aggregator/aggregator.go b/pkg/provider/aggregator/aggregator.go index fb2b1a6dc..3e45c2119 100644 --- a/pkg/provider/aggregator/aggregator.go +++ b/pkg/provider/aggregator/aggregator.go @@ -2,6 +2,7 @@ package aggregator import ( "context" + "fmt" "time" "github.com/rs/zerolog/log" @@ -203,8 +204,16 @@ func (p *ProviderAggregator) launchProvider(configurationChan chan<- dynamic.Mes log.Debug().Err(err).Msgf("Cannot marshal the provider configuration %T", prd) } - log.Info().Msgf("Starting provider %T", prd) - log.Debug().RawJSON("config", []byte(jsonConf)).Msgf("%T provider configuration", prd) + // Check if provider has namespace information. + var namespaceInfo string + if namespaceProvider, ok := prd.(provider.NamespacedProvider); ok { + if namespace := namespaceProvider.Namespace(); namespace != "" { + namespaceInfo = fmt.Sprintf(" (namespace: %s)", namespace) + } + } + + log.Info().Msgf("Starting provider %T%s", prd, namespaceInfo) + log.Debug().RawJSON("config", []byte(jsonConf)).Msgf("%T provider configuration%s", prd, namespaceInfo) if err := maybeThrottledProvide(prd, p.providersThrottleDuration)(configurationChan, pool); err != nil { log.Error().Err(err).Msgf("Cannot start the provider %T", prd) diff --git a/pkg/provider/aggregator/aggregator_test.go b/pkg/provider/aggregator/aggregator_test.go index 895dc6539..8bb609eb4 100644 --- a/pkg/provider/aggregator/aggregator_test.go +++ b/pkg/provider/aggregator/aggregator_test.go @@ -1,9 +1,13 @@ package aggregator import ( + "bytes" "testing" "time" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/provider" @@ -40,6 +44,34 @@ func TestProviderAggregator_Provide(t *testing.T) { require.NoError(t, <-errCh) } +func TestLaunchNamespacedProvider(t *testing.T) { + // Capture log output + var buf bytes.Buffer + + originalLogger := log.Logger + log.Logger = zerolog.New(&buf).Level(zerolog.InfoLevel) + + providerWithNamespace := &mockNamespacedProvider{namespace: "test-namespace"} + + aggregator := ProviderAggregator{ + internalProvider: providerWithNamespace, + } + + cfgCh := make(chan dynamic.Message) + pool := safe.NewPool(t.Context()) + + t.Cleanup(func() { + pool.Stop() + log.Logger = originalLogger + }) + + err := aggregator.Provide(cfgCh, pool) + require.NoError(t, err) + + output := buf.String() + assert.Contains(t, output, "Starting provider *aggregator.mockNamespacedProvider (namespace: test-namespace)") +} + // requireReceivedMessageFromProviders makes sure the given providers have emitted a message on the given message channel. // Providers order is not enforced. func requireReceivedMessageFromProviders(t *testing.T, cfgCh <-chan dynamic.Message, names []string) { @@ -76,3 +108,20 @@ func (p *providerMock) Provide(configurationChan chan<- dynamic.Message, pool *s return nil } + +// mockNamespacedProvider is a mock implementation of NamespacedProvider for testing. +type mockNamespacedProvider struct { + namespace string +} + +func (m *mockNamespacedProvider) Namespace() string { + return m.namespace +} + +func (m *mockNamespacedProvider) Provide(_ chan<- dynamic.Message, _ *safe.Pool) error { + return nil +} + +func (m *mockNamespacedProvider) Init() error { + return nil +} diff --git a/pkg/provider/consulcatalog/consul_catalog.go b/pkg/provider/consulcatalog/consul_catalog.go index 55c24edb6..d3fc9b06f 100644 --- a/pkg/provider/consulcatalog/consul_catalog.go +++ b/pkg/provider/consulcatalog/consul_catalog.go @@ -647,3 +647,8 @@ func repeatSend(ctx context.Context, interval time.Duration, c chan<- struct{}) } } } + +// Namespace returns the namespace of the ConsulCatalog provider. +func (p *Provider) Namespace() string { + return p.namespace +} diff --git a/pkg/provider/kv/consul/consul.go b/pkg/provider/kv/consul/consul.go index 2a5fdfd49..14d8e82b6 100644 --- a/pkg/provider/kv/consul/consul.go +++ b/pkg/provider/kv/consul/consul.go @@ -97,3 +97,8 @@ func (p *Provider) Init() error { return p.Provider.Init(consul.StoreName, p.name, config) } + +// Namespace returns the namespace of the Consul provider. +func (p *Provider) Namespace() string { + return p.namespace +} diff --git a/pkg/provider/nomad/nomad.go b/pkg/provider/nomad/nomad.go index 6f0b4774e..bdca8b6fb 100644 --- a/pkg/provider/nomad/nomad.go +++ b/pkg/provider/nomad/nomad.go @@ -571,3 +571,8 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s return eventsChanBuffered } + +// Namespace returns the namespace of the Nomad provider. +func (p *Provider) Namespace() string { + return p.namespace +} diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 2597a05e6..8ebe66055 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -12,3 +12,14 @@ type Provider interface { Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error Init() error } + +// NamespacedProvider is implemented by providers that support namespace-scoped configurations, +// where each configured namespace results in a dedicated provider instance. +// This enables clear identification of which namespace each provider instance serves during +// startup logging and operational monitoring. +type NamespacedProvider interface { + Provider + + // Namespace returns the specific namespace this provider instance is configured for. + Namespace() string +} From 13bcdebc898aa8a06ffc16e9fac54a458a233d2c Mon Sep 17 00:00:00 2001 From: idurgakalyan Date: Wed, 8 Oct 2025 01:32:05 -0700 Subject: [PATCH 009/134] Add Knative provider --- ...yaml => test-gateway-api-conformance.yaml} | 10 +- .github/workflows/test-integration.yaml | 4 + .../workflows/test-knative-conformance.yaml | 50 + Makefile | 7 +- .../kubernetes-knative-rbac.yml | 50 + .../configuration-options.md | 16 + .../providers/kubernetes/knative.md | 142 + .../kubernetes/knative.md | 96 + docs/mkdocs.yml | 2 + go.mod | 12 +- go.sum | 35 +- .../knative/00-knative-crd-v1.19.0.yml | 6692 ++++++++++++ integration/fixtures/knative/01-rbac.yml | 50 + integration/fixtures/knative/02-traefik.yml | 102 + .../knative/03-knative-serving-v1.19.0.yaml | 9513 +++++++++++++++++ .../knative/04-serving-tests-namespace.yaml | 4 + integration/fixtures/knative/tools.go | 14 + .../fixtures/knative/upload-test-images.sh | 41 + integration/integration_test.go | 8 +- integration/k8s_conformance_test.go | 7 - integration/knative_conformance_test.go | 178 + pkg/config/dynamic/http_config.go | 4 + pkg/config/dynamic/zz_generated.deepcopy.go | 7 + pkg/config/static/experimental.go | 1 + pkg/config/static/static_config.go | 8 + pkg/provider/aggregator/aggregator.go | 4 + pkg/provider/kubernetes/knative/client.go | 232 + .../knative/fixtures/cluster_local.yaml | 33 + .../knative/fixtures/external_ip.yaml | 33 + .../kubernetes/knative/fixtures/services.yaml | 39 + .../kubernetes/knative/fixtures/tls.yaml | 38 + .../knative/fixtures/wrong_ingress_class.yaml | 8 + pkg/provider/kubernetes/knative/kubernetes.go | 531 + .../kubernetes/knative/kubernetes_test.go | 478 + pkg/server/service/service.go | 26 +- pkg/server/service/service_test.go | 136 +- .../components/icons/providers/Knative.tsx | 11 + .../src/components/icons/providers/index.tsx | 4 + 38 files changed, 18589 insertions(+), 37 deletions(-) rename .github/workflows/{test-conformance.yaml => test-gateway-api-conformance.yaml} (77%) create mode 100644 .github/workflows/test-knative-conformance.yaml create mode 100644 docs/content/reference/dynamic-configuration/kubernetes-knative-rbac.yml create mode 100644 docs/content/reference/install-configuration/providers/kubernetes/knative.md create mode 100644 docs/content/reference/routing-configuration/kubernetes/knative.md create mode 100644 integration/fixtures/knative/00-knative-crd-v1.19.0.yml create mode 100644 integration/fixtures/knative/01-rbac.yml create mode 100644 integration/fixtures/knative/02-traefik.yml create mode 100644 integration/fixtures/knative/03-knative-serving-v1.19.0.yaml create mode 100644 integration/fixtures/knative/04-serving-tests-namespace.yaml create mode 100644 integration/fixtures/knative/tools.go create mode 100755 integration/fixtures/knative/upload-test-images.sh create mode 100644 integration/knative_conformance_test.go create mode 100644 pkg/provider/kubernetes/knative/client.go create mode 100644 pkg/provider/kubernetes/knative/fixtures/cluster_local.yaml create mode 100644 pkg/provider/kubernetes/knative/fixtures/external_ip.yaml create mode 100644 pkg/provider/kubernetes/knative/fixtures/services.yaml create mode 100644 pkg/provider/kubernetes/knative/fixtures/tls.yaml create mode 100644 pkg/provider/kubernetes/knative/fixtures/wrong_ingress_class.yaml create mode 100644 pkg/provider/kubernetes/knative/kubernetes.go create mode 100644 pkg/provider/kubernetes/knative/kubernetes_test.go create mode 100644 webui/src/components/icons/providers/Knative.tsx diff --git a/.github/workflows/test-conformance.yaml b/.github/workflows/test-gateway-api-conformance.yaml similarity index 77% rename from .github/workflows/test-conformance.yaml rename to .github/workflows/test-gateway-api-conformance.yaml index 6373d8f92..0fc4b09d2 100644 --- a/.github/workflows/test-conformance.yaml +++ b/.github/workflows/test-gateway-api-conformance.yaml @@ -5,18 +5,18 @@ on: branches: - '*' paths: - - '.github/workflows/test-conformance.yaml' + - '.github/workflows/test-gateway-api-conformance.yaml' - 'pkg/provider/kubernetes/gateway/**' - 'integration/fixtures/k8s-conformance/**' - 'integration/k8s_conformance_test.go' env: - GO_VERSION: '1.23' + GO_VERSION: '1.24' CGO_ENABLED: 0 jobs: - test-conformance: + test-gateway-api-conformance: runs-on: ubuntu-latest steps: @@ -30,6 +30,10 @@ jobs: with: go-version: ${{ env.GO_VERSION }} + - name: Avoid generating webui + run: | + touch webui/static/index.html + - name: K8s Gateway API conformance test and report run: | make test-gateway-api-conformance diff --git a/.github/workflows/test-integration.yaml b/.github/workflows/test-integration.yaml index 0f7504c06..5125d19c6 100644 --- a/.github/workflows/test-integration.yaml +++ b/.github/workflows/test-integration.yaml @@ -30,6 +30,10 @@ jobs: go-version: ${{ env.GO_VERSION }} check-latest: true + - name: Avoid generating webui + run: | + touch webui/static/index.html + - name: Build binary run: make binary-linux-amd64 diff --git a/.github/workflows/test-knative-conformance.yaml b/.github/workflows/test-knative-conformance.yaml new file mode 100644 index 000000000..3fb680ec0 --- /dev/null +++ b/.github/workflows/test-knative-conformance.yaml @@ -0,0 +1,50 @@ +name: Test Knative conformance + +on: + pull_request: + branches: + - '*' + paths: + - '.github/workflows/test-knative-conformance.yaml' + - 'pkg/provider/kubernetes/knative/**' + - 'integration/fixtures/knative/**' + - 'integration/knative_conformance_test.go' + +env: + GO_VERSION: '1.24' + CGO_ENABLED: 0 + +jobs: + + test-knative-conformance: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Set up KO + uses: ko-build/setup-ko@v0.6 + env: + KO_DOCKER_REPO: ko.local + + - name: Upload Test Images + run: | + # Download the test image templates. + go mod vendor + ./integration/fixtures/knative/upload-test-images.sh + + - name: Avoid generating webui + run: | + touch webui/static/index.html + + - name: Knative conformance test + run: | + make test-knative-conformance diff --git a/Makefile b/Makefile index 470b5c3e8..b33b23361 100644 --- a/Makefile +++ b/Makefile @@ -100,11 +100,16 @@ test-integration: GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -test.timeout=20m -failfast -v $(TESTFLAGS) .PHONY: test-gateway-api-conformance -#? test-gateway-api-conformance: Run the conformance tests +#? test-gateway-api-conformance: Run the Gateway API conformance tests test-gateway-api-conformance: build-image-dirty # In case of a new Minor/Major version, the k8sConformanceTraefikVersion needs to be updated. GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -v -test.run K8sConformanceSuite -k8sConformance -k8sConformanceTraefikVersion="v3.5" $(TESTFLAGS) +.PHONY: test-knative-conformance +#? test-knative-conformance: Run the Knative conformance tests +test-knative-conformance: build-image-dirty + GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration/integration_test.go ./integration/knative_conformance_test.go -v -tags knativeConformance -test.run KnativeConformanceSuite + .PHONY: test-ui-unit #? test-ui-unit: Run the unit tests for the webui test-ui-unit: diff --git a/docs/content/reference/dynamic-configuration/kubernetes-knative-rbac.yml b/docs/content/reference/dynamic-configuration/kubernetes-knative-rbac.yml new file mode 100644 index 000000000..00276e7ef --- /dev/null +++ b/docs/content/reference/dynamic-configuration/kubernetes-knative-rbac.yml @@ -0,0 +1,50 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: knative-networking-role +rules: + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - networking.internal.knative.dev + resources: + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - networking.internal.knative.dev + resources: + - ingresses/status + verbs: + - update + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: gateway-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: knative-networking-role +subjects: + - kind: ServiceAccount + name: traefik-controller + namespace: default diff --git a/docs/content/reference/install-configuration/configuration-options.md b/docs/content/reference/install-configuration/configuration-options.md index 12974690b..a8ce3757f 100644 --- a/docs/content/reference/install-configuration/configuration-options.md +++ b/docs/content/reference/install-configuration/configuration-options.md @@ -123,6 +123,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | experimental.abortonpluginfailure | Defines whether all plugins must be loaded successfully for Traefik to start. | false | | experimental.fastproxy | Enables the FastProxy implementation. | false | | experimental.fastproxy.debug | Enable debug mode for the FastProxy implementation. | false | +| experimental.knative | Allow the Knative provider usage. | false | | experimental.kubernetesgateway | (Deprecated) Allow the Kubernetes gateway api provider usage. | false | | experimental.kubernetesingressnginx | Allow the Kubernetes Ingress NGINX provider usage. | false | | experimental.localplugins._name_ | Local plugins configuration. | false | @@ -319,6 +320,21 @@ THIS FILE MUST NOT BE EDITED BY HAND | providers.http.tls.cert | TLS cert | | | providers.http.tls.insecureskipverify | TLS insecure skip verify | false | | providers.http.tls.key | TLS key | | +| providers.knative | Enables Knative provider. | false | +| providers.knative.certauthfilepath | Kubernetes certificate authority file path (not needed for in-cluster client). | | +| providers.knative.endpoint | Kubernetes server endpoint (required for external cluster client). | | +| providers.knative.labelselector | Kubernetes label selector to use. | | +| providers.knative.namespaces | Kubernetes namespaces. | | +| providers.knative.privateentrypoints | Entrypoint names used to expose the Ingress privately. If empty local Ingresses are skipped. | | +| providers.knative.privateservice | Kubernetes service used to expose the networking controller privately. | | +| providers.knative.privateservice.name | Name of the Kubernetes service. | | +| providers.knative.privateservice.namespace | Namespace of the Kubernetes service. | | +| providers.knative.publicentrypoints | Entrypoint names used to expose the Ingress publicly. If empty an Ingress is exposed on all entrypoints. | | +| providers.knative.publicservice | Kubernetes service used to expose the networking controller publicly. | | +| providers.knative.publicservice.name | Name of the Kubernetes service. | | +| providers.knative.publicservice.namespace | Namespace of the Kubernetes service. | | +| providers.knative.throttleduration | Ingress refresh throttle duration | 0 | +| providers.knative.token | Kubernetes bearer token (not needed for in-cluster client). | | | providers.kubernetescrd | Enables Kubernetes CRD provider. | false | | providers.kubernetescrd.allowcrossnamespace | Allow cross namespace resource reference. | false | | providers.kubernetescrd.allowemptyservices | Allow the creation of services without endpoints. | false | diff --git a/docs/content/reference/install-configuration/providers/kubernetes/knative.md b/docs/content/reference/install-configuration/providers/kubernetes/knative.md new file mode 100644 index 000000000..e02e952e5 --- /dev/null +++ b/docs/content/reference/install-configuration/providers/kubernetes/knative.md @@ -0,0 +1,142 @@ +--- +title: "Traefik Knative Documentation" +description: "Learn how to use the Knative as a provider for configuration discovery in Traefik Proxy. Read the technical documentation." +--- + +# Traefik & Knative + +The Traefik Knative provider integrates with Knative Serving to provide advanced traffic management and routing capabilities for serverless applications. + +[Knative](https://knative.dev) is a Kubernetes-based platform that enables serverless workloads with features like scale-to-zero, +automatic scaling, and revision management. + +The provider watches Knative `Ingress` resources and automatically configures Traefik routing rules, +enabling seamless integration between Traefik's networking capabilities and Knative's serverless platform. + +## Requirements + +{!kubernetes-requirements.md!} + +1. Install/update the Knative CRDs. + + ```bash + kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.19.0/serving-crds.yaml + ``` + +2. Install the Knative Serving core components. + + ```bash + kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.19.0/serving-core.yaml + ``` + +3. Update the config-network configuration to use the Traefik ingress class. + + ```bash + kubectl patch configmap/config-network \ + -n knative-serving \ + --type merge \ + -p '{"data":{"ingress.class":"traefik.ingress.networking.knative.dev"}}' + ``` + +4. Add a custom domain to your Knative configuration (Optional). + + ```bash + kubectl patch configmap config-domain \ + -n knative-serving \ + --type='merge' \ + -p='{"data":{"example.com":""}}' + ``` + +5. Install/update the Traefik [RBAC](../../../dynamic-configuration/kubernetes-knative-rbac.yml). + + ```bash + kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/reference/dynamic-configuration/kubernetes-knative-rbac.yml + ``` + +## Configuration Example + +As this provider is an experimental feature, it needs to be enabled in the experimental and in the provider sections of the configuration. +You can enable the Knative provider as detailed below: + +```yaml tab="File (YAML)" +experimental: + knative: true + +providers: + knative: {} +``` + +```toml tab="File (TOML)" +[experimental.knative] + +[providers.knative] +``` + +```bash tab="CLI" +--experimental.knative=true +--providers.knative=true +``` + +The Knative provider uses the Knative API to retrieve its routing configuration. +The provider then watches for incoming Knative events and derives the corresponding dynamic configuration from it. + +## Configuration Options + + + +| Field | Description | Default | Required | +|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| providers.knative.endpoint | Server endpoint URL.
More information [here](#endpoint). | | +| providers.knative.token | Bearer token used for the Kubernetes client configuration. | | +| providers.knative.certauthfilepath | Path to the certificate authority file.
Used for the Kubernetes client configuration. | | +| providers.knative.namespaces | Array of namespaces to watch.
If left empty, watch all namespaces. | | +| providers.knative.labelselector | Allow filtering Knative Ingress objects using label selectors. | | +| providers.knative.throttleduration | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.
This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.
If empty, every event is caught. | 0 | +| providers.knative.privateentrypoints | Entrypoint names used to expose the Ingress privately. If empty local Ingresses are skipped. | | +| providers.knative.privateservice | Kubernetes service used to expose the networking controller privately. | | +| providers.knative.privateservice.name | Name of the private Kubernetes service. | | +| providers.knative.privateservice.namespace | Namespace of the private Kubernetes service. | | +| providers.knative.publicentrypoints | Entrypoint names used to expose the Ingress publicly. If empty an Ingress is exposed on all entrypoints. | | +| providers.knative.publicservice | Kubernetes service used to expose the networking controller publicly. | | +| providers.knative.publicservice.name | Name of the public Kubernetes service. | | +| providers.knative.publicservice.namespace | Namespace of the public Kubernetes service. | | + + + +### `endpoint` + +The Kubernetes server endpoint URL. + +When deployed into Kubernetes, Traefik reads the environment variables `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` or `KUBECONFIG` to construct the endpoint. + +The access token is looked up in `/var/run/secrets/kubernetes.io/serviceaccount/token` and the SSL CA certificate in `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`. +Both are mounted automatically when deployed inside Kubernetes. + +The endpoint may be specified to override the environment variable values inside a cluster. + +When the environment variables are not found, Traefik tries to connect to the Knative API server with an external-cluster client. +In this case, the endpoint is required. +Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Knative cluster using the granted authentication and authorization of the associated kubeconfig. + +```yaml tab="File (YAML)" +providers: + knative: + endpoint: "http://localhost:8080" + # ... +``` + +```toml tab="File (TOML)" +[providers.knative] + endpoint = "http://localhost:8080" + # ... +``` + +```bash tab="CLI" +--providers.knative.endpoint=http://localhost:8080 +``` +## Routing Configuration + +See the dedicated section in [routing](../../../routing-configuration/kubernetes/knative.md). + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/kubernetes/knative.md b/docs/content/reference/routing-configuration/kubernetes/knative.md new file mode 100644 index 000000000..70d29a051 --- /dev/null +++ b/docs/content/reference/routing-configuration/kubernetes/knative.md @@ -0,0 +1,96 @@ +--- +title: "Traefik Knative Documentation" +description: "The Knative provider can be used for routing and load balancing in Traefik Proxy. View examples in the technical documentation." +--- + +# Traefik & Knative + +When using the Knative provider, Traefik leverages Knative's Custom Resource Definitions (CRDs) to obtain its routing configuration. +For detailed information on Knative concepts and resources, refer to the official [documentation](https://knative.dev/docs/). + +The Knative provider supports version [v1.19.0](https://github.com/knative/serving/releases/tag/knative-v1.19.0) of the specification. + +## Deploying a Knative Service + +A `Service` is a core resource in the Knative specification that defines the entry point for traffic into a Knative application. +It is linked to a `Ingress`, which specifies the Knative networking controller responsible for managing and handling the traffic, +ensuring that it is directed to the appropriate Knative backend services. + +The following `Service` manifest configures the running Traefik controller to handle the incoming traffic. + +```yaml +--- +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: helloworld-go + namespace: default +spec: + template: + spec: + containers: + - image: gcr.io/knative-samples/helloworld-go + env: + - name: TARGET + value: "Go Sample v1" +``` + +Once everything is deployed, sending a `GET` request to the HTTP endpoint should return the following response: + +```shell +$ curl http://helloworld-go.default.example.com + +Hello Go Sample v1! +``` + +!!! Note + + The `example.com` domain is the public domain configured when deploying the Traefik controller. + Check out [the install configuration](../../install-configuration/providers/kubernetes/knative.md) for more details. + +### Tag based routing + +To add tag-based routing with percentage in Knative, you can define the `traffic` section in your `Service` manifest to include different revisions with specific tags and percentages. +Here is an example: + +```yaml +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: helloworld-go + namespace: default +spec: + template: + spec: + containers: + - image: gcr.io/knative-samples/helloworld-go + env: + - name: TARGET + value: "Go Sample v2" + traffic: + - tag: v1 + revisionName: helloworld-go-00001 + percent: 50 + - tag: v2 + revisionName: helloworld-go-00002 + percent: 50 +``` + +In this example: +- The `traffic` section specifies two revisions (`helloworld-go-00001` and `helloworld-go-00002`) with tags `v1` and `v2`, each receiving 50% of the traffic. +- The `tag` field allows you to route traffic to specific revisions using the tag. + +You can access the tagged revisions using these URLs: + +- `http://v1-helloworld-go.default.example.com` +- `http://v2-helloworld-go.default.example.com` + +Use the default URL to access percentage-based routing: + +- `http://helloworld-go.default.example.com` + +### HTTP/HTTPS + +Check out the Knative documentation for [HTTP/HTTPS configuration](https://knative.dev/docs/serving/encryption/external-domain-tls/#configure-external-domain-encryption). + +{!traefik-for-business-applications.md!} diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 6e8663968..13b8c52d7 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -228,6 +228,7 @@ nav: - 'Kubernetes CRD' : 'reference/install-configuration/providers/kubernetes/kubernetes-crd.md' - 'Kubernetes Ingress' : 'reference/install-configuration/providers/kubernetes/kubernetes-ingress.md' - 'Kubernetes Ingress NGINX' : 'reference/install-configuration/providers/kubernetes/kubernetes-ingress-nginx.md' + - 'Knative': 'reference/install-configuration/providers/kubernetes/knative.md' - 'Docker': 'reference/install-configuration/providers/docker.md' - 'Swarm': 'reference/install-configuration/providers/swarm.md' - 'Hashicorp': @@ -345,6 +346,7 @@ nav: - 'IngressRouteUDP' : 'reference/routing-configuration/kubernetes/crd/udp/ingressrouteudp.md' - 'Ingress' : 'reference/routing-configuration/kubernetes/ingress.md' - 'Ingress NGINX' : 'reference/routing-configuration/kubernetes/ingress-nginx.md' + - 'Knative': 'reference/routing-configuration/kubernetes/knative.md' - 'Label & Tag Providers' : - 'Docker' : 'reference/routing-configuration/other-providers/docker.md' - 'Swarm' : 'reference/routing-configuration/other-providers/swarm.md' diff --git a/go.mod b/go.mod index 2e3ed523f..e11ce8eda 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/stealthrocket/wasi-go v0.8.0 github.com/stealthrocket/wazergo v0.19.1 github.com/stretchr/testify v1.11.1 - github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 // No tag on the repo. + github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807 // No tag on the repo. github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046 // No tag on the repo. github.com/testcontainers/testcontainers-go v0.32.0 github.com/testcontainers/testcontainers-go/modules/k3s v0.32.0 @@ -111,6 +111,8 @@ require ( k8s.io/apimachinery v0.32.3 k8s.io/client-go v0.32.3 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // No tag on the repo. + knative.dev/networking v0.0.0-20241022012959-60e29ff520dc + knative.dev/pkg v0.0.0-20241021183759-9b9d535af5ad mvdan.cc/xurls/v2 v2.5.0 sigs.k8s.io/controller-runtime v0.20.4 sigs.k8s.io/gateway-api v1.3.0 @@ -171,6 +173,7 @@ require ( github.com/baidubce/bce-sdk-go v0.9.243 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blendle/zapdriver v1.3.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/bytedance/sonic v1.10.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect @@ -191,7 +194,7 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/dnsimple/dnsimple-go/v4 v4.0.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/emicklei/go-restful/v3 v3.12.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/exoscale/egoscale/v3 v3.1.26 // indirect github.com/fatih/color v1.18.0 // indirect @@ -225,6 +228,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect @@ -309,6 +313,7 @@ require ( github.com/onsi/ginkgo v1.16.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/ovh/go-ovh v1.9.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/peterhellberg/link v1.2.0 // indirect @@ -321,6 +326,7 @@ require ( github.com/quic-go/qpack v0.5.1 // indirect github.com/regfish/regfish-dnsapi-go v0.1.1 // indirect github.com/rs/cors v1.7.0 // indirect + github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect github.com/sacloud/api-client-go v0.3.3 // indirect github.com/sacloud/go-http v0.1.9 // indirect github.com/sacloud/iaas-api-go v1.17.1 // indirect @@ -367,6 +373,7 @@ require ( go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect go.etcd.io/etcd/client/v3 v3.5.16 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect + go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/collector/featuregate v1.41.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect @@ -385,6 +392,7 @@ require ( golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/term v0.35.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/api v0.249.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect diff --git a/go.sum b/go.sum index d8229dfaa..4fd32533a 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,10 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d h1:LblfooH1lKOpp1hIhukktmSAxFkqMPFk9KR6iZ0MJNI= +contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= +contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= +contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -248,6 +252,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= +github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= @@ -267,6 +273,8 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -363,8 +371,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= -github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -372,6 +380,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/exoscale/egoscale/v3 v3.1.26 h1:bXXT0zVLbE4QFm6tmt0bg6ZPk9pQgUA3Z8SJrctQ7b0= @@ -482,8 +492,8 @@ github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaC github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= @@ -526,6 +536,7 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -1024,6 +1035,8 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= +github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE= github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -1097,6 +1110,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= +github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= @@ -1117,6 +1132,8 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8= +github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= @@ -1231,8 +1248,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 h1:XGopsea1Dw7ecQ8JscCNQXDGYAKDiWjDeXnpN/+BY9g= -github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= +github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807 h1:LUsDduamlucuNnWcaTbXQ6aLILFcLXADpOzeEH3U+OI= +github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= @@ -1357,6 +1374,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/collector/featuregate v1.41.0 h1:CL4UMsMQj35nMJC3/jUu8VvYB4MHirbAX4B0Z/fCVLY= @@ -1837,6 +1856,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= @@ -2005,6 +2026,10 @@ k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJ k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +knative.dev/networking v0.0.0-20241022012959-60e29ff520dc h1:0d9XXRLlyuHfINZLlYqo/BYe/+chqqNBMLKJldjTbtw= +knative.dev/networking v0.0.0-20241022012959-60e29ff520dc/go.mod h1:G56j6VCLzfaN9yZ4IqfNyN4c3U1czvhUmKeZX4UjQ8Q= +knative.dev/pkg v0.0.0-20241021183759-9b9d535af5ad h1:Nrjtr2H168rJeamH4QdyLMV1lEKHejNhaj1ymgQMfLk= +knative.dev/pkg v0.0.0-20241021183759-9b9d535af5ad/go.mod h1:StJI72GWcm/iErmk4RqFJiOo8RLbVqPbHxUqeVwAzeo= mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= diff --git a/integration/fixtures/knative/00-knative-crd-v1.19.0.yml b/integration/fixtures/knative/00-knative-crd-v1.19.0.yml new file mode 100644 index 000000000..82a700136 --- /dev/null +++ b/integration/fixtures/knative/00-knative-crd-v1.19.0.yml @@ -0,0 +1,6692 @@ +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: certificates.networking.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: networking + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: networking.internal.knative.dev + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: |- + Certificate is responsible for provisioning a SSL certificate for the + given hosts. It is a Knative abstraction for various SSL certificate + provisioning solutions (such as cert-manager or self-signed SSL certificate). + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec is the desired state of the Certificate. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + required: + - dnsNames + - secretName + properties: + dnsNames: + description: |- + DNSNames is a list of DNS names the Certificate could support. + The wildcard format of DNSNames (e.g. *.default.example.com) is supported. + type: array + items: + type: string + domain: + description: Domain is the top level domain of the values for DNSNames. + type: string + secretName: + description: SecretName is the name of the secret resource to store the SSL certificate in. + type: string + status: + description: |- + Status is the current state of the Certificate. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + properties: + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + http01Challenges: + description: |- + HTTP01Challenges is a list of HTTP01 challenges that need to be fulfilled + in order to get the TLS certificate.. + type: array + items: + description: |- + HTTP01Challenge defines the status of a HTTP01 challenge that a certificate needs + to fulfill. + type: object + properties: + serviceName: + description: ServiceName is the name of the service to serve HTTP01 challenge requests. + type: string + serviceNamespace: + description: ServiceNamespace is the namespace of the service to serve HTTP01 challenge requests. + type: string + servicePort: + description: ServicePort is the port of the service to serve HTTP01 challenge requests. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + url: + description: URL is the URL that the HTTP01 challenge is expected to serve on. + type: string + notAfter: + description: |- + The expiration time of the TLS certificate stored in the secret named + by this resource in spec.secretName. + type: string + format: date-time + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + additionalPrinterColumns: + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" + names: + kind: Certificate + plural: certificates + singular: certificate + categories: + - knative-internal + - networking + shortNames: + - kcert + scope: Namespaced + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: The schema part of the spec is auto-generated by hack/update-schemas.sh. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: configurations.serving.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" + duck.knative.dev/podspecable: "true" +spec: + group: serving.knative.dev + names: + kind: Configuration + plural: configurations + singular: configuration + categories: + - all + - knative + - serving + shortNames: + - config + - cfg + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: LatestCreated + type: string + jsonPath: .status.latestCreatedRevisionName + - name: LatestReady + type: string + jsonPath: .status.latestReadyRevisionName + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + schema: + openAPIV3Schema: + description: |- + Configuration represents the "floating HEAD" of a linear history of Revisions. + Users create new Revisions by updating the Configuration's spec. + The "latest created" revision's name is available under status, as is the + "latest ready" revision's name. + See also: https://github.com/knative/serving/blob/main/docs/spec/overview.md#configuration + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ConfigurationSpec holds the desired state of the Configuration (from the client). + type: object + properties: + template: + description: Template holds the latest specification for the Revision to be stamped out. + type: object + properties: + metadata: + type: object + properties: + annotations: + type: object + additionalProperties: + type: string + finalizers: + type: array + items: + type: string + labels: + type: object + additionalProperties: + type: string + name: + type: string + namespace: + type: string + x-kubernetes-preserve-unknown-fields: true + spec: + description: RevisionSpec holds the desired state of the Revision (from the client). + type: object + required: + - containers + properties: + affinity: + description: This is accessible behind a feature flag - kubernetes.podspec-affinity + type: object + x-kubernetes-preserve-unknown-fields: true + automountServiceAccountToken: + description: AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. + type: boolean + containerConcurrency: + description: |- + ContainerConcurrency specifies the maximum allowed in-flight (concurrent) + requests per container of the Revision. Defaults to `0` which means + concurrency to the application is not limited, and the system decides the + target concurrency for the autoscaler. + type: integer + format: int64 + containers: + description: |- + List of containers belonging to the pod. + Containers cannot currently be added or removed. + There must be at least one container in a Pod. + Cannot be updated. + type: array + items: + description: A single application container that you want to run within a pod. + type: object + properties: + args: + description: |- + Arguments to the entrypoint. + The container image's CMD is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + type: array + items: + type: string + x-kubernetes-list-type: atomic + command: + description: |- + Entrypoint array. Not executed within a shell. + The container image's ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + type: array + items: + type: string + x-kubernetes-list-type: atomic + env: + description: |- + List of environment variables to set in the container. + Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + fieldRef: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-fieldref + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + resourceFieldRef: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-fieldref + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + description: |- + List of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Cannot be updated. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps or Secrets + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + x-kubernetes-map-type: atomic + prefix: + description: Optional text to prepend to the name of each environment variable. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the Secret must be defined + type: boolean + x-kubernetes-map-type: atomic + x-kubernetes-list-type: atomic + image: + description: |- + Container image name. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + imagePullPolicy: + description: |- + Image pull policy. + One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + type: string + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + name: + description: |- + Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: |- + List of ports to expose from the container. Not specifying a port here + DOES NOT prevent that port from being exposed. Any port which is + listening on the default "0.0.0.0" address inside a container will be + accessible from the network. + Modifying this array with strategic merge patch may corrupt the data. + For more information See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + type: array + items: + description: ContainerPort represents a network port in a single container. + type: object + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + type: integer + format: int32 + name: + description: |- + If specified, this must be an IANA_SVC_NAME and unique within the pod. Each + named port in a pod must have a unique name. Name for the port that can be + referred to by services. + type: string + protocol: + description: |- + Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + default: TCP + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + resources: + description: |- + Compute Resources required by this container. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + properties: + limits: + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + securityContext: + description: |- + SecurityContext defines the security options the container should be run with. + If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + type: object + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + add: + description: This is accessible behind a feature flag - kubernetes.containerspec-addcapabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + x-kubernetes-list-type: atomic + privileged: + description: |- + Run container in privileged mode. This can only be set to explicitly to 'false' + type: boolean + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + type: object + required: + - type + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + This cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + terminationMessagePath: + description: |- + Optional: Path at which the file to which the container's termination message + will be written is mounted into the container's filesystem. + Message written is intended to be brief final status, such as an assertion failure message. + Will be truncated by the node if greater than 4096 bytes. The total message length across + all containers will be limited to 12kb. + Defaults to /dev/termination-log. + Cannot be updated. + type: string + terminationMessagePolicy: + description: |- + Indicate how the termination message should be populated. File will use the contents of + terminationMessagePath to populate the container status message on both success and failure. + FallbackToLogsOnError will use the last chunk of container log output if the termination + message file is empty and the container exited with an error. + The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + Defaults to File. + Cannot be updated. + type: string + volumeMounts: + description: |- + Pod volumes to mount into the container's filesystem. + Cannot be updated. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-mount-propagation + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + description: |- + Container's working directory. + If not specified, the container runtime's default will be used, which + might be configured in the container image. + Cannot be updated. + type: string + dnsConfig: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-dnsconfig + type: object + x-kubernetes-preserve-unknown-fields: true + dnsPolicy: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-dnspolicy + type: string + enableServiceLinks: + description: |- + EnableServiceLinks indicates whether information aboutservices should be injected into pod's environment variables, matching the syntax of Docker links. Optional: Knative defaults this to false. + type: boolean + hostAliases: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostaliases + type: array + items: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostaliases + type: object + x-kubernetes-preserve-unknown-fields: true + hostIPC: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostipc + type: boolean + hostNetwork: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostnetwork + type: boolean + hostPID: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostpid + type: boolean + idleTimeoutSeconds: + description: |- + IdleTimeoutSeconds is the maximum duration in seconds a request will be allowed + to stay open while not receiving any bytes from the user's application. If + unspecified, a system default will be provided. + type: integer + format: int64 + imagePullSecrets: + description: |- + ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. + If specified, these secrets will be passed to individual puller implementations for them to use. + More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + type: array + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + x-kubernetes-map-type: atomic + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + initContainers: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-init-containers + type: array + items: + description: This is accessible behind a feature flag - kubernetes.podspec-init-containers + type: object + x-kubernetes-preserve-unknown-fields: true + nodeSelector: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-nodeselector + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + priorityClassName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-priorityclassname + type: string + responseStartTimeoutSeconds: + description: |- + ResponseStartTimeoutSeconds is the maximum duration in seconds that the request + routing layer will wait for a request delivered to a container to begin + sending any network traffic. + type: integer + format: int64 + runtimeClassName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-runtimeclassname + type: string + schedulerName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-schedulername + type: string + securityContext: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-securitycontext + type: object + x-kubernetes-preserve-unknown-fields: true + serviceAccountName: + description: |- + ServiceAccountName is the name of the ServiceAccount to use to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + type: string + shareProcessNamespace: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-shareprocessnamespace + type: boolean + timeoutSeconds: + description: |- + TimeoutSeconds is the maximum duration in seconds that the request instance + is allowed to respond to a request. If unspecified, a system default will + be provided. + type: integer + format: int64 + tolerations: + description: This is accessible behind a feature flag - kubernetes.podspec-tolerations + type: array + items: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-tolerations + type: object + x-kubernetes-preserve-unknown-fields: true + topologySpreadConstraints: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-topologyspreadconstraints + type: array + items: + description: This is accessible behind a feature flag - kubernetes.podspec-topologyspreadconstraints + type: object + x-kubernetes-preserve-unknown-fields: true + volumes: + description: |- + List of volumes that can be mounted by containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes + type: array + items: + description: Volume represents a named volume in a pod that may be accessed by any container in the pod. + type: object + required: + - name + properties: + configMap: + description: configMap represents a configMap that should populate this volume + type: object + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + x-kubernetes-map-type: atomic + csi: + description: This is accessible behind a feature flag - kubernetes.podspec-volumes-csi + type: object + x-kubernetes-preserve-unknown-fields: true + emptyDir: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-emptydir + type: object + x-kubernetes-preserve-unknown-fields: true + hostPath: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-hostpath + type: object + x-kubernetes-preserve-unknown-fields: true + image: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-image + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + persistentVolumeClaim: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-persistent-volume-claim + type: object + x-kubernetes-preserve-unknown-fields: true + projected: + description: projected items for all in one resources secrets, configmaps, and downward API + type: object + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + type: array + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + type: object + properties: + configMap: + description: configMap information about the configMap data to project + type: object + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the downwardAPI data to project + type: object + properties: + items: + description: Items is a list of DownwardAPIVolume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name, namespace and uid are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + x-kubernetes-map-type: atomic + x-kubernetes-list-type: atomic + secret: + description: secret information about the secret data to project + type: object + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional field specify whether the Secret or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information about the serviceAccountToken data to project + type: object + required: + - path + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + type: integer + format: int64 + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + x-kubernetes-list-type: atomic + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: object + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether the Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + status: + description: ConfigurationStatus communicates the observed state of the Configuration (from the controller). + type: object + properties: + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + latestCreatedRevisionName: + description: |- + LatestCreatedRevisionName is the last revision that was created from this + Configuration. It might not be ready yet, for that use LatestReadyRevisionName. + type: string + latestReadyRevisionName: + description: |- + LatestReadyRevisionName holds the name of the latest Revision stamped out + from this Configuration that has had its "Ready" condition become "True". + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: clusterdomainclaims.networking.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: networking + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: networking.internal.knative.dev + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: ClusterDomainClaim is a cluster-wide reservation for a particular domain name. + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec is the desired state of the ClusterDomainClaim. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + required: + - namespace + properties: + namespace: + description: |- + Namespace is the namespace which is allowed to create a DomainMapping + using this ClusterDomainClaim's name. + type: string + names: + kind: ClusterDomainClaim + plural: clusterdomainclaims + singular: clusterdomainclaim + categories: + - knative-internal + - networking + shortNames: + - cdc + scope: Cluster + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: domainmappings.serving.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: serving.knative.dev + versions: + - name: v1beta1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: URL + type: string + jsonPath: .status.url + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + "schema": + "openAPIV3Schema": + description: DomainMapping is a mapping from a custom hostname to an Addressable. + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec is the desired state of the DomainMapping. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + required: + - ref + properties: + ref: + description: |- + Ref specifies the target of the Domain Mapping. + + The object identified by the Ref must be an Addressable with a URL of the + form `{name}.{namespace}.{domain}` where `{domain}` is the cluster domain, + and `{name}` and `{namespace}` are the name and namespace of a Kubernetes + Service. + + This contract is satisfied by Knative types such as Knative Services and + Knative Routes, and by Kubernetes Services. + type: object + required: + - kind + - name + properties: + address: + description: Address points to a specific Address Name. + type: string + apiVersion: + description: API version of the referent. + type: string + group: + description: |- + Group of the API, without the version of the group. This can be used as an alternative to the APIVersion, and then resolved using ResolveGroup. + Note: This API is EXPERIMENTAL and might break anytime. For more details: https://github.com/knative/eventing/issues/5086 + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + This is optional field, it gets defaulted to the object holding it if left out. + type: string + tls: + description: TLS allows the DomainMapping to terminate TLS traffic with an existing secret. + type: object + required: + - secretName + properties: + secretName: + description: SecretName is the name of the existing secret used to terminate TLS traffic. + type: string + status: + description: |- + Status is the current state of the DomainMapping. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + properties: + address: + description: Address holds the information needed for a DomainMapping to be the target of an event. + type: object + properties: + CACerts: + description: |- + CACerts is the Certification Authority (CA) certificates in PEM format + according to https://www.rfc-editor.org/rfc/rfc7468. + type: string + audience: + description: Audience is the OIDC audience for this address. + type: string + name: + description: Name is the name of the address. + type: string + url: + type: string + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + url: + description: URL is the URL of this DomainMapping. + type: string + names: + kind: DomainMapping + plural: domainmappings + singular: domainmapping + categories: + - all + - knative + - serving + shortNames: + - dm + scope: Namespaced + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: ingresses.networking.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: networking + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: networking.internal.knative.dev + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: |- + Ingress is a collection of rules that allow inbound connections to reach the endpoints defined + by a backend. An Ingress can be configured to give services externally-reachable URLs, load + balance traffic, offer name based virtual hosting, etc. + + This is heavily based on K8s Ingress https://godoc.org/k8s.io/api/networking/v1beta1#Ingress + which some highlighted modifications. + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec is the desired state of the Ingress. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + properties: + httpOption: + description: |- + HTTPOption is the option of HTTP. It has the following two values: + `HTTPOptionEnabled`, `HTTPOptionRedirected` + type: string + rules: + description: A list of host rules used to configure the Ingress. + type: array + items: + description: |- + IngressRule represents the rules mapping the paths under a specified host to + the related backend services. Incoming requests are first evaluated for a host + match, then routed to the backend associated with the matching IngressRuleValue. + type: object + properties: + hosts: + description: |- + Host is the fully qualified domain name of a network host, as defined + by RFC 3986. Note the following deviations from the "host" part of the + URI as defined in the RFC: + 1. IPs are not allowed. Currently a rule value can only apply to the + IP in the Spec of the parent . + 2. The `:` delimiter is not respected because ports are not allowed. + Currently the port of an Ingress is implicitly :80 for http and + :443 for https. + Both these may change in the future. + If the host is unspecified, the Ingress routes all traffic based on the + specified IngressRuleValue. + If multiple matching Hosts were provided, the first rule will take precedent. + type: array + items: + type: string + http: + description: |- + HTTP represents a rule to apply against incoming requests. If the + rule is satisfied, the request is routed to the specified backend. + type: object + required: + - paths + properties: + paths: + description: |- + A collection of paths that map requests to backends. + + If they are multiple matching paths, the first match takes precedence. + type: array + items: + description: |- + HTTPIngressPath associates a path regex with a backend. Incoming URLs matching + the path are forwarded to the backend. + type: object + required: + - splits + properties: + appendHeaders: + description: |- + AppendHeaders allow specifying additional HTTP headers to add + before forwarding a request to the destination service. + + NOTE: This differs from K8s Ingress which doesn't allow header appending. + type: object + additionalProperties: + type: string + headers: + description: |- + Headers defines header matching rules which is a map from a header name + to HeaderMatch which specify a matching condition. + When a request matched with all the header matching rules, + the request is routed by the corresponding ingress rule. + If it is empty, the headers are not used for matching + type: object + additionalProperties: + description: |- + HeaderMatch represents a matching value of Headers in HTTPIngressPath. + Currently, only the exact matching is supported. + type: object + required: + - exact + properties: + exact: + type: string + path: + description: |- + Path represents a literal prefix to which this rule should apply. + Currently it can contain characters disallowed from the conventional + "path" part of a URL as defined by RFC 3986. Paths must begin with + a '/'. If unspecified, the path defaults to a catch all sending + traffic to the backend. + type: string + rewriteHost: + description: |- + RewriteHost rewrites the incoming request's host header. + + This field is currently experimental and not supported by all Ingress + implementations. + type: string + splits: + description: |- + Splits defines the referenced service endpoints to which the traffic + will be forwarded to. + type: array + items: + description: IngressBackendSplit describes all endpoints for a given service and port. + type: object + required: + - serviceName + - serviceNamespace + - servicePort + properties: + appendHeaders: + description: |- + AppendHeaders allow specifying additional HTTP headers to add + before forwarding a request to the destination service. + + NOTE: This differs from K8s Ingress which doesn't allow header appending. + type: object + additionalProperties: + type: string + percent: + description: |- + Specifies the split percentage, a number between 0 and 100. If + only one split is specified, we default to 100. + + NOTE: This differs from K8s Ingress to allow percentage split. + type: integer + serviceName: + description: Specifies the name of the referenced service. + type: string + serviceNamespace: + description: |- + Specifies the namespace of the referenced service. + + NOTE: This differs from K8s Ingress to allow routing to different namespaces. + type: string + servicePort: + description: Specifies the port of the referenced service. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + visibility: + description: |- + Visibility signifies whether this rule should `ClusterLocal`. If it's not + specified then it defaults to `ExternalIP`. + type: string + tls: + description: |- + TLS configuration. Currently Ingress only supports a single TLS + port: 443. If multiple members of this list specify different hosts, they + will be multiplexed on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller fulfilling the + ingress supports SNI. + type: array + items: + description: IngressTLS describes the transport layer security associated with an Ingress. + type: object + properties: + hosts: + description: |- + Hosts is a list of hosts included in the TLS certificate. The values in + this list must match the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller fulfilling this + Ingress, if left unspecified. + type: array + items: + type: string + secretName: + description: SecretName is the name of the secret used to terminate SSL traffic. + type: string + secretNamespace: + description: |- + SecretNamespace is the namespace of the secret used to terminate SSL traffic. + If not set the namespace should be assumed to be the same as the Ingress. + If set the secret should have the same namespace as the Ingress otherwise + the behaviour is undefined and not supported. + type: string + status: + description: |- + Status is the current state of the Ingress. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + properties: + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + privateLoadBalancer: + description: PrivateLoadBalancer contains the current status of the load-balancer. + type: object + properties: + ingress: + description: |- + Ingress is a list containing ingress points for the load-balancer. + Traffic intended for the service should be sent to these ingress points. + type: array + items: + description: |- + LoadBalancerIngressStatus represents the status of a load-balancer ingress point: + traffic intended for the service should be sent to an ingress point. + type: object + properties: + domain: + description: |- + Domain is set for load-balancer ingress points that are DNS based + (typically AWS load-balancers) + type: string + domainInternal: + description: |- + DomainInternal is set if there is a cluster-local DNS name to access the Ingress. + + NOTE: This differs from K8s Ingress, since we also desire to have a cluster-local + DNS name to allow routing in case of not having a mesh. + type: string + ip: + description: |- + IP is set for load-balancer ingress points that are IP based + (typically GCE or OpenStack load-balancers) + type: string + meshOnly: + description: MeshOnly is set if the Ingress is only load-balanced through a Service mesh. + type: boolean + publicLoadBalancer: + description: PublicLoadBalancer contains the current status of the load-balancer. + type: object + properties: + ingress: + description: |- + Ingress is a list containing ingress points for the load-balancer. + Traffic intended for the service should be sent to these ingress points. + type: array + items: + description: |- + LoadBalancerIngressStatus represents the status of a load-balancer ingress point: + traffic intended for the service should be sent to an ingress point. + type: object + properties: + domain: + description: |- + Domain is set for load-balancer ingress points that are DNS based + (typically AWS load-balancers) + type: string + domainInternal: + description: |- + DomainInternal is set if there is a cluster-local DNS name to access the Ingress. + + NOTE: This differs from K8s Ingress, since we also desire to have a cluster-local + DNS name to allow routing in case of not having a mesh. + type: string + ip: + description: |- + IP is set for load-balancer ingress points that are IP based + (typically GCE or OpenStack load-balancers) + type: string + meshOnly: + description: MeshOnly is set if the Ingress is only load-balanced through a Service mesh. + type: boolean + additionalPrinterColumns: + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + names: + kind: Ingress + plural: ingresses + singular: ingress + categories: + - knative-internal + - networking + shortNames: + - kingress + - king + scope: Namespaced + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: The schema part of the spec is auto-generated by hack/update-schemas.sh. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: metrics.autoscaling.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: autoscaling.internal.knative.dev + names: + kind: Metric + plural: metrics + singular: metric + categories: + - knative-internal + - autoscaling + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + schema: + openAPIV3Schema: + description: Metric represents a resource to configure the metric collector with. + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec holds the desired state of the Metric (from the client). + type: object + required: + - panicWindow + - scrapeTarget + - stableWindow + properties: + panicWindow: + description: PanicWindow is the aggregation window for metrics where quick reactions are needed. + type: integer + format: int64 + scrapeTarget: + description: ScrapeTarget is the K8s service that publishes the metric endpoint. + type: string + stableWindow: + description: StableWindow is the aggregation window for metrics in a stable state. + type: integer + format: int64 + status: + description: Status communicates the observed state of the Metric (from the controller). + type: object + properties: + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: The schema part of the spec is auto-generated by hack/update-schemas.sh. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: podautoscalers.autoscaling.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: autoscaling.internal.knative.dev + names: + kind: PodAutoscaler + plural: podautoscalers + singular: podautoscaler + categories: + - knative-internal + - autoscaling + shortNames: + - kpa + - pa + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: DesiredScale + type: integer + jsonPath: ".status.desiredScale" + - name: ActualScale + type: integer + jsonPath: ".status.actualScale" + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + schema: + openAPIV3Schema: + description: |- + PodAutoscaler is a Knative abstraction that encapsulates the interface by which Knative + components instantiate autoscalers. This definition is an abstraction that may be backed + by multiple definitions. For more information, see the Knative Pluggability presentation: + https://docs.google.com/presentation/d/19vW9HFZ6Puxt31biNZF3uLRejDmu82rxJIk1cWmxF7w/edit + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec holds the desired state of the PodAutoscaler (from the client). + type: object + required: + - protocolType + - scaleTargetRef + properties: + containerConcurrency: + description: |- + ContainerConcurrency specifies the maximum allowed + in-flight (concurrent) requests per container of the Revision. + Defaults to `0` which means unlimited concurrency. + type: integer + format: int64 + protocolType: + description: The application-layer protocol. Matches `ProtocolType` inferred from the revision spec. + type: string + reachability: + description: |- + Reachability specifies whether or not the `ScaleTargetRef` can be reached (ie. has a route). + Defaults to `ReachabilityUnknown` + type: string + scaleTargetRef: + description: |- + ScaleTargetRef defines the /scale-able resource that this PodAutoscaler + is responsible for quickly right-sizing. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + x-kubernetes-map-type: atomic + status: + description: Status communicates the observed state of the PodAutoscaler (from the controller). + type: object + required: + - metricsServiceName + - serviceName + properties: + actualScale: + description: ActualScale shows the actual number of replicas for the revision. + type: integer + format: int32 + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + desiredScale: + description: DesiredScale shows the current desired number of replicas for the revision. + type: integer + format: int32 + metricsServiceName: + description: |- + MetricsServiceName is the K8s Service name that provides revision metrics. + The service is managed by the PA object. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + serviceName: + description: |- + ServiceName is the K8s Service name that serves the revision, scaled by this PA. + The service is created and owned by the ServerlessService object owned by this PA. + type: string + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: The schema part of the spec is auto-generated by hack/update-schemas.sh. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: revisions.serving.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: serving.knative.dev + names: + kind: Revision + plural: revisions + singular: revision + categories: + - all + - knative + - serving + shortNames: + - rev + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: Config Name + type: string + jsonPath: ".metadata.labels['serving\\.knative\\.dev/configuration']" + - name: Generation + type: string # int in string form :( + jsonPath: ".metadata.labels['serving\\.knative\\.dev/configurationGeneration']" + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + - name: Actual Replicas + type: integer + jsonPath: ".status.actualReplicas" + - name: Desired Replicas + type: integer + jsonPath: ".status.desiredReplicas" + schema: + openAPIV3Schema: + description: |- + Revision is an immutable snapshot of code and configuration. A revision + references a container image. Revisions are created by updates to a + Configuration. + + See also: https://github.com/knative/serving/blob/main/docs/spec/overview.md#revision + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: RevisionSpec holds the desired state of the Revision (from the client). + type: object + required: + - containers + properties: + affinity: + description: This is accessible behind a feature flag - kubernetes.podspec-affinity + type: object + x-kubernetes-preserve-unknown-fields: true + automountServiceAccountToken: + description: AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. + type: boolean + containerConcurrency: + description: |- + ContainerConcurrency specifies the maximum allowed in-flight (concurrent) + requests per container of the Revision. Defaults to `0` which means + concurrency to the application is not limited, and the system decides the + target concurrency for the autoscaler. + type: integer + format: int64 + containers: + description: |- + List of containers belonging to the pod. + Containers cannot currently be added or removed. + There must be at least one container in a Pod. + Cannot be updated. + type: array + items: + description: A single application container that you want to run within a pod. + type: object + properties: + args: + description: |- + Arguments to the entrypoint. + The container image's CMD is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + type: array + items: + type: string + x-kubernetes-list-type: atomic + command: + description: |- + Entrypoint array. Not executed within a shell. + The container image's ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + type: array + items: + type: string + x-kubernetes-list-type: atomic + env: + description: |- + List of environment variables to set in the container. + Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + fieldRef: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-fieldref + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + resourceFieldRef: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-fieldref + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + description: |- + List of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Cannot be updated. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps or Secrets + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + x-kubernetes-map-type: atomic + prefix: + description: Optional text to prepend to the name of each environment variable. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the Secret must be defined + type: boolean + x-kubernetes-map-type: atomic + x-kubernetes-list-type: atomic + image: + description: |- + Container image name. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + imagePullPolicy: + description: |- + Image pull policy. + One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + type: string + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + name: + description: |- + Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: |- + List of ports to expose from the container. Not specifying a port here + DOES NOT prevent that port from being exposed. Any port which is + listening on the default "0.0.0.0" address inside a container will be + accessible from the network. + Modifying this array with strategic merge patch may corrupt the data. + For more information See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + type: array + items: + description: ContainerPort represents a network port in a single container. + type: object + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + type: integer + format: int32 + name: + description: |- + If specified, this must be an IANA_SVC_NAME and unique within the pod. Each + named port in a pod must have a unique name. Name for the port that can be + referred to by services. + type: string + protocol: + description: |- + Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + default: TCP + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + resources: + description: |- + Compute Resources required by this container. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + properties: + limits: + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + securityContext: + description: |- + SecurityContext defines the security options the container should be run with. + If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + type: object + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + add: + description: This is accessible behind a feature flag - kubernetes.containerspec-addcapabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + x-kubernetes-list-type: atomic + privileged: + description: |- + Run container in privileged mode. This can only be set to explicitly to 'false' + type: boolean + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + type: object + required: + - type + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + This cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + terminationMessagePath: + description: |- + Optional: Path at which the file to which the container's termination message + will be written is mounted into the container's filesystem. + Message written is intended to be brief final status, such as an assertion failure message. + Will be truncated by the node if greater than 4096 bytes. The total message length across + all containers will be limited to 12kb. + Defaults to /dev/termination-log. + Cannot be updated. + type: string + terminationMessagePolicy: + description: |- + Indicate how the termination message should be populated. File will use the contents of + terminationMessagePath to populate the container status message on both success and failure. + FallbackToLogsOnError will use the last chunk of container log output if the termination + message file is empty and the container exited with an error. + The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + Defaults to File. + Cannot be updated. + type: string + volumeMounts: + description: |- + Pod volumes to mount into the container's filesystem. + Cannot be updated. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-mount-propagation + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + description: |- + Container's working directory. + If not specified, the container runtime's default will be used, which + might be configured in the container image. + Cannot be updated. + type: string + dnsConfig: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-dnsconfig + type: object + x-kubernetes-preserve-unknown-fields: true + dnsPolicy: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-dnspolicy + type: string + enableServiceLinks: + description: |- + EnableServiceLinks indicates whether information aboutservices should be injected into pod's environment variables, matching the syntax of Docker links. Optional: Knative defaults this to false. + type: boolean + hostAliases: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostaliases + type: array + items: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostaliases + type: object + x-kubernetes-preserve-unknown-fields: true + hostIPC: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostipc + type: boolean + hostNetwork: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostnetwork + type: boolean + hostPID: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostpid + type: boolean + idleTimeoutSeconds: + description: |- + IdleTimeoutSeconds is the maximum duration in seconds a request will be allowed + to stay open while not receiving any bytes from the user's application. If + unspecified, a system default will be provided. + type: integer + format: int64 + imagePullSecrets: + description: |- + ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. + If specified, these secrets will be passed to individual puller implementations for them to use. + More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + type: array + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + x-kubernetes-map-type: atomic + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + initContainers: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-init-containers + type: array + items: + description: This is accessible behind a feature flag - kubernetes.podspec-init-containers + type: object + x-kubernetes-preserve-unknown-fields: true + nodeSelector: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-nodeselector + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + priorityClassName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-priorityclassname + type: string + responseStartTimeoutSeconds: + description: |- + ResponseStartTimeoutSeconds is the maximum duration in seconds that the request + routing layer will wait for a request delivered to a container to begin + sending any network traffic. + type: integer + format: int64 + runtimeClassName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-runtimeclassname + type: string + schedulerName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-schedulername + type: string + securityContext: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-securitycontext + type: object + x-kubernetes-preserve-unknown-fields: true + serviceAccountName: + description: |- + ServiceAccountName is the name of the ServiceAccount to use to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + type: string + shareProcessNamespace: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-shareprocessnamespace + type: boolean + timeoutSeconds: + description: |- + TimeoutSeconds is the maximum duration in seconds that the request instance + is allowed to respond to a request. If unspecified, a system default will + be provided. + type: integer + format: int64 + tolerations: + description: This is accessible behind a feature flag - kubernetes.podspec-tolerations + type: array + items: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-tolerations + type: object + x-kubernetes-preserve-unknown-fields: true + topologySpreadConstraints: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-topologyspreadconstraints + type: array + items: + description: This is accessible behind a feature flag - kubernetes.podspec-topologyspreadconstraints + type: object + x-kubernetes-preserve-unknown-fields: true + volumes: + description: |- + List of volumes that can be mounted by containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes + type: array + items: + description: Volume represents a named volume in a pod that may be accessed by any container in the pod. + type: object + required: + - name + properties: + configMap: + description: configMap represents a configMap that should populate this volume + type: object + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + x-kubernetes-map-type: atomic + csi: + description: This is accessible behind a feature flag - kubernetes.podspec-volumes-csi + type: object + x-kubernetes-preserve-unknown-fields: true + emptyDir: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-emptydir + type: object + x-kubernetes-preserve-unknown-fields: true + hostPath: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-hostpath + type: object + x-kubernetes-preserve-unknown-fields: true + image: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-image + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + persistentVolumeClaim: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-persistent-volume-claim + type: object + x-kubernetes-preserve-unknown-fields: true + projected: + description: projected items for all in one resources secrets, configmaps, and downward API + type: object + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + type: array + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + type: object + properties: + configMap: + description: configMap information about the configMap data to project + type: object + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the downwardAPI data to project + type: object + properties: + items: + description: Items is a list of DownwardAPIVolume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name, namespace and uid are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + x-kubernetes-map-type: atomic + x-kubernetes-list-type: atomic + secret: + description: secret information about the secret data to project + type: object + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional field specify whether the Secret or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information about the serviceAccountToken data to project + type: object + required: + - path + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + type: integer + format: int64 + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + x-kubernetes-list-type: atomic + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: object + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether the Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + status: + description: RevisionStatus communicates the observed state of the Revision (from the controller). + type: object + properties: + actualReplicas: + description: ActualReplicas reflects the amount of ready pods running this revision. + type: integer + format: int32 + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + containerStatuses: + description: |- + ContainerStatuses is a slice of images present in .Spec.Container[*].Image + to their respective digests and their container name. + The digests are resolved during the creation of Revision. + ContainerStatuses holds the container name and image digests + for both serving and non serving containers. + ref: http://bit.ly/image-digests + type: array + items: + description: ContainerStatus holds the information of container name and image digest value + type: object + properties: + imageDigest: + type: string + name: + type: string + desiredReplicas: + description: DesiredReplicas reflects the desired amount of pods running this revision. + type: integer + format: int32 + initContainerStatuses: + description: |- + InitContainerStatuses is a slice of images present in .Spec.InitContainer[*].Image + to their respective digests and their container name. + The digests are resolved during the creation of Revision. + ContainerStatuses holds the container name and image digests + for both serving and non serving containers. + ref: http://bit.ly/image-digests + type: array + items: + description: ContainerStatus holds the information of container name and image digest value + type: object + properties: + imageDigest: + type: string + name: + type: string + logUrl: + description: |- + LogURL specifies the generated logging url for this particular revision + based on the revision url template specified in the controller's config. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: The schema part of the spec is auto-generated by hack/update-schemas.sh. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: routes.serving.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" + duck.knative.dev/addressable: "true" +spec: + group: serving.knative.dev + names: + kind: Route + plural: routes + singular: route + categories: + - all + - knative + - serving + shortNames: + - rt + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: URL + type: string + jsonPath: .status.url + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + schema: + openAPIV3Schema: + description: |- + Route is responsible for configuring ingress over a collection of Revisions. + Some of the Revisions a Route distributes traffic over may be specified by + referencing the Configuration responsible for creating them; in these cases + the Route is additionally responsible for monitoring the Configuration for + "latest ready revision" changes, and smoothly rolling out latest revisions. + See also: https://github.com/knative/serving/blob/main/docs/spec/overview.md#route + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec holds the desired state of the Route (from the client). + type: object + properties: + traffic: + description: |- + Traffic specifies how to distribute traffic over a collection of + revisions and configurations. + type: array + items: + description: TrafficTarget holds a single entry of the routing table for a Route. + type: object + properties: + configurationName: + description: |- + ConfigurationName of a configuration to whose latest revision we will send + this portion of traffic. When the "status.latestReadyRevisionName" of the + referenced configuration changes, we will automatically migrate traffic + from the prior "latest ready" revision to the new one. This field is never + set in Route's status, only its spec. This is mutually exclusive with + RevisionName. + type: string + latestRevision: + description: |- + LatestRevision may be optionally provided to indicate that the latest + ready Revision of the Configuration should be used for this traffic + target. When provided LatestRevision must be true if RevisionName is + empty; it must be false when RevisionName is non-empty. + type: boolean + percent: + description: |- + Percent indicates that percentage based routing should be used and + the value indicates the percent of traffic that is be routed to this + Revision or Configuration. `0` (zero) mean no traffic, `100` means all + traffic. + When percentage based routing is being used the follow rules apply: + - the sum of all percent values must equal 100 + - when not specified, the implied value for `percent` is zero for + that particular Revision or Configuration + type: integer + format: int64 + revisionName: + description: |- + RevisionName of a specific revision to which to send this portion of + traffic. This is mutually exclusive with ConfigurationName. + type: string + tag: + description: |- + Tag is optionally used to expose a dedicated url for referencing + this target exclusively. + type: string + url: + description: |- + URL displays the URL for accessing named traffic targets. URL is displayed in + status, and is disallowed on spec. URL must contain a scheme (e.g. http://) and + a hostname, but may not contain anything else (e.g. basic auth, url path, etc.) + type: string + status: + description: Status communicates the observed state of the Route (from the controller). + type: object + properties: + address: + description: Address holds the information needed for a Route to be the target of an event. + type: object + properties: + CACerts: + description: |- + CACerts is the Certification Authority (CA) certificates in PEM format + according to https://www.rfc-editor.org/rfc/rfc7468. + type: string + audience: + description: Audience is the OIDC audience for this address. + type: string + name: + description: Name is the name of the address. + type: string + url: + type: string + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + traffic: + description: |- + Traffic holds the configured traffic distribution. + These entries will always contain RevisionName references. + When ConfigurationName appears in the spec, this will hold the + LatestReadyRevisionName that we last observed. + type: array + items: + description: TrafficTarget holds a single entry of the routing table for a Route. + type: object + properties: + configurationName: + description: |- + ConfigurationName of a configuration to whose latest revision we will send + this portion of traffic. When the "status.latestReadyRevisionName" of the + referenced configuration changes, we will automatically migrate traffic + from the prior "latest ready" revision to the new one. This field is never + set in Route's status, only its spec. This is mutually exclusive with + RevisionName. + type: string + latestRevision: + description: |- + LatestRevision may be optionally provided to indicate that the latest + ready Revision of the Configuration should be used for this traffic + target. When provided LatestRevision must be true if RevisionName is + empty; it must be false when RevisionName is non-empty. + type: boolean + percent: + description: |- + Percent indicates that percentage based routing should be used and + the value indicates the percent of traffic that is be routed to this + Revision or Configuration. `0` (zero) mean no traffic, `100` means all + traffic. + When percentage based routing is being used the follow rules apply: + - the sum of all percent values must equal 100 + - when not specified, the implied value for `percent` is zero for + that particular Revision or Configuration + type: integer + format: int64 + revisionName: + description: |- + RevisionName of a specific revision to which to send this portion of + traffic. This is mutually exclusive with ConfigurationName. + type: string + tag: + description: |- + Tag is optionally used to expose a dedicated url for referencing + this target exclusively. + type: string + url: + description: |- + URL displays the URL for accessing named traffic targets. URL is displayed in + status, and is disallowed on spec. URL must contain a scheme (e.g. http://) and + a hostname, but may not contain anything else (e.g. basic auth, url path, etc.) + type: string + url: + description: |- + URL holds the url that will distribute traffic over the provided traffic targets. + It generally has the form http[s]://{route-name}.{route-namespace}.{cluster-level-suffix} + type: string + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: serverlessservices.networking.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: networking + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: networking.internal.knative.dev + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: |- + ServerlessService is a proxy for the K8s service objects containing the + endpoints for the revision, whether those are endpoints of the activator or + revision pods. + See: https://knative.page.link/naxz for details. + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec is the desired state of the ServerlessService. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + required: + - objectRef + - protocolType + properties: + mode: + description: Mode describes the mode of operation of the ServerlessService. + type: string + numActivators: + description: |- + NumActivators contains number of Activators that this revision should be + assigned. + O means — assign all. + type: integer + format: int32 + objectRef: + description: |- + ObjectRef defines the resource that this ServerlessService + is responsible for making "serverless". + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + x-kubernetes-map-type: atomic + protocolType: + description: |- + The application-layer protocol. Matches `RevisionProtocolType` set on the owning pa/revision. + serving imports networking, so just use string. + type: string + status: + description: |- + Status is the current state of the ServerlessService. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + properties: + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + privateServiceName: + description: |- + PrivateServiceName holds the name of a core K8s Service resource that + load balances over the user service pods backing this Revision. + type: string + serviceName: + description: |- + ServiceName holds the name of a core K8s Service resource that + load balances over the pods backing this Revision (activator or revision). + type: string + additionalPrinterColumns: + - name: Mode + type: string + jsonPath: ".spec.mode" + - name: Activators + type: integer + jsonPath: ".spec.numActivators" + - name: ServiceName + type: string + jsonPath: ".status.serviceName" + - name: PrivateServiceName + type: string + jsonPath: ".status.privateServiceName" + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + names: + kind: ServerlessService + plural: serverlessservices + singular: serverlessservice + categories: + - knative-internal + - networking + shortNames: + - sks + scope: Namespaced + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: The schema part of the spec is auto-generated by hack/update-schemas.sh. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: services.serving.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" + duck.knative.dev/addressable: "true" + duck.knative.dev/podspecable: "true" +spec: + group: serving.knative.dev + names: + kind: Service + plural: services + singular: service + categories: + - all + - knative + - serving + shortNames: + - kservice + - ksvc + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: URL + type: string + jsonPath: .status.url + - name: LatestCreated + type: string + jsonPath: .status.latestCreatedRevisionName + - name: LatestReady + type: string + jsonPath: .status.latestReadyRevisionName + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + schema: + openAPIV3Schema: + description: |- + Service acts as a top-level container that manages a Route and Configuration + which implement a network service. Service exists to provide a singular + abstraction which can be access controlled, reasoned about, and which + encapsulates software lifecycle decisions such as rollout policy and + team resource ownership. Service acts only as an orchestrator of the + underlying Routes and Configurations (much as a kubernetes Deployment + orchestrates ReplicaSets), and its usage is optional but recommended. + + The Service's controller will track the statuses of its owned Configuration + and Route, reflecting their statuses and conditions as its own. + + See also: https://github.com/knative/serving/blob/main/docs/spec/overview.md#service + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + ServiceSpec represents the configuration for the Service object. + A Service's specification is the union of the specifications for a Route + and Configuration. The Service restricts what can be expressed in these + fields, e.g. the Route must reference the provided Configuration; + however, these limitations also enable friendlier defaulting, + e.g. Route never needs a Configuration name, and may be defaulted to + the appropriate "run latest" spec. + type: object + properties: + template: + description: Template holds the latest specification for the Revision to be stamped out. + type: object + properties: + metadata: + type: object + properties: + annotations: + type: object + additionalProperties: + type: string + finalizers: + type: array + items: + type: string + labels: + type: object + additionalProperties: + type: string + name: + type: string + namespace: + type: string + x-kubernetes-preserve-unknown-fields: true + spec: + description: RevisionSpec holds the desired state of the Revision (from the client). + type: object + required: + - containers + properties: + affinity: + description: This is accessible behind a feature flag - kubernetes.podspec-affinity + type: object + x-kubernetes-preserve-unknown-fields: true + automountServiceAccountToken: + description: AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. + type: boolean + containerConcurrency: + description: |- + ContainerConcurrency specifies the maximum allowed in-flight (concurrent) + requests per container of the Revision. Defaults to `0` which means + concurrency to the application is not limited, and the system decides the + target concurrency for the autoscaler. + type: integer + format: int64 + containers: + description: |- + List of containers belonging to the pod. + Containers cannot currently be added or removed. + There must be at least one container in a Pod. + Cannot be updated. + type: array + items: + description: A single application container that you want to run within a pod. + type: object + properties: + args: + description: |- + Arguments to the entrypoint. + The container image's CMD is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + type: array + items: + type: string + x-kubernetes-list-type: atomic + command: + description: |- + Entrypoint array. Not executed within a shell. + The container image's ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + type: array + items: + type: string + x-kubernetes-list-type: atomic + env: + description: |- + List of environment variables to set in the container. + Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + fieldRef: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-fieldref + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + resourceFieldRef: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-fieldref + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + description: |- + List of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Cannot be updated. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps or Secrets + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + x-kubernetes-map-type: atomic + prefix: + description: Optional text to prepend to the name of each environment variable. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the Secret must be defined + type: boolean + x-kubernetes-map-type: atomic + x-kubernetes-list-type: atomic + image: + description: |- + Container image name. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + imagePullPolicy: + description: |- + Image pull policy. + One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + type: string + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + name: + description: |- + Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: |- + List of ports to expose from the container. Not specifying a port here + DOES NOT prevent that port from being exposed. Any port which is + listening on the default "0.0.0.0" address inside a container will be + accessible from the network. + Modifying this array with strategic merge patch may corrupt the data. + For more information See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + type: array + items: + description: ContainerPort represents a network port in a single container. + type: object + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + type: integer + format: int32 + name: + description: |- + If specified, this must be an IANA_SVC_NAME and unique within the pod. Each + named port in a pod must have a unique name. Name for the port that can be + referred to by services. + type: string + protocol: + description: |- + Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + default: TCP + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + resources: + description: |- + Compute Resources required by this container. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + properties: + limits: + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + securityContext: + description: |- + SecurityContext defines the security options the container should be run with. + If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + type: object + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + add: + description: This is accessible behind a feature flag - kubernetes.containerspec-addcapabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + x-kubernetes-list-type: atomic + privileged: + description: |- + Run container in privileged mode. This can only be set to explicitly to 'false' + type: boolean + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + type: object + required: + - type + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + This cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + terminationMessagePath: + description: |- + Optional: Path at which the file to which the container's termination message + will be written is mounted into the container's filesystem. + Message written is intended to be brief final status, such as an assertion failure message. + Will be truncated by the node if greater than 4096 bytes. The total message length across + all containers will be limited to 12kb. + Defaults to /dev/termination-log. + Cannot be updated. + type: string + terminationMessagePolicy: + description: |- + Indicate how the termination message should be populated. File will use the contents of + terminationMessagePath to populate the container status message on both success and failure. + FallbackToLogsOnError will use the last chunk of container log output if the termination + message file is empty and the container exited with an error. + The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + Defaults to File. + Cannot be updated. + type: string + volumeMounts: + description: |- + Pod volumes to mount into the container's filesystem. + Cannot be updated. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-mount-propagation + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + description: |- + Container's working directory. + If not specified, the container runtime's default will be used, which + might be configured in the container image. + Cannot be updated. + type: string + dnsConfig: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-dnsconfig + type: object + x-kubernetes-preserve-unknown-fields: true + dnsPolicy: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-dnspolicy + type: string + enableServiceLinks: + description: |- + EnableServiceLinks indicates whether information aboutservices should be injected into pod's environment variables, matching the syntax of Docker links. Optional: Knative defaults this to false. + type: boolean + hostAliases: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostaliases + type: array + items: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostaliases + type: object + x-kubernetes-preserve-unknown-fields: true + hostIPC: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostipc + type: boolean + hostNetwork: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostnetwork + type: boolean + hostPID: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostpid + type: boolean + idleTimeoutSeconds: + description: |- + IdleTimeoutSeconds is the maximum duration in seconds a request will be allowed + to stay open while not receiving any bytes from the user's application. If + unspecified, a system default will be provided. + type: integer + format: int64 + imagePullSecrets: + description: |- + ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. + If specified, these secrets will be passed to individual puller implementations for them to use. + More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + type: array + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + x-kubernetes-map-type: atomic + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + initContainers: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-init-containers + type: array + items: + description: This is accessible behind a feature flag - kubernetes.podspec-init-containers + type: object + x-kubernetes-preserve-unknown-fields: true + nodeSelector: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-nodeselector + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + priorityClassName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-priorityclassname + type: string + responseStartTimeoutSeconds: + description: |- + ResponseStartTimeoutSeconds is the maximum duration in seconds that the request + routing layer will wait for a request delivered to a container to begin + sending any network traffic. + type: integer + format: int64 + runtimeClassName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-runtimeclassname + type: string + schedulerName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-schedulername + type: string + securityContext: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-securitycontext + type: object + x-kubernetes-preserve-unknown-fields: true + serviceAccountName: + description: |- + ServiceAccountName is the name of the ServiceAccount to use to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + type: string + shareProcessNamespace: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-shareprocessnamespace + type: boolean + timeoutSeconds: + description: |- + TimeoutSeconds is the maximum duration in seconds that the request instance + is allowed to respond to a request. If unspecified, a system default will + be provided. + type: integer + format: int64 + tolerations: + description: This is accessible behind a feature flag - kubernetes.podspec-tolerations + type: array + items: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-tolerations + type: object + x-kubernetes-preserve-unknown-fields: true + topologySpreadConstraints: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-topologyspreadconstraints + type: array + items: + description: This is accessible behind a feature flag - kubernetes.podspec-topologyspreadconstraints + type: object + x-kubernetes-preserve-unknown-fields: true + volumes: + description: |- + List of volumes that can be mounted by containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes + type: array + items: + description: Volume represents a named volume in a pod that may be accessed by any container in the pod. + type: object + required: + - name + properties: + configMap: + description: configMap represents a configMap that should populate this volume + type: object + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + x-kubernetes-map-type: atomic + csi: + description: This is accessible behind a feature flag - kubernetes.podspec-volumes-csi + type: object + x-kubernetes-preserve-unknown-fields: true + emptyDir: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-emptydir + type: object + x-kubernetes-preserve-unknown-fields: true + hostPath: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-hostpath + type: object + x-kubernetes-preserve-unknown-fields: true + image: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-image + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + persistentVolumeClaim: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-persistent-volume-claim + type: object + x-kubernetes-preserve-unknown-fields: true + projected: + description: projected items for all in one resources secrets, configmaps, and downward API + type: object + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + type: array + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + type: object + properties: + configMap: + description: configMap information about the configMap data to project + type: object + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the downwardAPI data to project + type: object + properties: + items: + description: Items is a list of DownwardAPIVolume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name, namespace and uid are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + x-kubernetes-map-type: atomic + x-kubernetes-list-type: atomic + secret: + description: secret information about the secret data to project + type: object + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional field specify whether the Secret or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information about the serviceAccountToken data to project + type: object + required: + - path + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + type: integer + format: int64 + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + x-kubernetes-list-type: atomic + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: object + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether the Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + traffic: + description: |- + Traffic specifies how to distribute traffic over a collection of + revisions and configurations. + type: array + items: + description: TrafficTarget holds a single entry of the routing table for a Route. + type: object + properties: + configurationName: + description: |- + ConfigurationName of a configuration to whose latest revision we will send + this portion of traffic. When the "status.latestReadyRevisionName" of the + referenced configuration changes, we will automatically migrate traffic + from the prior "latest ready" revision to the new one. This field is never + set in Route's status, only its spec. This is mutually exclusive with + RevisionName. + type: string + latestRevision: + description: |- + LatestRevision may be optionally provided to indicate that the latest + ready Revision of the Configuration should be used for this traffic + target. When provided LatestRevision must be true if RevisionName is + empty; it must be false when RevisionName is non-empty. + type: boolean + percent: + description: |- + Percent indicates that percentage based routing should be used and + the value indicates the percent of traffic that is be routed to this + Revision or Configuration. `0` (zero) mean no traffic, `100` means all + traffic. + When percentage based routing is being used the follow rules apply: + - the sum of all percent values must equal 100 + - when not specified, the implied value for `percent` is zero for + that particular Revision or Configuration + type: integer + format: int64 + revisionName: + description: |- + RevisionName of a specific revision to which to send this portion of + traffic. This is mutually exclusive with ConfigurationName. + type: string + tag: + description: |- + Tag is optionally used to expose a dedicated url for referencing + this target exclusively. + type: string + url: + description: |- + URL displays the URL for accessing named traffic targets. URL is displayed in + status, and is disallowed on spec. URL must contain a scheme (e.g. http://) and + a hostname, but may not contain anything else (e.g. basic auth, url path, etc.) + type: string + status: + description: ServiceStatus represents the Status stanza of the Service resource. + type: object + properties: + address: + description: Address holds the information needed for a Route to be the target of an event. + type: object + properties: + CACerts: + description: |- + CACerts is the Certification Authority (CA) certificates in PEM format + according to https://www.rfc-editor.org/rfc/rfc7468. + type: string + audience: + description: Audience is the OIDC audience for this address. + type: string + name: + description: Name is the name of the address. + type: string + url: + type: string + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + latestCreatedRevisionName: + description: |- + LatestCreatedRevisionName is the last revision that was created from this + Configuration. It might not be ready yet, for that use LatestReadyRevisionName. + type: string + latestReadyRevisionName: + description: |- + LatestReadyRevisionName holds the name of the latest Revision stamped out + from this Configuration that has had its "Ready" condition become "True". + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + traffic: + description: |- + Traffic holds the configured traffic distribution. + These entries will always contain RevisionName references. + When ConfigurationName appears in the spec, this will hold the + LatestReadyRevisionName that we last observed. + type: array + items: + description: TrafficTarget holds a single entry of the routing table for a Route. + type: object + properties: + configurationName: + description: |- + ConfigurationName of a configuration to whose latest revision we will send + this portion of traffic. When the "status.latestReadyRevisionName" of the + referenced configuration changes, we will automatically migrate traffic + from the prior "latest ready" revision to the new one. This field is never + set in Route's status, only its spec. This is mutually exclusive with + RevisionName. + type: string + latestRevision: + description: |- + LatestRevision may be optionally provided to indicate that the latest + ready Revision of the Configuration should be used for this traffic + target. When provided LatestRevision must be true if RevisionName is + empty; it must be false when RevisionName is non-empty. + type: boolean + percent: + description: |- + Percent indicates that percentage based routing should be used and + the value indicates the percent of traffic that is be routed to this + Revision or Configuration. `0` (zero) mean no traffic, `100` means all + traffic. + When percentage based routing is being used the follow rules apply: + - the sum of all percent values must equal 100 + - when not specified, the implied value for `percent` is zero for + that particular Revision or Configuration + type: integer + format: int64 + revisionName: + description: |- + RevisionName of a specific revision to which to send this portion of + traffic. This is mutually exclusive with ConfigurationName. + type: string + tag: + description: |- + Tag is optionally used to expose a dedicated url for referencing + this target exclusively. + type: string + url: + description: |- + URL displays the URL for accessing named traffic targets. URL is displayed in + status, and is disallowed on spec. URL must contain a scheme (e.g. http://) and + a hostname, but may not contain anything else (e.g. basic auth, url path, etc.) + type: string + url: + description: |- + URL holds the url that will distribute traffic over the provided traffic targets. + It generally has the form http[s]://{route-name}.{route-namespace}.{cluster-level-suffix} + type: string + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: images.caching.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: caching.internal.knative.dev + names: + kind: Image + plural: images + singular: image + categories: + - knative-internal + - caching + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: |- + Image is a Knative abstraction that encapsulates the interface by which Knative + components express a desire to have a particular image cached. + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec holds the desired state of the Image (from the client). + type: object + required: + - image + properties: + image: + description: Image is the name of the container image url to cache across the cluster. + type: string + imagePullSecrets: + description: |- + ImagePullSecrets contains the names of the Kubernetes Secrets containing login + information used by the Pods which will run this container. + type: array + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + x-kubernetes-map-type: atomic + serviceAccountName: + description: |- + ServiceAccountName is the name of the Kubernetes ServiceAccount as which the Pods + will run this container. This is potentially used to authenticate the image pull + if the service account has attached pull secrets. For more information: + https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account + type: string + status: + description: Status communicates the observed state of the Image (from the controller). + type: object + properties: + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + additionalPrinterColumns: + - name: Image + type: string + jsonPath: .spec.image + +--- diff --git a/integration/fixtures/knative/01-rbac.yml b/integration/fixtures/knative/01-rbac.yml new file mode 100644 index 000000000..af29709e9 --- /dev/null +++ b/integration/fixtures/knative/01-rbac.yml @@ -0,0 +1,50 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: knative-networking-role +rules: + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - networking.internal.knative.dev + resources: + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - networking.internal.knative.dev + resources: + - ingresses/status + verbs: + - update + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: traefik +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: knative-networking-role +subjects: + - kind: ServiceAccount + name: traefik + namespace: traefik diff --git a/integration/fixtures/knative/02-traefik.yml b/integration/fixtures/knative/02-traefik.yml new file mode 100644 index 000000000..15588a4a6 --- /dev/null +++ b/integration/fixtures/knative/02-traefik.yml @@ -0,0 +1,102 @@ +--- +kind: Namespace +apiVersion: v1 +metadata: + name: traefik + +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + name: traefik + namespace: traefik + +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: traefik + namespace: traefik + labels: + app: traefik +spec: + replicas: 1 + selector: + matchLabels: + app: traefik + template: + metadata: + labels: + app: traefik + spec: + serviceAccountName: traefik + containers: + - name: traefik + image: traefik/traefik:latest + imagePullPolicy: Never + args: + - --api.insecure + - --log.level=debug + - --entrypoints.pweb.address=:80 + - --entrypoints.pwebsecure.address=:443 + - --entrypoints.privweb.address=:8080 + - --entrypoints.privwebsecure.address=:4443 + - --entrypoints.traefik.address=:9000 + - --experimental.knative + - --providers.knative.publicEntrypoints=pweb,pwebsecure + - --providers.knative.publicService.namespace=traefik + - --providers.knative.publicService.name=traefik + - --providers.knative.privateEntrypoints=privweb,privwebsecure + - --providers.knative.privateService.namespace=traefik + - --providers.knative.privateService.name=privtraefik + - --providers.knative.throttleduration=2s + + ports: + - name: pweb + containerPort: 80 + - name: pwebsecure + containerPort: 443 + - name: privweb + containerPort: 8080 + - name: privwebsecure + containerPort: 4443 + - name: traefik + containerPort: 9000 + +--- +apiVersion: v1 +kind: Service +metadata: + name: traefik + namespace: traefik +spec: + type: LoadBalancer + selector: + app: traefik + ports: + - port: 80 + name: web + targetPort: pweb + - port: 443 + name: websecure + targetPort: pwebsecure + - port: 9000 + name: traefik + targetPort: traefik + +--- +apiVersion: v1 +kind: Service +metadata: + name: privtraefik + namespace: traefik +spec: + selector: + app: traefik + ports: + - port: 80 + name: web + targetPort: privweb + - port: 443 + name: websecure + targetPort: privwebsecure diff --git a/integration/fixtures/knative/03-knative-serving-v1.19.0.yaml b/integration/fixtures/knative/03-knative-serving-v1.19.0.yaml new file mode 100644 index 000000000..98a89d394 --- /dev/null +++ b/integration/fixtures/knative/03-knative-serving-v1.19.0.yaml @@ -0,0 +1,9513 @@ +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Namespace +metadata: + name: knative-serving + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + +--- +# Copyright 2023 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: knative-serving-activator + namespace: knative-serving + labels: + serving.knative.dev/controller: "true" + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving +rules: + - apiGroups: [""] + resources: ["configmaps", "secrets"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] + resourceNames: ["routing-serving-certs", "knative-serving-certs"] +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: knative-serving-activator-cluster + labels: + serving.knative.dev/controller: "true" + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving +rules: + - apiGroups: [""] + resources: ["services", "endpoints"] + verbs: ["get", "list", "watch"] + - apiGroups: ["serving.knative.dev"] + resources: ["revisions"] + verbs: ["get", "list", "watch"] + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Use this aggregated ClusterRole when you need readonly access to "Addressables" +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + # Named like this to avoid clashing with eventing's existing `addressable-resolver` role + # (which should be identical, but isn't guaranteed to be installed alongside serving). + name: knative-serving-aggregated-addressable-resolver + labels: + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving +aggregationRule: + clusterRoleSelectors: + - matchLabels: + duck.knative.dev/addressable: "true" +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: knative-serving-addressable-resolver + labels: + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving + # Labeled to facilitate aggregated cluster roles that act on Addressables. + duck.knative.dev/addressable: "true" +# Do not use this role directly. These rules will be added to the "addressable-resolver" role. +rules: + - apiGroups: + - serving.knative.dev + resources: + - routes + - routes/status + - services + - services/status + verbs: + - get + - list + - watch + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: knative-serving-namespaced-admin + labels: + rbac.authorization.k8s.io/aggregate-to-admin: "true" + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving +rules: + - apiGroups: ["serving.knative.dev"] + resources: ["*"] + verbs: ["*"] + - apiGroups: ["networking.internal.knative.dev", "autoscaling.internal.knative.dev", "caching.internal.knative.dev"] + resources: ["*"] + verbs: ["get", "list", "watch"] +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: knative-serving-namespaced-edit + labels: + rbac.authorization.k8s.io/aggregate-to-edit: "true" + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving +rules: + - apiGroups: ["serving.knative.dev"] + resources: ["*"] + verbs: ["create", "update", "patch", "delete"] + - apiGroups: ["networking.internal.knative.dev", "autoscaling.internal.knative.dev", "caching.internal.knative.dev"] + resources: ["*"] + verbs: ["get", "list", "watch"] +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: knative-serving-namespaced-view + labels: + rbac.authorization.k8s.io/aggregate-to-view: "true" + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving +rules: + - apiGroups: ["serving.knative.dev", "networking.internal.knative.dev", "autoscaling.internal.knative.dev", "caching.internal.knative.dev"] + resources: ["*"] + verbs: ["get", "list", "watch"] + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: knative-serving-core + labels: + serving.knative.dev/controller: "true" + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving +rules: + - apiGroups: [""] + resources: ["pods", "namespaces", "secrets", "configmaps", "endpoints", "services", "events", "serviceaccounts"] + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] + - apiGroups: [""] + resources: ["endpoints/restricted"] # Permission for RestrictedEndpointsAdmission + verbs: ["create"] + - apiGroups: [""] + resources: ["namespaces/finalizers"] # finalizers are needed for the owner reference of the webhook + verbs: ["update"] + - apiGroups: ["apps"] + resources: ["deployments", "deployments/finalizers"] # finalizers are needed for the owner reference of the webhook + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] + - apiGroups: ["admissionregistration.k8s.io"] + resources: ["mutatingwebhookconfigurations", "validatingwebhookconfigurations"] + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions", "customresourcedefinitions/status"] + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] + - apiGroups: ["autoscaling"] + resources: ["horizontalpodautoscalers"] + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] + - apiGroups: ["serving.knative.dev", "autoscaling.internal.knative.dev", "networking.internal.knative.dev"] + resources: ["*", "*/status", "*/finalizers"] + verbs: ["get", "list", "create", "update", "delete", "deletecollection", "patch", "watch"] + - apiGroups: ["caching.internal.knative.dev"] + resources: ["images"] + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "clusterissuers", "certificaterequests", "issuers"] + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges"] + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] + - apiGroups: ["rbac.authorization.k8s.io"] + resources: ["clusterroles"] + verbs: ["delete"] + resourceNames: ["knative-serving-certmanager"] + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: knative-serving-podspecable-binding + labels: + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving + # Labeled to facilitate aggregated cluster roles that act on PodSpecables. + duck.knative.dev/podspecable: "true" +# Do not use this role directly. These rules will be added to the "podspecable-binder" role. +rules: + - apiGroups: + - serving.knative.dev + resources: + - configurations + - services + verbs: + - list + - watch + - patch + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: controller + namespace: knative-serving + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: knative-serving-admin + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +aggregationRule: + clusterRoleSelectors: + - matchLabels: + serving.knative.dev/controller: "true" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: knative-serving-controller-admin + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +subjects: + - kind: ServiceAccount + name: controller + namespace: knative-serving +roleRef: + kind: ClusterRole + name: knative-serving-admin + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: knative-serving-controller-addressable-resolver + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +subjects: + - kind: ServiceAccount + name: controller + namespace: knative-serving +roleRef: + kind: ClusterRole + name: knative-serving-aggregated-addressable-resolver + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: activator + namespace: knative-serving + labels: + app.kubernetes.io/component: activator + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: knative-serving-activator + namespace: knative-serving + labels: + app.kubernetes.io/component: activator + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +subjects: + - kind: ServiceAccount + name: activator + namespace: knative-serving +roleRef: + kind: Role + name: knative-serving-activator + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: knative-serving-activator-cluster + labels: + app.kubernetes.io/component: activator + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +subjects: + - kind: ServiceAccount + name: activator + namespace: knative-serving +roleRef: + kind: ClusterRole + name: knative-serving-activator-cluster + apiGroup: rbac.authorization.k8s.io + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: images.caching.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: caching.internal.knative.dev + names: + kind: Image + plural: images + singular: image + categories: + - knative-internal + - caching + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: |- + Image is a Knative abstraction that encapsulates the interface by which Knative + components express a desire to have a particular image cached. + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec holds the desired state of the Image (from the client). + type: object + required: + - image + properties: + image: + description: Image is the name of the container image url to cache across the cluster. + type: string + imagePullSecrets: + description: |- + ImagePullSecrets contains the names of the Kubernetes Secrets containing login + information used by the Pods which will run this container. + type: array + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + x-kubernetes-map-type: atomic + serviceAccountName: + description: |- + ServiceAccountName is the name of the Kubernetes ServiceAccount as which the Pods + will run this container. This is potentially used to authenticate the image pull + if the service account has attached pull secrets. For more information: + https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account + type: string + status: + description: Status communicates the observed state of the Image (from the controller). + type: object + properties: + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + additionalPrinterColumns: + - name: Image + type: string + jsonPath: .spec.image + +--- +apiVersion: networking.internal.knative.dev/v1alpha1 +kind: Certificate +metadata: + annotations: + networking.knative.dev/certificate.class: cert-manager.certificate.networking.knative.dev + labels: + networking.knative.dev/certificate-type: system-internal + name: routing-serving-certs + namespace: knative-serving +spec: + dnsNames: + - kn-routing + - data-plane.knative.dev # for reverse-compatibility with net-* implementations that do not work with multi-SANs + secretName: routing-serving-certs + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: certificates.networking.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: networking + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: networking.internal.knative.dev + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: |- + Certificate is responsible for provisioning a SSL certificate for the + given hosts. It is a Knative abstraction for various SSL certificate + provisioning solutions (such as cert-manager or self-signed SSL certificate). + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec is the desired state of the Certificate. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + required: + - dnsNames + - secretName + properties: + dnsNames: + description: |- + DNSNames is a list of DNS names the Certificate could support. + The wildcard format of DNSNames (e.g. *.default.example.com) is supported. + type: array + items: + type: string + domain: + description: Domain is the top level domain of the values for DNSNames. + type: string + secretName: + description: SecretName is the name of the secret resource to store the SSL certificate in. + type: string + status: + description: |- + Status is the current state of the Certificate. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + properties: + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + http01Challenges: + description: |- + HTTP01Challenges is a list of HTTP01 challenges that need to be fulfilled + in order to get the TLS certificate.. + type: array + items: + description: |- + HTTP01Challenge defines the status of a HTTP01 challenge that a certificate needs + to fulfill. + type: object + properties: + serviceName: + description: ServiceName is the name of the service to serve HTTP01 challenge requests. + type: string + serviceNamespace: + description: ServiceNamespace is the namespace of the service to serve HTTP01 challenge requests. + type: string + servicePort: + description: ServicePort is the port of the service to serve HTTP01 challenge requests. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + url: + description: URL is the URL that the HTTP01 challenge is expected to serve on. + type: string + notAfter: + description: |- + The expiration time of the TLS certificate stored in the secret named + by this resource in spec.secretName. + type: string + format: date-time + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + additionalPrinterColumns: + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" + names: + kind: Certificate + plural: certificates + singular: certificate + categories: + - knative-internal + - networking + shortNames: + - kcert + scope: Namespaced + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: The schema part of the spec is auto-generated by hack/update-schemas.sh. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: configurations.serving.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" + duck.knative.dev/podspecable: "true" +spec: + group: serving.knative.dev + names: + kind: Configuration + plural: configurations + singular: configuration + categories: + - all + - knative + - serving + shortNames: + - config + - cfg + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: LatestCreated + type: string + jsonPath: .status.latestCreatedRevisionName + - name: LatestReady + type: string + jsonPath: .status.latestReadyRevisionName + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + schema: + openAPIV3Schema: + description: |- + Configuration represents the "floating HEAD" of a linear history of Revisions. + Users create new Revisions by updating the Configuration's spec. + The "latest created" revision's name is available under status, as is the + "latest ready" revision's name. + See also: https://github.com/knative/serving/blob/main/docs/spec/overview.md#configuration + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ConfigurationSpec holds the desired state of the Configuration (from the client). + type: object + properties: + template: + description: Template holds the latest specification for the Revision to be stamped out. + type: object + properties: + metadata: + type: object + properties: + annotations: + type: object + additionalProperties: + type: string + finalizers: + type: array + items: + type: string + labels: + type: object + additionalProperties: + type: string + name: + type: string + namespace: + type: string + x-kubernetes-preserve-unknown-fields: true + spec: + description: RevisionSpec holds the desired state of the Revision (from the client). + type: object + required: + - containers + properties: + affinity: + description: This is accessible behind a feature flag - kubernetes.podspec-affinity + type: object + x-kubernetes-preserve-unknown-fields: true + automountServiceAccountToken: + description: AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. + type: boolean + containerConcurrency: + description: |- + ContainerConcurrency specifies the maximum allowed in-flight (concurrent) + requests per container of the Revision. Defaults to `0` which means + concurrency to the application is not limited, and the system decides the + target concurrency for the autoscaler. + type: integer + format: int64 + containers: + description: |- + List of containers belonging to the pod. + Containers cannot currently be added or removed. + There must be at least one container in a Pod. + Cannot be updated. + type: array + items: + description: A single application container that you want to run within a pod. + type: object + properties: + args: + description: |- + Arguments to the entrypoint. + The container image's CMD is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + type: array + items: + type: string + x-kubernetes-list-type: atomic + command: + description: |- + Entrypoint array. Not executed within a shell. + The container image's ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + type: array + items: + type: string + x-kubernetes-list-type: atomic + env: + description: |- + List of environment variables to set in the container. + Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + fieldRef: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-fieldref + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + resourceFieldRef: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-fieldref + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + description: |- + List of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Cannot be updated. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps or Secrets + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + x-kubernetes-map-type: atomic + prefix: + description: Optional text to prepend to the name of each environment variable. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the Secret must be defined + type: boolean + x-kubernetes-map-type: atomic + x-kubernetes-list-type: atomic + image: + description: |- + Container image name. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + imagePullPolicy: + description: |- + Image pull policy. + One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + type: string + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + name: + description: |- + Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: |- + List of ports to expose from the container. Not specifying a port here + DOES NOT prevent that port from being exposed. Any port which is + listening on the default "0.0.0.0" address inside a container will be + accessible from the network. + Modifying this array with strategic merge patch may corrupt the data. + For more information See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + type: array + items: + description: ContainerPort represents a network port in a single container. + type: object + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + type: integer + format: int32 + name: + description: |- + If specified, this must be an IANA_SVC_NAME and unique within the pod. Each + named port in a pod must have a unique name. Name for the port that can be + referred to by services. + type: string + protocol: + description: |- + Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + default: TCP + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + resources: + description: |- + Compute Resources required by this container. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + properties: + limits: + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + securityContext: + description: |- + SecurityContext defines the security options the container should be run with. + If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + type: object + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + add: + description: This is accessible behind a feature flag - kubernetes.containerspec-addcapabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + x-kubernetes-list-type: atomic + privileged: + description: |- + Run container in privileged mode. This can only be set to explicitly to 'false' + type: boolean + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + type: object + required: + - type + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + This cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + terminationMessagePath: + description: |- + Optional: Path at which the file to which the container's termination message + will be written is mounted into the container's filesystem. + Message written is intended to be brief final status, such as an assertion failure message. + Will be truncated by the node if greater than 4096 bytes. The total message length across + all containers will be limited to 12kb. + Defaults to /dev/termination-log. + Cannot be updated. + type: string + terminationMessagePolicy: + description: |- + Indicate how the termination message should be populated. File will use the contents of + terminationMessagePath to populate the container status message on both success and failure. + FallbackToLogsOnError will use the last chunk of container log output if the termination + message file is empty and the container exited with an error. + The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + Defaults to File. + Cannot be updated. + type: string + volumeMounts: + description: |- + Pod volumes to mount into the container's filesystem. + Cannot be updated. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-mount-propagation + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + description: |- + Container's working directory. + If not specified, the container runtime's default will be used, which + might be configured in the container image. + Cannot be updated. + type: string + dnsConfig: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-dnsconfig + type: object + x-kubernetes-preserve-unknown-fields: true + dnsPolicy: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-dnspolicy + type: string + enableServiceLinks: + description: |- + EnableServiceLinks indicates whether information aboutservices should be injected into pod's environment variables, matching the syntax of Docker links. Optional: Knative defaults this to false. + type: boolean + hostAliases: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostaliases + type: array + items: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostaliases + type: object + x-kubernetes-preserve-unknown-fields: true + hostIPC: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostipc + type: boolean + hostNetwork: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostnetwork + type: boolean + hostPID: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostpid + type: boolean + idleTimeoutSeconds: + description: |- + IdleTimeoutSeconds is the maximum duration in seconds a request will be allowed + to stay open while not receiving any bytes from the user's application. If + unspecified, a system default will be provided. + type: integer + format: int64 + imagePullSecrets: + description: |- + ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. + If specified, these secrets will be passed to individual puller implementations for them to use. + More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + type: array + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + x-kubernetes-map-type: atomic + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + initContainers: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-init-containers + type: array + items: + description: This is accessible behind a feature flag - kubernetes.podspec-init-containers + type: object + x-kubernetes-preserve-unknown-fields: true + nodeSelector: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-nodeselector + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + priorityClassName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-priorityclassname + type: string + responseStartTimeoutSeconds: + description: |- + ResponseStartTimeoutSeconds is the maximum duration in seconds that the request + routing layer will wait for a request delivered to a container to begin + sending any network traffic. + type: integer + format: int64 + runtimeClassName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-runtimeclassname + type: string + schedulerName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-schedulername + type: string + securityContext: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-securitycontext + type: object + x-kubernetes-preserve-unknown-fields: true + serviceAccountName: + description: |- + ServiceAccountName is the name of the ServiceAccount to use to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + type: string + shareProcessNamespace: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-shareprocessnamespace + type: boolean + timeoutSeconds: + description: |- + TimeoutSeconds is the maximum duration in seconds that the request instance + is allowed to respond to a request. If unspecified, a system default will + be provided. + type: integer + format: int64 + tolerations: + description: This is accessible behind a feature flag - kubernetes.podspec-tolerations + type: array + items: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-tolerations + type: object + x-kubernetes-preserve-unknown-fields: true + topologySpreadConstraints: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-topologyspreadconstraints + type: array + items: + description: This is accessible behind a feature flag - kubernetes.podspec-topologyspreadconstraints + type: object + x-kubernetes-preserve-unknown-fields: true + volumes: + description: |- + List of volumes that can be mounted by containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes + type: array + items: + description: Volume represents a named volume in a pod that may be accessed by any container in the pod. + type: object + required: + - name + properties: + configMap: + description: configMap represents a configMap that should populate this volume + type: object + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + x-kubernetes-map-type: atomic + csi: + description: This is accessible behind a feature flag - kubernetes.podspec-volumes-csi + type: object + x-kubernetes-preserve-unknown-fields: true + emptyDir: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-emptydir + type: object + x-kubernetes-preserve-unknown-fields: true + hostPath: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-hostpath + type: object + x-kubernetes-preserve-unknown-fields: true + image: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-image + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + persistentVolumeClaim: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-persistent-volume-claim + type: object + x-kubernetes-preserve-unknown-fields: true + projected: + description: projected items for all in one resources secrets, configmaps, and downward API + type: object + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + type: array + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + type: object + properties: + configMap: + description: configMap information about the configMap data to project + type: object + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the downwardAPI data to project + type: object + properties: + items: + description: Items is a list of DownwardAPIVolume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name, namespace and uid are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + x-kubernetes-map-type: atomic + x-kubernetes-list-type: atomic + secret: + description: secret information about the secret data to project + type: object + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional field specify whether the Secret or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information about the serviceAccountToken data to project + type: object + required: + - path + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + type: integer + format: int64 + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + x-kubernetes-list-type: atomic + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: object + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether the Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + status: + description: ConfigurationStatus communicates the observed state of the Configuration (from the controller). + type: object + properties: + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + latestCreatedRevisionName: + description: |- + LatestCreatedRevisionName is the last revision that was created from this + Configuration. It might not be ready yet, for that use LatestReadyRevisionName. + type: string + latestReadyRevisionName: + description: |- + LatestReadyRevisionName holds the name of the latest Revision stamped out + from this Configuration that has had its "Ready" condition become "True". + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: clusterdomainclaims.networking.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: networking + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: networking.internal.knative.dev + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: ClusterDomainClaim is a cluster-wide reservation for a particular domain name. + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec is the desired state of the ClusterDomainClaim. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + required: + - namespace + properties: + namespace: + description: |- + Namespace is the namespace which is allowed to create a DomainMapping + using this ClusterDomainClaim's name. + type: string + names: + kind: ClusterDomainClaim + plural: clusterdomainclaims + singular: clusterdomainclaim + categories: + - knative-internal + - networking + shortNames: + - cdc + scope: Cluster + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: domainmappings.serving.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: serving.knative.dev + versions: + - name: v1beta1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: URL + type: string + jsonPath: .status.url + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + "schema": + "openAPIV3Schema": + description: DomainMapping is a mapping from a custom hostname to an Addressable. + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec is the desired state of the DomainMapping. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + required: + - ref + properties: + ref: + description: |- + Ref specifies the target of the Domain Mapping. + + The object identified by the Ref must be an Addressable with a URL of the + form `{name}.{namespace}.{domain}` where `{domain}` is the cluster domain, + and `{name}` and `{namespace}` are the name and namespace of a Kubernetes + Service. + + This contract is satisfied by Knative types such as Knative Services and + Knative Routes, and by Kubernetes Services. + type: object + required: + - kind + - name + properties: + address: + description: Address points to a specific Address Name. + type: string + apiVersion: + description: API version of the referent. + type: string + group: + description: |- + Group of the API, without the version of the group. This can be used as an alternative to the APIVersion, and then resolved using ResolveGroup. + Note: This API is EXPERIMENTAL and might break anytime. For more details: https://github.com/knative/eventing/issues/5086 + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + This is optional field, it gets defaulted to the object holding it if left out. + type: string + tls: + description: TLS allows the DomainMapping to terminate TLS traffic with an existing secret. + type: object + required: + - secretName + properties: + secretName: + description: SecretName is the name of the existing secret used to terminate TLS traffic. + type: string + status: + description: |- + Status is the current state of the DomainMapping. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + properties: + address: + description: Address holds the information needed for a DomainMapping to be the target of an event. + type: object + properties: + CACerts: + description: |- + CACerts is the Certification Authority (CA) certificates in PEM format + according to https://www.rfc-editor.org/rfc/rfc7468. + type: string + audience: + description: Audience is the OIDC audience for this address. + type: string + name: + description: Name is the name of the address. + type: string + url: + type: string + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + url: + description: URL is the URL of this DomainMapping. + type: string + names: + kind: DomainMapping + plural: domainmappings + singular: domainmapping + categories: + - all + - knative + - serving + shortNames: + - dm + scope: Namespaced + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: ingresses.networking.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: networking + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: networking.internal.knative.dev + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: |- + Ingress is a collection of rules that allow inbound connections to reach the endpoints defined + by a backend. An Ingress can be configured to give services externally-reachable URLs, load + balance traffic, offer name based virtual hosting, etc. + + This is heavily based on K8s Ingress https://godoc.org/k8s.io/api/networking/v1beta1#Ingress + which some highlighted modifications. + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec is the desired state of the Ingress. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + properties: + httpOption: + description: |- + HTTPOption is the option of HTTP. It has the following two values: + `HTTPOptionEnabled`, `HTTPOptionRedirected` + type: string + rules: + description: A list of host rules used to configure the Ingress. + type: array + items: + description: |- + IngressRule represents the rules mapping the paths under a specified host to + the related backend services. Incoming requests are first evaluated for a host + match, then routed to the backend associated with the matching IngressRuleValue. + type: object + properties: + hosts: + description: |- + Host is the fully qualified domain name of a network host, as defined + by RFC 3986. Note the following deviations from the "host" part of the + URI as defined in the RFC: + 1. IPs are not allowed. Currently a rule value can only apply to the + IP in the Spec of the parent . + 2. The `:` delimiter is not respected because ports are not allowed. + Currently the port of an Ingress is implicitly :80 for http and + :443 for https. + Both these may change in the future. + If the host is unspecified, the Ingress routes all traffic based on the + specified IngressRuleValue. + If multiple matching Hosts were provided, the first rule will take precedent. + type: array + items: + type: string + http: + description: |- + HTTP represents a rule to apply against incoming requests. If the + rule is satisfied, the request is routed to the specified backend. + type: object + required: + - paths + properties: + paths: + description: |- + A collection of paths that map requests to backends. + + If they are multiple matching paths, the first match takes precedence. + type: array + items: + description: |- + HTTPIngressPath associates a path regex with a backend. Incoming URLs matching + the path are forwarded to the backend. + type: object + required: + - splits + properties: + appendHeaders: + description: |- + AppendHeaders allow specifying additional HTTP headers to add + before forwarding a request to the destination service. + + NOTE: This differs from K8s Ingress which doesn't allow header appending. + type: object + additionalProperties: + type: string + headers: + description: |- + Headers defines header matching rules which is a map from a header name + to HeaderMatch which specify a matching condition. + When a request matched with all the header matching rules, + the request is routed by the corresponding ingress rule. + If it is empty, the headers are not used for matching + type: object + additionalProperties: + description: |- + HeaderMatch represents a matching value of Headers in HTTPIngressPath. + Currently, only the exact matching is supported. + type: object + required: + - exact + properties: + exact: + type: string + path: + description: |- + Path represents a literal prefix to which this rule should apply. + Currently it can contain characters disallowed from the conventional + "path" part of a URL as defined by RFC 3986. Paths must begin with + a '/'. If unspecified, the path defaults to a catch all sending + traffic to the backend. + type: string + rewriteHost: + description: |- + RewriteHost rewrites the incoming request's host header. + + This field is currently experimental and not supported by all Ingress + implementations. + type: string + splits: + description: |- + Splits defines the referenced service endpoints to which the traffic + will be forwarded to. + type: array + items: + description: IngressBackendSplit describes all endpoints for a given service and port. + type: object + required: + - serviceName + - serviceNamespace + - servicePort + properties: + appendHeaders: + description: |- + AppendHeaders allow specifying additional HTTP headers to add + before forwarding a request to the destination service. + + NOTE: This differs from K8s Ingress which doesn't allow header appending. + type: object + additionalProperties: + type: string + percent: + description: |- + Specifies the split percentage, a number between 0 and 100. If + only one split is specified, we default to 100. + + NOTE: This differs from K8s Ingress to allow percentage split. + type: integer + serviceName: + description: Specifies the name of the referenced service. + type: string + serviceNamespace: + description: |- + Specifies the namespace of the referenced service. + + NOTE: This differs from K8s Ingress to allow routing to different namespaces. + type: string + servicePort: + description: Specifies the port of the referenced service. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + visibility: + description: |- + Visibility signifies whether this rule should `ClusterLocal`. If it's not + specified then it defaults to `ExternalIP`. + type: string + tls: + description: |- + TLS configuration. Currently Ingress only supports a single TLS + port: 443. If multiple members of this list specify different hosts, they + will be multiplexed on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller fulfilling the + ingress supports SNI. + type: array + items: + description: IngressTLS describes the transport layer security associated with an Ingress. + type: object + properties: + hosts: + description: |- + Hosts is a list of hosts included in the TLS certificate. The values in + this list must match the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller fulfilling this + Ingress, if left unspecified. + type: array + items: + type: string + secretName: + description: SecretName is the name of the secret used to terminate SSL traffic. + type: string + secretNamespace: + description: |- + SecretNamespace is the namespace of the secret used to terminate SSL traffic. + If not set the namespace should be assumed to be the same as the Ingress. + If set the secret should have the same namespace as the Ingress otherwise + the behaviour is undefined and not supported. + type: string + status: + description: |- + Status is the current state of the Ingress. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + properties: + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + privateLoadBalancer: + description: PrivateLoadBalancer contains the current status of the load-balancer. + type: object + properties: + ingress: + description: |- + Ingress is a list containing ingress points for the load-balancer. + Traffic intended for the service should be sent to these ingress points. + type: array + items: + description: |- + LoadBalancerIngressStatus represents the status of a load-balancer ingress point: + traffic intended for the service should be sent to an ingress point. + type: object + properties: + domain: + description: |- + Domain is set for load-balancer ingress points that are DNS based + (typically AWS load-balancers) + type: string + domainInternal: + description: |- + DomainInternal is set if there is a cluster-local DNS name to access the Ingress. + + NOTE: This differs from K8s Ingress, since we also desire to have a cluster-local + DNS name to allow routing in case of not having a mesh. + type: string + ip: + description: |- + IP is set for load-balancer ingress points that are IP based + (typically GCE or OpenStack load-balancers) + type: string + meshOnly: + description: MeshOnly is set if the Ingress is only load-balanced through a Service mesh. + type: boolean + publicLoadBalancer: + description: PublicLoadBalancer contains the current status of the load-balancer. + type: object + properties: + ingress: + description: |- + Ingress is a list containing ingress points for the load-balancer. + Traffic intended for the service should be sent to these ingress points. + type: array + items: + description: |- + LoadBalancerIngressStatus represents the status of a load-balancer ingress point: + traffic intended for the service should be sent to an ingress point. + type: object + properties: + domain: + description: |- + Domain is set for load-balancer ingress points that are DNS based + (typically AWS load-balancers) + type: string + domainInternal: + description: |- + DomainInternal is set if there is a cluster-local DNS name to access the Ingress. + + NOTE: This differs from K8s Ingress, since we also desire to have a cluster-local + DNS name to allow routing in case of not having a mesh. + type: string + ip: + description: |- + IP is set for load-balancer ingress points that are IP based + (typically GCE or OpenStack load-balancers) + type: string + meshOnly: + description: MeshOnly is set if the Ingress is only load-balanced through a Service mesh. + type: boolean + additionalPrinterColumns: + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + names: + kind: Ingress + plural: ingresses + singular: ingress + categories: + - knative-internal + - networking + shortNames: + - kingress + - king + scope: Namespaced + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: The schema part of the spec is auto-generated by hack/update-schemas.sh. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: metrics.autoscaling.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: autoscaling.internal.knative.dev + names: + kind: Metric + plural: metrics + singular: metric + categories: + - knative-internal + - autoscaling + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + schema: + openAPIV3Schema: + description: Metric represents a resource to configure the metric collector with. + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec holds the desired state of the Metric (from the client). + type: object + required: + - panicWindow + - scrapeTarget + - stableWindow + properties: + panicWindow: + description: PanicWindow is the aggregation window for metrics where quick reactions are needed. + type: integer + format: int64 + scrapeTarget: + description: ScrapeTarget is the K8s service that publishes the metric endpoint. + type: string + stableWindow: + description: StableWindow is the aggregation window for metrics in a stable state. + type: integer + format: int64 + status: + description: Status communicates the observed state of the Metric (from the controller). + type: object + properties: + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: The schema part of the spec is auto-generated by hack/update-schemas.sh. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: podautoscalers.autoscaling.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: autoscaling.internal.knative.dev + names: + kind: PodAutoscaler + plural: podautoscalers + singular: podautoscaler + categories: + - knative-internal + - autoscaling + shortNames: + - kpa + - pa + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: DesiredScale + type: integer + jsonPath: ".status.desiredScale" + - name: ActualScale + type: integer + jsonPath: ".status.actualScale" + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + schema: + openAPIV3Schema: + description: |- + PodAutoscaler is a Knative abstraction that encapsulates the interface by which Knative + components instantiate autoscalers. This definition is an abstraction that may be backed + by multiple definitions. For more information, see the Knative Pluggability presentation: + https://docs.google.com/presentation/d/19vW9HFZ6Puxt31biNZF3uLRejDmu82rxJIk1cWmxF7w/edit + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec holds the desired state of the PodAutoscaler (from the client). + type: object + required: + - protocolType + - scaleTargetRef + properties: + containerConcurrency: + description: |- + ContainerConcurrency specifies the maximum allowed + in-flight (concurrent) requests per container of the Revision. + Defaults to `0` which means unlimited concurrency. + type: integer + format: int64 + protocolType: + description: The application-layer protocol. Matches `ProtocolType` inferred from the revision spec. + type: string + reachability: + description: |- + Reachability specifies whether or not the `ScaleTargetRef` can be reached (ie. has a route). + Defaults to `ReachabilityUnknown` + type: string + scaleTargetRef: + description: |- + ScaleTargetRef defines the /scale-able resource that this PodAutoscaler + is responsible for quickly right-sizing. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + x-kubernetes-map-type: atomic + status: + description: Status communicates the observed state of the PodAutoscaler (from the controller). + type: object + required: + - metricsServiceName + - serviceName + properties: + actualScale: + description: ActualScale shows the actual number of replicas for the revision. + type: integer + format: int32 + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + desiredScale: + description: DesiredScale shows the current desired number of replicas for the revision. + type: integer + format: int32 + metricsServiceName: + description: |- + MetricsServiceName is the K8s Service name that provides revision metrics. + The service is managed by the PA object. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + serviceName: + description: |- + ServiceName is the K8s Service name that serves the revision, scaled by this PA. + The service is created and owned by the ServerlessService object owned by this PA. + type: string + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: The schema part of the spec is auto-generated by hack/update-schemas.sh. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: revisions.serving.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: serving.knative.dev + names: + kind: Revision + plural: revisions + singular: revision + categories: + - all + - knative + - serving + shortNames: + - rev + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: Config Name + type: string + jsonPath: ".metadata.labels['serving\\.knative\\.dev/configuration']" + - name: Generation + type: string # int in string form :( + jsonPath: ".metadata.labels['serving\\.knative\\.dev/configurationGeneration']" + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + - name: Actual Replicas + type: integer + jsonPath: ".status.actualReplicas" + - name: Desired Replicas + type: integer + jsonPath: ".status.desiredReplicas" + schema: + openAPIV3Schema: + description: |- + Revision is an immutable snapshot of code and configuration. A revision + references a container image. Revisions are created by updates to a + Configuration. + + See also: https://github.com/knative/serving/blob/main/docs/spec/overview.md#revision + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: RevisionSpec holds the desired state of the Revision (from the client). + type: object + required: + - containers + properties: + affinity: + description: This is accessible behind a feature flag - kubernetes.podspec-affinity + type: object + x-kubernetes-preserve-unknown-fields: true + automountServiceAccountToken: + description: AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. + type: boolean + containerConcurrency: + description: |- + ContainerConcurrency specifies the maximum allowed in-flight (concurrent) + requests per container of the Revision. Defaults to `0` which means + concurrency to the application is not limited, and the system decides the + target concurrency for the autoscaler. + type: integer + format: int64 + containers: + description: |- + List of containers belonging to the pod. + Containers cannot currently be added or removed. + There must be at least one container in a Pod. + Cannot be updated. + type: array + items: + description: A single application container that you want to run within a pod. + type: object + properties: + args: + description: |- + Arguments to the entrypoint. + The container image's CMD is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + type: array + items: + type: string + x-kubernetes-list-type: atomic + command: + description: |- + Entrypoint array. Not executed within a shell. + The container image's ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + type: array + items: + type: string + x-kubernetes-list-type: atomic + env: + description: |- + List of environment variables to set in the container. + Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + fieldRef: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-fieldref + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + resourceFieldRef: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-fieldref + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + description: |- + List of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Cannot be updated. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps or Secrets + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + x-kubernetes-map-type: atomic + prefix: + description: Optional text to prepend to the name of each environment variable. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the Secret must be defined + type: boolean + x-kubernetes-map-type: atomic + x-kubernetes-list-type: atomic + image: + description: |- + Container image name. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + imagePullPolicy: + description: |- + Image pull policy. + One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + type: string + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + name: + description: |- + Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: |- + List of ports to expose from the container. Not specifying a port here + DOES NOT prevent that port from being exposed. Any port which is + listening on the default "0.0.0.0" address inside a container will be + accessible from the network. + Modifying this array with strategic merge patch may corrupt the data. + For more information See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + type: array + items: + description: ContainerPort represents a network port in a single container. + type: object + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + type: integer + format: int32 + name: + description: |- + If specified, this must be an IANA_SVC_NAME and unique within the pod. Each + named port in a pod must have a unique name. Name for the port that can be + referred to by services. + type: string + protocol: + description: |- + Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + default: TCP + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + resources: + description: |- + Compute Resources required by this container. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + properties: + limits: + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + securityContext: + description: |- + SecurityContext defines the security options the container should be run with. + If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + type: object + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + add: + description: This is accessible behind a feature flag - kubernetes.containerspec-addcapabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + x-kubernetes-list-type: atomic + privileged: + description: |- + Run container in privileged mode. This can only be set to explicitly to 'false' + type: boolean + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + type: object + required: + - type + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + This cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + terminationMessagePath: + description: |- + Optional: Path at which the file to which the container's termination message + will be written is mounted into the container's filesystem. + Message written is intended to be brief final status, such as an assertion failure message. + Will be truncated by the node if greater than 4096 bytes. The total message length across + all containers will be limited to 12kb. + Defaults to /dev/termination-log. + Cannot be updated. + type: string + terminationMessagePolicy: + description: |- + Indicate how the termination message should be populated. File will use the contents of + terminationMessagePath to populate the container status message on both success and failure. + FallbackToLogsOnError will use the last chunk of container log output if the termination + message file is empty and the container exited with an error. + The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + Defaults to File. + Cannot be updated. + type: string + volumeMounts: + description: |- + Pod volumes to mount into the container's filesystem. + Cannot be updated. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-mount-propagation + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + description: |- + Container's working directory. + If not specified, the container runtime's default will be used, which + might be configured in the container image. + Cannot be updated. + type: string + dnsConfig: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-dnsconfig + type: object + x-kubernetes-preserve-unknown-fields: true + dnsPolicy: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-dnspolicy + type: string + enableServiceLinks: + description: |- + EnableServiceLinks indicates whether information aboutservices should be injected into pod's environment variables, matching the syntax of Docker links. Optional: Knative defaults this to false. + type: boolean + hostAliases: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostaliases + type: array + items: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostaliases + type: object + x-kubernetes-preserve-unknown-fields: true + hostIPC: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostipc + type: boolean + hostNetwork: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostnetwork + type: boolean + hostPID: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostpid + type: boolean + idleTimeoutSeconds: + description: |- + IdleTimeoutSeconds is the maximum duration in seconds a request will be allowed + to stay open while not receiving any bytes from the user's application. If + unspecified, a system default will be provided. + type: integer + format: int64 + imagePullSecrets: + description: |- + ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. + If specified, these secrets will be passed to individual puller implementations for them to use. + More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + type: array + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + x-kubernetes-map-type: atomic + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + initContainers: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-init-containers + type: array + items: + description: This is accessible behind a feature flag - kubernetes.podspec-init-containers + type: object + x-kubernetes-preserve-unknown-fields: true + nodeSelector: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-nodeselector + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + priorityClassName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-priorityclassname + type: string + responseStartTimeoutSeconds: + description: |- + ResponseStartTimeoutSeconds is the maximum duration in seconds that the request + routing layer will wait for a request delivered to a container to begin + sending any network traffic. + type: integer + format: int64 + runtimeClassName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-runtimeclassname + type: string + schedulerName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-schedulername + type: string + securityContext: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-securitycontext + type: object + x-kubernetes-preserve-unknown-fields: true + serviceAccountName: + description: |- + ServiceAccountName is the name of the ServiceAccount to use to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + type: string + shareProcessNamespace: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-shareprocessnamespace + type: boolean + timeoutSeconds: + description: |- + TimeoutSeconds is the maximum duration in seconds that the request instance + is allowed to respond to a request. If unspecified, a system default will + be provided. + type: integer + format: int64 + tolerations: + description: This is accessible behind a feature flag - kubernetes.podspec-tolerations + type: array + items: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-tolerations + type: object + x-kubernetes-preserve-unknown-fields: true + topologySpreadConstraints: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-topologyspreadconstraints + type: array + items: + description: This is accessible behind a feature flag - kubernetes.podspec-topologyspreadconstraints + type: object + x-kubernetes-preserve-unknown-fields: true + volumes: + description: |- + List of volumes that can be mounted by containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes + type: array + items: + description: Volume represents a named volume in a pod that may be accessed by any container in the pod. + type: object + required: + - name + properties: + configMap: + description: configMap represents a configMap that should populate this volume + type: object + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + x-kubernetes-map-type: atomic + csi: + description: This is accessible behind a feature flag - kubernetes.podspec-volumes-csi + type: object + x-kubernetes-preserve-unknown-fields: true + emptyDir: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-emptydir + type: object + x-kubernetes-preserve-unknown-fields: true + hostPath: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-hostpath + type: object + x-kubernetes-preserve-unknown-fields: true + image: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-image + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + persistentVolumeClaim: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-persistent-volume-claim + type: object + x-kubernetes-preserve-unknown-fields: true + projected: + description: projected items for all in one resources secrets, configmaps, and downward API + type: object + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + type: array + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + type: object + properties: + configMap: + description: configMap information about the configMap data to project + type: object + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the downwardAPI data to project + type: object + properties: + items: + description: Items is a list of DownwardAPIVolume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name, namespace and uid are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + x-kubernetes-map-type: atomic + x-kubernetes-list-type: atomic + secret: + description: secret information about the secret data to project + type: object + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional field specify whether the Secret or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information about the serviceAccountToken data to project + type: object + required: + - path + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + type: integer + format: int64 + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + x-kubernetes-list-type: atomic + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: object + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether the Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + status: + description: RevisionStatus communicates the observed state of the Revision (from the controller). + type: object + properties: + actualReplicas: + description: ActualReplicas reflects the amount of ready pods running this revision. + type: integer + format: int32 + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + containerStatuses: + description: |- + ContainerStatuses is a slice of images present in .Spec.Container[*].Image + to their respective digests and their container name. + The digests are resolved during the creation of Revision. + ContainerStatuses holds the container name and image digests + for both serving and non serving containers. + ref: http://bit.ly/image-digests + type: array + items: + description: ContainerStatus holds the information of container name and image digest value + type: object + properties: + imageDigest: + type: string + name: + type: string + desiredReplicas: + description: DesiredReplicas reflects the desired amount of pods running this revision. + type: integer + format: int32 + initContainerStatuses: + description: |- + InitContainerStatuses is a slice of images present in .Spec.InitContainer[*].Image + to their respective digests and their container name. + The digests are resolved during the creation of Revision. + ContainerStatuses holds the container name and image digests + for both serving and non serving containers. + ref: http://bit.ly/image-digests + type: array + items: + description: ContainerStatus holds the information of container name and image digest value + type: object + properties: + imageDigest: + type: string + name: + type: string + logUrl: + description: |- + LogURL specifies the generated logging url for this particular revision + based on the revision url template specified in the controller's config. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: The schema part of the spec is auto-generated by hack/update-schemas.sh. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: routes.serving.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" + duck.knative.dev/addressable: "true" +spec: + group: serving.knative.dev + names: + kind: Route + plural: routes + singular: route + categories: + - all + - knative + - serving + shortNames: + - rt + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: URL + type: string + jsonPath: .status.url + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + schema: + openAPIV3Schema: + description: |- + Route is responsible for configuring ingress over a collection of Revisions. + Some of the Revisions a Route distributes traffic over may be specified by + referencing the Configuration responsible for creating them; in these cases + the Route is additionally responsible for monitoring the Configuration for + "latest ready revision" changes, and smoothly rolling out latest revisions. + See also: https://github.com/knative/serving/blob/main/docs/spec/overview.md#route + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec holds the desired state of the Route (from the client). + type: object + properties: + traffic: + description: |- + Traffic specifies how to distribute traffic over a collection of + revisions and configurations. + type: array + items: + description: TrafficTarget holds a single entry of the routing table for a Route. + type: object + properties: + configurationName: + description: |- + ConfigurationName of a configuration to whose latest revision we will send + this portion of traffic. When the "status.latestReadyRevisionName" of the + referenced configuration changes, we will automatically migrate traffic + from the prior "latest ready" revision to the new one. This field is never + set in Route's status, only its spec. This is mutually exclusive with + RevisionName. + type: string + latestRevision: + description: |- + LatestRevision may be optionally provided to indicate that the latest + ready Revision of the Configuration should be used for this traffic + target. When provided LatestRevision must be true if RevisionName is + empty; it must be false when RevisionName is non-empty. + type: boolean + percent: + description: |- + Percent indicates that percentage based routing should be used and + the value indicates the percent of traffic that is be routed to this + Revision or Configuration. `0` (zero) mean no traffic, `100` means all + traffic. + When percentage based routing is being used the follow rules apply: + - the sum of all percent values must equal 100 + - when not specified, the implied value for `percent` is zero for + that particular Revision or Configuration + type: integer + format: int64 + revisionName: + description: |- + RevisionName of a specific revision to which to send this portion of + traffic. This is mutually exclusive with ConfigurationName. + type: string + tag: + description: |- + Tag is optionally used to expose a dedicated url for referencing + this target exclusively. + type: string + url: + description: |- + URL displays the URL for accessing named traffic targets. URL is displayed in + status, and is disallowed on spec. URL must contain a scheme (e.g. http://) and + a hostname, but may not contain anything else (e.g. basic auth, url path, etc.) + type: string + status: + description: Status communicates the observed state of the Route (from the controller). + type: object + properties: + address: + description: Address holds the information needed for a Route to be the target of an event. + type: object + properties: + CACerts: + description: |- + CACerts is the Certification Authority (CA) certificates in PEM format + according to https://www.rfc-editor.org/rfc/rfc7468. + type: string + audience: + description: Audience is the OIDC audience for this address. + type: string + name: + description: Name is the name of the address. + type: string + url: + type: string + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + traffic: + description: |- + Traffic holds the configured traffic distribution. + These entries will always contain RevisionName references. + When ConfigurationName appears in the spec, this will hold the + LatestReadyRevisionName that we last observed. + type: array + items: + description: TrafficTarget holds a single entry of the routing table for a Route. + type: object + properties: + configurationName: + description: |- + ConfigurationName of a configuration to whose latest revision we will send + this portion of traffic. When the "status.latestReadyRevisionName" of the + referenced configuration changes, we will automatically migrate traffic + from the prior "latest ready" revision to the new one. This field is never + set in Route's status, only its spec. This is mutually exclusive with + RevisionName. + type: string + latestRevision: + description: |- + LatestRevision may be optionally provided to indicate that the latest + ready Revision of the Configuration should be used for this traffic + target. When provided LatestRevision must be true if RevisionName is + empty; it must be false when RevisionName is non-empty. + type: boolean + percent: + description: |- + Percent indicates that percentage based routing should be used and + the value indicates the percent of traffic that is be routed to this + Revision or Configuration. `0` (zero) mean no traffic, `100` means all + traffic. + When percentage based routing is being used the follow rules apply: + - the sum of all percent values must equal 100 + - when not specified, the implied value for `percent` is zero for + that particular Revision or Configuration + type: integer + format: int64 + revisionName: + description: |- + RevisionName of a specific revision to which to send this portion of + traffic. This is mutually exclusive with ConfigurationName. + type: string + tag: + description: |- + Tag is optionally used to expose a dedicated url for referencing + this target exclusively. + type: string + url: + description: |- + URL displays the URL for accessing named traffic targets. URL is displayed in + status, and is disallowed on spec. URL must contain a scheme (e.g. http://) and + a hostname, but may not contain anything else (e.g. basic auth, url path, etc.) + type: string + url: + description: |- + URL holds the url that will distribute traffic over the provided traffic targets. + It generally has the form http[s]://{route-name}.{route-namespace}.{cluster-level-suffix} + type: string + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: serverlessservices.networking.internal.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: networking + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" +spec: + group: networking.internal.knative.dev + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: |- + ServerlessService is a proxy for the K8s service objects containing the + endpoints for the revision, whether those are endpoints of the activator or + revision pods. + See: https://knative.page.link/naxz for details. + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec is the desired state of the ServerlessService. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + required: + - objectRef + - protocolType + properties: + mode: + description: Mode describes the mode of operation of the ServerlessService. + type: string + numActivators: + description: |- + NumActivators contains number of Activators that this revision should be + assigned. + O means — assign all. + type: integer + format: int32 + objectRef: + description: |- + ObjectRef defines the resource that this ServerlessService + is responsible for making "serverless". + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + x-kubernetes-map-type: atomic + protocolType: + description: |- + The application-layer protocol. Matches `RevisionProtocolType` set on the owning pa/revision. + serving imports networking, so just use string. + type: string + status: + description: |- + Status is the current state of the ServerlessService. + More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + properties: + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + privateServiceName: + description: |- + PrivateServiceName holds the name of a core K8s Service resource that + load balances over the user service pods backing this Revision. + type: string + serviceName: + description: |- + ServiceName holds the name of a core K8s Service resource that + load balances over the pods backing this Revision (activator or revision). + type: string + additionalPrinterColumns: + - name: Mode + type: string + jsonPath: ".spec.mode" + - name: Activators + type: integer + jsonPath: ".spec.numActivators" + - name: ServiceName + type: string + jsonPath: ".status.serviceName" + - name: PrivateServiceName + type: string + jsonPath: ".status.privateServiceName" + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + names: + kind: ServerlessService + plural: serverlessservices + singular: serverlessservice + categories: + - knative-internal + - networking + shortNames: + - sks + scope: Namespaced + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: The schema part of the spec is auto-generated by hack/update-schemas.sh. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: services.serving.knative.dev + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + knative.dev/crd-install: "true" + duck.knative.dev/addressable: "true" + duck.knative.dev/podspecable: "true" +spec: + group: serving.knative.dev + names: + kind: Service + plural: services + singular: service + categories: + - all + - knative + - serving + shortNames: + - kservice + - ksvc + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: URL + type: string + jsonPath: .status.url + - name: LatestCreated + type: string + jsonPath: .status.latestCreatedRevisionName + - name: LatestReady + type: string + jsonPath: .status.latestReadyRevisionName + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + schema: + openAPIV3Schema: + description: |- + Service acts as a top-level container that manages a Route and Configuration + which implement a network service. Service exists to provide a singular + abstraction which can be access controlled, reasoned about, and which + encapsulates software lifecycle decisions such as rollout policy and + team resource ownership. Service acts only as an orchestrator of the + underlying Routes and Configurations (much as a kubernetes Deployment + orchestrates ReplicaSets), and its usage is optional but recommended. + + The Service's controller will track the statuses of its owned Configuration + and Route, reflecting their statuses and conditions as its own. + + See also: https://github.com/knative/serving/blob/main/docs/spec/overview.md#service + type: object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + ServiceSpec represents the configuration for the Service object. + A Service's specification is the union of the specifications for a Route + and Configuration. The Service restricts what can be expressed in these + fields, e.g. the Route must reference the provided Configuration; + however, these limitations also enable friendlier defaulting, + e.g. Route never needs a Configuration name, and may be defaulted to + the appropriate "run latest" spec. + type: object + properties: + template: + description: Template holds the latest specification for the Revision to be stamped out. + type: object + properties: + metadata: + type: object + properties: + annotations: + type: object + additionalProperties: + type: string + finalizers: + type: array + items: + type: string + labels: + type: object + additionalProperties: + type: string + name: + type: string + namespace: + type: string + x-kubernetes-preserve-unknown-fields: true + spec: + description: RevisionSpec holds the desired state of the Revision (from the client). + type: object + required: + - containers + properties: + affinity: + description: This is accessible behind a feature flag - kubernetes.podspec-affinity + type: object + x-kubernetes-preserve-unknown-fields: true + automountServiceAccountToken: + description: AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. + type: boolean + containerConcurrency: + description: |- + ContainerConcurrency specifies the maximum allowed in-flight (concurrent) + requests per container of the Revision. Defaults to `0` which means + concurrency to the application is not limited, and the system decides the + target concurrency for the autoscaler. + type: integer + format: int64 + containers: + description: |- + List of containers belonging to the pod. + Containers cannot currently be added or removed. + There must be at least one container in a Pod. + Cannot be updated. + type: array + items: + description: A single application container that you want to run within a pod. + type: object + properties: + args: + description: |- + Arguments to the entrypoint. + The container image's CMD is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + type: array + items: + type: string + x-kubernetes-list-type: atomic + command: + description: |- + Entrypoint array. Not executed within a shell. + The container image's ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + type: array + items: + type: string + x-kubernetes-list-type: atomic + env: + description: |- + List of environment variables to set in the container. + Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + fieldRef: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-fieldref + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + resourceFieldRef: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-fieldref + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + description: |- + List of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Cannot be updated. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps or Secrets + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + x-kubernetes-map-type: atomic + prefix: + description: Optional text to prepend to the name of each environment variable. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: Specify whether the Secret must be defined + type: boolean + x-kubernetes-map-type: atomic + x-kubernetes-list-type: atomic + image: + description: |- + Container image name. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + imagePullPolicy: + description: |- + Image pull policy. + One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + type: string + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + name: + description: |- + Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: |- + List of ports to expose from the container. Not specifying a port here + DOES NOT prevent that port from being exposed. Any port which is + listening on the default "0.0.0.0" address inside a container will be + accessible from the network. + Modifying this array with strategic merge patch may corrupt the data. + For more information See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + type: array + items: + description: ContainerPort represents a network port in a single container. + type: object + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + type: integer + format: int32 + name: + description: |- + If specified, this must be an IANA_SVC_NAME and unique within the pod. Each + named port in a pod must have a unique name. Name for the port that can be + referred to by services. + type: string + protocol: + description: |- + Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + default: TCP + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + resources: + description: |- + Compute Resources required by this container. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + properties: + limits: + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + securityContext: + description: |- + SecurityContext defines the security options the container should be run with. + If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + type: object + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + add: + description: This is accessible behind a feature flag - kubernetes.containerspec-addcapabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + x-kubernetes-list-type: atomic + privileged: + description: |- + Run container in privileged mode. This can only be set to explicitly to 'false' + type: boolean + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + type: object + required: + - type + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + This cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: object + properties: + exec: + description: Exec specifies a command to execute in the container. + type: object + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + x-kubernetes-list-type: atomic + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + type: object + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + default: "" + httpGet: + description: HTTPGet specifies an HTTP GET request to perform. + type: object + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + type: integer + format: int32 + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies a connection to a TCP port. + type: object + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + type: integer + format: int32 + terminationMessagePath: + description: |- + Optional: Path at which the file to which the container's termination message + will be written is mounted into the container's filesystem. + Message written is intended to be brief final status, such as an assertion failure message. + Will be truncated by the node if greater than 4096 bytes. The total message length across + all containers will be limited to 12kb. + Defaults to /dev/termination-log. + Cannot be updated. + type: string + terminationMessagePolicy: + description: |- + Indicate how the termination message should be populated. File will use the contents of + terminationMessagePath to populate the container status message on both success and failure. + FallbackToLogsOnError will use the last chunk of container log output if the termination + message file is empty and the container exited with an error. + The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + Defaults to File. + Cannot be updated. + type: string + volumeMounts: + description: |- + Pod volumes to mount into the container's filesystem. + Cannot be updated. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-mount-propagation + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + description: |- + Container's working directory. + If not specified, the container runtime's default will be used, which + might be configured in the container image. + Cannot be updated. + type: string + dnsConfig: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-dnsconfig + type: object + x-kubernetes-preserve-unknown-fields: true + dnsPolicy: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-dnspolicy + type: string + enableServiceLinks: + description: |- + EnableServiceLinks indicates whether information aboutservices should be injected into pod's environment variables, matching the syntax of Docker links. Optional: Knative defaults this to false. + type: boolean + hostAliases: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostaliases + type: array + items: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostaliases + type: object + x-kubernetes-preserve-unknown-fields: true + hostIPC: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostipc + type: boolean + hostNetwork: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostnetwork + type: boolean + hostPID: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-hostpid + type: boolean + idleTimeoutSeconds: + description: |- + IdleTimeoutSeconds is the maximum duration in seconds a request will be allowed + to stay open while not receiving any bytes from the user's application. If + unspecified, a system default will be provided. + type: integer + format: int64 + imagePullSecrets: + description: |- + ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. + If specified, these secrets will be passed to individual puller implementations for them to use. + More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + type: array + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + type: object + properties: + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + x-kubernetes-map-type: atomic + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + initContainers: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-init-containers + type: array + items: + description: This is accessible behind a feature flag - kubernetes.podspec-init-containers + type: object + x-kubernetes-preserve-unknown-fields: true + nodeSelector: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-nodeselector + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + priorityClassName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-priorityclassname + type: string + responseStartTimeoutSeconds: + description: |- + ResponseStartTimeoutSeconds is the maximum duration in seconds that the request + routing layer will wait for a request delivered to a container to begin + sending any network traffic. + type: integer + format: int64 + runtimeClassName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-runtimeclassname + type: string + schedulerName: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-schedulername + type: string + securityContext: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-securitycontext + type: object + x-kubernetes-preserve-unknown-fields: true + serviceAccountName: + description: |- + ServiceAccountName is the name of the ServiceAccount to use to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + type: string + shareProcessNamespace: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-shareprocessnamespace + type: boolean + timeoutSeconds: + description: |- + TimeoutSeconds is the maximum duration in seconds that the request instance + is allowed to respond to a request. If unspecified, a system default will + be provided. + type: integer + format: int64 + tolerations: + description: This is accessible behind a feature flag - kubernetes.podspec-tolerations + type: array + items: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-tolerations + type: object + x-kubernetes-preserve-unknown-fields: true + topologySpreadConstraints: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-topologyspreadconstraints + type: array + items: + description: This is accessible behind a feature flag - kubernetes.podspec-topologyspreadconstraints + type: object + x-kubernetes-preserve-unknown-fields: true + volumes: + description: |- + List of volumes that can be mounted by containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes + type: array + items: + description: Volume represents a named volume in a pod that may be accessed by any container in the pod. + type: object + required: + - name + properties: + configMap: + description: configMap represents a configMap that should populate this volume + type: object + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + x-kubernetes-map-type: atomic + csi: + description: This is accessible behind a feature flag - kubernetes.podspec-volumes-csi + type: object + x-kubernetes-preserve-unknown-fields: true + emptyDir: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-emptydir + type: object + x-kubernetes-preserve-unknown-fields: true + hostPath: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-hostpath + type: object + x-kubernetes-preserve-unknown-fields: true + image: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-volumes-image + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + persistentVolumeClaim: + description: |- + This is accessible behind a feature flag - kubernetes.podspec-persistent-volume-claim + type: object + x-kubernetes-preserve-unknown-fields: true + projected: + description: projected items for all in one resources secrets, configmaps, and downward API + type: object + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + type: array + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + type: object + properties: + configMap: + description: configMap information about the configMap data to project + type: object + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the downwardAPI data to project + type: object + properties: + items: + description: Items is a list of DownwardAPIVolume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name, namespace and uid are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + x-kubernetes-map-type: atomic + x-kubernetes-list-type: atomic + secret: + description: secret information about the secret data to project + type: object + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + name: + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + default: "" + optional: + description: optional field specify whether the Secret or its key must be defined + type: boolean + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information about the serviceAccountToken data to project + type: object + required: + - path + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + type: integer + format: int64 + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + x-kubernetes-list-type: atomic + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: object + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether the Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + traffic: + description: |- + Traffic specifies how to distribute traffic over a collection of + revisions and configurations. + type: array + items: + description: TrafficTarget holds a single entry of the routing table for a Route. + type: object + properties: + configurationName: + description: |- + ConfigurationName of a configuration to whose latest revision we will send + this portion of traffic. When the "status.latestReadyRevisionName" of the + referenced configuration changes, we will automatically migrate traffic + from the prior "latest ready" revision to the new one. This field is never + set in Route's status, only its spec. This is mutually exclusive with + RevisionName. + type: string + latestRevision: + description: |- + LatestRevision may be optionally provided to indicate that the latest + ready Revision of the Configuration should be used for this traffic + target. When provided LatestRevision must be true if RevisionName is + empty; it must be false when RevisionName is non-empty. + type: boolean + percent: + description: |- + Percent indicates that percentage based routing should be used and + the value indicates the percent of traffic that is be routed to this + Revision or Configuration. `0` (zero) mean no traffic, `100` means all + traffic. + When percentage based routing is being used the follow rules apply: + - the sum of all percent values must equal 100 + - when not specified, the implied value for `percent` is zero for + that particular Revision or Configuration + type: integer + format: int64 + revisionName: + description: |- + RevisionName of a specific revision to which to send this portion of + traffic. This is mutually exclusive with ConfigurationName. + type: string + tag: + description: |- + Tag is optionally used to expose a dedicated url for referencing + this target exclusively. + type: string + url: + description: |- + URL displays the URL for accessing named traffic targets. URL is displayed in + status, and is disallowed on spec. URL must contain a scheme (e.g. http://) and + a hostname, but may not contain anything else (e.g. basic auth, url path, etc.) + type: string + status: + description: ServiceStatus represents the Status stanza of the Service resource. + type: object + properties: + address: + description: Address holds the information needed for a Route to be the target of an event. + type: object + properties: + CACerts: + description: |- + CACerts is the Certification Authority (CA) certificates in PEM format + according to https://www.rfc-editor.org/rfc/rfc7468. + type: string + audience: + description: Audience is the OIDC audience for this address. + type: string + name: + description: Name is the name of the address. + type: string + url: + type: string + annotations: + description: |- + Annotations is additional Status fields for the Resource to save some + additional State as well as convey more information to the user. This is + roughly akin to Annotations on any k8s resource, just the reconciler conveying + richer information outwards. + type: object + additionalProperties: + type: string + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + description: |- + Condition defines a readiness condition for a Knative resource. + See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time the condition transitioned from one status to another. + We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic + differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: |- + Severity with which to treat failures of this type of condition. + When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + latestCreatedRevisionName: + description: |- + LatestCreatedRevisionName is the last revision that was created from this + Configuration. It might not be ready yet, for that use LatestReadyRevisionName. + type: string + latestReadyRevisionName: + description: |- + LatestReadyRevisionName holds the name of the latest Revision stamped out + from this Configuration that has had its "Ready" condition become "True". + type: string + observedGeneration: + description: |- + ObservedGeneration is the 'Generation' of the Service that + was last processed by the controller. + type: integer + format: int64 + traffic: + description: |- + Traffic holds the configured traffic distribution. + These entries will always contain RevisionName references. + When ConfigurationName appears in the spec, this will hold the + LatestReadyRevisionName that we last observed. + type: array + items: + description: TrafficTarget holds a single entry of the routing table for a Route. + type: object + properties: + configurationName: + description: |- + ConfigurationName of a configuration to whose latest revision we will send + this portion of traffic. When the "status.latestReadyRevisionName" of the + referenced configuration changes, we will automatically migrate traffic + from the prior "latest ready" revision to the new one. This field is never + set in Route's status, only its spec. This is mutually exclusive with + RevisionName. + type: string + latestRevision: + description: |- + LatestRevision may be optionally provided to indicate that the latest + ready Revision of the Configuration should be used for this traffic + target. When provided LatestRevision must be true if RevisionName is + empty; it must be false when RevisionName is non-empty. + type: boolean + percent: + description: |- + Percent indicates that percentage based routing should be used and + the value indicates the percent of traffic that is be routed to this + Revision or Configuration. `0` (zero) mean no traffic, `100` means all + traffic. + When percentage based routing is being used the follow rules apply: + - the sum of all percent values must equal 100 + - when not specified, the implied value for `percent` is zero for + that particular Revision or Configuration + type: integer + format: int64 + revisionName: + description: |- + RevisionName of a specific revision to which to send this portion of + traffic. This is mutually exclusive with ConfigurationName. + type: string + tag: + description: |- + Tag is optionally used to expose a dedicated url for referencing + this target exclusively. + type: string + url: + description: |- + URL displays the URL for accessing named traffic targets. URL is displayed in + status, and is disallowed on spec. URL must contain a scheme (e.g. http://) and + a hostname, but may not contain anything else (e.g. basic auth, url path, etc.) + type: string + url: + description: |- + URL holds the url that will distribute traffic over the provided traffic targets. + It generally has the form http[s]://{route-name}.{route-namespace}.{cluster-level-suffix} + type: string + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: caching.internal.knative.dev/v1alpha1 +kind: Image +metadata: + name: queue-proxy + namespace: knative-serving + labels: + app.kubernetes.io/component: queue-proxy + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +spec: + # This is the Go import path for the binary that is containerized + # and substituted here. + image: gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:1310917822086a5d8daa6328f6014001d5ea7ccfb0afc1a4e74b1b6a2eadc5ba + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-autoscaler + namespace: knative-serving + labels: + app.kubernetes.io/component: autoscaler + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + annotations: + knative.dev/example-checksum: "47c2487f" +data: + _example: | + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + + # The Revision ContainerConcurrency field specifies the maximum number + # of requests the Container can handle at once. Container concurrency + # target percentage is how much of that maximum to use in a stable + # state. E.g. if a Revision specifies ContainerConcurrency of 10, then + # the Autoscaler will try to maintain 7 concurrent connections per pod + # on average. + # Note: this limit will be applied to container concurrency set at every + # level (ConfigMap, Revision Spec or Annotation). + # For legacy and backwards compatibility reasons, this value also accepts + # fractional values in (0, 1] interval (i.e. 0.7 ⇒ 70%). + # Thus minimal percentage value must be greater than 1.0, or it will be + # treated as a fraction. + # NOTE: that this value does not affect actual number of concurrent requests + # the user container may receive, but only the average number of requests + # that the revision pods will receive. + container-concurrency-target-percentage: "70" + + # The container concurrency target default is what the Autoscaler will + # try to maintain when concurrency is used as the scaling metric for the + # Revision and the Revision specifies unlimited concurrency. + # When revision explicitly specifies container concurrency, that value + # will be used as a scaling target for autoscaler. + # When specifying unlimited concurrency, the autoscaler will + # horizontally scale the application based on this target concurrency. + # This is what we call "soft limit" in the documentation, i.e. it only + # affects number of pods and does not affect the number of requests + # individual pod processes. + # The value must be a positive number such that the value multiplied + # by container-concurrency-target-percentage is greater than 0.01. + # NOTE: that this value will be adjusted by application of + # container-concurrency-target-percentage, i.e. by default + # the system will target on average 70 concurrent requests + # per revision pod. + # NOTE: Only one metric can be used for autoscaling a Revision. + container-concurrency-target-default: "100" + + # The requests per second (RPS) target default is what the Autoscaler will + # try to maintain when RPS is used as the scaling metric for a Revision and + # the Revision specifies unlimited RPS. Even when specifying unlimited RPS, + # the autoscaler will horizontally scale the application based on this + # target RPS. + # Must be greater than 1.0. + # NOTE: Only one metric can be used for autoscaling a Revision. + requests-per-second-target-default: "200" + + # The target burst capacity specifies the size of burst in concurrent + # requests that the system operator expects the system will receive. + # Autoscaler will try to protect the system from queueing by introducing + # Activator in the request path if the current spare capacity of the + # service is less than this setting. + # If this setting is 0, then Activator will be in the request path only + # when the revision is scaled to 0. + # If this setting is > 0 and container-concurrency-target-percentage is + # 100% or 1.0, then activator will always be in the request path. + # -1 denotes unlimited target-burst-capacity and activator will always + # be in the request path. + # Other negative values are invalid. + target-burst-capacity: "211" + + # When operating in a stable mode, the autoscaler operates on the + # average concurrency over the stable window. + # Stable window must be in whole seconds. + stable-window: "60s" + + # When observed average concurrency during the panic window reaches + # panic-threshold-percentage the target concurrency, the autoscaler + # enters panic mode. When operating in panic mode, the autoscaler + # scales on the average concurrency over the panic window which is + # panic-window-percentage of the stable-window. + # Must be in the [1, 100] range. + # When computing the panic window it will be rounded to the closest + # whole second, at least 1s. + panic-window-percentage: "10.0" + + # The percentage of the container concurrency target at which to + # enter panic mode when reached within the panic window. + panic-threshold-percentage: "200.0" + + # Max scale up rate limits the rate at which the autoscaler will + # increase pod count. It is the maximum ratio of desired pods versus + # observed pods. + # Cannot be less or equal to 1. + # I.e with value of 2.0 the number of pods can at most go N to 2N + # over single Autoscaler period (2s), but at least N to + # N+1, if Autoscaler needs to scale up. + max-scale-up-rate: "1000.0" + + # Max scale down rate limits the rate at which the autoscaler will + # decrease pod count. It is the maximum ratio of observed pods versus + # desired pods. + # Cannot be less or equal to 1. + # I.e. with value of 2.0 the number of pods can at most go N to N/2 + # over single Autoscaler evaluation period (2s), but at + # least N to N-1, if Autoscaler needs to scale down. + max-scale-down-rate: "2.0" + + # Scale to zero feature flag. + enable-scale-to-zero: "true" + + # Scale to zero grace period is the time an inactive revision is left + # running before it is scaled to zero (must be positive, but recommended + # at least a few seconds if running with mesh networking). + # This is the upper limit and is provided not to enforce timeout after + # the revision stopped receiving requests for stable window, but to + # ensure network reprogramming to put activator in the path has completed. + # If the system determines that a shorter period is satisfactory, + # then the system will only wait that amount of time before scaling to 0. + # NOTE: this period might actually be 0, if activator has been + # in the request path sufficiently long. + # If there is necessity for the last pod to linger longer use + # scale-to-zero-pod-retention-period flag. + scale-to-zero-grace-period: "30s" + + # Scale to zero pod retention period defines the minimum amount + # of time the last pod will remain after Autoscaler has decided to + # scale to zero. + # This flag is for the situations where the pod startup is very expensive + # and the traffic is bursty (requiring smaller windows for fast action), + # but patchy. + # The larger of this flag and `scale-to-zero-grace-period` will effectively + # determine how the last pod will hang around. + scale-to-zero-pod-retention-period: "0s" + + # pod-autoscaler-class specifies the default pod autoscaler class + # that should be used if none is specified. If omitted, + # the Knative Pod Autoscaler (KPA) is used by default. + pod-autoscaler-class: "kpa.autoscaling.knative.dev" + + # The capacity of a single activator task. + # The `unit` is one concurrent request proxied by the activator. + # activator-capacity must be at least 1. + # This value is used for computation of the Activator subset size. + # See the algorithm here: http://bit.ly/38XiCZ3. + # TODO(vagababov): tune after actual benchmarking. + activator-capacity: "100.0" + + # initial-scale is the cluster-wide default value for the initial target + # scale of a revision after creation, unless overridden by the + # "autoscaling.knative.dev/initialScale" annotation. + # This value must be greater than 0 unless allow-zero-initial-scale is true. + initial-scale: "1" + + # allow-zero-initial-scale controls whether either the cluster-wide initial-scale flag, + # or the "autoscaling.knative.dev/initialScale" annotation, can be set to 0. + allow-zero-initial-scale: "false" + + # min-scale is the cluster-wide default value for the min scale of a revision, + # unless overridden by the "autoscaling.knative.dev/minScale" annotation. + min-scale: "0" + + # max-scale is the cluster-wide default value for the max scale of a revision, + # unless overridden by the "autoscaling.knative.dev/maxScale" annotation. + # If set to 0, the revision has no maximum scale. + max-scale: "0" + + # scale-down-delay is the amount of time that must pass at reduced + # concurrency before a scale down decision is applied. This can be useful, + # for example, to maintain replica count and avoid a cold start penalty if + # more requests come in within the scale down delay period. + # The default, 0s, imposes no delay at all. + scale-down-delay: "0s" + + # max-scale-limit sets the maximum permitted value for the max scale of a revision. + # When this is set to a positive value, a revision with a maxScale above that value + # (including a maxScale of "0" = unlimited) is disallowed. + # A value of zero (the default) allows any limit, including unlimited. + max-scale-limit: "0" + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-certmanager + namespace: knative-serving + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: controller + app.kubernetes.io/version: "1.19.0" + networking.knative.dev/certificate-provider: cert-manager + annotations: + knative.dev/example-checksum: "b7a9a602" +data: + _example: | + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this block and unindented to actually change the configuration. + + # issuerRef is a reference to the issuer for external-domain certificates used for ingress. + # IssuerRef should be either `ClusterIssuer` or `Issuer`. + # Please refer `IssuerRef` in https://cert-manager.io/docs/concepts/issuer/ + # for more details about IssuerRef configuration. + # If the issuerRef is not specified, the self-signed `knative-selfsigned-issuer` ClusterIssuer is used. + issuerRef: | + kind: ClusterIssuer + name: letsencrypt-issuer + + # clusterLocalIssuerRef is a reference to the issuer for cluster-local-domain certificates used for ingress. + # clusterLocalIssuerRef should be either `ClusterIssuer` or `Issuer`. + # Please refer `IssuerRef` in https://cert-manager.io/docs/concepts/issuer/ + # for more details about ClusterInternalIssuerRef configuration. + # If the clusterLocalIssuerRef is not specified, the self-signed `knative-selfsigned-issuer` ClusterIssuer is used. + clusterLocalIssuerRef: | + kind: ClusterIssuer + name: your-company-issuer + + # systemInternalIssuerRef is a reference to the issuer for certificates for system-internal-tls certificates used by Knative internal components. + # systemInternalIssuerRef should be either `ClusterIssuer` or `Issuer`. + # Please refer `IssuerRef` in https://cert-manager.io/docs/concepts/issuer/ + # for more details about ClusterInternalIssuerRef configuration. + # If the systemInternalIssuerRef is not specified, the self-signed `knative-selfsigned-issuer` ClusterIssuer is used. + systemInternalIssuerRef: | + kind: ClusterIssuer + name: knative-selfsigned-issuer + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-defaults + namespace: knative-serving + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: controller + app.kubernetes.io/version: "1.19.0" + annotations: + knative.dev/example-checksum: "5b64ff5c" +data: + _example: | + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + + # revision-timeout-seconds contains the default number of + # seconds to use for the revision's per-request timeout, if + # none is specified. + revision-timeout-seconds: "300" # 5 minutes + + # max-revision-timeout-seconds contains the maximum number of + # seconds that can be used for revision-timeout-seconds. + # This value must be greater than or equal to revision-timeout-seconds. + # If omitted, the system default is used (600 seconds). + # + # If this value is increased, the activator's terminationGracePeriodSeconds + # should also be increased to prevent in-flight requests being disrupted. + max-revision-timeout-seconds: "600" # 10 minutes + + # revision-response-start-timeout-seconds contains the default number of + # seconds a request will be allowed to stay open while waiting to + # receive any bytes from the user's application, if none is specified. + # + # This defaults to 'revision-timeout-seconds' + revision-response-start-timeout-seconds: "300" + + # revision-idle-timeout-seconds contains the default number of + # seconds a request will be allowed to stay open while not receiving any + # bytes from the user's application, if none is specified. + revision-idle-timeout-seconds: "0" # infinite + + # revision-cpu-request contains the cpu allocation to assign + # to revisions by default. If omitted, no value is specified + # and the system default is used. + # Below is an example of setting revision-cpu-request. + # By default, it is not set by Knative. + revision-cpu-request: "400m" # 0.4 of a CPU (aka 400 milli-CPU) + + # revision-memory-request contains the memory allocation to assign + # to revisions by default. If omitted, no value is specified + # and the system default is used. + # Below is an example of setting revision-memory-request. + # By default, it is not set by Knative. + revision-memory-request: "100M" # 100 megabytes of memory + + # revision-ephemeral-storage-request contains the ephemeral storage + # allocation to assign to revisions by default. If omitted, no value is + # specified and the system default is used. + revision-ephemeral-storage-request: "500M" # 500 megabytes of storage + + # revision-cpu-limit contains the cpu allocation to limit + # revisions to by default. If omitted, no value is specified + # and the system default is used. + # Below is an example of setting revision-cpu-limit. + # By default, it is not set by Knative. + revision-cpu-limit: "1000m" # 1 CPU (aka 1000 milli-CPU) + + # revision-memory-limit contains the memory allocation to limit + # revisions to by default. If omitted, no value is specified + # and the system default is used. + # Below is an example of setting revision-memory-limit. + # By default, it is not set by Knative. + revision-memory-limit: "200M" # 200 megabytes of memory + + # revision-ephemeral-storage-limit contains the ephemeral storage + # allocation to limit revisions to by default. If omitted, no value is + # specified and the system default is used. + revision-ephemeral-storage-limit: "750M" # 750 megabytes of storage + + # container-name-template contains a template for the default + # container name, if none is specified. This field supports + # Go templating and is supplied with the ObjectMeta of the + # enclosing Service or Configuration, so values such as + # {{.Name}} are also valid. + container-name-template: "user-container" + + # init-container-name-template contains a template for the default + # init container name, if none is specified. This field supports + # Go templating and is supplied with the ObjectMeta of the + # enclosing Service or Configuration, so values such as + # {{.Name}} are also valid. + init-container-name-template: "init-container" + + # container-concurrency specifies the maximum number + # of requests the Container can handle at once, and requests + # above this threshold are queued. Setting a value of zero + # disables this throttling and lets through as many requests as + # the pod receives. + container-concurrency: "0" + + # The container concurrency max limit is an operator setting ensuring that + # the individual revisions cannot have arbitrary large concurrency + # values, or autoscaling targets. `container-concurrency` default setting + # must be at or below this value. + # + # Must be greater than 1. + # + # Note: even with this set, a user can choose a containerConcurrency + # of 0 (i.e. unbounded) unless allow-container-concurrency-zero is + # set to "false". + container-concurrency-max-limit: "1000" + + # allow-container-concurrency-zero controls whether users can + # specify 0 (i.e. unbounded) for containerConcurrency. + allow-container-concurrency-zero: "true" + + # enable-service-links specifies the default value used for the + # enableServiceLinks field of the PodSpec, when it is omitted by the user. + # See: https://kubernetes.io/docs/concepts/services-networking/connect-applications-service/#accessing-the-service + # + # This is a tri-state flag with possible values of (true|false|default). + # + # In environments with large number of services it is suggested + # to set this value to `false`. + # See https://github.com/knative/serving/issues/8498. + enable-service-links: "false" + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-deployment + namespace: knative-serving + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: controller + app.kubernetes.io/version: "1.19.0" + annotations: + knative.dev/example-checksum: "720ddb97" +data: + # This is the Go import path for the binary that is containerized + # and substituted here. + queue-sidecar-image: gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:1310917822086a5d8daa6328f6014001d5ea7ccfb0afc1a4e74b1b6a2eadc5ba + _example: |- + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + + # List of repositories for which tag to digest resolving should be skipped + registries-skipping-tag-resolving: "kind.local,ko.local,dev.local" + + # Maximum time allowed for an image's digests to be resolved. + digest-resolution-timeout: "10s" + + # Duration we wait for the deployment to be ready before considering it failed. + progress-deadline: "600s" + + # Sets the queue proxy's CPU request. + # If omitted, a default value (currently "25m"), is used. + queue-sidecar-cpu-request: "25m" + + # Sets the queue proxy's CPU limit. + # If omitted, a default value (currently "1000m"), is used when + # `queueproxy.resource-defaults` is set to `Enabled`. + queue-sidecar-cpu-limit: "1000m" + + # Sets the queue proxy's memory request. + # If omitted, a default value (currently "400Mi"), is used when + # `queueproxy.resource-defaults` is set to `Enabled`. + queue-sidecar-memory-request: "400Mi" + + # Sets the queue proxy's memory limit. + # If omitted, a default value (currently "800Mi"), is used when + # `queueproxy.resource-defaults` is set to `Enabled`. + queue-sidecar-memory-limit: "800Mi" + + # Sets the queue proxy's ephemeral storage request. + # If omitted, no value is specified and the system default is used. + queue-sidecar-ephemeral-storage-request: "512Mi" + + # Sets the queue proxy's ephemeral storage limit. + # If omitted, no value is specified and the system default is used. + queue-sidecar-ephemeral-storage-limit: "1024Mi" + + # Sets tokens associated with specific audiences for queue proxy - used by QPOptions + # + # For example, to add the `service-x` audience: + # queue-sidecar-token-audiences: "service-x" + # Also supports a list of audiences, for example: + # queue-sidecar-token-audiences: "service-x,service-y" + # If omitted, or empty, no tokens are created + queue-sidecar-token-audiences: "" + + # Sets rootCA for the queue proxy - used by QPOptions + # If omitted, or empty, no rootCA is added to the golang rootCAs + queue-sidecar-rootca: "" + + # If set, it automatically configures pod anti-affinity requirements for all Knative services. + # It employs the `preferredDuringSchedulingIgnoredDuringExecution` weighted pod affinity term, + # aligning with the Knative revision label. It yields the configuration below in all workloads' deployments: + # ` + # affinity: + # podAntiAffinity: + # preferredDuringSchedulingIgnoredDuringExecution: + # - podAffinityTerm: + # topologyKey: kubernetes.io/hostname + # labelSelector: + # matchLabels: + # serving.knative.dev/revision: {{revision-name}} + # weight: 100 + # ` + # This may be "none" or "prefer-spread-revision-over-nodes" (default) + # default-affinity-type: "prefer-spread-revision-over-nodes" + + # runtime-class-name contains the selector for which runtimeClassName + # is selected to put in a revision. + # By default, it is not set by Knative. + # + # Example: + # runtime-class-name: | + # "": + # selector: + # use-default-runc: "yes" + # kata: {} + # gvisor: + # selector: + # use-gvisor: "please" + runtime-class-name: "" + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-domain + namespace: knative-serving + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: controller + app.kubernetes.io/version: "1.19.0" + annotations: + knative.dev/example-checksum: "26c09de5" +data: + _example: | + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + + # Default value for domain. + # Routes having the cluster domain suffix (by default 'svc.cluster.local') + # will not be exposed through Ingress. You can define your own label + # selector to assign that domain suffix to your Route here, or you can set + # the label + # "networking.knative.dev/visibility=cluster-local" + # to achieve the same effect. This shows how to make routes having + # the label app=secret only exposed to the local cluster. + svc.cluster.local: | + selector: + app: secret + + # These are example settings of domain. + # example.com will be used for all routes, but it is the least-specific rule so it + # will only be used if no other domain matches. + example.com: | + + # example.org will be used for routes having app=nonprofit. + example.org: | + selector: + app: nonprofit + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-features + namespace: knative-serving + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: controller + app.kubernetes.io/version: "1.19.0" + annotations: + knative.dev/example-checksum: "0f9b4ade" +data: + _example: |- + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + + # Default SecurityContext settings to secure-by-default values + # if unset. + # + # This value will default to "enabled" in a future release, + # probably Knative 1.10 + secure-pod-defaults: "disabled" + + # Indicates whether multi container support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/configuration/feature-flags/#multiple-containers + multi-container: "enabled" + + # Indicates whether multi container probing is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/configuration/feature-flags/#multiple-container-probing + multi-container-probing: "disabled" + + # Indicates whether Kubernetes affinity support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-node-affinity + kubernetes.podspec-affinity: "disabled" + + # Indicates whether Kubernetes topologySpreadConstraints support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-topology-spread-constraints + kubernetes.podspec-topologyspreadconstraints: "disabled" + + # Indicates whether Kubernetes hostAliases support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-host-aliases + kubernetes.podspec-hostaliases: "disabled" + + # Indicates whether Kubernetes nodeSelector support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-node-selector + kubernetes.podspec-nodeselector: "disabled" + + # Indicates whether Kubernetes tolerations support is enabled + # + # WARNING: Cannot safely be disabled once enabled + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-toleration + kubernetes.podspec-tolerations: "disabled" + + # Indicates whether Kubernetes FieldRef support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-fieldref + kubernetes.podspec-fieldref: "disabled" + + # Indicates whether Kubernetes RuntimeClassName support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-runtime-class + kubernetes.podspec-runtimeclassname: "disabled" + + # Indicates whether Kubernetes DNSPolicy support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-dnspolicy + kubernetes.podspec-dnspolicy: "disabled" + + # Indicates whether Kubernetes DNSConfig support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-dnsconfig + kubernetes.podspec-dnsconfig: "disabled" + + # This feature allows end-users to set a subset of fields on the Pod's SecurityContext + # + # When set to "enabled" or "allowed" it allows the following + # PodSecurityContext properties: + # - FSGroup + # - RunAsGroup + # - RunAsNonRoot + # - SupplementalGroups + # - RunAsUser + # - SeccompProfile + # + # This feature flag should be used with caution as the PodSecurityContext + # properties may have a side-effect on non-user sidecar containers that come + # from Knative or your service mesh + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-security-context + kubernetes.podspec-securitycontext: "disabled" + + # Indicated whether sharing the process namespace via ShareProcessNamespace pod spec is allowed. + # This can be especially useful for sharing data from images directly between sidecars + # + # See: https://knative.dev/docs/serving/configuration/feature-flags/#kubernetes-share-process-namespace + kubernetes.podspec-shareprocessnamespace: "disabled" + + # Indicates whether hostIPC support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See https://knative.dev/docs/serving/configuration/feature-flags/#kubernetes-host-ipc + kubernetes.podspec-hostipc: "disabled" + + # Indicates whether hostPID support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See https://knative.dev/docs/serving/configuration/feature-flags/#kubernetes-host-pid + kubernetes.podspec-hostpid: "disabled" + + # Indicates whether hostNetwork support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See See https://knative.dev/docs/serving/configuration/feature-flags/#kubernetes-host-network + kubernetes.podspec-hostnetwork: "disabled" + + # Indicates whether Kubernetes PriorityClassName support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-priority-class-name + kubernetes.podspec-priorityclassname: "disabled" + + # Indicates whether Kubernetes SchedulerName support is enabled + # + # WARNING: Cannot safely be disabled once enabled. + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-scheduler-name + kubernetes.podspec-schedulername: "disabled" + + # This feature flag allows end-users to add a subset of capabilities on the Pod's SecurityContext. + # + # When set to "enabled" or "allowed" it allows capabilities to be added to the container. + # For a list of possible capabilities, see https://man7.org/linux/man-pages/man7/capabilities.7.html + kubernetes.containerspec-addcapabilities: "disabled" + + # This feature validates PodSpecs from the validating webhook + # against the K8s API Server. + # + # When "enabled", the server will always run the extra validation. + # When "allowed", the server will not run the dry-run validation by default. + # However, clients may enable the behavior on an individual Service by + # attaching the following metadata annotation: "features.knative.dev/podspec-dryrun":"enabled". + # See: https://knative.dev/docs/serving/feature-flags/#kubernetes-dry-run + kubernetes.podspec-dryrun: "allowed" + + # Controls whether tag header based routing feature are enabled or not. + # 1. Enabled: enabling tag header based routing + # 2. Disabled: disabling tag header based routing + # See: https://knative.dev/docs/serving/feature-flags/#tag-header-based-routing + tag-header-based-routing: "disabled" + + # Controls whether http2 auto-detection should be enabled or not. + # 1. Enabled: http2 connection will be attempted via upgrade. + # 2. Disabled: http2 connection will only be attempted when port name is set to "h2c". + autodetect-http2: "disabled" + + # Controls whether volume support for EmptyDir is enabled or not. + # 1. Enabled: enabling EmptyDir volume support + # 2. Disabled: disabling EmptyDir volume support + kubernetes.podspec-volumes-emptydir: "enabled" + + # Controls whether volume support for image is enabled or not. + # 1. Enabled: enabling image volume support + # 2. Disabled: disabling image volume support + kubernetes.podspec-volumes-image: "disabled" + + # Controls whether volume support for HostPath is enabled or not. + # WARNING: Cannot safely be disabled once enabled. + # WARNING: If you can avoid using a hostPath volume, you should. + # Please read https://kubernetes.io/docs/concepts/storage/volumes/#hostpath before enabling this feature. + # 1. Enabled: enabling HostPath volume support + # 2. Disabled: disabling HostPath volume support + kubernetes.podspec-volumes-hostpath: "disabled" + + # Controls whether volume support for CSI is enabled or not. + # 1. Enabled: enabling CSI volume support + # 2. Disabled: disabling CSI volume support + kubernetes.podspec-volumes-csi: "disabled" + + # Controls whether init containers support is enabled or not. + # 1. Enabled: enabling init containers support + # 2. Disabled: disabling init containers support + kubernetes.podspec-init-containers: "disabled" + + # Controls whether persistent volume claim support is enabled or not. + # 1. Enabled: enabling persistent volume claim support + # 2. Disabled: disabling persistent volume claim support + kubernetes.podspec-persistent-volume-claim: "disabled" + + # Controls whether write access for persistent volumes is enabled or not. + # 1. Enabled: enabling write access for persistent volumes + # 2. Disabled: disabling write access for persistent volumes + kubernetes.podspec-persistent-volume-write: "disabled" + + # Controls whether volume mount propagation support is enabled or not. + # 1. Enabled: enabling volume mount propagation support + # 2. Disabled: disabling volume mount propagation support + kubernetes.podspec-volumes-mount-propagation: "disabled" + + # Controls if the queue proxy podInfo feature is enabled, allowed or disabled + # + # This feature should be enabled/allowed when using queue proxy Options (Extensions) + # Enabling will mount a podInfo volume to the queue proxy container. + # The volume will contains an 'annotations' file (from the pod's annotation field). + # The annotations in this file include the Service annotations set by the client creating the service. + # If mounted, the annotations can be accessed by queue proxy extensions at /etc/podinfo/annnotations + # + # 1. "enabled": always mount a podInfo volume + # 2. "disabled": never mount a podInfo volume + # 3. "allowed": by default, do not mount a podInfo volume + # However, a client may mount the podInfo volume on an individual Service by attaching + # the following metadata annotation to the Service: "features.knative.dev/queueproxy-podinfo":"enabled". + # + # NOTE THAT THIS IS AN EXPERIMENTAL / ALPHA FEATURE + queueproxy.mount-podinfo: "disabled" + + # Default queue proxy resource requests and limits to good values for most cases if set. + queueproxy.resource-defaults: "disabled" + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-gc + namespace: knative-serving + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: controller + app.kubernetes.io/version: "1.19.0" + annotations: + knative.dev/example-checksum: "aa3813a8" +data: + _example: | + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + + # --------------------------------------- + # Garbage Collector Settings + # --------------------------------------- + # + # Active + # * Revisions which are referenced by a Route are considered active. + # * Individual revisions may be marked with the annotation + # "serving.knative.dev/no-gc":"true" to be permanently considered active. + # * Active revisions are not considered for GC. + # Retention + # * Revisions are retained if they are any of the following: + # 1. Active + # 2. Were created within "retain-since-create-time" + # 3. Were last referenced by a route within + # "retain-since-last-active-time" + # 4. There are fewer than "min-non-active-revisions" + # If none of these conditions are met, or if the count of revisions exceed + # "max-non-active-revisions", they will be deleted by GC. + # The special value "disabled" may be used to turn off these limits. + # + # Example config to immediately collect any inactive revision: + # min-non-active-revisions: "0" + # max-non-active-revisions: "0" + # retain-since-create-time: "disabled" + # retain-since-last-active-time: "disabled" + # + # Example config to always keep around the last ten non-active revisions: + # retain-since-create-time: "disabled" + # retain-since-last-active-time: "disabled" + # max-non-active-revisions: "10" + # + # Example config to disable all garbage collection: + # retain-since-create-time: "disabled" + # retain-since-last-active-time: "disabled" + # max-non-active-revisions: "disabled" + # + # Example config to keep recently deployed or active revisions, + # always maintain the last two in case of rollback, and prevent + # burst activity from exploding the count of old revisions: + # retain-since-create-time: "48h" + # retain-since-last-active-time: "15h" + # min-non-active-revisions: "2" + # max-non-active-revisions: "1000" + + # Duration since creation before considering a revision for GC or "disabled". + retain-since-create-time: "48h" + + # Duration since active before considering a revision for GC or "disabled". + retain-since-last-active-time: "15h" + + # Minimum number of non-active revisions to retain. + min-non-active-revisions: "20" + + # Maximum number of non-active revisions to retain + # or "disabled" to disable any maximum limit. + max-non-active-revisions: "1000" + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-leader-election + namespace: knative-serving + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: controller + app.kubernetes.io/version: "1.19.0" + annotations: + knative.dev/example-checksum: "f4b71f57" +data: + _example: | + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + + # lease-duration is how long non-leaders will wait to try to acquire the + # lock; 15 seconds is the value used by core kubernetes controllers. + lease-duration: "60s" + + # renew-deadline is how long a leader will try to renew the lease before + # giving up; 10 seconds is the value used by core kubernetes controllers. + renew-deadline: "40s" + + # retry-period is how long the leader election client waits between tries of + # actions; 2 seconds is the value used by core kubernetes controllers. + retry-period: "10s" + + # buckets is the number of buckets used to partition key space of each + # Reconciler. If this number is M and the replica number of the controller + # is N, the N replicas will compete for the M buckets. The owner of a + # bucket will take care of the reconciling for the keys partitioned into + # that bucket. + buckets: "1" + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-logging + namespace: knative-serving + labels: + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/component: logging + app.kubernetes.io/name: knative-serving + annotations: + knative.dev/example-checksum: "9f25d429" +data: + _example: | + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + + # Common configuration for all Knative codebase + zap-logger-config: | + { + "level": "info", + "development": false, + "outputPaths": ["stdout"], + "errorOutputPaths": ["stderr"], + "encoding": "json", + "encoderConfig": { + "timeKey": "timestamp", + "levelKey": "severity", + "nameKey": "logger", + "callerKey": "caller", + "messageKey": "message", + "stacktraceKey": "stacktrace", + "lineEnding": "", + "levelEncoder": "", + "timeEncoder": "iso8601", + "durationEncoder": "", + "callerEncoder": "" + } + } + + # Log level overrides + # For all components except the queue proxy, + # changes are picked up immediately. + # For queue proxy, changes require recreation of the pods. + loglevel.controller: "info" + loglevel.autoscaler: "info" + loglevel.queueproxy: "info" + loglevel.webhook: "info" + loglevel.activator: "info" + loglevel.hpaautoscaler: "info" + loglevel.net-istio-controller: "info" + loglevel.net-contour-controller: "info" + loglevel.net-kourier-controller: "info" + loglevel.net-gateway-api-controller: "info" + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-network + namespace: knative-serving + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: networking + app.kubernetes.io/version: "1.19.0" + annotations: + knative.dev/example-checksum: "0573e07d" +data: + _example: | + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + + # ingress-class specifies the default ingress class + # to use when not dictated by Route annotation. + # + # If not specified, will use the Istio ingress. + # + # Note that changing the Ingress class of an existing Route + # will result in undefined behavior. Therefore it is best to only + # update this value during the setup of Knative, to avoid getting + # undefined behavior. + ingress-class: "istio.ingress.networking.knative.dev" + + # certificate-class specifies the default Certificate class + # to use when not dictated by Route annotation. + # + # If not specified, will use the Cert-Manager Certificate. + # + # Note that changing the Certificate class of an existing Route + # will result in undefined behavior. Therefore it is best to only + # update this value during the setup of Knative, to avoid getting + # undefined behavior. + certificate-class: "cert-manager.certificate.networking.knative.dev" + + # namespace-wildcard-cert-selector specifies a LabelSelector which + # determines which namespaces should have a wildcard certificate + # provisioned. + # + # Use an empty value to disable the feature (this is the default): + # namespace-wildcard-cert-selector: "" + # + # Use an empty object to enable for all namespaces + # namespace-wildcard-cert-selector: {} + # + # Useful labels include the "kubernetes.io/metadata.name" label to + # avoid provisioning a certificate for the "kube-system" namespaces. + # Use the following selector to match pre-1.0 behavior of using + # "networking.knative.dev/disableWildcardCert" to exclude namespaces: + # + # matchExpressions: + # - key: "networking.knative.dev/disableWildcardCert" + # operator: "NotIn" + # values: ["true"] + namespace-wildcard-cert-selector: "" + + # domain-template specifies the golang text template string to use + # when constructing the Knative service's DNS name. The default + # value is "{{.Name}}.{{.Namespace}}.{{.Domain}}". + # + # Valid variables defined in the template include Name, Namespace, Domain, + # Labels, and Annotations. Name will be the result of the tag-template + # below, if a tag is specified for the route. + # + # Changing this value might be necessary when the extra levels in + # the domain name generated is problematic for wildcard certificates + # that only support a single level of domain name added to the + # certificate's domain. In those cases you might consider using a value + # of "{{.Name}}-{{.Namespace}}.{{.Domain}}", or removing the Namespace + # entirely from the template. When choosing a new value be thoughtful + # of the potential for conflicts - for example, when users choose to use + # characters such as `-` in their service, or namespace, names. + # {{.Annotations}} or {{.Labels}} can be used for any customization in the + # go template if needed. + # We strongly recommend keeping namespace part of the template to avoid + # domain name clashes: + # eg. '{{.Name}}-{{.Namespace}}.{{ index .Annotations "sub"}}.{{.Domain}}' + # and you have an annotation {"sub":"foo"}, then the generated template + # would be {Name}-{Namespace}.foo.{Domain} + domain-template: "{{.Name}}.{{.Namespace}}.{{.Domain}}" + + # tag-template specifies the golang text template string to use + # when constructing the DNS name for "tags" within the traffic blocks + # of Routes and Configuration. This is used in conjunction with the + # domain-template above to determine the full URL for the tag. + tag-template: "{{.Tag}}-{{.Name}}" + + # auto-tls is deprecated and replaced by external-domain-tls + auto-tls: "Disabled" + + # Controls whether TLS certificates are automatically provisioned and + # installed in the Knative ingress to terminate TLS connections + # for cluster external domains (like: app.example.com) + # - Enabled: enables the TLS certificate provisioning feature for cluster external domains. + # - Disabled: disables the TLS certificate provisioning feature for cluster external domains. + external-domain-tls: "Disabled" + + # Controls weather TLS certificates are automatically provisioned and + # installed in the Knative ingress to terminate TLS connections + # for cluster local domains (like: app.namespace.svc.) + # - Enabled: enables the TLS certificate provisioning feature for cluster cluster-local domains. + # - Disabled: disables the TLS certificate provisioning feature for cluster cluster local domains. + # NOTE: This flag is in an alpha state and is mostly here to enable internal testing + # for now. Use with caution. + cluster-local-domain-tls: "Disabled" + + # internal-encryption is deprecated and replaced by system-internal-tls + internal-encryption: "false" + + # system-internal-tls controls weather TLS encryption is used for connections between + # the internal components of Knative: + # - ingress to activator + # - ingress to queue-proxy + # - activator to queue-proxy + # + # Possible values for this flag are: + # - Enabled: enables the TLS certificate provisioning feature for cluster cluster-local domains. + # - Disabled: disables the TLS certificate provisioning feature for cluster cluster local domains. + # NOTE: This flag is in an alpha state and is mostly here to enable internal testing + # for now. Use with caution. + system-internal-tls: "Disabled" + + # Controls the behavior of the HTTP endpoint for the Knative ingress. + # It requires auto-tls to be enabled. + # - Enabled: The Knative ingress will be able to serve HTTP connection. + # - Redirected: The Knative ingress will send a 301 redirect for all + # http connections, asking the clients to use HTTPS. + # + # "Disabled" option is deprecated. + http-protocol: "Enabled" + + # rollout-duration contains the minimal duration in seconds over which the + # Configuration traffic targets are rolled out to the newest revision. + rollout-duration: "0" + + # autocreate-cluster-domain-claims controls whether ClusterDomainClaims should + # be automatically created (and deleted) as needed when DomainMappings are + # reconciled. + # + # If this is "false" (the default), the cluster administrator is + # responsible for creating ClusterDomainClaims and delegating them to + # namespaces via their spec.Namespace field. This setting should be used in + # multitenant environments which need to control which namespace can use a + # particular domain name in a domain mapping. + # + # If this is "true", users are able to associate arbitrary names with their + # services via the DomainMapping feature. + autocreate-cluster-domain-claims: "false" + + # If true, networking plugins can add additional information to deployed + # applications to make their pods directly accessible via their IPs even if mesh is + # enabled and thus direct-addressability is usually not possible. + # Consumers like Knative Serving can use this setting to adjust their behavior + # accordingly, i.e. to drop fallback solutions for non-pod-addressable systems. + # + # NOTE: This flag is in an alpha state and is mostly here to enable internal testing + # for now. Use with caution. + enable-mesh-pod-addressability: "false" + + # mesh-compatibility-mode indicates whether consumers of network plugins + # should directly contact Pod IPs (most efficient), or should use the + # Cluster IP (less efficient, needed when mesh is enabled unless + # `enable-mesh-pod-addressability`, above, is set). + # Permitted values are: + # - "auto" (default): automatically determine which mesh mode to use by trying Pod IP and falling back to Cluster IP as needed. + # - "enabled": always use Cluster IP and do not attempt to use Pod IPs. + # - "disabled": always use Pod IPs and do not fall back to Cluster IP on failure. + mesh-compatibility-mode: "auto" + + # Defines the scheme used for external URLs if auto-tls is not enabled. + # This can be used for making Knative report all URLs as "HTTPS" for example, if you're + # fronting Knative with an external loadbalancer that deals with TLS termination and + # Knative doesn't know about that otherwise. + default-external-scheme: "http" + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-observability + namespace: knative-serving + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: observability + app.kubernetes.io/version: "1.19.0" + annotations: + knative.dev/example-checksum: "6bc8b73d" +data: + _example: | + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + + # logging.enable-var-log-collection defaults to false. + # The fluentd daemon set will be set up to collect /var/log if + # this flag is true. + logging.enable-var-log-collection: "false" + + # logging.revision-url-template provides a template to use for producing the + # logging URL that is injected into the status of each Revision. + logging.revision-url-template: "http://logging.example.com/?revisionUID=${REVISION_UID}" + + # If non-empty, this enables queue proxy writing user request logs to stdout, excluding probe + # requests. + # NB: after 0.18 release logging.enable-request-log must be explicitly set to true + # in order for request logging to be enabled. + # + # The value determines the shape of the request logs and it must be a valid go text/template. + # It is important to keep this as a single line. Multiple lines are parsed as separate entities + # by most collection agents and will split the request logs into multiple records. + # + # The following fields and functions are available to the template: + # + # Request: An http.Request (see https://golang.org/pkg/net/http/#Request) + # representing an HTTP request received by the server. + # + # Response: + # struct { + # Code int // HTTP status code (see https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml) + # Size int // An int representing the size of the response. + # Latency float64 // A float64 representing the latency of the response in seconds. + # } + # + # Revision: + # struct { + # Name string // Knative revision name + # Namespace string // Knative revision namespace + # Service string // Knative service name + # Configuration string // Knative configuration name + # PodName string // Name of the pod hosting the revision + # PodIP string // IP of the pod hosting the revision + # } + # + logging.request-log-template: '{"httpRequest": {"requestMethod": "{{.Request.Method}}", "requestUrl": "{{js .Request.RequestURI}}", "requestSize": "{{.Request.ContentLength}}", "status": {{.Response.Code}}, "responseSize": "{{.Response.Size}}", "userAgent": "{{js .Request.UserAgent}}", "remoteIp": "{{js .Request.RemoteAddr}}", "serverIp": "{{.Revision.PodIP}}", "referer": "{{js .Request.Referer}}", "latency": "{{.Response.Latency}}s", "protocol": "{{.Request.Proto}}"}, "traceId": "{{index .Request.Header "X-B3-Traceid"}}"}' + + # If true, the request logging will be enabled. + logging.enable-request-log: "false" + + # If true, this enables queue proxy writing request logs for probe requests to stdout. + # It uses the same template for user requests, i.e. logging.request-log-template. + logging.enable-probe-request-log: "false" + + # metrics-protocol field specifies the protocol used when exporting metrics + # It supports either 'none' (the default), 'prometheus', 'http/protobuf' (OTLP HTTP), 'grpc' (OTLP gRPC) + metrics-protocol: http/protobuf + + # metrics-endpoint field specifies the destination metrics should be exporter to. + # + # The endpoint MUST be set when the protocol is http/protobuf or grpc. + # The endpoint MUST NOT be set when the protocol is none. + # + # When the protocol is prometheus the endpoint can accept a 'host:port' string to customize the + # listening host interface and port. + metrics-endpoint: http://example.com/v1/traces + + # metrics-export-interval specifies the global metrics reporting period for control and data plane components. + # If a zero or negative value is passed the default reporting OTel period is used (60 secs). + metrics-export-interval: 60s + + # request-metrics-protocol field specifies the protocol used when exporting queue-proxy metrics + # It supports either 'none' (the default), 'prometheus', 'http/protobuf' (OTLP HTTP), 'grpc' (OTLP gRPC) + request-metrics-protocol: http/protobuf + + # request-metrics-endpoint field specifies the destination metrics from the queue proxy should be exporter to. + # + # The endpoint MUST be set when the protocol is http/protobuf or grpc. + # The endpoint MUST NOT be set when the protocol is none. + # + # When the protocol is prometheus the endpoint can accept a 'host:port' string to customize the + # listening host interface and port. + request-metrics-endpoint: http://promstack-kube-prometheus-prometheus.observability:9090/api/v1/otlp/v1/metrics + + # request-metrics-export-interval specifies the global metrics reporting period for the queue-proxy. + # + # If a zero or negative value is passed the default reporting OTel period is used (60 secs). + request-metrics-export-interval: 60s + + # runtime-profiling indicates whether it is allowed to retrieve runtime profiling data from + # the pods via an HTTP server in the format expected by the pprof visualization tool. When + # enabled, the Knative Serving pods expose the profiling data on an alternate HTTP port 8008. + # The HTTP context root for profiling is then /debug/pprof/. + runtime-profiling: enabled + + + # tracing-protocol field specifies the protocol used when exporting metrics + # It supports either 'none' (the default), 'prometheus', 'http/protobuf' (OTLP HTTP), 'grpc' (OTLP gRPC) + # or `stdout` for debugging purposes + tracing-protocol: http/protobuf + + # tracing-endpoint field specifies the destination traces should be exporter to. + # + # The endpoint MUST be set when the protocol is http/protobuf or grpc. + # The endpoint MUST NOT be set when the protocol is none. + tracing-endpoint: http://jaeger-collector.observability:4318/v1/traces + + # tracing-sampling-rate allows the user to specify what percentage of all traces should be exported + # The value should be between 0 (never sample) to 1 (always sample) + tracing-sampling-rate: "1" + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-tracing + namespace: knative-serving + labels: + app.kubernetes.io/name: knative-serving + app.kubernetes.io/component: tracing + app.kubernetes.io/version: "1.19.0" + annotations: + knative.dev/example-checksum: "04c7e9a3" +data: + _example: | + ########################################################### + # # + # This config is deprecated - use config-observability # + # # + ########################################################### + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: activator + namespace: knative-serving + labels: + app.kubernetes.io/component: activator + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +spec: + minReplicas: 1 + maxReplicas: 20 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: activator + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + # Percentage of the requested CPU + averageUtilization: 100 +--- +# Activator PDB. Currently we permit unavailability of 20% of tasks at the same time. +# Given the subsetting and that the activators are partially stateful systems, we want +# a slow rollout of the new versions and slow migration during node upgrades. +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: activator-pdb + namespace: knative-serving + labels: + app.kubernetes.io/component: activator + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +spec: + minAvailable: 80% + selector: + matchLabels: + app: activator + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: activator + namespace: knative-serving + labels: + app.kubernetes.io/component: activator + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving +spec: + selector: + matchLabels: + app: activator + role: activator + template: + metadata: + labels: + app: activator + role: activator + app.kubernetes.io/component: activator + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + spec: + # To avoid node becoming SPOF, spread our replicas to different nodes. + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + app: activator + topologyKey: kubernetes.io/hostname + weight: 100 + serviceAccountName: activator + containers: + - name: activator + # This is the Go import path for the binary that is containerized + # and substituted here. + image: gcr.io/knative-releases/knative.dev/serving/cmd/activator@sha256:3e81e0b0e2ead666c131a17b437b1759e59ec2b066db49c493e4663e42fa4452 + # The numbers are based on performance test results from + # https://github.com/knative/serving/issues/1625#issuecomment-511930023 + resources: + requests: + cpu: 300m + memory: 60Mi + limits: + cpu: 1000m + memory: 600Mi + env: + # Run Activator with GC collection when newly generated memory is 500%. + - name: GOGC + value: "500" + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: SYSTEM_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CONFIG_LOGGING_NAME + value: config-logging + - name: CONFIG_OBSERVABILITY_NAME + value: config-observability + # TODO(https://github.com/knative/pkg/pull/953): Remove stackdriver specific config + - name: METRICS_DOMAIN + value: knative.dev/internal/serving + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + ports: + - name: metrics + containerPort: 9090 + - name: profiling + containerPort: 8008 + - name: http1 + containerPort: 8012 + - name: h2c + containerPort: 8013 + readinessProbe: + httpGet: + port: 8012 + periodSeconds: 5 + failureThreshold: 5 + livenessProbe: + httpGet: + port: 8012 + periodSeconds: 10 + failureThreshold: 12 + initialDelaySeconds: 15 + # The activator (often) sits on the dataplane, and may proxy long (e.g. + # streaming, websockets) requests. We give a long grace period for the + # activator to "lame duck" and drain outstanding requests before we + # forcibly terminate the pod (and outstanding connections). This value + # should be at least as large as the upper bound on the Revision's + # timeoutSeconds property to avoid servicing events disrupting + # connections. + terminationGracePeriodSeconds: 600 +--- +apiVersion: v1 +kind: Service +metadata: + name: activator-service + namespace: knative-serving + labels: + app: activator + app.kubernetes.io/component: activator + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving +spec: + selector: + app: activator + ports: + # Define metrics and profiling for them to be accessible within service meshes. + - name: http-metrics + port: 9090 + targetPort: 9090 + - name: http-profiling + port: 8008 + targetPort: 8008 + - name: http + port: 80 + targetPort: 8012 + - name: http2 + port: 81 + targetPort: 8013 + - name: https + port: 443 + targetPort: 8112 + type: ClusterIP + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: autoscaler + namespace: knative-serving + labels: + app.kubernetes.io/component: autoscaler + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +spec: + replicas: 1 + selector: + matchLabels: + app: autoscaler + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + template: + metadata: + labels: + app: autoscaler + app.kubernetes.io/component: autoscaler + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + spec: + # To avoid node becoming SPOF, spread our replicas to different nodes. + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + app: autoscaler + topologyKey: kubernetes.io/hostname + weight: 100 + serviceAccountName: controller + containers: + - name: autoscaler + # This is the Go import path for the binary that is containerized + # and substituted here. + image: gcr.io/knative-releases/knative.dev/serving/cmd/autoscaler@sha256:998a790f7f74caec6e7fc9084d7b85f25b6c011e753b26076c7db766587b3e08 + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + cpu: 1000m + memory: 1000Mi + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: SYSTEM_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CONFIG_LOGGING_NAME + value: config-logging + - name: CONFIG_OBSERVABILITY_NAME + value: config-observability + # TODO(https://github.com/knative/pkg/pull/953): Remove stackdriver specific config + - name: METRICS_DOMAIN + value: knative.dev/serving + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + ports: + - name: metrics + containerPort: 9090 + - name: profiling + containerPort: 8008 + - name: websocket + containerPort: 8080 + readinessProbe: + httpGet: + port: 8080 + livenessProbe: + httpGet: + port: 8080 + failureThreshold: 6 +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: autoscaler + app.kubernetes.io/component: autoscaler + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + name: autoscaler + namespace: knative-serving +spec: + ports: + # Define metrics and profiling for them to be accessible within service meshes. + - name: http-metrics + port: 9090 + targetPort: 9090 + - name: http-profiling + port: 8008 + targetPort: 8008 + - name: http + port: 8080 + targetPort: 8080 + selector: + app: autoscaler + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller + namespace: knative-serving + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +spec: + selector: + matchLabels: + app: controller + template: + metadata: + labels: + app: controller + app.kubernetes.io/component: controller + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + spec: + # To avoid node becoming SPOF, spread our replicas to different nodes. + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + app: controller + topologyKey: kubernetes.io/hostname + weight: 100 + serviceAccountName: controller + containers: + - name: controller + # This is the Go import path for the binary that is containerized + # and substituted here. + image: gcr.io/knative-releases/knative.dev/serving/cmd/controller@sha256:d9f40097903d1d9f4108723d2e41dfc21039ff380671ab80723fc861d81b8071 + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + cpu: 1000m + memory: 1000Mi + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: SYSTEM_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CONFIG_LOGGING_NAME + value: config-logging + - name: CONFIG_OBSERVABILITY_NAME + value: config-observability + # TODO(https://github.com/knative/pkg/pull/953): Remove stackdriver specific config + - name: METRICS_DOMAIN + value: knative.dev/internal/serving + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + livenessProbe: + httpGet: + path: /health + port: probes + scheme: HTTP + periodSeconds: 5 + failureThreshold: 6 + readinessProbe: + httpGet: + path: /readiness + port: probes + scheme: HTTP + periodSeconds: 5 + failureThreshold: 3 + ports: + - name: metrics + containerPort: 9090 + - name: profiling + containerPort: 8008 + - name: probes + containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: controller + app.kubernetes.io/component: controller + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" + name: controller + namespace: knative-serving +spec: + ports: + # Define metrics and profiling for them to be accessible within service meshes. + - name: http-metrics + port: 9090 + targetPort: 9090 + - name: http-profiling + port: 8008 + targetPort: 8008 + selector: + app: controller + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: webhook + namespace: knative-serving + labels: + app.kubernetes.io/component: webhook + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +spec: + minReplicas: 1 + maxReplicas: 5 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: webhook + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + # Percentage of the requested CPU + averageUtilization: 100 +--- +# Webhook PDB. +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: webhook-pdb + namespace: knative-serving + labels: + app.kubernetes.io/component: webhook + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +spec: + minAvailable: 80% + selector: + matchLabels: + app: webhook + +--- +# Copyright 2018 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: webhook + namespace: knative-serving + labels: + app.kubernetes.io/component: webhook + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving +spec: + selector: + matchLabels: + app: webhook + role: webhook + template: + metadata: + labels: + app: webhook + role: webhook + app.kubernetes.io/component: webhook + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving + spec: + # To avoid node becoming SPOF, spread our replicas to different nodes. + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + app: webhook + topologyKey: kubernetes.io/hostname + weight: 100 + serviceAccountName: controller + containers: + - name: webhook + # This is the Go import path for the binary that is containerized + # and substituted here. + image: gcr.io/knative-releases/knative.dev/serving/cmd/webhook@sha256:deb7f4ff25b854c6a1f58c2435fe0799731eba974d50dd012b534b6daf8eebf3 + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + cpu: 500m + memory: 500Mi + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: SYSTEM_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CONFIG_LOGGING_NAME + value: config-logging + - name: CONFIG_OBSERVABILITY_NAME + value: config-observability + - name: WEBHOOK_NAME + value: webhook + - name: WEBHOOK_PORT + value: "8443" + # TODO(https://github.com/knative/pkg/pull/953): Remove stackdriver specific config + - name: METRICS_DOMAIN + value: knative.dev/internal/serving + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + ports: + - name: metrics + containerPort: 9090 + - name: profiling + containerPort: 8008 + - name: https-webhook + containerPort: 8443 + readinessProbe: + periodSeconds: 1 + httpGet: + scheme: HTTPS + port: 8443 + livenessProbe: + periodSeconds: 10 + httpGet: + scheme: HTTPS + port: 8443 + failureThreshold: 6 + initialDelaySeconds: 20 + # Our webhook should gracefully terminate by lame ducking first, set this to a sufficiently + # high value that we respect whatever value it has configured for the lame duck grace period. + terminationGracePeriodSeconds: 300 +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: webhook + role: webhook + app.kubernetes.io/component: webhook + app.kubernetes.io/version: "1.19.0" + app.kubernetes.io/name: knative-serving + name: webhook + namespace: knative-serving +spec: + ports: + # Define metrics and profiling for them to be accessible within service meshes. + - name: http-metrics + port: 9090 + targetPort: 9090 + - name: http-profiling + port: 8008 + targetPort: 8008 + - name: https-webhook + port: 443 + targetPort: 8443 + selector: + app: webhook + role: webhook + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: config.webhook.serving.knative.dev + labels: + app.kubernetes.io/component: webhook + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +webhooks: + - admissionReviewVersions: ["v1", "v1beta1"] + clientConfig: + service: + name: webhook + namespace: knative-serving + failurePolicy: Fail + sideEffects: None + name: config.webhook.serving.knative.dev + objectSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: ["knative-serving"] + - key: app.kubernetes.io/component + operator: In + values: ["autoscaler", "controller", "logging", "networking", "observability", "tracing", "net-certmanager"] + timeoutSeconds: 10 + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: webhook.serving.knative.dev + labels: + app.kubernetes.io/component: webhook + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +webhooks: + - admissionReviewVersions: ["v1", "v1beta1"] + clientConfig: + service: + name: webhook + namespace: knative-serving + failurePolicy: Fail + sideEffects: None + name: webhook.serving.knative.dev + timeoutSeconds: 10 + rules: + - apiGroups: + - autoscaling.internal.knative.dev + - networking.internal.knative.dev + - serving.knative.dev + apiVersions: + - "*" + operations: + - CREATE + - UPDATE + scope: "*" + resources: + - metrics + - podautoscalers + - certificates + - ingresses + - serverlessservices + - configurations + - revisions + - routes + - services + - domainmappings + - domainmappings/status + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validation.webhook.serving.knative.dev + labels: + app.kubernetes.io/component: webhook + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +webhooks: + - admissionReviewVersions: ["v1", "v1beta1"] + clientConfig: + service: + name: webhook + namespace: knative-serving + failurePolicy: Fail + sideEffects: None + name: validation.webhook.serving.knative.dev + timeoutSeconds: 10 + rules: + - apiGroups: + - autoscaling.internal.knative.dev + - networking.internal.knative.dev + - serving.knative.dev + apiVersions: + - "*" + operations: + - CREATE + - UPDATE + - DELETE + scope: "*" + resources: + - metrics + - podautoscalers + - certificates + - ingresses + - serverlessservices + - configurations + - revisions + - routes + - services + - domainmappings + - domainmappings/status + +--- +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Secret +metadata: + name: webhook-certs + namespace: knative-serving + labels: + app.kubernetes.io/component: webhook + app.kubernetes.io/name: knative-serving + app.kubernetes.io/version: "1.19.0" +# The data is populated at install time. + +--- diff --git a/integration/fixtures/knative/04-serving-tests-namespace.yaml b/integration/fixtures/knative/04-serving-tests-namespace.yaml new file mode 100644 index 000000000..0a6d3c67d --- /dev/null +++ b/integration/fixtures/knative/04-serving-tests-namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: serving-tests diff --git a/integration/fixtures/knative/tools.go b/integration/fixtures/knative/tools.go new file mode 100644 index 000000000..02790a0e8 --- /dev/null +++ b/integration/fixtures/knative/tools.go @@ -0,0 +1,14 @@ +//go:build tools + +package tools + +// The following dependencies are required by the Knative conformance tests. +// They allow to download the test_images when calling "go mod vendor". +import ( + _ "knative.dev/networking/test/test_images/grpc-ping" + _ "knative.dev/networking/test/test_images/httpproxy" + _ "knative.dev/networking/test/test_images/retry" + _ "knative.dev/networking/test/test_images/runtime" + _ "knative.dev/networking/test/test_images/timeout" + _ "knative.dev/networking/test/test_images/wsserver" +) diff --git a/integration/fixtures/knative/upload-test-images.sh b/integration/fixtures/knative/upload-test-images.sh new file mode 100755 index 000000000..cbbacb9b8 --- /dev/null +++ b/integration/fixtures/knative/upload-test-images.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit + +function upload_test_images() { + echo ">> Publishing test images" + ( + # Script needs to be executed from repo root + cd "$( dirname "$0")/../../../" + echo "Current working directory: $(pwd)" + local image_dir="vendor/knative.dev/networking/test/test_images" + local docker_tag=$1 + local tag_option="" + if [ -n "${docker_tag}" ]; then + tag_option="--tags $docker_tag,latest" + fi + + # ko resolve is being used for the side-effect of publishing images, + # so the resulting yaml produced is ignored. + # shellcheck disable=SC2086 + ko resolve --jobs=4 ${tag_option} -RBf "${image_dir}" > /dev/null + ) +} + +: "${KO_DOCKER_REPO:?"You must set 'KO_DOCKER_REPO', see DEVELOPMENT.md"}" + +upload_test_images "$@" diff --git a/integration/integration_test.go b/integration/integration_test.go index bb0fc2bfd..e1b967c1a 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -42,7 +42,13 @@ var ( k8sConformanceTraefikVersion = flag.String("k8sConformanceTraefikVersion", "dev", "specify the Traefik version for the K8s Gateway API conformance report") ) -const tailscaleSecretFilePath = "tailscale.secret" +const ( + k3sImage = "docker.io/rancher/k3s:v1.32.9-k3s1" + traefikImage = "traefik/traefik:latest" + traefikDeployment = "deployments/traefik" + traefikNamespace = "traefik" + tailscaleSecretFilePath = "tailscale.secret" +) type composeConfig struct { Services map[string]composeService `yaml:"services"` diff --git a/integration/k8s_conformance_test.go b/integration/k8s_conformance_test.go index 1e03f223b..22aae5b15 100644 --- a/integration/k8s_conformance_test.go +++ b/integration/k8s_conformance_test.go @@ -37,13 +37,6 @@ import ( "sigs.k8s.io/yaml" ) -const ( - k3sImage = "docker.io/rancher/k3s:v1.29.3-k3s1" - traefikImage = "traefik/traefik:latest" - traefikDeployment = "deployments/traefik" - traefikNamespace = "traefik" -) - // K8sConformanceSuite tests suite. type K8sConformanceSuite struct { BaseSuite diff --git a/integration/knative_conformance_test.go b/integration/knative_conformance_test.go new file mode 100644 index 000000000..89ffa323a --- /dev/null +++ b/integration/knative_conformance_test.go @@ -0,0 +1,178 @@ +// Use a build tag to include and run Knative conformance tests. +// The Knative conformance toolkit redefines the skip-tests flag, +// which conflicts with the testing library and causes a panic. +//go:build knativeConformance + +package integration + +import ( + "flag" + "io" + "os" + "slices" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/k3s" + "github.com/testcontainers/testcontainers-go/network" + "github.com/traefik/traefik/v3/integration/try" + "knative.dev/networking/test/conformance/ingress" + klog "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +const knativeNamespace = "knative-serving" + +var imageNames = []string{ + traefikImage, + "ko.local/grpc-ping:latest", + "ko.local/httpproxy:latest", + "ko.local/retry:latest", + "ko.local/runtime:latest", + "ko.local/wsserver:latest", + "ko.local/timeout:latest", +} + +type KnativeConformanceSuite struct { + BaseSuite + + k3sContainer *k3s.K3sContainer +} + +func TestKnativeConformanceSuite(t *testing.T) { + suite.Run(t, new(KnativeConformanceSuite)) +} + +func (s *KnativeConformanceSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + // Avoid panic. + klog.SetLogger(zap.New()) + + provider, err := testcontainers.ProviderDocker.GetProvider() + if err != nil { + s.T().Fatal(err) + } + + ctx := s.T().Context() + + // Ensure image is available locally. + images, err := provider.ListImages(ctx) + if err != nil { + s.T().Fatal(err) + } + + if !slices.ContainsFunc(images, func(img testcontainers.ImageInfo) bool { + return img.Name == traefikImage + }) { + s.T().Fatal("Traefik image is not present") + } + + s.k3sContainer, err = k3s.Run(ctx, + k3sImage, + k3s.WithManifest("./fixtures/knative/00-knative-crd-v1.19.0.yml"), + k3s.WithManifest("./fixtures/knative/01-rbac.yml"), + k3s.WithManifest("./fixtures/knative/02-traefik.yml"), + k3s.WithManifest("./fixtures/knative/03-knative-serving-v1.19.0.yaml"), + k3s.WithManifest("./fixtures/knative/04-serving-tests-namespace.yaml"), + network.WithNetwork(nil, s.network), + ) + if err != nil { + s.T().Fatal(err) + } + + for _, imageName := range imageNames { + if err = s.k3sContainer.LoadImages(ctx, imageName); err != nil { + s.T().Fatal(err) + } + } + + exitCode, _, err := s.k3sContainer.Exec(ctx, []string{"kubectl", "wait", "-n", traefikNamespace, traefikDeployment, "--for=condition=Available", "--timeout=10s"}) + if err != nil || exitCode > 0 { + s.T().Fatalf("Traefik pod is not ready: %v", err) + } + + exitCode, _, err = s.k3sContainer.Exec(ctx, []string{"kubectl", "wait", "-n", knativeNamespace, "deployment/activator", "--for=condition=Available", "--timeout=10s"}) + if err != nil || exitCode > 0 { + s.T().Fatalf("Activator pod is not ready: %v", err) + } + + exitCode, _, err = s.k3sContainer.Exec(ctx, []string{"kubectl", "wait", "-n", knativeNamespace, "deployment/controller", "--for=condition=Available", "--timeout=10s"}) + if err != nil || exitCode > 0 { + s.T().Fatalf("Controller pod is not ready: %v", err) + } + + exitCode, _, err = s.k3sContainer.Exec(ctx, []string{"kubectl", "wait", "-n", knativeNamespace, "deployment/autoscaler", "--for=condition=Available", "--timeout=10s"}) + if err != nil || exitCode > 0 { + s.T().Fatalf("Autoscaler pod is not ready: %v", err) + } + + exitCode, _, err = s.k3sContainer.Exec(ctx, []string{"kubectl", "wait", "-n", knativeNamespace, "deployment/webhook", "--for=condition=Available", "--timeout=10s"}) + if err != nil || exitCode > 0 { + s.T().Fatalf("Webhook pod is not ready: %v", err) + } +} + +func (s *KnativeConformanceSuite) TearDownSuite() { + ctx := s.T().Context() + + if s.T().Failed() || *showLog { + k3sLogs, err := s.k3sContainer.Logs(ctx) + if err == nil { + if res, err := io.ReadAll(k3sLogs); err == nil { + s.T().Log(string(res)) + } + } + + exitCode, result, err := s.k3sContainer.Exec(ctx, []string{"kubectl", "logs", "-n", traefikNamespace, traefikDeployment}) + if err == nil || exitCode == 0 { + if res, err := io.ReadAll(result); err == nil { + s.T().Log(string(res)) + } + } + } + + if err := s.k3sContainer.Terminate(ctx); err != nil { + s.T().Fatal(err) + } + + s.BaseSuite.TearDownSuite() +} + +func (s *KnativeConformanceSuite) TestKnativeConformance() { + // Wait for traefik to start + k3sContainerIP, err := s.k3sContainer.ContainerIP(s.T().Context()) + require.NoError(s.T(), err) + + err = try.GetRequest("http://"+k3sContainerIP+":9000/api/entrypoints", 10*time.Second, try.BodyContains(`"name":"pweb"`)) + require.NoError(s.T(), err) + + kubeconfig, err := s.k3sContainer.GetKubeConfig(s.T().Context()) + if err != nil { + s.T().Fatal(err) + } + + // Write the kubeconfig.yaml in a temp file. + kubeconfigFile := s.T().TempDir() + "/kubeconfig.yaml" + + if err = os.WriteFile(kubeconfigFile, kubeconfig, 0o644); err != nil { + s.T().Fatal(err) + } + + if err = flag.CommandLine.Set("kubeconfig", kubeconfigFile); err != nil { + s.T().Fatal(err) + } + + if err = flag.CommandLine.Set("ingressClass", "traefik.ingress.networking.knative.dev"); err != nil { + s.T().Fatal(err) + } + + if err = flag.CommandLine.Set("skip-tests", "headers/probe"); err != nil { + s.T().Fatal(err) + } + + ingress.RunConformance(s.T()) +} diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index 1cb0c67c7..5d07f96aa 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -177,6 +177,10 @@ type WRRService struct { Name string `json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty" export:"true"` Weight *int `json:"weight,omitempty" toml:"weight,omitempty" yaml:"weight,omitempty" export:"true"` + // Headers defines the HTTP headers that should be added to the request when calling the service. + // This is required by the Knative implementation which expects specific headers to be sent. + Headers map[string]string `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` + // Status defines an HTTP status code that should be returned when calling the service. // This is required by the Gateway API implementation which expects specific HTTP status to be returned. Status *int `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index c7df112e5..0d4dbca75 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -2491,6 +2491,13 @@ func (in *WRRService) DeepCopyInto(out *WRRService) { *out = new(int) **out = **in } + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.Status != nil { in, out := &in.Status, &out.Status *out = new(int) diff --git a/pkg/config/static/experimental.go b/pkg/config/static/experimental.go index dba89fec8..e0db0589c 100644 --- a/pkg/config/static/experimental.go +++ b/pkg/config/static/experimental.go @@ -9,6 +9,7 @@ type Experimental struct { AbortOnPluginFailure bool `description:"Defines whether all plugins must be loaded successfully for Traefik to start." json:"abortOnPluginFailure,omitempty" toml:"abortOnPluginFailure,omitempty" yaml:"abortOnPluginFailure,omitempty" export:"true"` FastProxy *FastProxyConfig `description:"Enables the FastProxy implementation." json:"fastProxy,omitempty" toml:"fastProxy,omitempty" yaml:"fastProxy,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` OTLPLogs bool `description:"Enables the OpenTelemetry logs integration." json:"otlplogs,omitempty" toml:"otlplogs,omitempty" yaml:"otlplogs,omitempty" export:"true"` + Knative bool `description:"Allow the Knative provider usage." json:"knative,omitempty" toml:"knative,omitempty" yaml:"knative,omitempty" export:"true"` KubernetesIngressNGINX bool `description:"Allow the Kubernetes Ingress NGINX provider usage." json:"kubernetesIngressNGINX,omitempty" toml:"kubernetesIngressNGINX,omitempty" yaml:"kubernetesIngressNGINX,omitempty" export:"true"` // Deprecated: KubernetesGateway provider is not an experimental feature starting with v3.1. Please remove its usage from the static configuration. diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index fb15a0b18..bdbefdbe9 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -24,6 +24,7 @@ import ( "github.com/traefik/traefik/v3/pkg/provider/kubernetes/gateway" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/ingress" ingressnginx "github.com/traefik/traefik/v3/pkg/provider/kubernetes/ingress-nginx" + "github.com/traefik/traefik/v3/pkg/provider/kubernetes/knative" "github.com/traefik/traefik/v3/pkg/provider/kv/consul" "github.com/traefik/traefik/v3/pkg/provider/kv/etcd" "github.com/traefik/traefik/v3/pkg/provider/kv/redis" @@ -239,6 +240,7 @@ type Providers struct { KubernetesIngressNGINX *ingressnginx.Provider `description:"Enables Kubernetes Ingress NGINX provider." json:"kubernetesIngressNGINX,omitempty" toml:"kubernetesIngressNGINX,omitempty" yaml:"kubernetesIngressNGINX,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` KubernetesCRD *crd.Provider `description:"Enables Kubernetes CRD provider." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` KubernetesGateway *gateway.Provider `description:"Enables Kubernetes Gateway API provider." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Knative *knative.Provider `description:"Enables Knative provider." json:"knative,omitempty" toml:"knative,omitempty" yaml:"knative,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Rest *rest.Provider `description:"Enables Rest provider." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enables Consul Catalog provider." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Nomad *nomad.ProviderBuilder `description:"Enables Nomad provider." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` @@ -431,6 +433,12 @@ func (c *Configuration) ValidateConfiguration() error { } } + if c.Providers != nil && c.Providers.Knative != nil { + if c.Experimental == nil || !c.Experimental.Knative { + return errors.New("the experimental Knative feature must be enabled to use the Knative provider") + } + } + if c.AccessLog != nil && c.AccessLog.OTLP != nil { if c.Experimental == nil || !c.Experimental.OTLPLogs { return errors.New("the experimental OTLPLogs feature must be enabled to use OTLP access logging") diff --git a/pkg/provider/aggregator/aggregator.go b/pkg/provider/aggregator/aggregator.go index 3e45c2119..95bb0b220 100644 --- a/pkg/provider/aggregator/aggregator.go +++ b/pkg/provider/aggregator/aggregator.go @@ -101,6 +101,10 @@ func NewProviderAggregator(conf static.Providers) *ProviderAggregator { p.quietAddProvider(conf.KubernetesCRD) } + if conf.Knative != nil { + p.quietAddProvider(conf.Knative) + } + if conf.KubernetesGateway != nil { p.quietAddProvider(conf.KubernetesGateway) } diff --git a/pkg/provider/kubernetes/knative/client.go b/pkg/provider/kubernetes/knative/client.go new file mode 100644 index 000000000..bfb87c332 --- /dev/null +++ b/pkg/provider/kubernetes/knative/client.go @@ -0,0 +1,232 @@ +package knative + +import ( + "context" + "errors" + "fmt" + "os" + "time" + + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + kinformers "k8s.io/client-go/informers" + kclientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + knativenetworkingv1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1" + knativenetworkingclientset "knative.dev/networking/pkg/client/clientset/versioned" + knativenetworkinginformers "knative.dev/networking/pkg/client/informers/externalversions" +) + +const resyncPeriod = 10 * time.Minute + +type clientWrapper struct { + csKnativeNetworking knativenetworkingclientset.Interface + csKube kclientset.Interface + + factoriesKnativeNetworking map[string]knativenetworkinginformers.SharedInformerFactory + factoriesKube map[string]kinformers.SharedInformerFactory + + labelSelector string + + isNamespaceAll bool + watchedNamespaces []string +} + +func createClientFromConfig(c *rest.Config) (*clientWrapper, error) { + csKnativeNetworking, err := knativenetworkingclientset.NewForConfig(c) + if err != nil { + return nil, err + } + + csKube, err := kclientset.NewForConfig(c) + if err != nil { + return nil, err + } + + return newClientImpl(csKnativeNetworking, csKube), nil +} + +func newClientImpl(csKnativeNetworking knativenetworkingclientset.Interface, csKube kclientset.Interface) *clientWrapper { + return &clientWrapper{ + csKnativeNetworking: csKnativeNetworking, + csKube: csKube, + factoriesKnativeNetworking: make(map[string]knativenetworkinginformers.SharedInformerFactory), + factoriesKube: make(map[string]kinformers.SharedInformerFactory), + } +} + +// newInClusterClient returns a new Provider client that is expected to run +// inside the cluster. +func newInClusterClient(endpoint string) (*clientWrapper, error) { + config, err := rest.InClusterConfig() + if err != nil { + return nil, fmt.Errorf("creating in-cluster configuration: %w", err) + } + + if endpoint != "" { + config.Host = endpoint + } + + return createClientFromConfig(config) +} + +func newExternalClusterClientFromFile(file string) (*clientWrapper, error) { + configFromFlags, err := clientcmd.BuildConfigFromFlags("", file) + if err != nil { + return nil, err + } + return createClientFromConfig(configFromFlags) +} + +// newExternalClusterClient returns a new Provider client that may run outside +// of the cluster. +// The endpoint parameter must not be empty. +func newExternalClusterClient(endpoint, token, caFilePath string) (*clientWrapper, error) { + if endpoint == "" { + return nil, errors.New("endpoint missing for external cluster client") + } + + config := &rest.Config{ + Host: endpoint, + BearerToken: token, + } + + if caFilePath != "" { + caData, err := os.ReadFile(caFilePath) + if err != nil { + return nil, fmt.Errorf("reading CA file %s: %w", caFilePath, err) + } + + config.TLSClientConfig = rest.TLSClientConfig{CAData: caData} + } + + return createClientFromConfig(config) +} + +// WatchAll starts namespace-specific controllers for all relevant kinds. +func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) { + eventCh := make(chan interface{}, 1) + eventHandler := &k8s.ResourceEventHandler{Ev: eventCh} + + if len(namespaces) == 0 { + namespaces = []string{metav1.NamespaceAll} + c.isNamespaceAll = true + } + c.watchedNamespaces = namespaces + + for _, ns := range namespaces { + factory := knativenetworkinginformers.NewSharedInformerFactoryWithOptions(c.csKnativeNetworking, resyncPeriod, knativenetworkinginformers.WithNamespace(ns), knativenetworkinginformers.WithTweakListOptions(func(opts *metav1.ListOptions) { + opts.LabelSelector = c.labelSelector + })) + _, err := factory.Networking().V1alpha1().Ingresses().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + + factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns)) + _, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + _, err = factoryKube.Core().V1().Secrets().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + + c.factoriesKube[ns] = factoryKube + c.factoriesKnativeNetworking[ns] = factory + } + + for _, ns := range namespaces { + c.factoriesKnativeNetworking[ns].Start(stopCh) + c.factoriesKube[ns].Start(stopCh) + } + + for _, ns := range namespaces { + for t, ok := range c.factoriesKnativeNetworking[ns].WaitForCacheSync(stopCh) { + if !ok { + return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns) + } + } + for t, ok := range c.factoriesKube[ns].WaitForCacheSync(stopCh) { + if !ok { + return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns) + } + } + } + + return eventCh, nil +} + +func (c *clientWrapper) ListIngresses() []*knativenetworkingv1alpha1.Ingress { + var result []*knativenetworkingv1alpha1.Ingress + + for ns, factory := range c.factoriesKnativeNetworking { + ings, err := factory.Networking().V1alpha1().Ingresses().Lister().List(labels.Everything()) // todo: label selector + if err != nil { + log.Error().Msgf("Failed to list ingresses in namespace %s: %s", ns, err) + } + result = append(result, ings...) + } + + return result +} + +func (c *clientWrapper) UpdateIngressStatus(ingress *knativenetworkingv1alpha1.Ingress) error { + _, err := c.csKnativeNetworking.NetworkingV1alpha1().Ingresses(ingress.Namespace).UpdateStatus(context.TODO(), ingress, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("updating knative ingress status %s/%s: %w", ingress.Namespace, ingress.Name, err) + } + + log.Info().Msgf("Updated status on knative ingress %s/%s", ingress.Namespace, ingress.Name) + return nil +} + +// GetService returns the named service from the given namespace. +func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("getting service %s/%s: namespace is not within watched namespaces", namespace, name) + } + + return c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Services().Lister().Services(namespace).Get(name) +} + +// GetSecret returns the named secret from the given namespace. +func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("getting secret %s/%s: namespace is not within watched namespaces", namespace, name) + } + + return c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Secrets().Lister().Secrets(namespace).Get(name) +} + +// isWatchedNamespace checks to ensure that the namespace is being watched before we request +// it to ensure we don't panic by requesting an out-of-watch object. +func (c *clientWrapper) isWatchedNamespace(ns string) bool { + if c.isNamespaceAll { + return true + } + for _, watchedNamespace := range c.watchedNamespaces { + if watchedNamespace == ns { + return true + } + } + return false +} + +// lookupNamespace returns the lookup namespace key for the given namespace. +// When listening on all namespaces, it returns the client-go identifier ("") +// for all-namespaces. Otherwise, it returns the given namespace. +// The distinction is necessary because we index all informers on the special +// identifier iff all-namespaces are requested but receive specific namespace +// identifiers from the Kubernetes API, so we have to bridge this gap. +func (c *clientWrapper) lookupNamespace(ns string) string { + if c.isNamespaceAll { + return metav1.NamespaceAll + } + return ns +} diff --git a/pkg/provider/kubernetes/knative/fixtures/cluster_local.yaml b/pkg/provider/kubernetes/knative/fixtures/cluster_local.yaml new file mode 100644 index 000000000..0d9c0fcea --- /dev/null +++ b/pkg/provider/kubernetes/knative/fixtures/cluster_local.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: networking.internal.knative.dev/v1alpha1 +kind: Ingress +metadata: + annotations: + networking.knative.dev/ingress.class: traefik.ingress.networking.knative.dev + name: helloworld-go + namespace: default +spec: + httpOption: Enabled + rules: + - hosts: + - helloworld-go.default + - helloworld-go.default.svc + - helloworld-go.default.svc.cluster.local + http: + paths: + - splits: + - appendHeaders: + Knative-Serving-Namespace: default + Knative-Serving-Revision: helloworld-go-00001 + percent: 50 + serviceName: helloworld-go-00001 + serviceNamespace: default + servicePort: 80 + - appendHeaders: + Knative-Serving-Namespace: default + Knative-Serving-Revision: helloworld-go-00002 + percent: 50 + serviceName: helloworld-go-00002 + serviceNamespace: default + servicePort: 80 + visibility: ClusterLocal diff --git a/pkg/provider/kubernetes/knative/fixtures/external_ip.yaml b/pkg/provider/kubernetes/knative/fixtures/external_ip.yaml new file mode 100644 index 000000000..0ddd4fe06 --- /dev/null +++ b/pkg/provider/kubernetes/knative/fixtures/external_ip.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: networking.internal.knative.dev/v1alpha1 +kind: Ingress +metadata: + annotations: + networking.knative.dev/ingress.class: traefik.ingress.networking.knative.dev + name: helloworld-go + namespace: default +spec: + httpOption: Enabled + rules: + - hosts: + - helloworld-go.default + - helloworld-go.default.svc + - helloworld-go.default.svc.cluster.local + http: + paths: + - splits: + - appendHeaders: + Knative-Serving-Namespace: default + Knative-Serving-Revision: helloworld-go-00001 + percent: 50 + serviceName: helloworld-go-00001 + serviceNamespace: default + servicePort: 80 + - appendHeaders: + Knative-Serving-Namespace: default + Knative-Serving-Revision: helloworld-go-00002 + percent: 50 + serviceName: helloworld-go-00002 + serviceNamespace: default + servicePort: 80 + visibility: ExternalIP diff --git a/pkg/provider/kubernetes/knative/fixtures/services.yaml b/pkg/provider/kubernetes/knative/fixtures/services.yaml new file mode 100644 index 000000000..d2d69ce83 --- /dev/null +++ b/pkg/provider/kubernetes/knative/fixtures/services.yaml @@ -0,0 +1,39 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: helloworld-go-00001 + namespace: default +spec: + clusterIP: 10.43.38.208 + clusterIPs: + - 10.43.38.208 + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 8012 + - name: https + port: 443 + protocol: TCP + targetPort: 8112 + +--- +apiVersion: v1 +kind: Service +metadata: + name: helloworld-go-00002 + namespace: default +spec: + clusterIP: 10.43.44.18 + clusterIPs: + - 10.43.44.18 + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 8012 + - name: https + port: 443 + protocol: TCP + targetPort: 8112 diff --git a/pkg/provider/kubernetes/knative/fixtures/tls.yaml b/pkg/provider/kubernetes/knative/fixtures/tls.yaml new file mode 100644 index 000000000..3d85ad696 --- /dev/null +++ b/pkg/provider/kubernetes/knative/fixtures/tls.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: networking.internal.knative.dev/v1alpha1 +kind: Ingress +metadata: + annotations: + networking.knative.dev/ingress.class: traefik.ingress.networking.knative.dev + name: helloworld-go + namespace: default +spec: + httpOption: Enabled + tls: + - hosts: + - helloworld-go.default.svc.cluster.local + secretName: secretName + secretNamespace: secretNamespace + rules: + - hosts: + - helloworld-go.default + - helloworld-go.default.svc + - helloworld-go.default.svc.cluster.local + http: + paths: + - splits: + - appendHeaders: + Knative-Serving-Namespace: default + Knative-Serving-Revision: helloworld-go-00001 + percent: 50 + serviceName: helloworld-go-00001 + serviceNamespace: default + servicePort: 80 + - appendHeaders: + Knative-Serving-Namespace: default + Knative-Serving-Revision: helloworld-go-00002 + percent: 50 + serviceName: helloworld-go-00002 + serviceNamespace: default + servicePort: 80 + visibility: ExternalIP diff --git a/pkg/provider/kubernetes/knative/fixtures/wrong_ingress_class.yaml b/pkg/provider/kubernetes/knative/fixtures/wrong_ingress_class.yaml new file mode 100644 index 000000000..aaaff85a0 --- /dev/null +++ b/pkg/provider/kubernetes/knative/fixtures/wrong_ingress_class.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: networking.internal.knative.dev/v1alpha1 +kind: Ingress +metadata: + annotations: + networking.knative.dev/ingress.class: foo.ingress.networking.knative.dev + name: helloworld-go + namespace: default diff --git a/pkg/provider/kubernetes/knative/kubernetes.go b/pkg/provider/kubernetes/knative/kubernetes.go new file mode 100644 index 000000000..ae9c636d3 --- /dev/null +++ b/pkg/provider/kubernetes/knative/kubernetes.go @@ -0,0 +1,531 @@ +package knative + +import ( + "context" + "errors" + "fmt" + "maps" + "net" + "os" + "slices" + "strconv" + "strings" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/mitchellh/hashstructure" + "github.com/rs/zerolog/log" + ptypes "github.com/traefik/paerser/types" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/job" + "github.com/traefik/traefik/v3/pkg/observability/logs" + "github.com/traefik/traefik/v3/pkg/safe" + "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" + knativenetworking "knative.dev/networking/pkg/apis/networking" + knativenetworkingv1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1" + "knative.dev/pkg/network" +) + +const ( + providerName = "knative" + traefikIngressClassName = "traefik.ingress.networking.knative.dev" +) + +// ServiceRef holds a Kubernetes service reference. +type ServiceRef struct { + Name string `description:"Name of the Kubernetes service." json:"desc,omitempty" toml:"desc,omitempty" yaml:"desc,omitempty"` + Namespace string `description:"Namespace of the Kubernetes service." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"` +} + +// Provider holds configurations of the provider. +type Provider struct { + Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"` + CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` + Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"` + LabelSelector string `description:"Kubernetes label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` + PublicEntrypoints []string `description:"Entrypoint names used to expose the Ingress publicly. If empty an Ingress is exposed on all entrypoints." json:"publicEntrypoints,omitempty" toml:"publicEntrypoints,omitempty" yaml:"publicEntrypoints,omitempty" export:"true"` + PublicService ServiceRef `description:"Kubernetes service used to expose the networking controller publicly." json:"publicService,omitempty" toml:"publicService,omitempty" yaml:"publicService,omitempty" export:"true"` + PrivateEntrypoints []string `description:"Entrypoint names used to expose the Ingress privately. If empty local Ingresses are skipped." json:"privateEntrypoints,omitempty" toml:"privateEntrypoints,omitempty" yaml:"privateEntrypoints,omitempty" export:"true"` + PrivateService ServiceRef `description:"Kubernetes service used to expose the networking controller privately." json:"privateService,omitempty" toml:"privateService,omitempty" yaml:"privateService,omitempty" export:"true"` + ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty"` + + client *clientWrapper + lastConfiguration safe.Safe +} + +// Init the provider. +func (p *Provider) Init() error { + logger := log.With().Str(logs.ProviderName, providerName).Logger() + + // Initializes Kubernetes client. + var err error + p.client, err = p.newK8sClient(logger.WithContext(context.Background())) + if err != nil { + return fmt.Errorf("creating kubernetes client: %w", err) + } + + return nil +} + +// Provide allows the knative provider to provide configurations to traefik using the given configuration channel. +func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { + logger := log.With().Str(logs.ProviderName, providerName).Logger() + ctxLog := logger.WithContext(context.Background()) + + pool.GoCtx(func(ctxPool context.Context) { + operation := func() error { + eventsChan, err := p.client.WatchAll(p.Namespaces, ctxPool.Done()) + if err != nil { + logger.Error().Msgf("Error watching kubernetes events: %v", err) + timer := time.NewTimer(1 * time.Second) + select { + case <-timer.C: + return err + case <-ctxPool.Done(): + return nil + } + } + + throttleDuration := time.Duration(p.ThrottleDuration) + throttledChan := throttleEvents(ctxLog, throttleDuration, pool, eventsChan) + if throttledChan != nil { + eventsChan = throttledChan + } + + for { + select { + case <-ctxPool.Done(): + return nil + case event := <-eventsChan: + // Note that event is the *first* event that came in during this throttling interval -- if we're hitting our throttle, we may have dropped events. + // This is fine, because we don't treat different event types differently. + // But if we do in the future, we'll need to track more information about the dropped events. + conf, ingressStatuses := p.loadConfiguration(ctxLog) + + confHash, err := hashstructure.Hash(conf, nil) + switch { + case err != nil: + logger.Error().Msg("Unable to hash the configuration") + case p.lastConfiguration.Get() == confHash: + logger.Debug().Msgf("Skipping Kubernetes event kind %T", event) + default: + p.lastConfiguration.Set(confHash) + configurationChan <- dynamic.Message{ + ProviderName: providerName, + Configuration: conf, + } + } + + // If we're throttling, + // we sleep here for the throttle duration to enforce that we don't refresh faster than our throttle. + // time.Sleep returns immediately if p.ThrottleDuration is 0 (no throttle). + time.Sleep(throttleDuration) + + // Updating the ingress status after the throttleDuration allows to wait to make sure that the dynamic conf is updated before updating the status. + // This is needed for the conformance tests to pass, for example. + for _, ingress := range ingressStatuses { + if err := p.updateKnativeIngressStatus(ctxLog, ingress); err != nil { + logger.Error().Err(err).Msgf("Error updating status for Ingress %s/%s", ingress.Namespace, ingress.Name) + } + } + } + } + } + + notify := func(err error, time time.Duration) { + logger.Error().Msgf("Provider connection error: %v; retrying in %s", err, time) + } + err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxPool), notify) + if err != nil { + logger.Error().Msgf("Cannot connect to Provider: %v", err) + } + }) + + return nil +} + +func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { + logger := log.Ctx(ctx).With().Logger() + + _, err := labels.Parse(p.LabelSelector) + if err != nil { + return nil, fmt.Errorf("parsing label selector: %q", p.LabelSelector) + } + logger.Info().Msgf("Label selector is: %q", p.LabelSelector) + + withEndpoint := "" + if p.Endpoint != "" { + withEndpoint = fmt.Sprintf(" with endpoint %s", p.Endpoint) + } + + var client *clientWrapper + switch { + case os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "": + logger.Info().Msgf("Creating in-cluster Provider client%s", withEndpoint) + client, err = newInClusterClient(p.Endpoint) + case os.Getenv("KUBECONFIG") != "": + logger.Info().Msgf("Creating cluster-external Provider client from KUBECONFIG %s", os.Getenv("KUBECONFIG")) + client, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG")) + default: + logger.Info().Msgf("Creating cluster-external Provider client%s", withEndpoint) + client, err = newExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath) + } + if err != nil { + return nil, err + } + + client.labelSelector = p.LabelSelector + return client, nil +} + +func (p *Provider) loadConfiguration(ctx context.Context) (*dynamic.Configuration, []*knativenetworkingv1alpha1.Ingress) { + conf := &dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Routers: make(map[string]*dynamic.Router), + Middlewares: make(map[string]*dynamic.Middleware), + Services: make(map[string]*dynamic.Service), + }, + } + + var ingressStatuses []*knativenetworkingv1alpha1.Ingress + + uniqCerts := make(map[string]*tls.CertAndStores) + for _, ingress := range p.client.ListIngresses() { + logger := log.Ctx(ctx).With(). + Str("ingress", ingress.Name). + Str("namespace", ingress.Namespace). + Logger() + + if ingress.Annotations[knativenetworking.IngressClassAnnotationKey] != traefikIngressClassName { + logger.Debug().Msgf("Skipping Ingress %s/%s", ingress.Namespace, ingress.Name) + continue + } + + if err := p.loadCertificates(ctx, ingress, uniqCerts); err != nil { + logger.Error().Err(err).Msg("Error loading TLS certificates") + continue + } + + conf.HTTP = mergeHTTPConfigs(conf.HTTP, p.buildRouters(ctx, ingress)) + + // TODO: should we handle configuration errors? + ingressStatuses = append(ingressStatuses, ingress) + } + + if len(uniqCerts) > 0 { + conf.TLS = &dynamic.TLSConfiguration{ + Certificates: slices.Collect(maps.Values(uniqCerts)), + } + } + + return conf, ingressStatuses +} + +// loadCertificates loads the TLS certificates for the given Knative Ingress. +// This method mutates the uniqCerts map to add the loaded certificates. +func (p *Provider) loadCertificates(ctx context.Context, ingress *knativenetworkingv1alpha1.Ingress, uniqCerts map[string]*tls.CertAndStores) error { + for _, t := range ingress.Spec.TLS { + // TODO: maybe this could be allowed with an allowCrossNamespace option in the future. + if t.SecretNamespace != ingress.Namespace { + log.Ctx(ctx).Debug().Msg("TLS secret namespace has to be the same as the Ingress one") + continue + } + + key := ingress.Namespace + "-" + t.SecretName + + // TODO: as specified in the GoDoc we should validate that the certificates contain the configured Hosts. + if _, exists := uniqCerts[key]; !exists { + cert, err := p.loadCertificate(ingress.Namespace, t.SecretName) + if err != nil { + return fmt.Errorf("getting certificate: %w", err) + } + uniqCerts[key] = &tls.CertAndStores{Certificate: cert} + } + } + + return nil +} + +func (p *Provider) loadCertificate(namespace, secretName string) (tls.Certificate, error) { + secret, err := p.client.GetSecret(namespace, secretName) + if err != nil { + return tls.Certificate{}, fmt.Errorf("getting secret %s/%s: %w", namespace, secretName, err) + } + + certBytes, hasCert := secret.Data[corev1.TLSCertKey] + keyBytes, hasKey := secret.Data[corev1.TLSPrivateKeyKey] + + if (!hasCert || len(certBytes) == 0) || (!hasKey || len(keyBytes) == 0) { + return tls.Certificate{}, errors.New("secret does not contain a keypair") + } + + return tls.Certificate{ + CertFile: types.FileOrContent(certBytes), + KeyFile: types.FileOrContent(keyBytes), + }, nil +} + +func (p *Provider) buildRouters(ctx context.Context, ingress *knativenetworkingv1alpha1.Ingress) *dynamic.HTTPConfiguration { + logger := log.Ctx(ctx).With().Logger() + + conf := &dynamic.HTTPConfiguration{ + Routers: make(map[string]*dynamic.Router), + Middlewares: make(map[string]*dynamic.Middleware), + Services: make(map[string]*dynamic.Service), + } + + for ri, rule := range ingress.Spec.Rules { + if rule.HTTP == nil { + logger.Debug().Msgf("No HTTP rule defined for rule %d in Ingress %s", ri, ingress.Name) + continue + } + + entrypoints := p.PublicEntrypoints + if rule.Visibility == knativenetworkingv1alpha1.IngressVisibilityClusterLocal { + if p.PrivateEntrypoints == nil { + // Skip route creation as no internal entrypoints are defined for cluster local visibility. + continue + } + entrypoints = p.PrivateEntrypoints + } + + // TODO: support rewrite host + for pi, path := range rule.HTTP.Paths { + routerKey := fmt.Sprintf("%s-%s-rule-%d-path-%d", ingress.Namespace, ingress.Name, ri, pi) + router := &dynamic.Router{ + EntryPoints: entrypoints, + Rule: buildRule(rule.Hosts, path.Headers, path.Path), + Middlewares: make([]string, 0), + Service: routerKey + "-wrr", + } + + if len(path.AppendHeaders) > 0 { + midKey := fmt.Sprintf("%s-append-headers", routerKey) + + router.Middlewares = append(router.Middlewares, midKey) + conf.Middlewares[midKey] = &dynamic.Middleware{ + Headers: &dynamic.Headers{ + CustomRequestHeaders: path.AppendHeaders, + }, + } + } + + wrr, services, err := p.buildWeightedRoundRobin(routerKey, path.Splits) + if err != nil { + logger.Error().Err(err).Msg("Error building weighted round robin") + continue + } + + // TODO: support Ingress#HTTPOption to check if HTTP router should redirect to the HTTPS one. + conf.Routers[routerKey] = router + + // TODO: at some point we should allow to define a default TLS secret at the provider level to enable TLS with a custom cert when external-domain-tls is disabled. + // see https://knative.dev/docs/serving/encryption/external-domain-tls/#manually-obtain-and-renew-certificates + if len(ingress.Spec.TLS) > 0 { + conf.Routers[routerKey+"-tls"] = &dynamic.Router{ + EntryPoints: router.EntryPoints, + Rule: router.Rule, // TODO: maybe the rule should be a new one containing the TLS hosts injected by Knative. + Middlewares: router.Middlewares, + Service: router.Service, + TLS: &dynamic.RouterTLSConfig{}, + } + } + + conf.Services[routerKey+"-wrr"] = &dynamic.Service{Weighted: wrr} + for k, v := range services { + conf.Services[k] = v + } + } + } + + return conf +} + +func (p *Provider) buildWeightedRoundRobin(routerKey string, splits []knativenetworkingv1alpha1.IngressBackendSplit) (*dynamic.WeightedRoundRobin, map[string]*dynamic.Service, error) { + wrr := &dynamic.WeightedRoundRobin{ + Services: make([]dynamic.WRRService, 0), + } + + services := make(map[string]*dynamic.Service) + for si, split := range splits { + serviceKey := fmt.Sprintf("%s-split-%d", routerKey, si) + + var err error + services[serviceKey], err = p.buildService(split.ServiceNamespace, split.ServiceName, split.ServicePort) + if err != nil { + return nil, nil, fmt.Errorf("building service: %w", err) + } + + // As described in the spec if there is only one split it defaults to 100. + percent := split.Percent + if len(splits) == 1 { + percent = 100 + } + + wrr.Services = append(wrr.Services, dynamic.WRRService{ + Name: serviceKey, + Weight: ptr.To(percent), + Headers: split.AppendHeaders, + }) + } + + return wrr, services, nil +} + +func (p *Provider) buildService(namespace, serviceName string, port intstr.IntOrString) (*dynamic.Service, error) { + servers, err := p.buildServers(namespace, serviceName, port) + if err != nil { + return nil, fmt.Errorf("building servers: %w", err) + } + + var lb dynamic.ServersLoadBalancer + lb.SetDefaults() + lb.Servers = servers + + return &dynamic.Service{LoadBalancer: &lb}, nil +} + +func (p *Provider) buildServers(namespace, serviceName string, port intstr.IntOrString) ([]dynamic.Server, error) { + service, err := p.client.GetService(namespace, serviceName) + if err != nil { + return nil, fmt.Errorf("getting service %s/%s: %w", namespace, serviceName, err) + } + + var svcPort *corev1.ServicePort + for _, p := range service.Spec.Ports { + if p.Name == port.String() || strconv.Itoa(int(p.Port)) == port.String() { + svcPort = &p + break + } + } + if svcPort == nil { + return nil, errors.New("service port not found") + } + + if service.Spec.ClusterIP == "" { + return nil, errors.New("service does not have a ClusterIP") + } + + scheme := "http" + if svcPort.AppProtocol != nil && *svcPort.AppProtocol == knativenetworking.AppProtocolH2C { + scheme = "h2c" + } + + hostPort := net.JoinHostPort(service.Spec.ClusterIP, strconv.Itoa(int(svcPort.Port))) + return []dynamic.Server{{URL: fmt.Sprintf("%s://%s", scheme, hostPort)}}, nil +} + +func (p *Provider) updateKnativeIngressStatus(ctx context.Context, ingress *knativenetworkingv1alpha1.Ingress) error { + log.Ctx(ctx).Debug().Msgf("Updating status for Ingress %s/%s", ingress.Namespace, ingress.Name) + + var publicLbs []knativenetworkingv1alpha1.LoadBalancerIngressStatus + if p.PublicService.Name != "" && p.PublicService.Namespace != "" { + publicLbs = append(publicLbs, knativenetworkingv1alpha1.LoadBalancerIngressStatus{ + DomainInternal: network.GetServiceHostname(p.PublicService.Name, p.PublicService.Namespace), + }) + } + + var privateLbs []knativenetworkingv1alpha1.LoadBalancerIngressStatus + if p.PrivateService.Name != "" && p.PrivateService.Namespace != "" { + privateLbs = append(privateLbs, knativenetworkingv1alpha1.LoadBalancerIngressStatus{ + DomainInternal: network.GetServiceHostname(p.PrivateService.Name, p.PrivateService.Namespace), + }) + } + + if ingress.GetStatus() == nil || !ingress.GetStatus().GetCondition(knativenetworkingv1alpha1.IngressConditionNetworkConfigured).IsTrue() || ingress.GetGeneration() != ingress.GetStatus().ObservedGeneration { + ingress.Status.MarkNetworkConfigured() + ingress.Status.MarkLoadBalancerReady(publicLbs, privateLbs) + ingress.Status.ObservedGeneration = ingress.GetGeneration() + + return p.client.UpdateIngressStatus(ingress) + } + return nil +} + +func buildRule(hosts []string, headers map[string]knativenetworkingv1alpha1.HeaderMatch, path string) string { + var operands []string + + if len(hosts) > 0 { + var hostRules []string + for _, host := range hosts { + hostRules = append(hostRules, fmt.Sprintf("Host(`%v`)", host)) + } + operands = append(operands, fmt.Sprintf("(%s)", strings.Join(hostRules, " || "))) + } + + if len(headers) > 0 { + headerKeys := slices.Collect(maps.Keys(headers)) + slices.Sort(headerKeys) + + var headerRules []string + for _, key := range headerKeys { + headerRules = append(headerRules, fmt.Sprintf("Header(`%s`,`%s`)", key, headers[key].Exact)) + } + operands = append(operands, fmt.Sprintf("(%s)", strings.Join(headerRules, " && "))) + } + + if len(path) > 0 { + operands = append(operands, fmt.Sprintf("PathPrefix(`%s`)", path)) + } + + return strings.Join(operands, " && ") +} + +func mergeHTTPConfigs(confs ...*dynamic.HTTPConfiguration) *dynamic.HTTPConfiguration { + conf := &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + } + + for _, c := range confs { + for k, v := range c.Routers { + conf.Routers[k] = v + } + for k, v := range c.Middlewares { + conf.Middlewares[k] = v + } + for k, v := range c.Services { + conf.Services[k] = v + } + } + + return conf +} + +func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *safe.Pool, eventsChan <-chan interface{}) chan interface{} { + logger := log.Ctx(ctx).With().Logger() + if throttleDuration == 0 { + return nil + } + // Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling) + eventsChanBuffered := make(chan interface{}, 1) + + // Run a goroutine that reads events from eventChan and does a non-blocking write to pendingEvent. + // This guarantees that writing to eventChan will never block, + // and that pendingEvent will have something in it if there's been an event since we read from that channel. + pool.GoCtx(func(ctxPool context.Context) { + for { + select { + case <-ctxPool.Done(): + return + case nextEvent := <-eventsChan: + select { + case eventsChanBuffered <- nextEvent: + default: + // We already have an event in eventsChanBuffered, so we'll do a refresh as soon as our throttle allows us to. + // It's fine to drop the event and keep whatever's in the buffer -- we don't do different things for different events + logger.Debug().Msgf("Dropping event kind %T due to throttling", nextEvent) + } + } + } + }) + + return eventsChanBuffered +} diff --git a/pkg/provider/kubernetes/knative/kubernetes_test.go b/pkg/provider/kubernetes/knative/kubernetes_test.go new file mode 100644 index 000000000..08ea0e2c3 --- /dev/null +++ b/pkg/provider/kubernetes/knative/kubernetes_test.go @@ -0,0 +1,478 @@ +package knative + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/paerser/types" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" + "k8s.io/apimachinery/pkg/runtime" + kubefake "k8s.io/client-go/kubernetes/fake" + kscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/utils/ptr" + knativenetworkingv1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1" + knfake "knative.dev/networking/pkg/client/clientset/versioned/fake" +) + +func init() { + // required by k8s.MustParseYaml + if err := knativenetworkingv1alpha1.AddToScheme(kscheme.Scheme); err != nil { + panic(err) + } +} + +func Test_loadConfiguration(t *testing.T) { + testCases := []struct { + desc string + paths []string + want *dynamic.Configuration + wantLen int + }{ + { + desc: "Wrong ingress class", + paths: []string{"wrong_ingress_class.yaml"}, + wantLen: 0, + want: &dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Services: map[string]*dynamic.Service{}, + Middlewares: map[string]*dynamic.Middleware{}, + }, + }, + }, + { + desc: "Cluster Local", + paths: []string{"cluster_local.yaml", "services.yaml"}, + wantLen: 1, + want: &dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-helloworld-go-rule-0-path-0": { + EntryPoints: []string{"priv-http", "priv-https"}, + Service: "default-helloworld-go-rule-0-path-0-wrr", + Rule: "(Host(`helloworld-go.default`) || Host(`helloworld-go.default.svc`) || Host(`helloworld-go.default.svc.cluster.local`))", + Middlewares: []string{}, + }, + }, + Services: map[string]*dynamic.Service{ + "default-helloworld-go-rule-0-path-0-split-0": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: types.Duration(100 * time.Millisecond), + }, + Servers: []dynamic.Server{ + { + URL: "http://10.43.38.208:80", + }, + }, + }, + }, + "default-helloworld-go-rule-0-path-0-split-1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: types.Duration(100 * time.Millisecond), + }, + Servers: []dynamic.Server{ + { + URL: "http://10.43.44.18:80", + }, + }, + }, + }, + "default-helloworld-go-rule-0-path-0-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-helloworld-go-rule-0-path-0-split-0", + Weight: ptr.To(50), + Headers: map[string]string{ + "Knative-Serving-Namespace": "default", + "Knative-Serving-Revision": "helloworld-go-00001", + }, + }, + { + Name: "default-helloworld-go-rule-0-path-0-split-1", + Weight: ptr.To(50), + Headers: map[string]string{ + "Knative-Serving-Namespace": "default", + "Knative-Serving-Revision": "helloworld-go-00002", + }, + }, + }, + }, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + }, + }, + }, + { + desc: "External IP", + paths: []string{"external_ip.yaml", "services.yaml"}, + wantLen: 1, + want: &dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-helloworld-go-rule-0-path-0": { + EntryPoints: []string{"http", "https"}, + Service: "default-helloworld-go-rule-0-path-0-wrr", + Rule: "(Host(`helloworld-go.default`) || Host(`helloworld-go.default.svc`) || Host(`helloworld-go.default.svc.cluster.local`))", + Middlewares: []string{}, + }, + }, + Services: map[string]*dynamic.Service{ + "default-helloworld-go-rule-0-path-0-split-0": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: types.Duration(100 * time.Millisecond), + }, + Servers: []dynamic.Server{ + { + URL: "http://10.43.38.208:80", + }, + }, + }, + }, + "default-helloworld-go-rule-0-path-0-split-1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: types.Duration(100 * time.Millisecond), + }, + Servers: []dynamic.Server{ + { + URL: "http://10.43.44.18:80", + }, + }, + }, + }, + "default-helloworld-go-rule-0-path-0-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-helloworld-go-rule-0-path-0-split-0", + Weight: ptr.To(50), + Headers: map[string]string{ + "Knative-Serving-Namespace": "default", + "Knative-Serving-Revision": "helloworld-go-00001", + }, + }, + { + Name: "default-helloworld-go-rule-0-path-0-split-1", + Weight: ptr.To(50), + Headers: map[string]string{ + "Knative-Serving-Namespace": "default", + "Knative-Serving-Revision": "helloworld-go-00002", + }, + }, + }, + }, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + }, + }, + }, + { + desc: "TLS", + paths: []string{"tls.yaml", "services.yaml"}, + wantLen: 1, + want: &dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-helloworld-go-rule-0-path-0": { + EntryPoints: []string{"http", "https"}, + Service: "default-helloworld-go-rule-0-path-0-wrr", + Rule: "(Host(`helloworld-go.default`) || Host(`helloworld-go.default.svc`) || Host(`helloworld-go.default.svc.cluster.local`))", + Middlewares: []string{}, + }, + "default-helloworld-go-rule-0-path-0-tls": { + EntryPoints: []string{"http", "https"}, + Service: "default-helloworld-go-rule-0-path-0-wrr", + Rule: "(Host(`helloworld-go.default`) || Host(`helloworld-go.default.svc`) || Host(`helloworld-go.default.svc.cluster.local`))", + Middlewares: []string{}, + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + Services: map[string]*dynamic.Service{ + "default-helloworld-go-rule-0-path-0-split-0": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: types.Duration(100 * time.Millisecond), + }, + Servers: []dynamic.Server{ + { + URL: "http://10.43.38.208:80", + }, + }, + }, + }, + "default-helloworld-go-rule-0-path-0-split-1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: types.Duration(100 * time.Millisecond), + }, + Servers: []dynamic.Server{ + { + URL: "http://10.43.44.18:80", + }, + }, + }, + }, + "default-helloworld-go-rule-0-path-0-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-helloworld-go-rule-0-path-0-split-0", + Weight: ptr.To(50), + Headers: map[string]string{ + "Knative-Serving-Namespace": "default", + "Knative-Serving-Revision": "helloworld-go-00001", + }, + }, + { + Name: "default-helloworld-go-rule-0-path-0-split-1", + Weight: ptr.To(50), + Headers: map[string]string{ + "Knative-Serving-Namespace": "default", + "Knative-Serving-Revision": "helloworld-go-00002", + }, + }, + }, + }, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.desc, func(t *testing.T) { + t.Parallel() + + k8sObjects, knObjects := readResources(t, testCase.paths) + + k8sClient := kubefake.NewClientset(k8sObjects...) + knClient := knfake.NewSimpleClientset(knObjects...) + + client := newClientImpl(knClient, k8sClient) + + eventCh, err := client.WatchAll(nil, make(chan struct{})) + require.NoError(t, err) + + if len(k8sObjects) > 0 || len(knObjects) > 0 { + // just wait for the first event + <-eventCh + } + + p := Provider{ + PublicEntrypoints: []string{"http", "https"}, + PrivateEntrypoints: []string{"priv-http", "priv-https"}, + client: client, + } + + got, gotIngresses := p.loadConfiguration(t.Context()) + assert.Len(t, gotIngresses, testCase.wantLen) + assert.Equal(t, testCase.want, got) + }) + } +} + +func Test_buildRule(t *testing.T) { + testCases := []struct { + desc string + hosts []string + headers map[string]knativenetworkingv1alpha1.HeaderMatch + path string + want string + }{ + { + desc: "single host, no headers, no path", + hosts: []string{"example.com"}, + want: "(Host(`example.com`))", + }, + { + desc: "multiple hosts, no headers, no path", + hosts: []string{"example.com", "foo.com"}, + want: "(Host(`example.com`) || Host(`foo.com`))", + }, + { + desc: "single host, single header, no path", + hosts: []string{"example.com"}, + headers: map[string]knativenetworkingv1alpha1.HeaderMatch{ + "X-Header": {Exact: "value"}, + }, + want: "(Host(`example.com`)) && (Header(`X-Header`,`value`))", + }, + { + desc: "single host, multiple headers, no path", + hosts: []string{"example.com"}, + headers: map[string]knativenetworkingv1alpha1.HeaderMatch{ + "X-Header": {Exact: "value"}, + "X-Header2": {Exact: "value2"}, + }, + want: "(Host(`example.com`)) && (Header(`X-Header`,`value`) && Header(`X-Header2`,`value2`))", + }, + { + desc: "single host, multiple headers, with path", + hosts: []string{"example.com"}, + headers: map[string]knativenetworkingv1alpha1.HeaderMatch{ + "X-Header": {Exact: "value"}, + "X-Header2": {Exact: "value2"}, + }, + path: "/foo", + want: "(Host(`example.com`)) && (Header(`X-Header`,`value`) && Header(`X-Header2`,`value2`)) && PathPrefix(`/foo`)", + }, + { + desc: "single host, no headers, with path", + hosts: []string{"example.com"}, + path: "/foo", + want: "(Host(`example.com`)) && PathPrefix(`/foo`)", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + got := buildRule(test.hosts, test.headers, test.path) + assert.Equal(t, test.want, got) + }) + } +} + +func Test_mergeHTTPConfigs(t *testing.T) { + testCases := []struct { + desc string + configs []*dynamic.HTTPConfiguration + want *dynamic.HTTPConfiguration + }{ + { + desc: "one empty configuration", + configs: []*dynamic.HTTPConfiguration{ + { + Routers: map[string]*dynamic.Router{ + "router1": {Rule: "Host(`example.com`)"}, + }, + Middlewares: map[string]*dynamic.Middleware{ + "middleware1": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"X-Test": "value"}}}, + }, + Services: map[string]*dynamic.Service{ + "service1": {LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "http://example.com"}}}}, + }, + }, + { + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + }, + want: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "router1": {Rule: "Host(`example.com`)"}, + }, + Middlewares: map[string]*dynamic.Middleware{ + "middleware1": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"X-Test": "value"}}}, + }, + Services: map[string]*dynamic.Service{ + "service1": {LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "http://example.com"}}}}, + }, + }, + }, + { + desc: "merging two non-empty configurations", + configs: []*dynamic.HTTPConfiguration{ + { + Routers: map[string]*dynamic.Router{ + "router1": {Rule: "Host(`example.com`)"}, + }, + Middlewares: map[string]*dynamic.Middleware{ + "middleware1": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"X-Test": "value"}}}, + }, + Services: map[string]*dynamic.Service{ + "service1": {LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "http://example.com"}}}}, + }, + }, + { + Routers: map[string]*dynamic.Router{ + "router2": {Rule: "PathPrefix(`/test`)"}, + }, + Middlewares: map[string]*dynamic.Middleware{ + "middleware2": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"X-Test": "value"}}}, + }, + Services: map[string]*dynamic.Service{ + "service2": {LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "http://example.com"}}}}, + }, + }, + }, + want: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "router1": {Rule: "Host(`example.com`)"}, + "router2": {Rule: "PathPrefix(`/test`)"}, + }, + Middlewares: map[string]*dynamic.Middleware{ + "middleware1": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"X-Test": "value"}}}, + "middleware2": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"X-Test": "value"}}}, + }, + Services: map[string]*dynamic.Service{ + "service1": {LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "http://example.com"}}}}, + "service2": {LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "http://example.com"}}}}, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + got := mergeHTTPConfigs(test.configs...) + assert.Equal(t, test.want, got) + }) + } +} + +func readResources(t *testing.T, paths []string) ([]runtime.Object, []runtime.Object) { + t.Helper() + + var ( + k8sObjects []runtime.Object + knObjects []runtime.Object + ) + for _, path := range paths { + yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path)) + if err != nil { + panic(err) + } + + objects := k8s.MustParseYaml(yamlContent) + for _, obj := range objects { + switch obj.GetObjectKind().GroupVersionKind().Group { + case "networking.internal.knative.dev": + knObjects = append(knObjects, obj) + default: + k8sObjects = append(k8sObjects, obj) + } + } + } + + return k8sObjects, knObjects +} diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index d8782c843..427cf9bc9 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -286,13 +286,13 @@ func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, } func (m *Manager) getServiceHandler(ctx context.Context, service dynamic.WRRService) (http.Handler, error) { - switch { - case service.Status != nil: + if service.Status != nil { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(*service.Status) }), nil + } - case service.GRPCStatus != nil: + if service.GRPCStatus != nil { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { st := status.New(service.GRPCStatus.Code, service.GRPCStatus.Msg) @@ -307,10 +307,24 @@ func (m *Manager) getServiceHandler(ctx context.Context, service dynamic.WRRServ _, _ = rw.Write(body) }), nil - - default: - return m.BuildHTTP(ctx, service.Name) } + + svcHandler, err := m.BuildHTTP(ctx, service.Name) + if err != nil { + return nil, fmt.Errorf("building HTTP service: %w", err) + } + + if service.Headers != nil { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + for k, v := range service.Headers { + req.Header.Set(k, v) + } + + svcHandler.ServeHTTP(rw, req) + }), nil + } + + return svcHandler, nil } func (m *Manager) getHRWServiceHandler(ctx context.Context, serviceName string, config *dynamic.HighestRandomWeight) (http.Handler, error) { diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go index 4952dde12..05d674e16 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -160,7 +160,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) { serviceName: "test", service: &dynamic.ServersLoadBalancer{ Strategy: dynamic.BalancerStrategyWRR, - PassHostHeader: boolPtr(true), + PassHostHeader: pointer(true), Servers: []dynamic.Server{ { URL: server1.URL, @@ -479,16 +479,6 @@ func Test1xxResponses(t *testing.T) { } } -type serviceBuilderFunc func(ctx context.Context, serviceName string) (http.Handler, error) - -func (s serviceBuilderFunc) BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error) { - return s(ctx, serviceName) -} - -type internalHandler struct{} - -func (internalHandler) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {} - func TestManager_ServiceBuilders(t *testing.T) { var internalHandler internalHandler @@ -605,7 +595,129 @@ func TestMultipleTypeOnBuildHTTP(t *testing.T) { assert.Error(t, err, "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead") } -func boolPtr(v bool) *bool { return &v } +func TestGetServiceHandler_Headers(t *testing.T) { + pb := httputil.NewProxyBuilder(&transportManagerMock{}, nil) + + testCases := []struct { + desc string + service dynamic.WRRService + userAgent string + expectedHeaders map[string]string + }{ + { + desc: "Service with custom headers", + service: dynamic.WRRService{ + Name: "target-service", + Headers: map[string]string{ + "X-Custom-Header": "custom-value", + "X-Service-Type": "knative-service", + "Authorization": "bearer token123", + }, + }, + userAgent: "test-agent", + expectedHeaders: map[string]string{ + "X-Custom-Header": "custom-value", + "X-Service-Type": "knative-service", + "Authorization": "bearer token123", + }, + }, + { + desc: "Service with empty headers map", + service: dynamic.WRRService{ + Name: "target-service", + Headers: map[string]string{}, + }, + userAgent: "test-agent", + expectedHeaders: map[string]string{}, + }, + { + desc: "Service with nil headers", + service: dynamic.WRRService{ + Name: "target-service", + Headers: nil, + }, + userAgent: "test-agent", + expectedHeaders: map[string]string{}, + }, + { + desc: "Service with headers that override existing request headers", + service: dynamic.WRRService{ + Name: "target-service", + Headers: map[string]string{ + "User-Agent": "overridden-agent", + "Accept": "application/json", + }, + }, + userAgent: "original-agent", + expectedHeaders: map[string]string{ + "User-Agent": "overridden-agent", + "Accept": "application/json", + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + // Create a test server that will verify the headers are properly set for this specific test case + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify expected headers are present + for key, expectedValue := range test.expectedHeaders { + actualValue := r.Header.Get(key) + assert.Equal(t, expectedValue, actualValue, "Header %s should be %s", key, expectedValue) + } + + w.Header().Set("X-Response", "success") + w.WriteHeader(http.StatusOK) + })) + t.Cleanup(testServer.Close) + + // Create the target service that the WRRService will point to + targetServiceInfo := &runtime.ServiceInfo{ + Service: &dynamic.Service{ + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + {URL: testServer.URL}, + }, + }, + }, + } + + // Create a fresh manager for each test case + sm := NewManager(map[string]*runtime.ServiceInfo{ + "target-service": targetServiceInfo, + }, nil, nil, &transportManagerMock{}, pb) + + // Get the service handler + handler, err := sm.getServiceHandler(t.Context(), test.service) + require.NoError(t, err) + require.NotNil(t, handler) + + // Create a test request + req := testhelpers.MustNewRequest(http.MethodGet, "http://test.example.com/path", nil) + if test.userAgent != "" { + req.Header.Set("User-Agent", test.userAgent) + } + + // Execute the request + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, req) + + // Verify the response was successful + assert.Equal(t, http.StatusOK, recorder.Code) + }) + } +} + +type serviceBuilderFunc func(ctx context.Context, serviceName string) (http.Handler, error) + +func (s serviceBuilderFunc) BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error) { + return s(ctx, serviceName) +} + +type internalHandler struct{} + +func (internalHandler) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {} type forwarderMock struct{} diff --git a/webui/src/components/icons/providers/Knative.tsx b/webui/src/components/icons/providers/Knative.tsx new file mode 100644 index 000000000..e6adf46e2 --- /dev/null +++ b/webui/src/components/icons/providers/Knative.tsx @@ -0,0 +1,11 @@ +import { ProviderIconProps } from 'components/icons/providers' + +export default function Knative(props: ProviderIconProps) { + return ( + + + + ) +} diff --git a/webui/src/components/icons/providers/index.tsx b/webui/src/components/icons/providers/index.tsx index 388c66090..cd953d1b0 100644 --- a/webui/src/components/icons/providers/index.tsx +++ b/webui/src/components/icons/providers/index.tsx @@ -8,6 +8,7 @@ import File from 'components/icons/providers/File' import Http from 'components/icons/providers/Http' import Hub from 'components/icons/providers/Hub' import Internal from 'components/icons/providers/Internal' +import Knative from "components/icons/providers/Knative"; import Kubernetes from 'components/icons/providers/Kubernetes' import Nomad from 'components/icons/providers/Nomad' import Plugin from 'components/icons/providers/Plugin' @@ -49,6 +50,9 @@ export default function ProviderIcon({ name, size = 32 }: { name: string; size?: if (['kubernetes'].some((prefix) => nameLowerCase.startsWith(prefix))) { return Kubernetes } + if (['knative'].some((prefix) => nameLowerCase.startsWith(prefix))) { + return Knative + } if (['nomad', 'nomad-'].some((prefix) => nameLowerCase.startsWith(prefix))) { return Nomad } From cd028267ef0df96f38f5037d36d89665266a5215 Mon Sep 17 00:00:00 2001 From: James Callahan <35791147+james-callahan@users.noreply.github.com> Date: Thu, 9 Oct 2025 19:18:04 +1100 Subject: [PATCH 010/134] Allow publishing services with type ExternalName --- .../kubernetes/kubernetes-ingress.md | 1 + .../Published-Service-ExternalName.yml | 83 +++++++++++++++++++ pkg/provider/kubernetes/ingress/kubernetes.go | 5 ++ .../kubernetes/ingress/kubernetes_test.go | 8 ++ 4 files changed, 97 insertions(+) create mode 100644 pkg/provider/kubernetes/ingress/fixtures/Published-Service-ExternalName.yml diff --git a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-ingress.md b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-ingress.md index e2a171ac3..a5af268aa 100644 --- a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-ingress.md +++ b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-ingress.md @@ -116,6 +116,7 @@ depending on the service type: - **ClusterIP:** The ExternalIPs of the service will be propagated to the ingress status. - **NodePort:** The ExternalIP addresses of the nodes in the cluster will be propagated to the ingress status. - **LoadBalancer:** The IPs from the service's `loadBalancer.status` field (which contains the endpoints provided by the load balancer) will be propagated to the ingress status. +- **ExternalName:** The hostname from the service's `spec.externalName` field will be propagated to the ingress status. When using third-party tools such as External-DNS, this option enables the copying of external service IPs to the ingress resources. diff --git a/pkg/provider/kubernetes/ingress/fixtures/Published-Service-ExternalName.yml b/pkg/provider/kubernetes/ingress/fixtures/Published-Service-ExternalName.yml new file mode 100644 index 000000000..150c3b7c8 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Published-Service-ExternalName.yml @@ -0,0 +1,83 @@ +--- +kind: Node +apiVersion: v1 +metadata: + name: node1 + +status: + addresses: + - type: ExternalIP + address: 1.2.3.4 + +--- +kind: Node +apiVersion: v1 +metadata: + name: node2 + +status: + addresses: + - type: ExternalIP + address: 5.6.7.8 + +--- +kind: Service +apiVersion: v1 +metadata: + name: published-service + namespace: default + +spec: + type: ExternalName + externalName: example.com + +--- +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: foo + namespace: default + +spec: + rules: + - host: "*.foo.com" + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: service1 + port: + number: 80 + +--- +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: default + +spec: + ports: + - port: 80 + + clusterIP: 10.0.0.1 + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: service1-abc + labels: + kubernetes.io/service-name: service1 + +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: + - addresses: + - 10.10.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 7a1919b3e..775ff2a7d 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -484,6 +484,11 @@ func (p *Provider) updateIngressStatus(ing *netv1.Ingress, k8sClient Client) err } } + case corev1.ServiceTypeExternalName: + ingressStatus = []netv1.IngressLoadBalancerIngress{{ + Hostname: service.Spec.ExternalName, + }} + default: return fmt.Errorf("unsupported service type: %s", service.Spec.Type) } diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index d965e68a2..0b95bf173 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -2298,6 +2298,14 @@ func TestIngressEndpointPublishedService(t *testing.T) { }, }, }, + { + desc: "Published Service ExternalName", + expected: []netv1.IngressLoadBalancerIngress{ + { + Hostname: "example.com", + }, + }, + }, } for _, test := range testCases { From d28d719276f221958c2fd12ddfa79f44c75751d2 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Fri, 10 Oct 2025 12:12:04 +0200 Subject: [PATCH 011/134] Bump sigs.k8s.io/gateway-api to v1.4.0 --- .../test-gateway-api-conformance.yaml | 6 +- .gitignore | 2 +- .golangci.yml | 2 + Makefile | 4 +- docs/content/migrate/v3.md | 19 + docs/content/providers/kubernetes-gateway.md | 8 +- .../kubernetes/kubernetes-gateway.md | 6 +- .../routing/providers/kubernetes-gateway.md | 4 +- go.mod | 65 +- go.sum | 132 +- .../00-experimental-v1.4.0.yml} | 4407 +++++++-- .../01-rbac.yml | 0 .../02-traefik.yml | 0 ...-v1.2.1.yml => 00-experimental-v1.4.0.yml} | 8031 ++++++++++++++--- .../fixtures/k8s_gateway_conformance.toml | 21 - .../experimental-v3.6-default-report.yaml} | 8 +- ...est.go => gateway_api_conformance_test.go} | 34 +- integration/integration_test.go | 5 +- pkg/provider/kubernetes/gateway/client.go | 199 +- pkg/provider/kubernetes/gateway/features.go | 62 +- .../httproute/with_backend_tls_policy.yml | 6 +- .../with_backend_tls_policy_system.yml | 2 +- .../httproute/with_protocol_https.yml | 4 +- ...th_protocol_https_with_tls_passthrough.yml | 4 +- .../fixtures/httproute/with_protocol_tls.yml | 4 +- .../with_two_gateways_one_httproute.yml | 4 +- .../with_two_listeners_one_httproute.yml | 4 +- .../gateway/fixtures/mixed/simple.yml | 4 +- .../fixtures/mixed/with_core_group.yml | 4 +- ...ners_using_same_hostname_port_protocol.yml | 4 +- .../fixtures/mixed/with_namespace_all.yml | 4 +- .../fixtures/mixed/with_namespace_same.yml | 4 +- .../mixed/with_namespace_selector.yml | 4 +- .../fixtures/referencegrant/for_secret.yml | 4 +- .../referencegrant/for_secret_missing.yml | 4 +- .../for_secret_not_matching_from.yml | 4 +- .../for_secret_not_matching_to.yml | 4 +- .../fixtures/tcproute/with_protocol_https.yml | 4 +- .../fixtures/tcproute/with_protocol_tls.yml | 4 +- .../without_tcproute_tls_protocol.yml | 4 +- .../gatewayclass_with_unknown_controller.yml | 4 +- .../tlsroute/simple_TLS_to_TCPRoute.yml | 4 +- .../tlsroute/simple_TLS_to_TLSRoute.yml | 4 +- .../tlsroute/simple_cross_provider.yml | 4 +- .../fixtures/tlsroute/simple_nativelb.yml | 4 +- .../tlsroute/with_multiple_routes_kind.yml | 4 +- .../tlsroute/with_passthrough_tls.yml | 4 +- .../fixtures/tlsroute/with_protocol_https.yml | 4 +- .../tlsroute/with_wrong_service_port.yml | 4 +- pkg/provider/kubernetes/gateway/httproute.go | 211 +- pkg/provider/kubernetes/gateway/kubernetes.go | 46 +- .../kubernetes/gateway/kubernetes_test.go | 151 +- 52 files changed, 11290 insertions(+), 2249 deletions(-) rename integration/fixtures/{k8s-conformance/00-experimental-v1.3.0.yml => gateway-api-conformance/00-experimental-v1.4.0.yml} (81%) rename integration/fixtures/{k8s-conformance => gateway-api-conformance}/01-rbac.yml (100%) rename integration/fixtures/{k8s-conformance => gateway-api-conformance}/02-traefik.yml (100%) rename integration/fixtures/k8s/{00-experimental-v1.2.1.yml => 00-experimental-v1.4.0.yml} (68%) delete mode 100644 integration/fixtures/k8s_gateway_conformance.toml rename integration/{conformance-reports/v1.3.0/experimental-v3.5-default-report.yaml => gateway-api-conformance-reports/v1.4.0/experimental-v3.6-default-report.yaml} (93%) rename integration/{k8s_conformance_test.go => gateway_api_conformance_test.go} (85%) diff --git a/.github/workflows/test-gateway-api-conformance.yaml b/.github/workflows/test-gateway-api-conformance.yaml index 0fc4b09d2..724a45776 100644 --- a/.github/workflows/test-gateway-api-conformance.yaml +++ b/.github/workflows/test-gateway-api-conformance.yaml @@ -7,8 +7,8 @@ on: paths: - '.github/workflows/test-gateway-api-conformance.yaml' - 'pkg/provider/kubernetes/gateway/**' - - 'integration/fixtures/k8s-conformance/**' - - 'integration/k8s_conformance_test.go' + - 'integration/fixtures/gateway-api-conformance/**' + - 'integration/gateway_api_conformance_test.go' env: GO_VERSION: '1.24' @@ -34,7 +34,7 @@ jobs: run: | touch webui/static/index.html - - name: K8s Gateway API conformance test and report + - name: Gateway API conformance test and report run: | make test-gateway-api-conformance git diff --exit-code diff --git a/.gitignore b/.gitignore index 1754b1c70..741873dc9 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,4 @@ plugins-storage/ plugins-local/ traefik_changelog.md integration/tailscale.secret -integration/conformance-reports/**/experimental-dev-default-report.yaml +integration/gateway-api-conformance-reports/**/experimental-dev-default-report.yaml diff --git a/.golangci.yml b/.golangci.yml index 25de20ba1..62e29ed6d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -318,6 +318,8 @@ linters: - recvcheck - path: pkg/proxy/httputil/bufferpool.go text: 'SA6002: argument should be pointer-like to avoid allocations' + - path: integration/integration_test.go + text: 'var (gatewayAPIConformanceRunTest|traefikVersion) is unused' paths: - pkg/provider/kubernetes/crd/generated/ diff --git a/Makefile b/Makefile index b33b23361..7f361cfb2 100644 --- a/Makefile +++ b/Makefile @@ -102,8 +102,8 @@ test-integration: .PHONY: test-gateway-api-conformance #? test-gateway-api-conformance: Run the Gateway API conformance tests test-gateway-api-conformance: build-image-dirty - # In case of a new Minor/Major version, the k8sConformanceTraefikVersion needs to be updated. - GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -v -test.run K8sConformanceSuite -k8sConformance -k8sConformanceTraefikVersion="v3.5" $(TESTFLAGS) + # In case of a new Minor/Major version, the traefikVersion needs to be updated. + GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -v -tags gatewayAPIConformance -test.run GatewayAPIConformanceSuite -traefikVersion="v3.6" $(TESTFLAGS) .PHONY: test-knative-conformance #? test-knative-conformance: Run the Knative conformance tests diff --git a/docs/content/migrate/v3.md b/docs/content/migrate/v3.md index 8f8194fef..f046b2e69 100644 --- a/docs/content/migrate/v3.md +++ b/docs/content/migrate/v3.md @@ -489,3 +489,22 @@ To use the new `proxyprotocol` option in the Kubernetes CRD provider, you need t ```shell kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml ``` + +## v3.6.0 + +### Kubernetes Gateway API Provider + +Starting with `v3.6.0`, the Kubernetes Gateway API provider only supports version [v1.4.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.4.0) of the specification, +which requires the Gateway API CRDs to be updated. + +**Apply Updated CRDs:** + +```shell +kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml +``` + +For the experimental channel: + +```shell +kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/experimental-install.yaml +``` diff --git a/docs/content/providers/kubernetes-gateway.md b/docs/content/providers/kubernetes-gateway.md index 22158dfb0..bf2dff9cb 100644 --- a/docs/content/providers/kubernetes-gateway.md +++ b/docs/content/providers/kubernetes-gateway.md @@ -8,11 +8,11 @@ description: "Learn how to use the Kubernetes Gateway API as a provider for conf The Kubernetes Gateway provider is a Traefik implementation of the [Gateway API](https://gateway-api.sigs.k8s.io/) specification from the Kubernetes Special Interest Groups (SIGs). -This provider supports Standard version [v1.3.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.3.0) of the Gateway API specification. +This provider supports Standard version [v1.4.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.4.0) of the Gateway API specification. It fully supports all HTTP core and some extended features, as well as the `TCPRoute` and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels). -For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.3.0/traefik-traefik). +For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.4.0/traefik-traefik). ## Requirements @@ -27,7 +27,7 @@ For more details, check out the conformance [report](https://github.com/kubernet ```bash # Install Gateway API CRDs from the Standard channel. - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml ``` 2. Install the additional Traefik RBAC required for Gateway API. @@ -275,7 +275,7 @@ providers: ```bash # Install Gateway API CRDs from the Experimental channel. - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/experimental-install.yaml + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/experimental-install.yaml ``` ### `labelselector` diff --git a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md index 9d73ba18e..1a864c058 100644 --- a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md +++ b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md @@ -8,11 +8,11 @@ description: "Learn how to use the Kubernetes Gateway API as a provider for conf The Kubernetes Gateway provider is a Traefik implementation of the [Gateway API](https://gateway-api.sigs.k8s.io/) specification from the Kubernetes Special Interest Groups (SIGs). -This provider supports Standard version [v1.3.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.3.0) of the Gateway API specification. +This provider supports Standard version [v1.4.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.4.0) of the Gateway API specification. It fully supports all HTTP core and some extended features, as well as the `TCPRoute` and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels). -For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.3.0/traefik-traefik). +For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.4.0/traefik-traefik). !!! info "Using The Helm Chart" @@ -27,7 +27,7 @@ For more details, check out the conformance [report](https://github.com/kubernet ```bash # Install Gateway API CRDs from the Standard channel. - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml ``` 2. Install/update the Traefik [RBAC](../../../dynamic-configuration/kubernetes-gateway-rbac.yml). diff --git a/docs/content/routing/providers/kubernetes-gateway.md b/docs/content/routing/providers/kubernetes-gateway.md index f658ca4ba..512f64784 100644 --- a/docs/content/routing/providers/kubernetes-gateway.md +++ b/docs/content/routing/providers/kubernetes-gateway.md @@ -8,11 +8,11 @@ description: "The Kubernetes Gateway API can be used as a provider for routing a When using the Kubernetes Gateway API provider, Traefik leverages the Gateway API Custom Resource Definitions (CRDs) to obtain its routing configuration. For detailed information on the Gateway API concepts and resources, refer to the official [documentation](https://gateway-api.sigs.k8s.io/). -The Kubernetes Gateway API provider supports version [v1.3.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.3.0) of the specification. +The Kubernetes Gateway API provider supports version [v1.4.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.4.0) of the specification. It fully supports all `HTTPRoute` core and some extended features, like `GRPCRoute`, as well as the `TCPRoute` and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels). -For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.3.0/traefik-traefik). +For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.4.0/traefik-traefik). ## Deploying a Gateway diff --git a/go.mod b/go.mod index e11ce8eda..1753588e8 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/golang/protobuf v1.5.4 github.com/google/go-github/v28 v28.1.1 github.com/gorilla/mux v1.8.1 - github.com/gorilla/websocket v1.5.3 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/hashicorp/consul/api v1.26.1 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-multierror v1.1.1 @@ -53,8 +53,8 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pires/go-proxyproto v0.8.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // No tag on the repo. - github.com/prometheus/client_golang v1.22.0 - github.com/prometheus/client_model v0.6.1 + github.com/prometheus/client_golang v1.23.0 + github.com/prometheus/client_model v0.6.2 github.com/quic-go/quic-go v0.54.0 github.com/redis/go-redis/v9 v9.8.0 github.com/rs/zerolog v1.33.0 @@ -103,20 +103,20 @@ require ( golang.org/x/text v0.29.0 golang.org/x/time v0.13.0 golang.org/x/tools v0.36.0 - google.golang.org/grpc v1.75.0 + google.golang.org/grpc v1.75.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.32.3 - k8s.io/apiextensions-apiserver v0.32.3 - k8s.io/apimachinery v0.32.3 - k8s.io/client-go v0.32.3 - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // No tag on the repo. + k8s.io/api v0.34.1 + k8s.io/apiextensions-apiserver v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/client-go v0.34.1 + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // No tag on the repo. knative.dev/networking v0.0.0-20241022012959-60e29ff520dc knative.dev/pkg v0.0.0-20241021183759-9b9d535af5ad mvdan.cc/xurls/v2 v2.5.0 - sigs.k8s.io/controller-runtime v0.20.4 - sigs.k8s.io/gateway-api v1.3.0 - sigs.k8s.io/yaml v1.4.0 + sigs.k8s.io/controller-runtime v0.22.1 + sigs.k8s.io/gateway-api v1.4.0 + sigs.k8s.io/yaml v1.6.0 ) require ( @@ -194,12 +194,12 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/dnsimple/dnsimple-go/v4 v4.0.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/exoscale/egoscale/v3 v3.1.26 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/gin-gonic/gin v1.9.1 // indirect @@ -213,9 +213,9 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonpointer v0.21.2 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -223,16 +223,15 @@ require ( github.com/go-resty/resty/v2 v2.16.5 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-zookeeper/zk v1.0.3 // indirect - github.com/goccy/go-yaml v1.11.3 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect @@ -256,7 +255,7 @@ require ( github.com/imdario/mergo v0.3.16 // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 // indirect - github.com/jonboulle/clockwork v0.4.0 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect @@ -274,7 +273,7 @@ require ( github.com/mailgun/minheap v0.0.0-20170619185613-3dbe6c6bf55f // indirect github.com/mailgun/multibuf v0.1.2 // indirect github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mimuret/golang-iij-dpf v0.9.1 // indirect @@ -321,8 +320,8 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect github.com/pquerna/otp v1.5.0 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/regfish/regfish-dnsapi-go v0.1.1 // indirect github.com/rs/cors v1.7.0 // indirect @@ -369,9 +368,9 @@ require ( github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/errs v1.4.0 // indirect - go.etcd.io/etcd/api/v3 v3.5.16 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect - go.etcd.io/etcd/client/v3 v3.5.16 // indirect + go.etcd.io/etcd/api/v3 v3.6.4 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect + go.etcd.io/etcd/client/v3 v3.6.4 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect @@ -387,26 +386,28 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.1 // indirect go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.4.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/api v0.249.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect google.golang.org/protobuf v1.36.8 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ns1/ns1-go.v2 v2.15.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3 // indirect nhooyr.io/websocket v1.8.7 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect ) // Containous forks diff --git a/go.sum b/go.sum index 4fd32533a..4c44ae06c 100644 --- a/go.sum +++ b/go.sum @@ -371,8 +371,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= -github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -409,8 +409,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= @@ -464,13 +464,13 @@ github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA= +github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -513,8 +513,8 @@ github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE= -github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= -github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= @@ -572,8 +572,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -598,8 +598,6 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -638,8 +636,8 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf h1:C1GPyPJrOlJlIrcaBBiBpDsqZena2Ks8spa5xZqr1XQ= github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf/go.mod h1:zXqxTI6jXDdKnlf8s+nT+3c8LrwUEy3yNpO4XJL90lA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -759,8 +757,8 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= -github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -863,8 +861,8 @@ github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f h1:ZZYhg16XocqSKPGN github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f/go.mod h1:8heskWJ5c0v5J9WH89ADhyal1DOZcayll8fSbhB+/9A= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -1082,14 +1080,14 @@ github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQ github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -1098,8 +1096,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -1108,8 +1106,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= @@ -1357,15 +1355,15 @@ github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0= -go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28= +go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= +go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q= -go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E= +go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= +go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE= -go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50= +go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= +go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -1461,6 +1459,10 @@ go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -1854,8 +1856,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -1924,8 +1924,8 @@ google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuO google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1943,8 +1943,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= -google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1969,8 +1969,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -2012,20 +2012,20 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= -k8s.io/apiextensions-apiserver v0.32.3 h1:4D8vy+9GWerlErCwVIbcQjsWunF9SUGNu7O7hiQTyPY= -k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3 h1:liMHz39T5dJO1aOKHLvwaCjDbf07wVh6yaUlTpunnkE= +k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= knative.dev/networking v0.0.0-20241022012959-60e29ff520dc h1:0d9XXRLlyuHfINZLlYqo/BYe/+chqqNBMLKJldjTbtw= knative.dev/networking v0.0.0-20241022012959-60e29ff520dc/go.mod h1:G56j6VCLzfaN9yZ4IqfNyN4c3U1czvhUmKeZX4UjQ8Q= knative.dev/pkg v0.0.0-20241021183759-9b9d535af5ad h1:Nrjtr2H168rJeamH4QdyLMV1lEKHejNhaj1ymgQMfLk= @@ -2039,16 +2039,16 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= -sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= -sigs.k8s.io/gateway-api v1.3.0 h1:q6okN+/UKDATola4JY7zXzx40WO4VISk7i9DIfOvr9M= -sigs.k8s.io/gateway-api v1.3.0/go.mod h1:d8NV8nJbaRbEKem+5IuxkL8gJGOZ+FJ+NvOIltV8gDk= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016 h1:kXv6kKdoEtedwuqMmkqhbkgvYKeycVbC8+iPCP9j5kQ= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg= +sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY= +sigs.k8s.io/gateway-api v1.4.0 h1:ZwlNM6zOHq0h3WUX2gfByPs2yAEsy/EenYJB78jpQfQ= +sigs.k8s.io/gateway-api v1.4.0/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/integration/fixtures/k8s-conformance/00-experimental-v1.3.0.yml b/integration/fixtures/gateway-api-conformance/00-experimental-v1.4.0.yml similarity index 81% rename from integration/fixtures/k8s-conformance/00-experimental-v1.3.0.yml rename to integration/fixtures/gateway-api-conformance/00-experimental-v1.4.0.yml index 75725cc0f..b1e7bd2f2 100644 --- a/integration/fixtures/k8s-conformance/00-experimental-v1.3.0.yml +++ b/integration/fixtures/gateway-api-conformance/00-experimental-v1.4.0.yml @@ -24,9 +24,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null labels: gateway.networking.k8s.io/policy: Direct name: backendtlspolicies.gateway.networking.k8s.io @@ -47,7 +46,7 @@ spec: - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha3 + name: v1 schema: openAPIV3Schema: description: |- @@ -114,6 +113,22 @@ spec: be unique across all targetRef entries in the BackendTLSPolicy. * They select different sectionNames in the same target. + When more than one BackendTLSPolicy selects the same target and + sectionName, implementations MUST determine precedence using the + following criteria, continuing on ties: + + * The older policy by creation timestamp takes precedence. For + example, a policy with a creation timestamp of "2021-07-15 + 01:02:03" MUST be given precedence over a policy with a + creation timestamp of "2021-07-15 01:02:04". + * The policy appearing first in alphabetical order by {name}. + For example, a policy named `bar` is given precedence over a + policy named `baz`. + + For any BackendTLSPolicy that does not take precedence, the + implementation MUST ensure the `Accepted` Condition is set to + `status: False`, with Reason `Conflicted`. + Support: Extended for Kubernetes Service Support: Implementation-specific for any other resource @@ -170,6 +185,7 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName must be specified when targetRefs includes 2 or more references to the same target @@ -198,8 +214,31 @@ spec: not both. If CACertificateRefs is empty or unspecified, the configuration for WellKnownCACertificates MUST be honored instead if supported by the implementation. - References to a resource in a different namespace are invalid for the - moment, although we will revisit this in the future. + A CACertificateRef is invalid if: + + * It refers to a resource that cannot be resolved (e.g., the referenced resource + does not exist) or is misconfigured (e.g., a ConfigMap does not contain a key + named `ca.crt`). In this case, the Reason must be set to `InvalidCACertificateRef` + and the Message of the Condition must indicate which reference is invalid and why. + + * It refers to an unknown or unsupported kind of resource. In this case, the Reason + must be set to `InvalidKind` and the Message of the Condition must explain which + kind of resource is unknown or unsupported. + + * It refers to a resource in another namespace. This may change in future + spec updates. + + Implementations MAY choose to perform further validation of the certificate + content (e.g., checking expiry or enforcing specific formats). In such cases, + an implementation-specific Reason and Message must be set for the invalid reference. + + In all cases, the implementation MUST ensure the `ResolvedRefs` Condition on + the BackendTLSPolicy is set to `status: False`, with a Reason and Message + that indicate the cause of the error. Connections using an invalid + CACertificateRef MUST fail, and the client MUST receive an HTTP 5xx error + response. If ALL CACertificateRefs are invalid, the implementation MUST also + ensure the `Accepted` Condition on the BackendTLSPolicy is set to + `status: False`, with a Reason `NoValidCACertificate`. A single CACertificateRef to a Kubernetes ConfigMap kind has "Core" support. Implementations MAY choose to support attaching multiple certificates to @@ -208,8 +247,8 @@ spec: Support: Core - An optional single reference to a Kubernetes ConfigMap, with the CA certificate in a key named `ca.crt`. - Support: Implementation-specific (More than one reference, or other kinds - of resources). + Support: Implementation-specific - More than one reference, other kinds + of resources, or a single reference that includes multiple certificates. items: description: |- LocalObjectReference identifies an API object within the namespace of the @@ -247,15 +286,18 @@ spec: type: object maxItems: 8 type: array + x-kubernetes-list-type: atomic hostname: description: |- Hostname is used for two purposes in the connection between Gateways and backends: 1. Hostname MUST be used as the SNI to connect to the backend (RFC 6066). - 2. Hostname MUST be used for authentication and MUST match the certificate served by the matching backend, unless SubjectAltNames is specified. - authentication and MUST match the certificate served by the matching - backend. + 2. Hostname MUST be used for authentication and MUST match the certificate + served by the matching backend, unless SubjectAltNames is specified. + 3. If SubjectAltNames are specified, Hostname can be used for certificate selection + but MUST NOT be used for authentication. If you want to use the value + of the Hostname field for authentication, you MUST add it to the SubjectAltNames list. Support: Core maxLength: 253 @@ -325,6 +367,7 @@ spec: "")' maxItems: 5 type: array + x-kubernetes-list-type: atomic wellKnownCACertificates: description: |- WellKnownCACertificates specifies whether system CA certificates may be used in @@ -332,10 +375,11 @@ spec: If WellKnownCACertificates is unspecified or empty (""), then CACertificateRefs must be specified with at least one entry for a valid configuration. Only one of - CACertificateRefs or WellKnownCACertificates may be specified, not both. If an - implementation does not support the WellKnownCACertificates field or the value - supplied is not supported, the Status Conditions on the Policy MUST be - updated to include an Accepted: False Condition with Reason: Invalid. + CACertificateRefs or WellKnownCACertificates may be specified, not both. + If an implementation does not support the WellKnownCACertificates field, or + the supplied value is not recognized, the implementation MUST ensure the + `Accepted` Condition on the BackendTLSPolicy is set to `status: False`, with + a Reason `Invalid`. Support: Implementation-specific enum: @@ -646,10 +690,12 @@ spec: type: string required: - ancestorRef + - conditions - controllerName type: object maxItems: 16 type: array + x-kubernetes-list-type: atomic required: - ancestors type: object @@ -660,6 +706,667 @@ spec: storage: true subresources: status: {} + - deprecated: true + deprecationWarning: The v1alpha3 version of BackendTLSPolicy has been deprecated + and will be removed in a future release of the API. Please upgrade to v1. + name: v1alpha3 + schema: + openAPIV3Schema: + description: |- + BackendTLSPolicy provides a way to configure how a Gateway + connects to a Backend via TLS. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of BackendTLSPolicy. + properties: + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + targetRefs: + description: |- + TargetRefs identifies an API object to apply the policy to. + Only Services have Extended support. Implementations MAY support + additional objects, with Implementation Specific support. + Note that this config applies to the entire referenced resource + by default, but this default may change in the future to provide + a more granular application of the policy. + + TargetRefs must be _distinct_. This means either that: + + * They select different targets. If this is the case, then targetRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, and `name` must + be unique across all targetRef entries in the BackendTLSPolicy. + * They select different sectionNames in the same target. + + When more than one BackendTLSPolicy selects the same target and + sectionName, implementations MUST determine precedence using the + following criteria, continuing on ties: + + * The older policy by creation timestamp takes precedence. For + example, a policy with a creation timestamp of "2021-07-15 + 01:02:03" MUST be given precedence over a policy with a + creation timestamp of "2021-07-15 01:02:04". + * The policy appearing first in alphabetical order by {name}. + For example, a policy named `bar` is given precedence over a + policy named `baz`. + + For any BackendTLSPolicy that does not take precedence, the + implementation MUST ensure the `Accepted` Condition is set to + `status: False`, with Reason `Conflicted`. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + 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 target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: sectionName must be specified when targetRefs includes + 2 or more references to the same target + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name ? ((!has(p1.sectionName) || p1.sectionName + == '''') == (!has(p2.sectionName) || p2.sectionName == '''')) + : true))' + - message: sectionName must be unique when targetRefs includes 2 or + more references to the same target + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.sectionName) || + p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) + validation: + description: Validation contains backend TLS validation configuration. + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to Kubernetes objects that + contain a PEM-encoded TLS CA certificate bundle, which is used to + validate a TLS handshake between the Gateway and backend Pod. + + If CACertificateRefs is empty or unspecified, then WellKnownCACertificates must be + specified. Only one of CACertificateRefs or WellKnownCACertificates may be specified, + not both. If CACertificateRefs is empty or unspecified, the configuration for + WellKnownCACertificates MUST be honored instead if supported by the implementation. + + A CACertificateRef is invalid if: + + * It refers to a resource that cannot be resolved (e.g., the referenced resource + does not exist) or is misconfigured (e.g., a ConfigMap does not contain a key + named `ca.crt`). In this case, the Reason must be set to `InvalidCACertificateRef` + and the Message of the Condition must indicate which reference is invalid and why. + + * It refers to an unknown or unsupported kind of resource. In this case, the Reason + must be set to `InvalidKind` and the Message of the Condition must explain which + kind of resource is unknown or unsupported. + + * It refers to a resource in another namespace. This may change in future + spec updates. + + Implementations MAY choose to perform further validation of the certificate + content (e.g., checking expiry or enforcing specific formats). In such cases, + an implementation-specific Reason and Message must be set for the invalid reference. + + In all cases, the implementation MUST ensure the `ResolvedRefs` Condition on + the BackendTLSPolicy is set to `status: False`, with a Reason and Message + that indicate the cause of the error. Connections using an invalid + CACertificateRef MUST fail, and the client MUST receive an HTTP 5xx error + response. If ALL CACertificateRefs are invalid, the implementation MUST also + ensure the `Accepted` Condition on the BackendTLSPolicy is set to + `status: False`, with a Reason `NoValidCACertificate`. + + A single CACertificateRef to a Kubernetes ConfigMap kind has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a backend, but this behavior is implementation-specific. + + Support: Core - An optional single reference to a Kubernetes ConfigMap, + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific - More than one reference, other kinds + of resources, or a single reference that includes multiple certificates. + items: + description: |- + LocalObjectReference identifies an API object within the namespace of the + referrer. + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + 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: + description: Kind is kind of the referent. For example "HTTPRoute" + or "Service". + 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 + required: + - group + - kind + - name + type: object + maxItems: 8 + type: array + x-kubernetes-list-type: atomic + hostname: + description: |- + Hostname is used for two purposes in the connection between Gateways and + backends: + + 1. Hostname MUST be used as the SNI to connect to the backend (RFC 6066). + 2. Hostname MUST be used for authentication and MUST match the certificate + served by the matching backend, unless SubjectAltNames is specified. + 3. If SubjectAltNames are specified, Hostname can be used for certificate selection + but MUST NOT be used for authentication. If you want to use the value + of the Hostname field for authentication, you MUST add it to the SubjectAltNames list. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + subjectAltNames: + description: |- + SubjectAltNames contains one or more Subject Alternative Names. + When specified the certificate served from the backend MUST + have at least one Subject Alternate Name matching one of the specified SubjectAltNames. + + Support: Extended + items: + description: SubjectAltName represents Subject Alternative Name. + properties: + hostname: + description: |- + Hostname contains Subject Alternative Name specified in DNS name format. + Required when Type is set to Hostname, ignored otherwise. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: + description: |- + Type determines the format of the Subject Alternative Name. Always required. + + Support: Core + enum: + - Hostname + - URI + type: string + uri: + description: |- + URI contains Subject Alternative Name specified in a full URI format. + It MUST include both a scheme (e.g., "http" or "ftp") and a scheme-specific-part. + Common values include SPIFFE IDs like "spiffe://mycluster.example.com/ns/myns/sa/svc1sa". + Required when Type is set to URI, ignored otherwise. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: SubjectAltName element must contain Hostname, if + Type is set to Hostname + rule: '!(self.type == "Hostname" && (!has(self.hostname) || + self.hostname == ""))' + - message: SubjectAltName element must not contain Hostname, + if Type is not set to Hostname + rule: '!(self.type != "Hostname" && has(self.hostname) && + self.hostname != "")' + - message: SubjectAltName element must contain URI, if Type + is set to URI + rule: '!(self.type == "URI" && (!has(self.uri) || self.uri + == ""))' + - message: SubjectAltName element must not contain URI, if Type + is not set to URI + rule: '!(self.type != "URI" && has(self.uri) && self.uri != + "")' + maxItems: 5 + type: array + x-kubernetes-list-type: atomic + wellKnownCACertificates: + description: |- + WellKnownCACertificates specifies whether system CA certificates may be used in + the TLS handshake between the gateway and backend pod. + + If WellKnownCACertificates is unspecified or empty (""), then CACertificateRefs + must be specified with at least one entry for a valid configuration. Only one of + CACertificateRefs or WellKnownCACertificates may be specified, not both. + If an implementation does not support the WellKnownCACertificates field, or + the supplied value is not recognized, the implementation MUST ensure the + `Accepted` Condition on the BackendTLSPolicy is set to `status: False`, with + a Reason `Invalid`. + + Support: Implementation-specific + enum: + - System + type: string + required: + - hostname + type: object + x-kubernetes-validations: + - message: must not contain both CACertificateRefs and WellKnownCACertificates + rule: '!(has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "")' + - message: must specify either CACertificateRefs or WellKnownCACertificates + rule: (has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "") + required: + - targetRefs + - validation + type: object + status: + description: Status defines the current state of BackendTLSPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + 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: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + 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. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - conditions + - controllerName + type: object + maxItems: 16 + type: array + x-kubernetes-list-type: atomic + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: false status: acceptedNames: kind: "" @@ -675,9 +1382,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: gatewayclasses.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -1195,9 +1901,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: gateways.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -1257,7 +1962,7 @@ spec: Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST - indicate this in the associated entry in GatewayStatus.Addresses. + indicate this in an associated entry in GatewayStatus.Conditions. The Addresses field represents a request for the address(es) on the "outside of the Gateway", that traffic bound for this Gateway will use. @@ -1312,19 +2017,22 @@ spec: type: string type: object x-kubernetes-validations: - - message: Hostname value must only contain valid characters (matching - ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) - rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + - message: Hostname value must be empty or contain only valid characters + (matching ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? (!has(self.value) || self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$""")): true' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: IPAddress values must be unique - rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, - a2.type == a1.type && a2.value == a1.value) : true )' + rule: 'self.all(a1, a1.type == ''IPAddress'' && has(a1.value) ? + self.exists_one(a2, a2.type == a1.type && has(a2.value) && a2.value + == a1.value) : true )' - message: Hostname values must be unique - rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, - a2.type == a1.type && a2.value == a1.value) : true )' + rule: 'self.all(a1, a1.type == ''Hostname'' && has(a1.value) ? + self.exists_one(a2, a2.type == a1.type && has(a2.value) && a2.value + == a1.value) : true )' allowedListeners: description: |- AllowedListeners defines which ListenerSets can be attached to this Gateway. @@ -1406,70 +2114,29 @@ spec: x-kubernetes-map-type: atomic type: object type: object - backendTLS: + defaultScope: description: |- - BackendTLS configures TLS settings for when this Gateway is connecting to - backends with TLS. + DefaultScope, when set, configures the Gateway as a default Gateway, + meaning it will dynamically and implicitly have Routes (e.g. HTTPRoute) + attached to it, according to the scope configured here. - Support: Core - properties: - clientCertificateRef: - description: |- - ClientCertificateRef is a reference to an object that contains a Client - Certificate and the associated private key. - - References to a resource in different namespace are invalid UNLESS there - is a ReferenceGrant in the target namespace that allows the certificate - to be attached. If a ReferenceGrant does not allow this reference, the - "ResolvedRefs" condition MUST be set to False for this listener with the - "RefNotPermitted" reason. - - ClientCertificateRef can reference to standard Kubernetes resources, i.e. - Secret, or implementation-specific custom resources. - - This setting can be overridden on the service level by use of BackendTLSPolicy. - - Support: Core - 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: Secret - description: Kind is kind of the referent. For example "Secret". - 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 referenced object. 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 - required: - - name - type: object - type: object + If unset (the default) or set to None, the Gateway will not act as a + default Gateway; if set, the Gateway will claim any Route with a + matching scope set in its UseDefaultGateway field, subject to the usual + rules about which routes the Gateway can attach to. + + Think carefully before using this functionality! While the normal rules + about which Route can apply are still enforced, it is simply easier for + the wrong Route to be accidentally attached to this Gateway in this + configuration. If the Gateway operator is not also the operator in + control of the scope (e.g. namespace) with tight controls and checks on + what kind of workloads and Routes get added in that scope, we strongly + recommend not using this just because it seems convenient, and instead + stick to direct Route attachment. + enum: + - All + - None + type: string gatewayClassName: description: |- GatewayClassName used for this Gateway. This is the name of a @@ -1825,6 +2492,7 @@ spec: type: object maxItems: 8 type: array + x-kubernetes-list-type: atomic namespaces: default: from: Same @@ -1992,7 +2660,7 @@ spec: the Protocol field is "HTTPS" or "TLS". It is invalid to set this field if the Protocol field is "HTTP", "TCP", or "UDP". - The association of SNIs to Certificate defined in GatewayTLSConfig is + The association of SNIs to Certificate defined in ListenerTLSConfig is defined based on the Hostname field for this listener. The GatewayClass MUST use the longest matching SNI out of all @@ -2079,93 +2747,7 @@ spec: type: object maxItems: 64 type: array - frontendValidation: - description: |- - FrontendValidation holds configuration information for validating the frontend (client). - Setting this field will require clients to send a client certificate - required for validation during the TLS handshake. In browsers this may result in a dialog appearing - that requests a user to specify the client certificate. - The maximum depth of a certificate chain accepted in verification is Implementation specific. - - Support: Extended - properties: - caCertificateRefs: - description: |- - CACertificateRefs contains one or more references to - Kubernetes objects that contain TLS certificates of - the Certificate Authorities that can be used - as a trust anchor to validate the certificates presented by the client. - - A single CA certificate reference to a Kubernetes ConfigMap - has "Core" support. - Implementations MAY choose to support attaching multiple CA certificates to - a Listener, but this behavior is implementation-specific. - - Support: Core - A single reference to a Kubernetes ConfigMap - with the CA certificate in a key named `ca.crt`. - - Support: Implementation-specific (More than one reference, or other kinds - of resources). - - References to a resource in a different namespace are invalid UNLESS there - is a ReferenceGrant in the target namespace that allows the certificate - to be attached. If a ReferenceGrant does not allow this reference, the - "ResolvedRefs" condition MUST be set to False for this listener with the - "RefNotPermitted" reason. - items: - description: |- - ObjectReference identifies an API object including its namespace. - - The API object must be valid in the cluster; the Group and Kind must - be registered in the cluster for this reference to be valid. - - References to objects with invalid Group and Kind are not valid, and must - be rejected by the implementation, with appropriate Conditions set - on the containing object. - properties: - group: - description: |- - Group is the group of the referent. For example, "gateway.networking.k8s.io". - When set to the 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: - description: Kind is kind of the referent. For - example "ConfigMap" or "Service". - 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 referenced object. 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 - required: - - group - - kind - - name - type: object - maxItems: 8 - minItems: 1 - type: array - type: object + x-kubernetes-list-type: atomic mode: default: Terminate description: |- @@ -2244,6 +2826,366 @@ spec: rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + tls: + description: |- + TLS specifies frontend and backend tls configuration for entire gateway. + + Support: Extended + properties: + backend: + description: |- + Backend describes TLS configuration for gateway when connecting + to backends. + + Note that this contains only details for the Gateway as a TLS client, + and does _not_ imply behavior about how to choose which backend should + get a TLS connection. That is determined by the presence of a BackendTLSPolicy. + + Support: Core + properties: + clientCertificateRef: + description: |- + ClientCertificateRef is a reference to an object that contains a Client + Certificate and the associated private key. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + ClientCertificateRef can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core + 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: Secret + description: Kind is kind of the referent. For example + "Secret". + 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 referenced object. 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 + required: + - name + type: object + type: object + frontend: + description: |- + Frontend describes TLS config when client connects to Gateway. + Support: Core + properties: + default: + description: |- + Default specifies the default client certificate validation configuration + for all Listeners handling HTTPS traffic, unless a per-port configuration + is defined. + + support: Core + properties: + validation: + description: |- + Validation holds configuration information for validating the frontend (client). + Setting this field will result in mutual authentication when connecting to the gateway. + In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Core + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one certificate in a ConfigMap + with different keys or more than one reference, or other kinds of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the 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: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + 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 referenced object. 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 + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + mode: + default: AllowValidOnly + description: |- + FrontendValidationMode defines the mode for validating the client certificate. + There are two possible modes: + + - AllowValidOnly: In this mode, the gateway will accept connections only if + the client presents a valid certificate. This certificate must successfully + pass validation against the CA certificates specified in `CACertificateRefs`. + - AllowInsecureFallback: In this mode, the gateway will accept connections + even if the client certificate is not presented or fails verification. + + This approach delegates client authorization to the backend and introduce + a significant security risk. It should be used in testing environments or + on a temporary basis in non-testing environments. + + Defaults to AllowValidOnly. + + Support: Core + enum: + - AllowValidOnly + - AllowInsecureFallback + type: string + required: + - caCertificateRefs + type: object + type: object + perPort: + description: |- + PerPort specifies tls configuration assigned per port. + Per port configuration is optional. Once set this configuration overrides + the default configuration for all Listeners handling HTTPS traffic + that match this port. + Each override port requires a unique TLS configuration. + + support: Core + items: + properties: + port: + description: |- + The Port indicates the Port Number to which the TLS configuration will be + applied. This configuration will be applied to all Listeners handling HTTPS + traffic that match this port. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + tls: + description: |- + TLS store the configuration that will be applied to all Listeners handling + HTTPS traffic and matching given port. + + Support: Core + properties: + validation: + description: |- + Validation holds configuration information for validating the frontend (client). + Setting this field will result in mutual authentication when connecting to the gateway. + In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Core + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one certificate in a ConfigMap + with different keys or more than one reference, or other kinds of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the 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: + description: Kind is kind of the referent. + For example "ConfigMap" or "Service". + 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 referenced object. 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 + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + mode: + default: AllowValidOnly + description: |- + FrontendValidationMode defines the mode for validating the client certificate. + There are two possible modes: + + - AllowValidOnly: In this mode, the gateway will accept connections only if + the client presents a valid certificate. This certificate must successfully + pass validation against the CA certificates specified in `CACertificateRefs`. + - AllowInsecureFallback: In this mode, the gateway will accept connections + even if the client certificate is not presented or fails verification. + + This approach delegates client authorization to the backend and introduce + a significant security risk. It should be used in testing environments or + on a temporary basis in non-testing environments. + + Defaults to AllowValidOnly. + + Support: Core + enum: + - AllowValidOnly + - AllowInsecureFallback + type: string + required: + - caCertificateRefs + type: object + type: object + required: + - port + - tls + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: Port for TLS configuration must be unique within + the Gateway + rule: self.all(t1, self.exists_one(t2, t1.port == t2.port)) + required: + - default + type: object + type: object required: - gatewayClassName - listeners @@ -2318,6 +3260,7 @@ spec: true' maxItems: 16 type: array + x-kubernetes-list-type: atomic conditions: default: - lastTransitionTime: "1970-01-01T00:00:00Z" @@ -2531,6 +3474,7 @@ spec: type: object maxItems: 8 type: array + x-kubernetes-list-type: atomic required: - attachedRoutes - conditions @@ -2595,7 +3539,7 @@ spec: Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST - indicate this in the associated entry in GatewayStatus.Addresses. + indicate this in an associated entry in GatewayStatus.Conditions. The Addresses field represents a request for the address(es) on the "outside of the Gateway", that traffic bound for this Gateway will use. @@ -2650,19 +3594,22 @@ spec: type: string type: object x-kubernetes-validations: - - message: Hostname value must only contain valid characters (matching - ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) - rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + - message: Hostname value must be empty or contain only valid characters + (matching ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? (!has(self.value) || self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$""")): true' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: IPAddress values must be unique - rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, - a2.type == a1.type && a2.value == a1.value) : true )' + rule: 'self.all(a1, a1.type == ''IPAddress'' && has(a1.value) ? + self.exists_one(a2, a2.type == a1.type && has(a2.value) && a2.value + == a1.value) : true )' - message: Hostname values must be unique - rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, - a2.type == a1.type && a2.value == a1.value) : true )' + rule: 'self.all(a1, a1.type == ''Hostname'' && has(a1.value) ? + self.exists_one(a2, a2.type == a1.type && has(a2.value) && a2.value + == a1.value) : true )' allowedListeners: description: |- AllowedListeners defines which ListenerSets can be attached to this Gateway. @@ -2744,70 +3691,29 @@ spec: x-kubernetes-map-type: atomic type: object type: object - backendTLS: + defaultScope: description: |- - BackendTLS configures TLS settings for when this Gateway is connecting to - backends with TLS. + DefaultScope, when set, configures the Gateway as a default Gateway, + meaning it will dynamically and implicitly have Routes (e.g. HTTPRoute) + attached to it, according to the scope configured here. - Support: Core - properties: - clientCertificateRef: - description: |- - ClientCertificateRef is a reference to an object that contains a Client - Certificate and the associated private key. - - References to a resource in different namespace are invalid UNLESS there - is a ReferenceGrant in the target namespace that allows the certificate - to be attached. If a ReferenceGrant does not allow this reference, the - "ResolvedRefs" condition MUST be set to False for this listener with the - "RefNotPermitted" reason. - - ClientCertificateRef can reference to standard Kubernetes resources, i.e. - Secret, or implementation-specific custom resources. - - This setting can be overridden on the service level by use of BackendTLSPolicy. - - Support: Core - 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: Secret - description: Kind is kind of the referent. For example "Secret". - 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 referenced object. 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 - required: - - name - type: object - type: object + If unset (the default) or set to None, the Gateway will not act as a + default Gateway; if set, the Gateway will claim any Route with a + matching scope set in its UseDefaultGateway field, subject to the usual + rules about which routes the Gateway can attach to. + + Think carefully before using this functionality! While the normal rules + about which Route can apply are still enforced, it is simply easier for + the wrong Route to be accidentally attached to this Gateway in this + configuration. If the Gateway operator is not also the operator in + control of the scope (e.g. namespace) with tight controls and checks on + what kind of workloads and Routes get added in that scope, we strongly + recommend not using this just because it seems convenient, and instead + stick to direct Route attachment. + enum: + - All + - None + type: string gatewayClassName: description: |- GatewayClassName used for this Gateway. This is the name of a @@ -3163,6 +4069,7 @@ spec: type: object maxItems: 8 type: array + x-kubernetes-list-type: atomic namespaces: default: from: Same @@ -3330,7 +4237,7 @@ spec: the Protocol field is "HTTPS" or "TLS". It is invalid to set this field if the Protocol field is "HTTP", "TCP", or "UDP". - The association of SNIs to Certificate defined in GatewayTLSConfig is + The association of SNIs to Certificate defined in ListenerTLSConfig is defined based on the Hostname field for this listener. The GatewayClass MUST use the longest matching SNI out of all @@ -3417,93 +4324,7 @@ spec: type: object maxItems: 64 type: array - frontendValidation: - description: |- - FrontendValidation holds configuration information for validating the frontend (client). - Setting this field will require clients to send a client certificate - required for validation during the TLS handshake. In browsers this may result in a dialog appearing - that requests a user to specify the client certificate. - The maximum depth of a certificate chain accepted in verification is Implementation specific. - - Support: Extended - properties: - caCertificateRefs: - description: |- - CACertificateRefs contains one or more references to - Kubernetes objects that contain TLS certificates of - the Certificate Authorities that can be used - as a trust anchor to validate the certificates presented by the client. - - A single CA certificate reference to a Kubernetes ConfigMap - has "Core" support. - Implementations MAY choose to support attaching multiple CA certificates to - a Listener, but this behavior is implementation-specific. - - Support: Core - A single reference to a Kubernetes ConfigMap - with the CA certificate in a key named `ca.crt`. - - Support: Implementation-specific (More than one reference, or other kinds - of resources). - - References to a resource in a different namespace are invalid UNLESS there - is a ReferenceGrant in the target namespace that allows the certificate - to be attached. If a ReferenceGrant does not allow this reference, the - "ResolvedRefs" condition MUST be set to False for this listener with the - "RefNotPermitted" reason. - items: - description: |- - ObjectReference identifies an API object including its namespace. - - The API object must be valid in the cluster; the Group and Kind must - be registered in the cluster for this reference to be valid. - - References to objects with invalid Group and Kind are not valid, and must - be rejected by the implementation, with appropriate Conditions set - on the containing object. - properties: - group: - description: |- - Group is the group of the referent. For example, "gateway.networking.k8s.io". - When set to the 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: - description: Kind is kind of the referent. For - example "ConfigMap" or "Service". - 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 referenced object. 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 - required: - - group - - kind - - name - type: object - maxItems: 8 - minItems: 1 - type: array - type: object + x-kubernetes-list-type: atomic mode: default: Terminate description: |- @@ -3582,6 +4403,366 @@ spec: rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + tls: + description: |- + TLS specifies frontend and backend tls configuration for entire gateway. + + Support: Extended + properties: + backend: + description: |- + Backend describes TLS configuration for gateway when connecting + to backends. + + Note that this contains only details for the Gateway as a TLS client, + and does _not_ imply behavior about how to choose which backend should + get a TLS connection. That is determined by the presence of a BackendTLSPolicy. + + Support: Core + properties: + clientCertificateRef: + description: |- + ClientCertificateRef is a reference to an object that contains a Client + Certificate and the associated private key. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + ClientCertificateRef can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core + 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: Secret + description: Kind is kind of the referent. For example + "Secret". + 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 referenced object. 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 + required: + - name + type: object + type: object + frontend: + description: |- + Frontend describes TLS config when client connects to Gateway. + Support: Core + properties: + default: + description: |- + Default specifies the default client certificate validation configuration + for all Listeners handling HTTPS traffic, unless a per-port configuration + is defined. + + support: Core + properties: + validation: + description: |- + Validation holds configuration information for validating the frontend (client). + Setting this field will result in mutual authentication when connecting to the gateway. + In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Core + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one certificate in a ConfigMap + with different keys or more than one reference, or other kinds of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the 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: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + 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 referenced object. 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 + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + mode: + default: AllowValidOnly + description: |- + FrontendValidationMode defines the mode for validating the client certificate. + There are two possible modes: + + - AllowValidOnly: In this mode, the gateway will accept connections only if + the client presents a valid certificate. This certificate must successfully + pass validation against the CA certificates specified in `CACertificateRefs`. + - AllowInsecureFallback: In this mode, the gateway will accept connections + even if the client certificate is not presented or fails verification. + + This approach delegates client authorization to the backend and introduce + a significant security risk. It should be used in testing environments or + on a temporary basis in non-testing environments. + + Defaults to AllowValidOnly. + + Support: Core + enum: + - AllowValidOnly + - AllowInsecureFallback + type: string + required: + - caCertificateRefs + type: object + type: object + perPort: + description: |- + PerPort specifies tls configuration assigned per port. + Per port configuration is optional. Once set this configuration overrides + the default configuration for all Listeners handling HTTPS traffic + that match this port. + Each override port requires a unique TLS configuration. + + support: Core + items: + properties: + port: + description: |- + The Port indicates the Port Number to which the TLS configuration will be + applied. This configuration will be applied to all Listeners handling HTTPS + traffic that match this port. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + tls: + description: |- + TLS store the configuration that will be applied to all Listeners handling + HTTPS traffic and matching given port. + + Support: Core + properties: + validation: + description: |- + Validation holds configuration information for validating the frontend (client). + Setting this field will result in mutual authentication when connecting to the gateway. + In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Core + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one certificate in a ConfigMap + with different keys or more than one reference, or other kinds of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the 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: + description: Kind is kind of the referent. + For example "ConfigMap" or "Service". + 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 referenced object. 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 + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + mode: + default: AllowValidOnly + description: |- + FrontendValidationMode defines the mode for validating the client certificate. + There are two possible modes: + + - AllowValidOnly: In this mode, the gateway will accept connections only if + the client presents a valid certificate. This certificate must successfully + pass validation against the CA certificates specified in `CACertificateRefs`. + - AllowInsecureFallback: In this mode, the gateway will accept connections + even if the client certificate is not presented or fails verification. + + This approach delegates client authorization to the backend and introduce + a significant security risk. It should be used in testing environments or + on a temporary basis in non-testing environments. + + Defaults to AllowValidOnly. + + Support: Core + enum: + - AllowValidOnly + - AllowInsecureFallback + type: string + required: + - caCertificateRefs + type: object + type: object + required: + - port + - tls + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: Port for TLS configuration must be unique within + the Gateway + rule: self.all(t1, self.exists_one(t2, t1.port == t2.port)) + required: + - default + type: object + type: object required: - gatewayClassName - listeners @@ -3656,6 +4837,7 @@ spec: true' maxItems: 16 type: array + x-kubernetes-list-type: atomic conditions: default: - lastTransitionTime: "1970-01-01T00:00:00Z" @@ -3869,6 +5051,7 @@ spec: type: object maxItems: 8 type: array + x-kubernetes-list-type: atomic required: - attachedRoutes - conditions @@ -3903,9 +5086,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: grpcroutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -4051,6 +5233,7 @@ spec: type: string maxItems: 16 type: array + x-kubernetes-list-type: atomic parentRefs: description: |- ParentRefs references the resources (usually Gateways) that a Route wants @@ -4263,6 +5446,7 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent @@ -4884,6 +6068,7 @@ spec: rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: RequestHeaderModifier filter cannot be repeated rule: self.filter(f, f.type == 'RequestHeaderModifier').size() @@ -4980,6 +6165,7 @@ spec: ? has(self.port) : true' maxItems: 16 type: array + x-kubernetes-list-type: atomic filters: description: |- Filters define the filters that are applied to requests that match @@ -5530,6 +6716,7 @@ spec: rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: RequestHeaderModifier filter cannot be repeated rule: self.filter(f, f.type == 'RequestHeaderModifier').size() @@ -5707,6 +6894,7 @@ spec: type: object maxItems: 64 type: array + x-kubernetes-list-type: atomic name: description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. @@ -5808,6 +6996,7 @@ spec: type: object maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: While 16 rules and 64 matches per rule are allowed, the total number of matches across all rules in a route must be less @@ -5832,6 +7021,24 @@ spec: - message: Rule name must be unique within the route rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string type: object status: description: Status defines the current state of GRPCRoute. @@ -6096,14 +7303,18 @@ spec: - name type: object required: + - conditions - controllerName - parentRef type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic required: - parents type: object + required: + - spec type: object served: true storage: true @@ -6124,9 +7335,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: httproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -6252,6 +7462,7 @@ spec: type: string maxItems: 16 type: array + x-kubernetes-list-type: atomic parentRefs: description: |- ParentRefs references the resources (usually Gateways) that a Route wants @@ -6464,6 +7675,7 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent @@ -6589,16 +7801,14 @@ spec: AllowCredentials indicates whether the actual cross-origin request allows to include credentials. - The only valid value for the `Access-Control-Allow-Credentials` response - header is true (case-sensitive). + When set to true, the gateway will include the `Access-Control-Allow-Credentials` + response header with value true (case-sensitive). - If the credentials are not allowed in cross-origin requests, the gateway - will omit the header `Access-Control-Allow-Credentials` entirely rather - than setting its value to false. + When set to false or omitted the gateway will omit the header + `Access-Control-Allow-Credentials` entirely (this is the standard CORS + behavior). Support: Extended - enum: - - true type: boolean allowHeaders: description: |- @@ -6625,9 +7835,9 @@ spec: A wildcard indicates that the requests with all HTTP headers are allowed. The `Access-Control-Allow-Headers` response header can only use `*` - wildcard as value when the `AllowCredentials` field is unspecified. + wildcard as value when the `AllowCredentials` field is false or omitted. - When the `AllowCredentials` field is specified and `AllowHeaders` field + When the `AllowCredentials` field is true and `AllowHeaders` field specified with the `*` wildcard, the gateway must specify one or more HTTP headers in the value of the `Access-Control-Allow-Headers` response header. The value of the header `Access-Control-Allow-Headers` is same as @@ -6688,9 +7898,9 @@ spec: side. The `Access-Control-Allow-Methods` response header can only use `*` - wildcard as value when the `AllowCredentials` field is unspecified. + wildcard as value when the `AllowCredentials` field is false or omitted. - When the `AllowCredentials` field is specified and `AllowMethods` field + When the `AllowCredentials` field is true and `AllowMethods` field specified with the `*` wildcard, the gateway must specify one HTTP method in the value of the Access-Control-Allow-Methods response header. The value of the header `Access-Control-Allow-Methods` is same as the @@ -6766,9 +7976,9 @@ spec: Therefore, the client doesn't attempt the actual cross-origin request. The `Access-Control-Allow-Origin` response header can only use `*` - wildcard as value when the `AllowCredentials` field is unspecified. + wildcard as value when the `AllowCredentials` field is false or omitted. - When the `AllowCredentials` field is specified and `AllowOrigins` field + When the `AllowCredentials` field is true and `AllowOrigins` field specified with the `*` wildcard, the gateway must return a single origin in the value of the `Access-Control-Allow-Origin` response header, instead of specifying the `*` wildcard. The value of the header @@ -6778,18 +7988,22 @@ spec: Support: Extended items: description: |- - The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and - encoding rules specified in RFC3986. The AbsoluteURI MUST include both a - scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that - include an authority MUST include a fully qualified domain name or + The CORSOrigin MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The CORSOrigin MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part, or it should be a single '*' character. + URIs that include an authority MUST include a fully qualified domain name or IP address as the host. maxLength: 253 minLength: 1 - pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + pattern: (^\*$)|(^([a-zA-Z][a-zA-Z0-9+\-.]+):\/\/([^:/?#]+)(:([0-9]{1,5}))?$) type: string maxItems: 64 type: array x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowOrigins cannot contain '*' alongside + other origins + rule: '!(''*'' in self && self.size() > 1)' exposeHeaders: description: |- ExposeHeaders indicates which HTTP response headers can be exposed @@ -6819,8 +8033,7 @@ spec: A wildcard indicates that the responses with all HTTP headers are exposed to clients. The `Access-Control-Expose-Headers` response header can only - use `*` wildcard as value when the `AllowCredentials` field is - unspecified. + use `*` wildcard as value when the `AllowCredentials` field is false or omitted. Support: Extended items: @@ -6895,6 +8108,253 @@ spec: - kind - name type: object + externalAuth: + description: |- + ExternalAuth configures settings related to sending request details + to an external auth service. The external service MUST authenticate + the request, and MAY authorize the request as well. + + If there is any problem communicating with the external service, + this filter MUST fail closed. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef is a reference to a backend to send authorization + requests to. + + The backend must speak the selected protocol (GRPC or HTTP) on the + referenced port. + + If the backend service requires TLS, use BackendTLSPolicy to tell the + implementation to supply the TLS details to be used to connect to that + backend. + 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' + forwardBody: + description: |- + ForwardBody controls if requests to the authorization server should include + the body of the client request; and if so, how big that body is allowed + to be. + + It is expected that implementations will buffer the request body up to + `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a + 4xx series error (413 or 403 are common examples), and fail processing + of the filter. + + If unset, or `forwardBody.maxSize` is set to `0`, then the body will not + be forwarded. + + Feature Name: HTTPRouteExternalAuthForwardBody + properties: + maxSize: + description: |- + MaxSize specifies how large in bytes the largest body that will be buffered + and sent to the authorization server. If the body size is larger than + `maxSize`, then the body sent to the authorization server must be + truncated to `maxSize` bytes. + + Experimental note: This behavior needs to be checked against + various dataplanes; it may need to be changed. + See https://github.com/kubernetes-sigs/gateway-api/pull/4001#discussion_r2291405746 + for more. + + If 0, the body will not be sent to the authorization server. + type: integer + type: object + grpc: + description: |- + GRPCAuthConfig contains configuration for communication with ext_authz + protocol-speaking backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what headers from the client request + will be sent to the authorization server. + + If this list is empty, then all headers must be sent. + + If the list has entries, only those entries must be sent. + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + http: + description: |- + HTTPAuthConfig contains configuration for communication with HTTP-speaking + backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what additional headers from the client request + will be sent to the authorization server. + + The following headers must always be sent to the authorization server, + regardless of this setting: + + * `Host` + * `Method` + * `Path` + * `Content-Length` + * `Authorization` + + If this list is empty, then only those headers must be sent. + + Note that `Content-Length` has a special behavior, in that the length + sent must be correct for the actual request to the external authorization + server - that is, it must reflect the actual number of bytes sent in the + body of the request to the authorization server. + + So if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set + to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set + to anything other than `0`, then the `Content-Length` of the authorization + request must be set to the actual number of bytes forwarded. + items: + type: string + type: array + x-kubernetes-list-type: set + allowedResponseHeaders: + description: |- + AllowedResponseHeaders specifies what headers from the authorization response + will be copied into the request to the backend. + + If this list is empty, then all headers from the authorization server + except Authority or Host must be copied. + items: + type: string + type: array + x-kubernetes-list-type: set + path: + description: |- + Path sets the prefix that paths from the client request will have added + when forwarded to the authorization server. + + When empty or unspecified, no prefix is added. + + Valid values are the same as the "value" regex for path values in the `match` + stanza, and the validation regex will screen out invalid paths in the same way. + Even with the validation, implementations MUST sanitize this input before using it + directly. + maxLength: 1024 + pattern: ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$ + type: string + type: object + protocol: + description: |- + ExternalAuthProtocol describes which protocol to use when communicating with an + ext_authz authorization server. + + When this is set to GRPC, each backend must use the Envoy ext_authz protocol + on the port specified in `backendRefs`. Requests and responses are defined + in the protobufs explained at: + https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto + + When this is set to HTTP, each backend must respond with a `200` status + code in on a successful authorization. Any other code is considered + an authorization failure. + + Feature Names: + GRPC Support - HTTPRouteExternalAuthGRPC + HTTP Support - HTTPRouteExternalAuthHTTP + enum: + - HTTP + - GRPC + type: string + required: + - backendRef + - protocol + type: object + x-kubernetes-validations: + - message: grpc must be specified when protocol + is set to 'GRPC' + rule: 'self.protocol == ''GRPC'' ? has(self.grpc) + : true' + - message: protocol must be 'GRPC' when grpc is + set + rule: 'has(self.grpc) ? self.protocol == ''GRPC'' + : true' + - message: http must be specified when protocol + is set to 'HTTP' + rule: 'self.protocol == ''HTTP'' ? has(self.http) + : true' + - message: protocol must be 'HTTP' when http is + set + rule: 'has(self.http) ? self.protocol == ''HTTP'' + : true' requestHeaderModifier: description: |- RequestHeaderModifier defines a schema for a filter that modifies request @@ -7508,6 +8968,7 @@ spec: - URLRewrite - ExtensionRef - CORS + - ExternalAuth type: string urlRewrite: description: |- @@ -7645,13 +9106,16 @@ spec: rule: '!(has(self.cors) && self.type != ''CORS'')' - message: filter.cors must be specified for CORS filter.type rule: '!(!has(self.cors) && self.type == ''CORS'')' + - message: filter.externalAuth must be nil if the filter.type + is not ExternalAuth + rule: '!(has(self.externalAuth) && self.type != ''ExternalAuth'')' + - message: filter.externalAuth must be specified for + ExternalAuth filter.type + rule: '!(!has(self.externalAuth) && self.type == ''ExternalAuth'')' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - - message: May specify either httpRouteFilterRequestRedirect - or httpRouteFilterRequestRewrite, but not both - rule: '!(self.exists(f, f.type == ''RequestRedirect'') - && self.exists(f, f.type == ''URLRewrite''))' - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both rule: '!(self.exists(f, f.type == ''RequestRedirect'') @@ -7757,6 +9221,7 @@ spec: ? has(self.port) : true' maxItems: 16 type: array + x-kubernetes-list-type: atomic filters: description: |- Filters define the filters that are applied to requests that match @@ -7816,16 +9281,14 @@ spec: AllowCredentials indicates whether the actual cross-origin request allows to include credentials. - The only valid value for the `Access-Control-Allow-Credentials` response - header is true (case-sensitive). + When set to true, the gateway will include the `Access-Control-Allow-Credentials` + response header with value true (case-sensitive). - If the credentials are not allowed in cross-origin requests, the gateway - will omit the header `Access-Control-Allow-Credentials` entirely rather - than setting its value to false. + When set to false or omitted the gateway will omit the header + `Access-Control-Allow-Credentials` entirely (this is the standard CORS + behavior). Support: Extended - enum: - - true type: boolean allowHeaders: description: |- @@ -7852,9 +9315,9 @@ spec: A wildcard indicates that the requests with all HTTP headers are allowed. The `Access-Control-Allow-Headers` response header can only use `*` - wildcard as value when the `AllowCredentials` field is unspecified. + wildcard as value when the `AllowCredentials` field is false or omitted. - When the `AllowCredentials` field is specified and `AllowHeaders` field + When the `AllowCredentials` field is true and `AllowHeaders` field specified with the `*` wildcard, the gateway must specify one or more HTTP headers in the value of the `Access-Control-Allow-Headers` response header. The value of the header `Access-Control-Allow-Headers` is same as @@ -7915,9 +9378,9 @@ spec: side. The `Access-Control-Allow-Methods` response header can only use `*` - wildcard as value when the `AllowCredentials` field is unspecified. + wildcard as value when the `AllowCredentials` field is false or omitted. - When the `AllowCredentials` field is specified and `AllowMethods` field + When the `AllowCredentials` field is true and `AllowMethods` field specified with the `*` wildcard, the gateway must specify one HTTP method in the value of the Access-Control-Allow-Methods response header. The value of the header `Access-Control-Allow-Methods` is same as the @@ -7993,9 +9456,9 @@ spec: Therefore, the client doesn't attempt the actual cross-origin request. The `Access-Control-Allow-Origin` response header can only use `*` - wildcard as value when the `AllowCredentials` field is unspecified. + wildcard as value when the `AllowCredentials` field is false or omitted. - When the `AllowCredentials` field is specified and `AllowOrigins` field + When the `AllowCredentials` field is true and `AllowOrigins` field specified with the `*` wildcard, the gateway must return a single origin in the value of the `Access-Control-Allow-Origin` response header, instead of specifying the `*` wildcard. The value of the header @@ -8005,18 +9468,22 @@ spec: Support: Extended items: description: |- - The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and - encoding rules specified in RFC3986. The AbsoluteURI MUST include both a - scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that - include an authority MUST include a fully qualified domain name or + The CORSOrigin MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The CORSOrigin MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part, or it should be a single '*' character. + URIs that include an authority MUST include a fully qualified domain name or IP address as the host. maxLength: 253 minLength: 1 - pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + pattern: (^\*$)|(^([a-zA-Z][a-zA-Z0-9+\-.]+):\/\/([^:/?#]+)(:([0-9]{1,5}))?$) type: string maxItems: 64 type: array x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowOrigins cannot contain '*' alongside + other origins + rule: '!(''*'' in self && self.size() > 1)' exposeHeaders: description: |- ExposeHeaders indicates which HTTP response headers can be exposed @@ -8046,8 +9513,7 @@ spec: A wildcard indicates that the responses with all HTTP headers are exposed to clients. The `Access-Control-Expose-Headers` response header can only - use `*` wildcard as value when the `AllowCredentials` field is - unspecified. + use `*` wildcard as value when the `AllowCredentials` field is false or omitted. Support: Extended items: @@ -8122,6 +9588,251 @@ spec: - kind - name type: object + externalAuth: + description: |- + ExternalAuth configures settings related to sending request details + to an external auth service. The external service MUST authenticate + the request, and MAY authorize the request as well. + + If there is any problem communicating with the external service, + this filter MUST fail closed. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef is a reference to a backend to send authorization + requests to. + + The backend must speak the selected protocol (GRPC or HTTP) on the + referenced port. + + If the backend service requires TLS, use BackendTLSPolicy to tell the + implementation to supply the TLS details to be used to connect to that + backend. + 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' + forwardBody: + description: |- + ForwardBody controls if requests to the authorization server should include + the body of the client request; and if so, how big that body is allowed + to be. + + It is expected that implementations will buffer the request body up to + `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a + 4xx series error (413 or 403 are common examples), and fail processing + of the filter. + + If unset, or `forwardBody.maxSize` is set to `0`, then the body will not + be forwarded. + + Feature Name: HTTPRouteExternalAuthForwardBody + properties: + maxSize: + description: |- + MaxSize specifies how large in bytes the largest body that will be buffered + and sent to the authorization server. If the body size is larger than + `maxSize`, then the body sent to the authorization server must be + truncated to `maxSize` bytes. + + Experimental note: This behavior needs to be checked against + various dataplanes; it may need to be changed. + See https://github.com/kubernetes-sigs/gateway-api/pull/4001#discussion_r2291405746 + for more. + + If 0, the body will not be sent to the authorization server. + type: integer + type: object + grpc: + description: |- + GRPCAuthConfig contains configuration for communication with ext_authz + protocol-speaking backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what headers from the client request + will be sent to the authorization server. + + If this list is empty, then all headers must be sent. + + If the list has entries, only those entries must be sent. + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + http: + description: |- + HTTPAuthConfig contains configuration for communication with HTTP-speaking + backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what additional headers from the client request + will be sent to the authorization server. + + The following headers must always be sent to the authorization server, + regardless of this setting: + + * `Host` + * `Method` + * `Path` + * `Content-Length` + * `Authorization` + + If this list is empty, then only those headers must be sent. + + Note that `Content-Length` has a special behavior, in that the length + sent must be correct for the actual request to the external authorization + server - that is, it must reflect the actual number of bytes sent in the + body of the request to the authorization server. + + So if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set + to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set + to anything other than `0`, then the `Content-Length` of the authorization + request must be set to the actual number of bytes forwarded. + items: + type: string + type: array + x-kubernetes-list-type: set + allowedResponseHeaders: + description: |- + AllowedResponseHeaders specifies what headers from the authorization response + will be copied into the request to the backend. + + If this list is empty, then all headers from the authorization server + except Authority or Host must be copied. + items: + type: string + type: array + x-kubernetes-list-type: set + path: + description: |- + Path sets the prefix that paths from the client request will have added + when forwarded to the authorization server. + + When empty or unspecified, no prefix is added. + + Valid values are the same as the "value" regex for path values in the `match` + stanza, and the validation regex will screen out invalid paths in the same way. + Even with the validation, implementations MUST sanitize this input before using it + directly. + maxLength: 1024 + pattern: ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$ + type: string + type: object + protocol: + description: |- + ExternalAuthProtocol describes which protocol to use when communicating with an + ext_authz authorization server. + + When this is set to GRPC, each backend must use the Envoy ext_authz protocol + on the port specified in `backendRefs`. Requests and responses are defined + in the protobufs explained at: + https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto + + When this is set to HTTP, each backend must respond with a `200` status + code in on a successful authorization. Any other code is considered + an authorization failure. + + Feature Names: + GRPC Support - HTTPRouteExternalAuthGRPC + HTTP Support - HTTPRouteExternalAuthHTTP + enum: + - HTTP + - GRPC + type: string + required: + - backendRef + - protocol + type: object + x-kubernetes-validations: + - message: grpc must be specified when protocol is set + to 'GRPC' + rule: 'self.protocol == ''GRPC'' ? has(self.grpc) : + true' + - message: protocol must be 'GRPC' when grpc is set + rule: 'has(self.grpc) ? self.protocol == ''GRPC'' : + true' + - message: http must be specified when protocol is set + to 'HTTP' + rule: 'self.protocol == ''HTTP'' ? has(self.http) : + true' + - message: protocol must be 'HTTP' when http is set + rule: 'has(self.http) ? self.protocol == ''HTTP'' : + true' requestHeaderModifier: description: |- RequestHeaderModifier defines a schema for a filter that modifies request @@ -8731,6 +10442,7 @@ spec: - URLRewrite - ExtensionRef - CORS + - ExternalAuth type: string urlRewrite: description: |- @@ -8865,8 +10577,15 @@ spec: rule: '!(has(self.cors) && self.type != ''CORS'')' - message: filter.cors must be specified for CORS filter.type rule: '!(!has(self.cors) && self.type == ''CORS'')' + - message: filter.externalAuth must be nil if the filter.type + is not ExternalAuth + rule: '!(has(self.externalAuth) && self.type != ''ExternalAuth'')' + - message: filter.externalAuth must be specified for ExternalAuth + filter.type + rule: '!(!has(self.externalAuth) && self.type == ''ExternalAuth'')' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both @@ -9178,6 +10897,7 @@ spec: type: object maxItems: 64 type: array + x-kubernetes-list-type: atomic name: description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. @@ -9273,6 +10993,7 @@ spec: minimum: 400 type: integer type: array + x-kubernetes-list-type: atomic type: object sessionPersistence: description: |- @@ -9468,6 +11189,7 @@ spec: != ''PathPrefix'') ? false : true) : true' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: While 16 rules and 64 matches per rule are allowed, the total number of matches across all rules in a route must be less @@ -9486,6 +11208,24 @@ spec: - message: Rule name must be unique within the route rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string type: object status: description: Status defines the current state of HTTPRoute. @@ -9750,11 +11490,13 @@ spec: - name type: object required: + - conditions - controllerName - parentRef type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic required: - parents type: object @@ -9878,6 +11620,7 @@ spec: type: string maxItems: 16 type: array + x-kubernetes-list-type: atomic parentRefs: description: |- ParentRefs references the resources (usually Gateways) that a Route wants @@ -10090,6 +11833,7 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent @@ -10215,16 +11959,14 @@ spec: AllowCredentials indicates whether the actual cross-origin request allows to include credentials. - The only valid value for the `Access-Control-Allow-Credentials` response - header is true (case-sensitive). + When set to true, the gateway will include the `Access-Control-Allow-Credentials` + response header with value true (case-sensitive). - If the credentials are not allowed in cross-origin requests, the gateway - will omit the header `Access-Control-Allow-Credentials` entirely rather - than setting its value to false. + When set to false or omitted the gateway will omit the header + `Access-Control-Allow-Credentials` entirely (this is the standard CORS + behavior). Support: Extended - enum: - - true type: boolean allowHeaders: description: |- @@ -10251,9 +11993,9 @@ spec: A wildcard indicates that the requests with all HTTP headers are allowed. The `Access-Control-Allow-Headers` response header can only use `*` - wildcard as value when the `AllowCredentials` field is unspecified. + wildcard as value when the `AllowCredentials` field is false or omitted. - When the `AllowCredentials` field is specified and `AllowHeaders` field + When the `AllowCredentials` field is true and `AllowHeaders` field specified with the `*` wildcard, the gateway must specify one or more HTTP headers in the value of the `Access-Control-Allow-Headers` response header. The value of the header `Access-Control-Allow-Headers` is same as @@ -10314,9 +12056,9 @@ spec: side. The `Access-Control-Allow-Methods` response header can only use `*` - wildcard as value when the `AllowCredentials` field is unspecified. + wildcard as value when the `AllowCredentials` field is false or omitted. - When the `AllowCredentials` field is specified and `AllowMethods` field + When the `AllowCredentials` field is true and `AllowMethods` field specified with the `*` wildcard, the gateway must specify one HTTP method in the value of the Access-Control-Allow-Methods response header. The value of the header `Access-Control-Allow-Methods` is same as the @@ -10392,9 +12134,9 @@ spec: Therefore, the client doesn't attempt the actual cross-origin request. The `Access-Control-Allow-Origin` response header can only use `*` - wildcard as value when the `AllowCredentials` field is unspecified. + wildcard as value when the `AllowCredentials` field is false or omitted. - When the `AllowCredentials` field is specified and `AllowOrigins` field + When the `AllowCredentials` field is true and `AllowOrigins` field specified with the `*` wildcard, the gateway must return a single origin in the value of the `Access-Control-Allow-Origin` response header, instead of specifying the `*` wildcard. The value of the header @@ -10404,18 +12146,22 @@ spec: Support: Extended items: description: |- - The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and - encoding rules specified in RFC3986. The AbsoluteURI MUST include both a - scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that - include an authority MUST include a fully qualified domain name or + The CORSOrigin MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The CORSOrigin MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part, or it should be a single '*' character. + URIs that include an authority MUST include a fully qualified domain name or IP address as the host. maxLength: 253 minLength: 1 - pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + pattern: (^\*$)|(^([a-zA-Z][a-zA-Z0-9+\-.]+):\/\/([^:/?#]+)(:([0-9]{1,5}))?$) type: string maxItems: 64 type: array x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowOrigins cannot contain '*' alongside + other origins + rule: '!(''*'' in self && self.size() > 1)' exposeHeaders: description: |- ExposeHeaders indicates which HTTP response headers can be exposed @@ -10445,8 +12191,7 @@ spec: A wildcard indicates that the responses with all HTTP headers are exposed to clients. The `Access-Control-Expose-Headers` response header can only - use `*` wildcard as value when the `AllowCredentials` field is - unspecified. + use `*` wildcard as value when the `AllowCredentials` field is false or omitted. Support: Extended items: @@ -10521,6 +12266,253 @@ spec: - kind - name type: object + externalAuth: + description: |- + ExternalAuth configures settings related to sending request details + to an external auth service. The external service MUST authenticate + the request, and MAY authorize the request as well. + + If there is any problem communicating with the external service, + this filter MUST fail closed. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef is a reference to a backend to send authorization + requests to. + + The backend must speak the selected protocol (GRPC or HTTP) on the + referenced port. + + If the backend service requires TLS, use BackendTLSPolicy to tell the + implementation to supply the TLS details to be used to connect to that + backend. + 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' + forwardBody: + description: |- + ForwardBody controls if requests to the authorization server should include + the body of the client request; and if so, how big that body is allowed + to be. + + It is expected that implementations will buffer the request body up to + `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a + 4xx series error (413 or 403 are common examples), and fail processing + of the filter. + + If unset, or `forwardBody.maxSize` is set to `0`, then the body will not + be forwarded. + + Feature Name: HTTPRouteExternalAuthForwardBody + properties: + maxSize: + description: |- + MaxSize specifies how large in bytes the largest body that will be buffered + and sent to the authorization server. If the body size is larger than + `maxSize`, then the body sent to the authorization server must be + truncated to `maxSize` bytes. + + Experimental note: This behavior needs to be checked against + various dataplanes; it may need to be changed. + See https://github.com/kubernetes-sigs/gateway-api/pull/4001#discussion_r2291405746 + for more. + + If 0, the body will not be sent to the authorization server. + type: integer + type: object + grpc: + description: |- + GRPCAuthConfig contains configuration for communication with ext_authz + protocol-speaking backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what headers from the client request + will be sent to the authorization server. + + If this list is empty, then all headers must be sent. + + If the list has entries, only those entries must be sent. + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + http: + description: |- + HTTPAuthConfig contains configuration for communication with HTTP-speaking + backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what additional headers from the client request + will be sent to the authorization server. + + The following headers must always be sent to the authorization server, + regardless of this setting: + + * `Host` + * `Method` + * `Path` + * `Content-Length` + * `Authorization` + + If this list is empty, then only those headers must be sent. + + Note that `Content-Length` has a special behavior, in that the length + sent must be correct for the actual request to the external authorization + server - that is, it must reflect the actual number of bytes sent in the + body of the request to the authorization server. + + So if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set + to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set + to anything other than `0`, then the `Content-Length` of the authorization + request must be set to the actual number of bytes forwarded. + items: + type: string + type: array + x-kubernetes-list-type: set + allowedResponseHeaders: + description: |- + AllowedResponseHeaders specifies what headers from the authorization response + will be copied into the request to the backend. + + If this list is empty, then all headers from the authorization server + except Authority or Host must be copied. + items: + type: string + type: array + x-kubernetes-list-type: set + path: + description: |- + Path sets the prefix that paths from the client request will have added + when forwarded to the authorization server. + + When empty or unspecified, no prefix is added. + + Valid values are the same as the "value" regex for path values in the `match` + stanza, and the validation regex will screen out invalid paths in the same way. + Even with the validation, implementations MUST sanitize this input before using it + directly. + maxLength: 1024 + pattern: ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$ + type: string + type: object + protocol: + description: |- + ExternalAuthProtocol describes which protocol to use when communicating with an + ext_authz authorization server. + + When this is set to GRPC, each backend must use the Envoy ext_authz protocol + on the port specified in `backendRefs`. Requests and responses are defined + in the protobufs explained at: + https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto + + When this is set to HTTP, each backend must respond with a `200` status + code in on a successful authorization. Any other code is considered + an authorization failure. + + Feature Names: + GRPC Support - HTTPRouteExternalAuthGRPC + HTTP Support - HTTPRouteExternalAuthHTTP + enum: + - HTTP + - GRPC + type: string + required: + - backendRef + - protocol + type: object + x-kubernetes-validations: + - message: grpc must be specified when protocol + is set to 'GRPC' + rule: 'self.protocol == ''GRPC'' ? has(self.grpc) + : true' + - message: protocol must be 'GRPC' when grpc is + set + rule: 'has(self.grpc) ? self.protocol == ''GRPC'' + : true' + - message: http must be specified when protocol + is set to 'HTTP' + rule: 'self.protocol == ''HTTP'' ? has(self.http) + : true' + - message: protocol must be 'HTTP' when http is + set + rule: 'has(self.http) ? self.protocol == ''HTTP'' + : true' requestHeaderModifier: description: |- RequestHeaderModifier defines a schema for a filter that modifies request @@ -11134,6 +13126,7 @@ spec: - URLRewrite - ExtensionRef - CORS + - ExternalAuth type: string urlRewrite: description: |- @@ -11271,13 +13264,16 @@ spec: rule: '!(has(self.cors) && self.type != ''CORS'')' - message: filter.cors must be specified for CORS filter.type rule: '!(!has(self.cors) && self.type == ''CORS'')' + - message: filter.externalAuth must be nil if the filter.type + is not ExternalAuth + rule: '!(has(self.externalAuth) && self.type != ''ExternalAuth'')' + - message: filter.externalAuth must be specified for + ExternalAuth filter.type + rule: '!(!has(self.externalAuth) && self.type == ''ExternalAuth'')' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - - message: May specify either httpRouteFilterRequestRedirect - or httpRouteFilterRequestRewrite, but not both - rule: '!(self.exists(f, f.type == ''RequestRedirect'') - && self.exists(f, f.type == ''URLRewrite''))' - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both rule: '!(self.exists(f, f.type == ''RequestRedirect'') @@ -11383,6 +13379,7 @@ spec: ? has(self.port) : true' maxItems: 16 type: array + x-kubernetes-list-type: atomic filters: description: |- Filters define the filters that are applied to requests that match @@ -11442,16 +13439,14 @@ spec: AllowCredentials indicates whether the actual cross-origin request allows to include credentials. - The only valid value for the `Access-Control-Allow-Credentials` response - header is true (case-sensitive). + When set to true, the gateway will include the `Access-Control-Allow-Credentials` + response header with value true (case-sensitive). - If the credentials are not allowed in cross-origin requests, the gateway - will omit the header `Access-Control-Allow-Credentials` entirely rather - than setting its value to false. + When set to false or omitted the gateway will omit the header + `Access-Control-Allow-Credentials` entirely (this is the standard CORS + behavior). Support: Extended - enum: - - true type: boolean allowHeaders: description: |- @@ -11478,9 +13473,9 @@ spec: A wildcard indicates that the requests with all HTTP headers are allowed. The `Access-Control-Allow-Headers` response header can only use `*` - wildcard as value when the `AllowCredentials` field is unspecified. + wildcard as value when the `AllowCredentials` field is false or omitted. - When the `AllowCredentials` field is specified and `AllowHeaders` field + When the `AllowCredentials` field is true and `AllowHeaders` field specified with the `*` wildcard, the gateway must specify one or more HTTP headers in the value of the `Access-Control-Allow-Headers` response header. The value of the header `Access-Control-Allow-Headers` is same as @@ -11541,9 +13536,9 @@ spec: side. The `Access-Control-Allow-Methods` response header can only use `*` - wildcard as value when the `AllowCredentials` field is unspecified. + wildcard as value when the `AllowCredentials` field is false or omitted. - When the `AllowCredentials` field is specified and `AllowMethods` field + When the `AllowCredentials` field is true and `AllowMethods` field specified with the `*` wildcard, the gateway must specify one HTTP method in the value of the Access-Control-Allow-Methods response header. The value of the header `Access-Control-Allow-Methods` is same as the @@ -11619,9 +13614,9 @@ spec: Therefore, the client doesn't attempt the actual cross-origin request. The `Access-Control-Allow-Origin` response header can only use `*` - wildcard as value when the `AllowCredentials` field is unspecified. + wildcard as value when the `AllowCredentials` field is false or omitted. - When the `AllowCredentials` field is specified and `AllowOrigins` field + When the `AllowCredentials` field is true and `AllowOrigins` field specified with the `*` wildcard, the gateway must return a single origin in the value of the `Access-Control-Allow-Origin` response header, instead of specifying the `*` wildcard. The value of the header @@ -11631,18 +13626,22 @@ spec: Support: Extended items: description: |- - The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and - encoding rules specified in RFC3986. The AbsoluteURI MUST include both a - scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that - include an authority MUST include a fully qualified domain name or + The CORSOrigin MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The CORSOrigin MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part, or it should be a single '*' character. + URIs that include an authority MUST include a fully qualified domain name or IP address as the host. maxLength: 253 minLength: 1 - pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + pattern: (^\*$)|(^([a-zA-Z][a-zA-Z0-9+\-.]+):\/\/([^:/?#]+)(:([0-9]{1,5}))?$) type: string maxItems: 64 type: array x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowOrigins cannot contain '*' alongside + other origins + rule: '!(''*'' in self && self.size() > 1)' exposeHeaders: description: |- ExposeHeaders indicates which HTTP response headers can be exposed @@ -11672,8 +13671,7 @@ spec: A wildcard indicates that the responses with all HTTP headers are exposed to clients. The `Access-Control-Expose-Headers` response header can only - use `*` wildcard as value when the `AllowCredentials` field is - unspecified. + use `*` wildcard as value when the `AllowCredentials` field is false or omitted. Support: Extended items: @@ -11748,6 +13746,251 @@ spec: - kind - name type: object + externalAuth: + description: |- + ExternalAuth configures settings related to sending request details + to an external auth service. The external service MUST authenticate + the request, and MAY authorize the request as well. + + If there is any problem communicating with the external service, + this filter MUST fail closed. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef is a reference to a backend to send authorization + requests to. + + The backend must speak the selected protocol (GRPC or HTTP) on the + referenced port. + + If the backend service requires TLS, use BackendTLSPolicy to tell the + implementation to supply the TLS details to be used to connect to that + backend. + 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' + forwardBody: + description: |- + ForwardBody controls if requests to the authorization server should include + the body of the client request; and if so, how big that body is allowed + to be. + + It is expected that implementations will buffer the request body up to + `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a + 4xx series error (413 or 403 are common examples), and fail processing + of the filter. + + If unset, or `forwardBody.maxSize` is set to `0`, then the body will not + be forwarded. + + Feature Name: HTTPRouteExternalAuthForwardBody + properties: + maxSize: + description: |- + MaxSize specifies how large in bytes the largest body that will be buffered + and sent to the authorization server. If the body size is larger than + `maxSize`, then the body sent to the authorization server must be + truncated to `maxSize` bytes. + + Experimental note: This behavior needs to be checked against + various dataplanes; it may need to be changed. + See https://github.com/kubernetes-sigs/gateway-api/pull/4001#discussion_r2291405746 + for more. + + If 0, the body will not be sent to the authorization server. + type: integer + type: object + grpc: + description: |- + GRPCAuthConfig contains configuration for communication with ext_authz + protocol-speaking backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what headers from the client request + will be sent to the authorization server. + + If this list is empty, then all headers must be sent. + + If the list has entries, only those entries must be sent. + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + http: + description: |- + HTTPAuthConfig contains configuration for communication with HTTP-speaking + backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what additional headers from the client request + will be sent to the authorization server. + + The following headers must always be sent to the authorization server, + regardless of this setting: + + * `Host` + * `Method` + * `Path` + * `Content-Length` + * `Authorization` + + If this list is empty, then only those headers must be sent. + + Note that `Content-Length` has a special behavior, in that the length + sent must be correct for the actual request to the external authorization + server - that is, it must reflect the actual number of bytes sent in the + body of the request to the authorization server. + + So if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set + to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set + to anything other than `0`, then the `Content-Length` of the authorization + request must be set to the actual number of bytes forwarded. + items: + type: string + type: array + x-kubernetes-list-type: set + allowedResponseHeaders: + description: |- + AllowedResponseHeaders specifies what headers from the authorization response + will be copied into the request to the backend. + + If this list is empty, then all headers from the authorization server + except Authority or Host must be copied. + items: + type: string + type: array + x-kubernetes-list-type: set + path: + description: |- + Path sets the prefix that paths from the client request will have added + when forwarded to the authorization server. + + When empty or unspecified, no prefix is added. + + Valid values are the same as the "value" regex for path values in the `match` + stanza, and the validation regex will screen out invalid paths in the same way. + Even with the validation, implementations MUST sanitize this input before using it + directly. + maxLength: 1024 + pattern: ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$ + type: string + type: object + protocol: + description: |- + ExternalAuthProtocol describes which protocol to use when communicating with an + ext_authz authorization server. + + When this is set to GRPC, each backend must use the Envoy ext_authz protocol + on the port specified in `backendRefs`. Requests and responses are defined + in the protobufs explained at: + https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto + + When this is set to HTTP, each backend must respond with a `200` status + code in on a successful authorization. Any other code is considered + an authorization failure. + + Feature Names: + GRPC Support - HTTPRouteExternalAuthGRPC + HTTP Support - HTTPRouteExternalAuthHTTP + enum: + - HTTP + - GRPC + type: string + required: + - backendRef + - protocol + type: object + x-kubernetes-validations: + - message: grpc must be specified when protocol is set + to 'GRPC' + rule: 'self.protocol == ''GRPC'' ? has(self.grpc) : + true' + - message: protocol must be 'GRPC' when grpc is set + rule: 'has(self.grpc) ? self.protocol == ''GRPC'' : + true' + - message: http must be specified when protocol is set + to 'HTTP' + rule: 'self.protocol == ''HTTP'' ? has(self.http) : + true' + - message: protocol must be 'HTTP' when http is set + rule: 'has(self.http) ? self.protocol == ''HTTP'' : + true' requestHeaderModifier: description: |- RequestHeaderModifier defines a schema for a filter that modifies request @@ -12357,6 +14600,7 @@ spec: - URLRewrite - ExtensionRef - CORS + - ExternalAuth type: string urlRewrite: description: |- @@ -12491,8 +14735,15 @@ spec: rule: '!(has(self.cors) && self.type != ''CORS'')' - message: filter.cors must be specified for CORS filter.type rule: '!(!has(self.cors) && self.type == ''CORS'')' + - message: filter.externalAuth must be nil if the filter.type + is not ExternalAuth + rule: '!(has(self.externalAuth) && self.type != ''ExternalAuth'')' + - message: filter.externalAuth must be specified for ExternalAuth + filter.type + rule: '!(!has(self.externalAuth) && self.type == ''ExternalAuth'')' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both @@ -12804,6 +15055,7 @@ spec: type: object maxItems: 64 type: array + x-kubernetes-list-type: atomic name: description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. @@ -12899,6 +15151,7 @@ spec: minimum: 400 type: integer type: array + x-kubernetes-list-type: atomic type: object sessionPersistence: description: |- @@ -13094,6 +15347,7 @@ spec: != ''PathPrefix'') ? false : true) : true' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: While 16 rules and 64 matches per rule are allowed, the total number of matches across all rules in a route must be less @@ -13112,6 +15366,24 @@ spec: - message: Rule name must be unique within the route rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string type: object status: description: Status defines the current state of HTTPRoute. @@ -13376,11 +15648,13 @@ spec: - name type: object required: + - conditions - controllerName - parentRef type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic required: - parents type: object @@ -13406,9 +15680,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: referencegrants.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -13527,6 +15800,7 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic to: description: |- To describes the resources that may be referenced by the resources @@ -13576,6 +15850,7 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic required: - from - to @@ -13599,9 +15874,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: tcproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -13858,6 +16132,7 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent @@ -14022,6 +16297,7 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic name: description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. @@ -14031,14 +16307,35 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string + required: + - backendRefs type: object maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: Rule name must be unique within the route rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string required: - rules type: object @@ -14305,11 +16602,13 @@ spec: - name type: object required: + - conditions - controllerName - parentRef type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic required: - parents type: object @@ -14335,9 +16634,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: tlsroutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -14442,6 +16740,7 @@ spec: type: string maxItems: 16 type: array + x-kubernetes-list-type: atomic parentRefs: description: |- ParentRefs references the resources (usually Gateways) that a Route wants @@ -14654,6 +16953,7 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent @@ -14821,6 +17121,7 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic name: description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. @@ -14830,14 +17131,35 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string + required: + - backendRefs type: object maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: Rule name must be unique within the route rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string required: - rules type: object @@ -15104,11 +17426,810 @@ spec: - name type: object required: + - conditions - controllerName - parentRef type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha3 + schema: + openAPIV3Schema: + description: |- + The TLSRoute resource is similar to TCPRoute, but can be configured + to match against TLS-specific metadata. This allows more flexibility + in matching streams for a given TLS listener. + + If you need to forward traffic to a single target for a TLS listener, you + could choose to use a TCPRoute with a TLS listener. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TLSRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of SNI hostnames that should match against the + SNI attribute of TLS ClientHello message in TLS handshake. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed in SNI hostnames per RFC 6066. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and TLSRoute, there + must be at least one intersecting hostname for the TLSRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches TLSRoutes + that have specified at least one of `test.example.com` or + `*.example.com`. + * A Listener with `*.example.com` as the hostname matches TLSRoutes + that have specified at least one hostname that matches the Listener + hostname. For example, `test.example.com` and `*.example.com` would both + match. On the other hand, `example.com` and `test.example.net` would not + match. + + If both the Listener and TLSRoute have specified hostnames, any + TLSRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + TLSRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and TLSRoute have specified hostnames, and none + match with the criteria above, then the TLSRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + 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: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + 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. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of actions. + items: + description: TLSRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. If unspecified or invalid (refers to a nonexistent resource or + a Service with no endpoints), the rule performs no forwarding; if no + filters are specified that would result in a response being sent, the + underlying implementation must actively reject request attempts to this + backend, by rejecting the connection or returning a 500 status code. + Request rejections must respect weight; if an invalid backend is + requested to have 80% of requests, then 80% of requests must be rejected + instead. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Extended + items: + description: |- + BackendRef defines how a Route should forward a request to a Kubernetes + resource. + + 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. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + + + Note that when the BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. See the fields + where this struct is used for more information about the exact behavior. + 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 + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + 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: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - backendRefs + type: object + maxItems: 1 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string + required: + - hostnames + - rules + type: object + status: + description: Status defines the current state of TLSRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + 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: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + 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. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - conditions + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + x-kubernetes-list-type: atomic required: - parents type: object @@ -15134,9 +18255,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: udproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -15393,6 +18513,7 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent @@ -15557,6 +18678,7 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic name: description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. @@ -15566,14 +18688,35 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string + required: + - backendRefs type: object maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: Rule name must be unique within the route rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string required: - rules type: object @@ -15840,11 +18983,13 @@ spec: - name type: object required: + - conditions - controllerName - parentRef type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic required: - parents type: object @@ -15870,9 +19015,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null labels: gateway.networking.k8s.io/policy: Direct name: xbackendtrafficpolicies.gateway.networking.x-k8s.io @@ -16450,10 +19594,12 @@ spec: type: string required: - ancestorRef + - conditions - controllerName type: object maxItems: 16 type: array + x-kubernetes-list-type: atomic required: - ancestors type: object @@ -16479,9 +19625,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: xlistenersets.gateway.networking.x-k8s.io spec: group: gateway.networking.x-k8s.io @@ -16510,8 +19655,33 @@ spec: schema: openAPIV3Schema: description: |- - XListenerSet defines a set of additional listeners - to attach to an existing Gateway. + XListenerSet defines a set of additional listeners to attach to an existing Gateway. + This resource provides a mechanism to merge multiple listeners into a single Gateway. + + The parent Gateway must explicitly allow ListenerSet attachment through its + AllowedListeners configuration. By default, Gateways do not allow ListenerSet + attachment. + + Routes can attach to a ListenerSet by specifying it as a parentRef, and can + optionally target specific listeners using the sectionName field. + + Policy Attachment: + - Policies that attach to a ListenerSet apply to all listeners defined in that resource + - Policies do not impact listeners in the parent Gateway + - Different ListenerSets attached to the same Gateway can have different policies + - If an implementation cannot apply a policy to specific listeners, it should reject the policy + + ReferenceGrant Semantics: + - ReferenceGrants applied to a Gateway are not inherited by child ListenerSets + - ReferenceGrants applied to a ListenerSet do not grant permission to the parent Gateway's listeners + - A ListenerSet can reference secrets/backends in its own namespace without a ReferenceGrant + + Gateway Integration: + - The parent Gateway's status will include an "AttachedListenerSets" condition + - This condition will be: + - True: when AllowedListeners is set and at least one child ListenerSet is attached + - False: when AllowedListeners is set but no valid listeners are attached, or when AllowedListeners is not set or false + - Unknown: when no AllowedListeners config is present properties: apiVersion: description: |- @@ -16549,10 +19719,10 @@ spec: 1. "parent" Gateway 2. ListenerSet ordered by creation time (oldest first) - 3. ListenerSet ordered alphabetically by “{namespace}/{name}”. + 3. ListenerSet ordered alphabetically by "{namespace}/{name}". An implementation MAY reject listeners by setting the ListenerEntryStatus - `Accepted`` condition to False with the Reason `TooManyListeners` + `Accepted` condition to False with the Reason `TooManyListeners` If a listener has a conflict, this will be reported in the Status.ListenerEntryStatus setting the `Conflicted` condition to True. @@ -16625,6 +19795,7 @@ spec: type: object maxItems: 8 type: array + x-kubernetes-list-type: atomic namespaces: default: from: Same @@ -16747,12 +19918,18 @@ spec: pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string port: + default: 0 description: |- Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. + + If the port is not set or specified as zero, the implementation will assign + a unique port. If the implementation does not support dynamic port + assignment, it MUST set `Accepted` condition to `False` with the + `UnsupportedPort` reason. format: int32 maximum: 65535 - minimum: 1 + minimum: 0 type: integer protocol: description: Protocol specifies the network protocol this listener @@ -16767,7 +19944,7 @@ spec: the Protocol field is "HTTPS" or "TLS". It is invalid to set this field if the Protocol field is "HTTP", "TCP", or "UDP". - The association of SNIs to Certificate defined in GatewayTLSConfig is + The association of SNIs to Certificate defined in ListenerTLSConfig is defined based on the Hostname field for this listener. The GatewayClass MUST use the longest matching SNI out of all @@ -16852,93 +20029,7 @@ spec: type: object maxItems: 64 type: array - frontendValidation: - description: |- - FrontendValidation holds configuration information for validating the frontend (client). - Setting this field will require clients to send a client certificate - required for validation during the TLS handshake. In browsers this may result in a dialog appearing - that requests a user to specify the client certificate. - The maximum depth of a certificate chain accepted in verification is Implementation specific. - - Support: Extended - properties: - caCertificateRefs: - description: |- - CACertificateRefs contains one or more references to - Kubernetes objects that contain TLS certificates of - the Certificate Authorities that can be used - as a trust anchor to validate the certificates presented by the client. - - A single CA certificate reference to a Kubernetes ConfigMap - has "Core" support. - Implementations MAY choose to support attaching multiple CA certificates to - a Listener, but this behavior is implementation-specific. - - Support: Core - A single reference to a Kubernetes ConfigMap - with the CA certificate in a key named `ca.crt`. - - Support: Implementation-specific (More than one reference, or other kinds - of resources). - - References to a resource in a different namespace are invalid UNLESS there - is a ReferenceGrant in the target namespace that allows the certificate - to be attached. If a ReferenceGrant does not allow this reference, the - "ResolvedRefs" condition MUST be set to False for this listener with the - "RefNotPermitted" reason. - items: - description: |- - ObjectReference identifies an API object including its namespace. - - The API object must be valid in the cluster; the Group and Kind must - be registered in the cluster for this reference to be valid. - - References to objects with invalid Group and Kind are not valid, and must - be rejected by the implementation, with appropriate Conditions set - on the containing object. - properties: - group: - description: |- - Group is the group of the referent. For example, "gateway.networking.k8s.io". - When set to the 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: - description: Kind is kind of the referent. For - example "ConfigMap" or "Service". - 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 referenced object. 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 - required: - - group - - kind - - name - type: object - maxItems: 8 - minItems: 1 - type: array - type: object + x-kubernetes-list-type: atomic mode: default: Terminate description: |- @@ -16990,7 +20081,6 @@ spec: > 0 || size(self.options) > 0 : true' required: - name - - port - protocol type: object maxItems: 64 @@ -17290,6 +20380,7 @@ spec: type: object maxItems: 8 type: array + x-kubernetes-list-type: atomic required: - attachedRoutes - conditions @@ -17316,3 +20407,255 @@ status: plural: "" conditions: null storedVersions: null +--- +# +# config/crd/experimental/gateway.networking.x-k8s.io_xmeshes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.4.0 + gateway.networking.k8s.io/channel: experimental + name: xmeshes.gateway.networking.x-k8s.io +spec: + group: gateway.networking.x-k8s.io + names: + categories: + - gateway-api + kind: XMesh + listKind: XMeshList + plural: xmeshes + shortNames: + - mesh + singular: xmesh + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: XMesh defines mesh-wide characteristics of a GAMMA-compliant + service mesh. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of XMesh. + properties: + controllerName: + description: |- + ControllerName is the name of a controller that is managing Gateway API + resources for mesh traffic management. The value of this field MUST be a + domain prefixed path. + + Example: "example.com/awesome-mesh". + + This field is not mutable and cannot be empty. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description optionally provides a human-readable description + of a Mesh. + maxLength: 64 + type: string + parametersRef: + description: |- + ParametersRef is an optional reference to a resource that contains + implementation-specific configuration for this Mesh. If no + implementation-specific parameters are needed, this field MUST be + omitted. + + ParametersRef can reference a standard Kubernetes resource, i.e. + ConfigMap, or an implementation-specific custom resource. The resource + can be cluster-scoped or namespace-scoped. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Mesh MUST be rejected + with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + 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 referent. + This field is required when referring to a Namespace-scoped resource and + MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: Status defines the current state of XMesh. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions is the current status from the controller for + this Mesh. + + Controllers should prefer to publish conditions using values + of MeshConditionType for the type of each Condition. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: |- + SupportedFeatures is the set of features the Mesh support. + It MUST be sorted in ascending alphabetical order by the Name key. + items: + properties: + name: + description: |- + FeatureName is used to describe distinct features that are covered by + conformance tests. + type: string + required: + - name + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/integration/fixtures/k8s-conformance/01-rbac.yml b/integration/fixtures/gateway-api-conformance/01-rbac.yml similarity index 100% rename from integration/fixtures/k8s-conformance/01-rbac.yml rename to integration/fixtures/gateway-api-conformance/01-rbac.yml diff --git a/integration/fixtures/k8s-conformance/02-traefik.yml b/integration/fixtures/gateway-api-conformance/02-traefik.yml similarity index 100% rename from integration/fixtures/k8s-conformance/02-traefik.yml rename to integration/fixtures/gateway-api-conformance/02-traefik.yml diff --git a/integration/fixtures/k8s/00-experimental-v1.2.1.yml b/integration/fixtures/k8s/00-experimental-v1.4.0.yml similarity index 68% rename from integration/fixtures/k8s/00-experimental-v1.2.1.yml rename to integration/fixtures/k8s/00-experimental-v1.4.0.yml index 69b689846..b1e7bd2f2 100644 --- a/integration/fixtures/k8s/00-experimental-v1.2.1.yml +++ b/integration/fixtures/k8s/00-experimental-v1.4.0.yml @@ -1,4 +1,4 @@ -# Copyright 2024 The Kubernetes Authors. +# Copyright 2025 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,507 +17,6 @@ # --- # -# config/crd/experimental/gateway.networking.k8s.io_backendlbpolicies.yaml -# -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - labels: - gateway.networking.k8s.io/policy: Direct - name: backendlbpolicies.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: BackendLBPolicy - listKind: BackendLBPolicyList - plural: backendlbpolicies - shortNames: - - blbpolicy - singular: backendlbpolicy - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: |- - BackendLBPolicy provides a way to define load balancing rules - for a backend. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of BackendLBPolicy. - properties: - sessionPersistence: - description: |- - SessionPersistence defines and configures session persistence - for the backend. - - Support: Extended - properties: - absoluteTimeout: - description: |- - AbsoluteTimeout defines the absolute timeout of the persistent - session. Once the AbsoluteTimeout duration has elapsed, the - session becomes invalid. - - Support: Extended - pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ - type: string - cookieConfig: - description: |- - CookieConfig provides configuration settings that are specific - to cookie-based session persistence. - - Support: Core - properties: - lifetimeType: - default: Session - description: |- - LifetimeType specifies whether the cookie has a permanent or - session-based lifetime. A permanent cookie persists until its - specified expiry time, defined by the Expires or Max-Age cookie - attributes, while a session cookie is deleted when the current - session ends. - - When set to "Permanent", AbsoluteTimeout indicates the - cookie's lifetime via the Expires or Max-Age cookie attributes - and is required. - - When set to "Session", AbsoluteTimeout indicates the - absolute lifetime of the cookie tracked by the gateway and - is optional. - - Support: Core for "Session" type - - Support: Extended for "Permanent" type - enum: - - Permanent - - Session - type: string - type: object - idleTimeout: - description: |- - IdleTimeout defines the idle timeout of the persistent session. - Once the session has been idle for more than the specified - IdleTimeout duration, the session becomes invalid. - - Support: Extended - pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ - type: string - sessionName: - description: |- - SessionName defines the name of the persistent session token - which may be reflected in the cookie or the header. Users - should avoid reusing session names to prevent unintended - consequences, such as rejection or unpredictable behavior. - - Support: Implementation-specific - maxLength: 128 - type: string - type: - default: Cookie - description: |- - Type defines the type of session persistence such as through - the use a header or cookie. Defaults to cookie based session - persistence. - - Support: Core for "Cookie" type - - Support: Extended for "Header" type - enum: - - Cookie - - Header - type: string - type: object - x-kubernetes-validations: - - message: AbsoluteTimeout must be specified when cookie lifetimeType - is Permanent - rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) - || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' - targetRefs: - description: |- - TargetRef identifies an API object to apply policy to. - Currently, Backends (i.e. Service, ServiceImport, or any - implementation-specific backendRef) are the only valid API - target references. - items: - description: |- - LocalPolicyTargetReference identifies an API object to apply a direct or - inherited policy to. This should be used as part of Policy resources - that can target Gateway API resources. For more information on how this - policy attachment model works, and a sample Policy resource, refer to - the policy attachment documentation for Gateway API. - properties: - group: - description: Group is the group of the target resource. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the target resource. - 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 target resource. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - maxItems: 16 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - group - - kind - - name - x-kubernetes-list-type: map - required: - - targetRefs - type: object - status: - description: Status defines the current state of BackendLBPolicy. - properties: - ancestors: - description: |- - Ancestors is a list of ancestor resources (usually Gateways) that are - associated with the policy, and the status of the policy with respect to - each ancestor. When this policy attaches to a parent, the controller that - manages the parent and the ancestors MUST add an entry to this list when - the controller first sees the policy and SHOULD update the entry as - appropriate when the relevant ancestor is modified. - - Note that choosing the relevant ancestor is left to the Policy designers; - an important part of Policy design is designing the right object level at - which to namespace this status. - - Note also that implementations MUST ONLY populate ancestor status for - the Ancestor resources they are responsible for. Implementations MUST - use the ControllerName field to uniquely identify the entries in this list - that they are responsible for. - - Note that to achieve this, the list of PolicyAncestorStatus structs - MUST be treated as a map with a composite key, made up of the AncestorRef - and ControllerName fields combined. - - A maximum of 16 ancestors will be represented in this list. An empty list - means the Policy is not relevant for any ancestors. - - If this slice is full, implementations MUST NOT add further entries. - Instead they MUST consider the policy unimplementable and signal that - on any related resources such as the ancestor that would be referenced - here. For example, if this list was full on BackendTLSPolicy, no - additional Gateways would be able to reference the Service targeted by - the BackendTLSPolicy. - items: - description: |- - PolicyAncestorStatus describes the status of a route with respect to an - associated Ancestor. - - Ancestors refer to objects that are either the Target of a policy or above it - in terms of object hierarchy. For example, if a policy targets a Service, the - Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and - the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most - useful object to place Policy status on, so we recommend that implementations - SHOULD use Gateway as the PolicyAncestorStatus object unless the designers - have a _very_ good reason otherwise. - - In the context of policy attachment, the Ancestor is used to distinguish which - resource results in a distinct application of this policy. For example, if a policy - targets a Service, it may have a distinct result per attached Gateway. - - Policies targeting the same resource may have different effects depending on the - ancestors of those resources. For example, different Gateways targeting the same - Service may have different capabilities, especially if they have different underlying - implementations. - - For example, in BackendTLSPolicy, the Policy attaches to a Service that is - used as a backend in a HTTPRoute that is itself attached to a Gateway. - In this case, the relevant object for status is the Gateway, and that is the - ancestor object referred to in this status. - - Note that a parent is also an ancestor, so for objects where the parent is the - relevant object for status, this struct SHOULD still be used. - - This struct is intended to be used in a slice that's effectively a map, - with a composite key made up of the AncestorRef and the ControllerName. - properties: - ancestorRef: - description: |- - AncestorRef corresponds with a ParentRef in the spec that this - PolicyAncestorStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: |- - Group is the group of the referent. - When unspecified, "gateway.networking.k8s.io" is inferred. - To set the core API group (such as for a "Service" kind referent), - Group must be explicitly set to "" (empty string). - - Support: Core - 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: Gateway - description: |- - Kind is kind of the referent. - - There are two kinds of parent resources with "Core" support: - - * Gateway (Gateway conformance profile) - * Service (Mesh conformance profile, ClusterIP Services only) - - Support for other resources is Implementation-Specific. - 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. - - Support: Core - maxLength: 253 - minLength: 1 - type: string - namespace: - description: |- - Namespace is the namespace of the referent. When unspecified, this refers - to the local namespace of the Route. - - Note that there are specific rules for ParentRefs which cross namespace - boundaries. Cross-namespace references are only valid if they are explicitly - allowed by something in the namespace they are referring to. For example: - Gateway has the AllowedRoutes field, and ReferenceGrant provides a - generic way to enable any other kind of cross-namespace reference. - - - ParentRefs from a Route to a Service in the same namespace are "producer" - routes, which apply default routing rules to inbound connections from - any namespace to the Service. - - ParentRefs from a Route to a Service in a different namespace are - "consumer" routes, and these routing rules are only applied to outbound - connections originating from the same namespace as the Route, for which - the intended destination of the connections are a Service targeted as a - ParentRef of the Route. - - - Support: Core - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: |- - Port is the network port this Route targets. It can be interpreted - differently based on the type of parent resource. - - When the parent resource is a Gateway, this targets all listeners - listening on the specified port that also support this kind of Route(and - select this Route). It's not recommended to set `Port` unless the - networking behaviors specified in a Route must apply to a specific port - as opposed to a listener(s) whose port(s) may be changed. When both Port - and SectionName are specified, the name and port of the selected listener - must match both specified values. - - - When the parent resource is a Service, this targets a specific port in the - Service spec. When both Port (experimental) and SectionName are specified, - the name and port of the selected port must match both specified values. - - - Implementations MAY choose to support other parent resources. - Implementations supporting other types of parent resources MUST clearly - document how/if Port is interpreted. - - For the purpose of status, an attachment is considered successful as - long as the parent resource accepts it partially. For example, Gateway - listeners can restrict which Routes can attach to them by Route kind, - namespace, or hostname. If 1 of 2 Gateway listeners accept attachment - from the referencing Route, the Route MUST be considered successfully - attached. If no Gateway listeners accept attachment from this Route, - the Route MUST be considered detached from the Gateway. - - Support: Extended - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: |- - SectionName is the name of a section within the target resource. In the - following resources, SectionName is interpreted as the following: - - * Gateway: Listener name. When both Port (experimental) and SectionName - are specified, the name and port of the selected listener must match - both specified values. - * Service: Port name. When both Port (experimental) and SectionName - are specified, the name and port of the selected listener must match - both specified values. - - Implementations MAY choose to support attaching Routes to other resources. - If that is the case, they MUST clearly document how SectionName is - interpreted. - - When unspecified (empty string), this will reference the entire resource. - For the purpose of status, an attachment is considered successful if at - least one section in the parent resource accepts it. For example, Gateway - listeners can restrict which Routes can attach to them by Route kind, - namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from - the referencing Route, the Route MUST be considered successfully - attached. If no Gateway listeners accept attachment from this Route, the - Route MUST be considered detached from the Gateway. - - Support: Core - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - conditions: - description: Conditions describes the status of the Policy with - respect to the given Ancestor. - items: - description: Condition contains details for one aspect of - the current state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: |- - ControllerName is a domain/path string that indicates the name of the - controller that wrote this status. This corresponds with the - controllerName field on GatewayClass. - - Example: "example.net/gateway-controller". - - The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are - valid Kubernetes names - (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). - - Controllers MUST populate this field when writing status. Controllers should ensure that - entries to status populated with their ControllerName are cleaned up when they are no - longer necessary. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - required: - - ancestorRef - - controllerName - type: object - maxItems: 16 - type: array - required: - - ancestors - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null ---- -# # config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml # apiVersion: apiextensions.k8s.io/v1 @@ -525,9 +24,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null labels: gateway.networking.k8s.io/policy: Direct name: backendtlspolicies.gateway.networking.k8s.io @@ -548,7 +46,7 @@ spec: - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha3 + name: v1 schema: openAPIV3Schema: description: |- @@ -607,6 +105,30 @@ spec: by default, but this default may change in the future to provide a more granular application of the policy. + TargetRefs must be _distinct_. This means either that: + + * They select different targets. If this is the case, then targetRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, and `name` must + be unique across all targetRef entries in the BackendTLSPolicy. + * They select different sectionNames in the same target. + + When more than one BackendTLSPolicy selects the same target and + sectionName, implementations MUST determine precedence using the + following criteria, continuing on ties: + + * The older policy by creation timestamp takes precedence. For + example, a policy with a creation timestamp of "2021-07-15 + 01:02:03" MUST be given precedence over a policy with a + creation timestamp of "2021-07-15 01:02:04". + * The policy appearing first in alphabetical order by {name}. + For example, a policy named `bar` is given precedence over a + policy named `baz`. + + For any BackendTLSPolicy that does not take precedence, the + implementation MUST ensure the `Accepted` Condition is set to + `status: False`, with Reason `Conflicted`. + Support: Extended for Kubernetes Service Support: Implementation-specific for any other resource @@ -663,6 +185,21 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: sectionName must be specified when targetRefs includes + 2 or more references to the same target + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name ? ((!has(p1.sectionName) || p1.sectionName + == '''') == (!has(p2.sectionName) || p2.sectionName == '''')) + : true))' + - message: sectionName must be unique when targetRefs includes 2 or + more references to the same target + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.sectionName) || + p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) validation: description: Validation contains backend TLS validation configuration. properties: @@ -674,11 +211,34 @@ spec: If CACertificateRefs is empty or unspecified, then WellKnownCACertificates must be specified. Only one of CACertificateRefs or WellKnownCACertificates may be specified, - not both. If CACertifcateRefs is empty or unspecified, the configuration for + not both. If CACertificateRefs is empty or unspecified, the configuration for WellKnownCACertificates MUST be honored instead if supported by the implementation. - References to a resource in a different namespace are invalid for the - moment, although we will revisit this in the future. + A CACertificateRef is invalid if: + + * It refers to a resource that cannot be resolved (e.g., the referenced resource + does not exist) or is misconfigured (e.g., a ConfigMap does not contain a key + named `ca.crt`). In this case, the Reason must be set to `InvalidCACertificateRef` + and the Message of the Condition must indicate which reference is invalid and why. + + * It refers to an unknown or unsupported kind of resource. In this case, the Reason + must be set to `InvalidKind` and the Message of the Condition must explain which + kind of resource is unknown or unsupported. + + * It refers to a resource in another namespace. This may change in future + spec updates. + + Implementations MAY choose to perform further validation of the certificate + content (e.g., checking expiry or enforcing specific formats). In such cases, + an implementation-specific Reason and Message must be set for the invalid reference. + + In all cases, the implementation MUST ensure the `ResolvedRefs` Condition on + the BackendTLSPolicy is set to `status: False`, with a Reason and Message + that indicate the cause of the error. Connections using an invalid + CACertificateRef MUST fail, and the client MUST receive an HTTP 5xx error + response. If ALL CACertificateRefs are invalid, the implementation MUST also + ensure the `Accepted` Condition on the BackendTLSPolicy is set to + `status: False`, with a Reason `NoValidCACertificate`. A single CACertificateRef to a Kubernetes ConfigMap kind has "Core" support. Implementations MAY choose to support attaching multiple certificates to @@ -687,8 +247,8 @@ spec: Support: Core - An optional single reference to a Kubernetes ConfigMap, with the CA certificate in a key named `ca.crt`. - Support: Implementation-specific (More than one reference, or other kinds - of resources). + Support: Implementation-specific - More than one reference, other kinds + of resources, or a single reference that includes multiple certificates. items: description: |- LocalObjectReference identifies an API object within the namespace of the @@ -726,15 +286,18 @@ spec: type: object maxItems: 8 type: array + x-kubernetes-list-type: atomic hostname: description: |- Hostname is used for two purposes in the connection between Gateways and backends: 1. Hostname MUST be used as the SNI to connect to the backend (RFC 6066). - 2. If SubjectAltNames is not specified, Hostname MUST be used for - authentication and MUST match the certificate served by the matching - backend. + 2. Hostname MUST be used for authentication and MUST match the certificate + served by the matching backend, unless SubjectAltNames is specified. + 3. If SubjectAltNames are specified, Hostname can be used for certificate selection + but MUST NOT be used for authentication. If you want to use the value + of the Hostname field for authentication, you MUST add it to the SubjectAltNames list. Support: Core maxLength: 253 @@ -744,10 +307,10 @@ spec: subjectAltNames: description: |- SubjectAltNames contains one or more Subject Alternative Names. - When specified, the certificate served from the backend MUST have at least one - Subject Alternate Name matching one of the specified SubjectAltNames. + When specified the certificate served from the backend MUST + have at least one Subject Alternate Name matching one of the specified SubjectAltNames. - Support: Core + Support: Extended items: description: SubjectAltName represents Subject Alternative Name. properties: @@ -804,6 +367,7 @@ spec: "")' maxItems: 5 type: array + x-kubernetes-list-type: atomic wellKnownCACertificates: description: |- WellKnownCACertificates specifies whether system CA certificates may be used in @@ -811,10 +375,11 @@ spec: If WellKnownCACertificates is unspecified or empty (""), then CACertificateRefs must be specified with at least one entry for a valid configuration. Only one of - CACertificateRefs or WellKnownCACertificates may be specified, not both. If an - implementation does not support the WellKnownCACertificates field or the value - supplied is not supported, the Status Conditions on the Policy MUST be - updated to include an Accepted: False Condition with Reason: Invalid. + CACertificateRefs or WellKnownCACertificates may be specified, not both. + If an implementation does not support the WellKnownCACertificates field, or + the supplied value is not recognized, the implementation MUST ensure the + `Accepted` Condition on the BackendTLSPolicy is set to `status: False`, with + a Reason `Invalid`. Support: Implementation-specific enum: @@ -1125,10 +690,12 @@ spec: type: string required: - ancestorRef + - conditions - controllerName type: object maxItems: 16 type: array + x-kubernetes-list-type: atomic required: - ancestors type: object @@ -1139,6 +706,667 @@ spec: storage: true subresources: status: {} + - deprecated: true + deprecationWarning: The v1alpha3 version of BackendTLSPolicy has been deprecated + and will be removed in a future release of the API. Please upgrade to v1. + name: v1alpha3 + schema: + openAPIV3Schema: + description: |- + BackendTLSPolicy provides a way to configure how a Gateway + connects to a Backend via TLS. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of BackendTLSPolicy. + properties: + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + targetRefs: + description: |- + TargetRefs identifies an API object to apply the policy to. + Only Services have Extended support. Implementations MAY support + additional objects, with Implementation Specific support. + Note that this config applies to the entire referenced resource + by default, but this default may change in the future to provide + a more granular application of the policy. + + TargetRefs must be _distinct_. This means either that: + + * They select different targets. If this is the case, then targetRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, and `name` must + be unique across all targetRef entries in the BackendTLSPolicy. + * They select different sectionNames in the same target. + + When more than one BackendTLSPolicy selects the same target and + sectionName, implementations MUST determine precedence using the + following criteria, continuing on ties: + + * The older policy by creation timestamp takes precedence. For + example, a policy with a creation timestamp of "2021-07-15 + 01:02:03" MUST be given precedence over a policy with a + creation timestamp of "2021-07-15 01:02:04". + * The policy appearing first in alphabetical order by {name}. + For example, a policy named `bar` is given precedence over a + policy named `baz`. + + For any BackendTLSPolicy that does not take precedence, the + implementation MUST ensure the `Accepted` Condition is set to + `status: False`, with Reason `Conflicted`. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + 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 target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: sectionName must be specified when targetRefs includes + 2 or more references to the same target + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name ? ((!has(p1.sectionName) || p1.sectionName + == '''') == (!has(p2.sectionName) || p2.sectionName == '''')) + : true))' + - message: sectionName must be unique when targetRefs includes 2 or + more references to the same target + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.sectionName) || + p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) + validation: + description: Validation contains backend TLS validation configuration. + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to Kubernetes objects that + contain a PEM-encoded TLS CA certificate bundle, which is used to + validate a TLS handshake between the Gateway and backend Pod. + + If CACertificateRefs is empty or unspecified, then WellKnownCACertificates must be + specified. Only one of CACertificateRefs or WellKnownCACertificates may be specified, + not both. If CACertificateRefs is empty or unspecified, the configuration for + WellKnownCACertificates MUST be honored instead if supported by the implementation. + + A CACertificateRef is invalid if: + + * It refers to a resource that cannot be resolved (e.g., the referenced resource + does not exist) or is misconfigured (e.g., a ConfigMap does not contain a key + named `ca.crt`). In this case, the Reason must be set to `InvalidCACertificateRef` + and the Message of the Condition must indicate which reference is invalid and why. + + * It refers to an unknown or unsupported kind of resource. In this case, the Reason + must be set to `InvalidKind` and the Message of the Condition must explain which + kind of resource is unknown or unsupported. + + * It refers to a resource in another namespace. This may change in future + spec updates. + + Implementations MAY choose to perform further validation of the certificate + content (e.g., checking expiry or enforcing specific formats). In such cases, + an implementation-specific Reason and Message must be set for the invalid reference. + + In all cases, the implementation MUST ensure the `ResolvedRefs` Condition on + the BackendTLSPolicy is set to `status: False`, with a Reason and Message + that indicate the cause of the error. Connections using an invalid + CACertificateRef MUST fail, and the client MUST receive an HTTP 5xx error + response. If ALL CACertificateRefs are invalid, the implementation MUST also + ensure the `Accepted` Condition on the BackendTLSPolicy is set to + `status: False`, with a Reason `NoValidCACertificate`. + + A single CACertificateRef to a Kubernetes ConfigMap kind has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a backend, but this behavior is implementation-specific. + + Support: Core - An optional single reference to a Kubernetes ConfigMap, + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific - More than one reference, other kinds + of resources, or a single reference that includes multiple certificates. + items: + description: |- + LocalObjectReference identifies an API object within the namespace of the + referrer. + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + 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: + description: Kind is kind of the referent. For example "HTTPRoute" + or "Service". + 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 + required: + - group + - kind + - name + type: object + maxItems: 8 + type: array + x-kubernetes-list-type: atomic + hostname: + description: |- + Hostname is used for two purposes in the connection between Gateways and + backends: + + 1. Hostname MUST be used as the SNI to connect to the backend (RFC 6066). + 2. Hostname MUST be used for authentication and MUST match the certificate + served by the matching backend, unless SubjectAltNames is specified. + 3. If SubjectAltNames are specified, Hostname can be used for certificate selection + but MUST NOT be used for authentication. If you want to use the value + of the Hostname field for authentication, you MUST add it to the SubjectAltNames list. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + subjectAltNames: + description: |- + SubjectAltNames contains one or more Subject Alternative Names. + When specified the certificate served from the backend MUST + have at least one Subject Alternate Name matching one of the specified SubjectAltNames. + + Support: Extended + items: + description: SubjectAltName represents Subject Alternative Name. + properties: + hostname: + description: |- + Hostname contains Subject Alternative Name specified in DNS name format. + Required when Type is set to Hostname, ignored otherwise. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: + description: |- + Type determines the format of the Subject Alternative Name. Always required. + + Support: Core + enum: + - Hostname + - URI + type: string + uri: + description: |- + URI contains Subject Alternative Name specified in a full URI format. + It MUST include both a scheme (e.g., "http" or "ftp") and a scheme-specific-part. + Common values include SPIFFE IDs like "spiffe://mycluster.example.com/ns/myns/sa/svc1sa". + Required when Type is set to URI, ignored otherwise. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: SubjectAltName element must contain Hostname, if + Type is set to Hostname + rule: '!(self.type == "Hostname" && (!has(self.hostname) || + self.hostname == ""))' + - message: SubjectAltName element must not contain Hostname, + if Type is not set to Hostname + rule: '!(self.type != "Hostname" && has(self.hostname) && + self.hostname != "")' + - message: SubjectAltName element must contain URI, if Type + is set to URI + rule: '!(self.type == "URI" && (!has(self.uri) || self.uri + == ""))' + - message: SubjectAltName element must not contain URI, if Type + is not set to URI + rule: '!(self.type != "URI" && has(self.uri) && self.uri != + "")' + maxItems: 5 + type: array + x-kubernetes-list-type: atomic + wellKnownCACertificates: + description: |- + WellKnownCACertificates specifies whether system CA certificates may be used in + the TLS handshake between the gateway and backend pod. + + If WellKnownCACertificates is unspecified or empty (""), then CACertificateRefs + must be specified with at least one entry for a valid configuration. Only one of + CACertificateRefs or WellKnownCACertificates may be specified, not both. + If an implementation does not support the WellKnownCACertificates field, or + the supplied value is not recognized, the implementation MUST ensure the + `Accepted` Condition on the BackendTLSPolicy is set to `status: False`, with + a Reason `Invalid`. + + Support: Implementation-specific + enum: + - System + type: string + required: + - hostname + type: object + x-kubernetes-validations: + - message: must not contain both CACertificateRefs and WellKnownCACertificates + rule: '!(has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "")' + - message: must specify either CACertificateRefs or WellKnownCACertificates + rule: (has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "") + required: + - targetRefs + - validation + type: object + status: + description: Status defines the current state of BackendTLSPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + 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: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + 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. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - conditions + - controllerName + type: object + maxItems: 16 + type: array + x-kubernetes-list-type: atomic + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: false status: acceptedNames: kind: "" @@ -1154,9 +1382,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: gatewayclasses.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -1389,7 +1616,7 @@ spec: - type x-kubernetes-list-type: map supportedFeatures: - description: | + description: |- SupportedFeatures is the set of features the GatewayClass support. It MUST be sorted in ascending alphabetical order by the Name key. items: @@ -1633,7 +1860,7 @@ spec: - type x-kubernetes-list-type: map supportedFeatures: - description: | + description: |- SupportedFeatures is the set of features the GatewayClass support. It MUST be sorted in ascending alphabetical order by the Name key. items: @@ -1674,9 +1901,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: gateways.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -1732,11 +1958,11 @@ spec: description: Spec defines the desired state of Gateway. properties: addresses: - description: |+ + description: |- Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST - indicate this in the associated entry in GatewayStatus.Addresses. + indicate this in an associated entry in GatewayStatus.Conditions. The Addresses field represents a request for the address(es) on the "outside of the Gateway", that traffic bound for this Gateway will use. @@ -1753,10 +1979,9 @@ spec: GatewayStatus.Addresses. Support: Extended - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. oneOf: - properties: type: @@ -1781,96 +2006,137 @@ spec: type: string value: description: |- - Value of the address. The validity of the values will depend - on the type and support by the controller. + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". Examples: `1.2.3.4`, `128::1`, `my-ip-address`. maxLength: 253 - minLength: 1 type: string - required: - - value type: object x-kubernetes-validations: - - message: Hostname value must only contain valid characters (matching - ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) - rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + - message: Hostname value must be empty or contain only valid characters + (matching ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? (!has(self.value) || self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$""")): true' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: IPAddress values must be unique - rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, - a2.type == a1.type && a2.value == a1.value) : true )' + rule: 'self.all(a1, a1.type == ''IPAddress'' && has(a1.value) ? + self.exists_one(a2, a2.type == a1.type && has(a2.value) && a2.value + == a1.value) : true )' - message: Hostname values must be unique - rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, - a2.type == a1.type && a2.value == a1.value) : true )' - backendTLS: - description: |+ - BackendTLS configures TLS settings for when this Gateway is connecting to - backends with TLS. - - Support: Core - + rule: 'self.all(a1, a1.type == ''Hostname'' && has(a1.value) ? + self.exists_one(a2, a2.type == a1.type && has(a2.value) && a2.value + == a1.value) : true )' + allowedListeners: + description: |- + AllowedListeners defines which ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. properties: - clientCertificateRef: - description: |+ - ClientCertificateRef is a reference to an object that contains a Client - Certificate and the associated private key. - - References to a resource in different namespace are invalid UNLESS there - is a ReferenceGrant in the target namespace that allows the certificate - to be attached. If a ReferenceGrant does not allow this reference, the - "ResolvedRefs" condition MUST be set to False for this listener with the - "RefNotPermitted" reason. - - ClientCertificateRef can reference to standard Kubernetes resources, i.e. - Secret, or implementation-specific custom resources. - - This setting can be overridden on the service level by use of BackendTLSPolicy. - - Support: Core - + namespaces: + default: + from: None + description: |- + Namespaces defines which namespaces ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. properties: - group: - default: "" + from: + default: None 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])?)*$ + From indicates where ListenerSets can attach to this Gateway. Possible + values are: + + * Same: Only ListenerSets in the same namespace may be attached to this Gateway. + * Selector: ListenerSets in namespaces selected by the selector may be attached to this Gateway. + * All: ListenerSets in all namespaces may be attached to this Gateway. + * None: Only listeners defined in the Gateway's spec are allowed + + While this feature is experimental, the default value None + enum: + - All + - Selector + - Same + - None type: string - kind: - default: Secret - description: Kind is kind of the referent. For example "Secret". - 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: + selector: description: |- - Namespace is the namespace of the referenced object. 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 - required: - - name + Selector must be specified when From is set to "Selector". In that case, + only ListenerSets in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic type: object type: object + defaultScope: + description: |- + DefaultScope, when set, configures the Gateway as a default Gateway, + meaning it will dynamically and implicitly have Routes (e.g. HTTPRoute) + attached to it, according to the scope configured here. + + If unset (the default) or set to None, the Gateway will not act as a + default Gateway; if set, the Gateway will claim any Route with a + matching scope set in its UseDefaultGateway field, subject to the usual + rules about which routes the Gateway can attach to. + + Think carefully before using this functionality! While the normal rules + about which Route can apply are still enforced, it is simply easier for + the wrong Route to be accidentally attached to this Gateway in this + configuration. If the Gateway operator is not also the operator in + control of the scope (e.g. namespace) with tight controls and checks on + what kind of workloads and Routes get added in that scope, we strongly + recommend not using this just because it seems convenient, and instead + stick to direct Route attachment. + enum: + - All + - None + type: string gatewayClassName: description: |- GatewayClassName used for this Gateway. This is the name of a @@ -1965,6 +2231,11 @@ spec: the merging behavior is implementation specific. It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + Support: Implementation-specific properties: group: @@ -1995,6 +2266,8 @@ spec: logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. + ## Distinct Listeners + Each Listener in a set of Listeners (for example, in a single Gateway) MUST be _distinct_, in that a traffic flow MUST be able to be assigned to exactly one listener. (This section uses "set of Listeners" rather than @@ -2006,55 +2279,76 @@ spec: combination of Port, Protocol, and, if supported by the protocol, Hostname. Some combinations of port, protocol, and TLS settings are considered - Core support and MUST be supported by implementations based on their - targeted conformance profile: + Core support and MUST be supported by implementations based on the objects + they support: - HTTP Profile + HTTPRoute 1. HTTPRoute, Port: 80, Protocol: HTTP 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided - TLS Profile + TLSRoute 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough "Distinct" Listeners have the following property: - The implementation can match inbound requests to a single distinct - Listener. When multiple Listeners share values for fields (for + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for example, two Listeners with the same Port value), the implementation can match requests to only one of the Listeners using other Listener fields. - For example, the following Listener scenarios are distinct: + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. - 1. Multiple Listeners with the same Port that all use the "HTTP" - Protocol that all have unique Hostname values. - 2. Multiple Listeners with the same Port that use either the "HTTPS" or - "TLS" Protocol that all have unique Hostname values. - 3. A mixture of "TCP" and "UDP" Protocol Listeners, where no Listener - with the same Protocol has the same Port value. + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. - Some fields in the Listener struct have possible values that affect - whether the Listener is distinct. Hostname is particularly relevant - for HTTP or HTTPS protocols. + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: - When using the Hostname value to select between same-Port, same-Protocol - Listeners, the Hostname value must be different on each Listener for the - Listener to be distinct. + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port - When the Listeners are distinct based on Hostname, inbound request + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request hostnames MUST match from the most specific to least specific Hostname values to choose the correct Listener and its associated set of Routes. - Exact matches must be processed before wildcard matches, and wildcard - matches must be processed before fallback (empty Hostname value) + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) matches. For example, `"foo.example.com"` takes precedence over `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. Additionally, if there are multiple wildcard entries, more specific wildcard entries must be processed before less specific wildcard entries. For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + The precise definition here is that the higher the number of dots in the hostname to the right of the wildcard character, the higher the precedence. @@ -2062,18 +2356,26 @@ spec: the left, however, so `"*.example.com"` will match both `"foo.bar.example.com"` _and_ `"bar.example.com"`. + ## Handling indistinct Listeners + If a set of Listeners contains Listeners that are not distinct, then those - Listeners are Conflicted, and the implementation MUST set the "Conflicted" + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" condition in the Listener Status to "True". + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + Implementations MAY choose to accept a Gateway with some Conflicted Listeners only if they only accept the partial Listener set that contains - no Conflicted Listeners. To put this another way, implementations may - accept a partial Listener set only if they throw out *all* the conflicting - Listeners. No picking one of the conflicting listeners as the winner. - This also means that the Gateway must have at least one non-conflicting - Listener in this case, otherwise it violates the requirement that at - least one Listener must be present. + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. The implementation MUST set a "ListenersNotValid" condition on the Gateway Status when the Gateway contains Conflicted Listeners whether or @@ -2082,7 +2384,25 @@ spec: Accepted. Additionally, the Listener status for those listeners SHOULD indicate which Listeners are conflicted and not Accepted. - A Gateway's Listeners are considered "compatible" if: + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: 1. They are distinct. 2. The implementation can serve them in compliance with the Addresses @@ -2097,16 +2417,11 @@ spec: on the same address, or cannot mix HTTPS and generic TLS listens on the same port would not consider those cases compatible, even though they are distinct. - Note that requests SHOULD match at most one Listener. For example, if - Listeners are defined for "foo.example.com" and "*.example.com", a - request to "foo.example.com" SHOULD only be routed using routes attached - to the "foo.example.com" Listener (and not the "*.example.com" Listener). - This concept is known as "Listener Isolation". Implementations that do - not support Listener Isolation MUST clearly document this. - Implementations MAY merge separate Gateways onto a single set of Addresses if all Listeners across all Gateways are compatible. + In a future release the MinItems=1 requirement MAY be dropped. + Support: Core items: description: |- @@ -2177,6 +2492,7 @@ spec: type: object maxItems: 8 type: array + x-kubernetes-list-type: atomic namespaces: default: from: Same @@ -2268,10 +2584,31 @@ spec: * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. - * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP - protocol layers as described above. If an implementation does not - ensure that both the SNI and Host header match the Listener hostname, - it MUST clearly document that. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, @@ -2323,7 +2660,7 @@ spec: the Protocol field is "HTTPS" or "TLS". It is invalid to set this field if the Protocol field is "HTTP", "TCP", or "UDP". - The association of SNIs to Certificate defined in GatewayTLSConfig is + The association of SNIs to Certificate defined in ListenerTLSConfig is defined based on the Hostname field for this listener. The GatewayClass MUST use the longest matching SNI out of all @@ -2410,94 +2747,7 @@ spec: type: object maxItems: 64 type: array - frontendValidation: - description: |+ - FrontendValidation holds configuration information for validating the frontend (client). - Setting this field will require clients to send a client certificate - required for validation during the TLS handshake. In browsers this may result in a dialog appearing - that requests a user to specify the client certificate. - The maximum depth of a certificate chain accepted in verification is Implementation specific. - - Support: Extended - - properties: - caCertificateRefs: - description: |- - CACertificateRefs contains one or more references to - Kubernetes objects that contain TLS certificates of - the Certificate Authorities that can be used - as a trust anchor to validate the certificates presented by the client. - - A single CA certificate reference to a Kubernetes ConfigMap - has "Core" support. - Implementations MAY choose to support attaching multiple CA certificates to - a Listener, but this behavior is implementation-specific. - - Support: Core - A single reference to a Kubernetes ConfigMap - with the CA certificate in a key named `ca.crt`. - - Support: Implementation-specific (More than one reference, or other kinds - of resources). - - References to a resource in a different namespace are invalid UNLESS there - is a ReferenceGrant in the target namespace that allows the certificate - to be attached. If a ReferenceGrant does not allow this reference, the - "ResolvedRefs" condition MUST be set to False for this listener with the - "RefNotPermitted" reason. - items: - description: |- - ObjectReference identifies an API object including its namespace. - - The API object must be valid in the cluster; the Group and Kind must - be registered in the cluster for this reference to be valid. - - References to objects with invalid Group and Kind are not valid, and must - be rejected by the implementation, with appropriate Conditions set - on the containing object. - properties: - group: - 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: - description: Kind is kind of the referent. For - example "ConfigMap" or "Service". - 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 referenced object. 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 - required: - - group - - kind - - name - type: object - maxItems: 8 - minItems: 1 - type: array - type: object + x-kubernetes-list-type: atomic mode: default: Terminate description: |- @@ -2576,6 +2826,366 @@ spec: rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + tls: + description: |- + TLS specifies frontend and backend tls configuration for entire gateway. + + Support: Extended + properties: + backend: + description: |- + Backend describes TLS configuration for gateway when connecting + to backends. + + Note that this contains only details for the Gateway as a TLS client, + and does _not_ imply behavior about how to choose which backend should + get a TLS connection. That is determined by the presence of a BackendTLSPolicy. + + Support: Core + properties: + clientCertificateRef: + description: |- + ClientCertificateRef is a reference to an object that contains a Client + Certificate and the associated private key. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + ClientCertificateRef can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core + 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: Secret + description: Kind is kind of the referent. For example + "Secret". + 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 referenced object. 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 + required: + - name + type: object + type: object + frontend: + description: |- + Frontend describes TLS config when client connects to Gateway. + Support: Core + properties: + default: + description: |- + Default specifies the default client certificate validation configuration + for all Listeners handling HTTPS traffic, unless a per-port configuration + is defined. + + support: Core + properties: + validation: + description: |- + Validation holds configuration information for validating the frontend (client). + Setting this field will result in mutual authentication when connecting to the gateway. + In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Core + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one certificate in a ConfigMap + with different keys or more than one reference, or other kinds of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the 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: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + 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 referenced object. 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 + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + mode: + default: AllowValidOnly + description: |- + FrontendValidationMode defines the mode for validating the client certificate. + There are two possible modes: + + - AllowValidOnly: In this mode, the gateway will accept connections only if + the client presents a valid certificate. This certificate must successfully + pass validation against the CA certificates specified in `CACertificateRefs`. + - AllowInsecureFallback: In this mode, the gateway will accept connections + even if the client certificate is not presented or fails verification. + + This approach delegates client authorization to the backend and introduce + a significant security risk. It should be used in testing environments or + on a temporary basis in non-testing environments. + + Defaults to AllowValidOnly. + + Support: Core + enum: + - AllowValidOnly + - AllowInsecureFallback + type: string + required: + - caCertificateRefs + type: object + type: object + perPort: + description: |- + PerPort specifies tls configuration assigned per port. + Per port configuration is optional. Once set this configuration overrides + the default configuration for all Listeners handling HTTPS traffic + that match this port. + Each override port requires a unique TLS configuration. + + support: Core + items: + properties: + port: + description: |- + The Port indicates the Port Number to which the TLS configuration will be + applied. This configuration will be applied to all Listeners handling HTTPS + traffic that match this port. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + tls: + description: |- + TLS store the configuration that will be applied to all Listeners handling + HTTPS traffic and matching given port. + + Support: Core + properties: + validation: + description: |- + Validation holds configuration information for validating the frontend (client). + Setting this field will result in mutual authentication when connecting to the gateway. + In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Core + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one certificate in a ConfigMap + with different keys or more than one reference, or other kinds of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the 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: + description: Kind is kind of the referent. + For example "ConfigMap" or "Service". + 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 referenced object. 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 + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + mode: + default: AllowValidOnly + description: |- + FrontendValidationMode defines the mode for validating the client certificate. + There are two possible modes: + + - AllowValidOnly: In this mode, the gateway will accept connections only if + the client presents a valid certificate. This certificate must successfully + pass validation against the CA certificates specified in `CACertificateRefs`. + - AllowInsecureFallback: In this mode, the gateway will accept connections + even if the client certificate is not presented or fails verification. + + This approach delegates client authorization to the backend and introduce + a significant security risk. It should be used in testing environments or + on a temporary basis in non-testing environments. + + Defaults to AllowValidOnly. + + Support: Core + enum: + - AllowValidOnly + - AllowInsecureFallback + type: string + required: + - caCertificateRefs + type: object + type: object + required: + - port + - tls + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: Port for TLS configuration must be unique within + the Gateway + rule: self.all(t1, self.exists_one(t2, t1.port == t2.port)) + required: + - default + type: object + type: object required: - gatewayClassName - listeners @@ -2596,7 +3206,7 @@ spec: description: Status defines the current state of Gateway. properties: addresses: - description: |+ + description: |- Addresses lists the network addresses that have been bound to the Gateway. @@ -2606,7 +3216,6 @@ spec: * no addresses are specified, all addresses are dynamically assigned * a combination of specified and dynamic addresses are assigned * a specified address was unusable (e.g. already in use) - items: description: GatewayStatusAddress describes a network address that is bound to a Gateway. @@ -2651,6 +3260,7 @@ spec: true' maxItems: 16 type: array + x-kubernetes-list-type: atomic conditions: default: - lastTransitionTime: "1970-01-01T00:00:00Z" @@ -2864,6 +3474,7 @@ spec: type: object maxItems: 8 type: array + x-kubernetes-list-type: atomic required: - attachedRoutes - conditions @@ -2924,11 +3535,11 @@ spec: description: Spec defines the desired state of Gateway. properties: addresses: - description: |+ + description: |- Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST - indicate this in the associated entry in GatewayStatus.Addresses. + indicate this in an associated entry in GatewayStatus.Conditions. The Addresses field represents a request for the address(es) on the "outside of the Gateway", that traffic bound for this Gateway will use. @@ -2945,10 +3556,9 @@ spec: GatewayStatus.Addresses. Support: Extended - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. oneOf: - properties: type: @@ -2973,96 +3583,137 @@ spec: type: string value: description: |- - Value of the address. The validity of the values will depend - on the type and support by the controller. + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". Examples: `1.2.3.4`, `128::1`, `my-ip-address`. maxLength: 253 - minLength: 1 type: string - required: - - value type: object x-kubernetes-validations: - - message: Hostname value must only contain valid characters (matching - ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) - rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + - message: Hostname value must be empty or contain only valid characters + (matching ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? (!has(self.value) || self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$""")): true' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: IPAddress values must be unique - rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, - a2.type == a1.type && a2.value == a1.value) : true )' + rule: 'self.all(a1, a1.type == ''IPAddress'' && has(a1.value) ? + self.exists_one(a2, a2.type == a1.type && has(a2.value) && a2.value + == a1.value) : true )' - message: Hostname values must be unique - rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, - a2.type == a1.type && a2.value == a1.value) : true )' - backendTLS: - description: |+ - BackendTLS configures TLS settings for when this Gateway is connecting to - backends with TLS. - - Support: Core - + rule: 'self.all(a1, a1.type == ''Hostname'' && has(a1.value) ? + self.exists_one(a2, a2.type == a1.type && has(a2.value) && a2.value + == a1.value) : true )' + allowedListeners: + description: |- + AllowedListeners defines which ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. properties: - clientCertificateRef: - description: |+ - ClientCertificateRef is a reference to an object that contains a Client - Certificate and the associated private key. - - References to a resource in different namespace are invalid UNLESS there - is a ReferenceGrant in the target namespace that allows the certificate - to be attached. If a ReferenceGrant does not allow this reference, the - "ResolvedRefs" condition MUST be set to False for this listener with the - "RefNotPermitted" reason. - - ClientCertificateRef can reference to standard Kubernetes resources, i.e. - Secret, or implementation-specific custom resources. - - This setting can be overridden on the service level by use of BackendTLSPolicy. - - Support: Core - + namespaces: + default: + from: None + description: |- + Namespaces defines which namespaces ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. properties: - group: - default: "" + from: + default: None 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])?)*$ + From indicates where ListenerSets can attach to this Gateway. Possible + values are: + + * Same: Only ListenerSets in the same namespace may be attached to this Gateway. + * Selector: ListenerSets in namespaces selected by the selector may be attached to this Gateway. + * All: ListenerSets in all namespaces may be attached to this Gateway. + * None: Only listeners defined in the Gateway's spec are allowed + + While this feature is experimental, the default value None + enum: + - All + - Selector + - Same + - None type: string - kind: - default: Secret - description: Kind is kind of the referent. For example "Secret". - 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: + selector: description: |- - Namespace is the namespace of the referenced object. 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 - required: - - name + Selector must be specified when From is set to "Selector". In that case, + only ListenerSets in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic type: object type: object + defaultScope: + description: |- + DefaultScope, when set, configures the Gateway as a default Gateway, + meaning it will dynamically and implicitly have Routes (e.g. HTTPRoute) + attached to it, according to the scope configured here. + + If unset (the default) or set to None, the Gateway will not act as a + default Gateway; if set, the Gateway will claim any Route with a + matching scope set in its UseDefaultGateway field, subject to the usual + rules about which routes the Gateway can attach to. + + Think carefully before using this functionality! While the normal rules + about which Route can apply are still enforced, it is simply easier for + the wrong Route to be accidentally attached to this Gateway in this + configuration. If the Gateway operator is not also the operator in + control of the scope (e.g. namespace) with tight controls and checks on + what kind of workloads and Routes get added in that scope, we strongly + recommend not using this just because it seems convenient, and instead + stick to direct Route attachment. + enum: + - All + - None + type: string gatewayClassName: description: |- GatewayClassName used for this Gateway. This is the name of a @@ -3157,6 +3808,11 @@ spec: the merging behavior is implementation specific. It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + Support: Implementation-specific properties: group: @@ -3187,6 +3843,8 @@ spec: logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. + ## Distinct Listeners + Each Listener in a set of Listeners (for example, in a single Gateway) MUST be _distinct_, in that a traffic flow MUST be able to be assigned to exactly one listener. (This section uses "set of Listeners" rather than @@ -3198,55 +3856,76 @@ spec: combination of Port, Protocol, and, if supported by the protocol, Hostname. Some combinations of port, protocol, and TLS settings are considered - Core support and MUST be supported by implementations based on their - targeted conformance profile: + Core support and MUST be supported by implementations based on the objects + they support: - HTTP Profile + HTTPRoute 1. HTTPRoute, Port: 80, Protocol: HTTP 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided - TLS Profile + TLSRoute 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough "Distinct" Listeners have the following property: - The implementation can match inbound requests to a single distinct - Listener. When multiple Listeners share values for fields (for + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for example, two Listeners with the same Port value), the implementation can match requests to only one of the Listeners using other Listener fields. - For example, the following Listener scenarios are distinct: + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. - 1. Multiple Listeners with the same Port that all use the "HTTP" - Protocol that all have unique Hostname values. - 2. Multiple Listeners with the same Port that use either the "HTTPS" or - "TLS" Protocol that all have unique Hostname values. - 3. A mixture of "TCP" and "UDP" Protocol Listeners, where no Listener - with the same Protocol has the same Port value. + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. - Some fields in the Listener struct have possible values that affect - whether the Listener is distinct. Hostname is particularly relevant - for HTTP or HTTPS protocols. + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: - When using the Hostname value to select between same-Port, same-Protocol - Listeners, the Hostname value must be different on each Listener for the - Listener to be distinct. + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port - When the Listeners are distinct based on Hostname, inbound request + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request hostnames MUST match from the most specific to least specific Hostname values to choose the correct Listener and its associated set of Routes. - Exact matches must be processed before wildcard matches, and wildcard - matches must be processed before fallback (empty Hostname value) + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) matches. For example, `"foo.example.com"` takes precedence over `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. Additionally, if there are multiple wildcard entries, more specific wildcard entries must be processed before less specific wildcard entries. For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + The precise definition here is that the higher the number of dots in the hostname to the right of the wildcard character, the higher the precedence. @@ -3254,18 +3933,26 @@ spec: the left, however, so `"*.example.com"` will match both `"foo.bar.example.com"` _and_ `"bar.example.com"`. + ## Handling indistinct Listeners + If a set of Listeners contains Listeners that are not distinct, then those - Listeners are Conflicted, and the implementation MUST set the "Conflicted" + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" condition in the Listener Status to "True". + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + Implementations MAY choose to accept a Gateway with some Conflicted Listeners only if they only accept the partial Listener set that contains - no Conflicted Listeners. To put this another way, implementations may - accept a partial Listener set only if they throw out *all* the conflicting - Listeners. No picking one of the conflicting listeners as the winner. - This also means that the Gateway must have at least one non-conflicting - Listener in this case, otherwise it violates the requirement that at - least one Listener must be present. + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. The implementation MUST set a "ListenersNotValid" condition on the Gateway Status when the Gateway contains Conflicted Listeners whether or @@ -3274,7 +3961,25 @@ spec: Accepted. Additionally, the Listener status for those listeners SHOULD indicate which Listeners are conflicted and not Accepted. - A Gateway's Listeners are considered "compatible" if: + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: 1. They are distinct. 2. The implementation can serve them in compliance with the Addresses @@ -3289,16 +3994,11 @@ spec: on the same address, or cannot mix HTTPS and generic TLS listens on the same port would not consider those cases compatible, even though they are distinct. - Note that requests SHOULD match at most one Listener. For example, if - Listeners are defined for "foo.example.com" and "*.example.com", a - request to "foo.example.com" SHOULD only be routed using routes attached - to the "foo.example.com" Listener (and not the "*.example.com" Listener). - This concept is known as "Listener Isolation". Implementations that do - not support Listener Isolation MUST clearly document this. - Implementations MAY merge separate Gateways onto a single set of Addresses if all Listeners across all Gateways are compatible. + In a future release the MinItems=1 requirement MAY be dropped. + Support: Core items: description: |- @@ -3369,6 +4069,7 @@ spec: type: object maxItems: 8 type: array + x-kubernetes-list-type: atomic namespaces: default: from: Same @@ -3460,10 +4161,31 @@ spec: * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. - * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP - protocol layers as described above. If an implementation does not - ensure that both the SNI and Host header match the Listener hostname, - it MUST clearly document that. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, @@ -3515,7 +4237,7 @@ spec: the Protocol field is "HTTPS" or "TLS". It is invalid to set this field if the Protocol field is "HTTP", "TCP", or "UDP". - The association of SNIs to Certificate defined in GatewayTLSConfig is + The association of SNIs to Certificate defined in ListenerTLSConfig is defined based on the Hostname field for this listener. The GatewayClass MUST use the longest matching SNI out of all @@ -3602,94 +4324,7 @@ spec: type: object maxItems: 64 type: array - frontendValidation: - description: |+ - FrontendValidation holds configuration information for validating the frontend (client). - Setting this field will require clients to send a client certificate - required for validation during the TLS handshake. In browsers this may result in a dialog appearing - that requests a user to specify the client certificate. - The maximum depth of a certificate chain accepted in verification is Implementation specific. - - Support: Extended - - properties: - caCertificateRefs: - description: |- - CACertificateRefs contains one or more references to - Kubernetes objects that contain TLS certificates of - the Certificate Authorities that can be used - as a trust anchor to validate the certificates presented by the client. - - A single CA certificate reference to a Kubernetes ConfigMap - has "Core" support. - Implementations MAY choose to support attaching multiple CA certificates to - a Listener, but this behavior is implementation-specific. - - Support: Core - A single reference to a Kubernetes ConfigMap - with the CA certificate in a key named `ca.crt`. - - Support: Implementation-specific (More than one reference, or other kinds - of resources). - - References to a resource in a different namespace are invalid UNLESS there - is a ReferenceGrant in the target namespace that allows the certificate - to be attached. If a ReferenceGrant does not allow this reference, the - "ResolvedRefs" condition MUST be set to False for this listener with the - "RefNotPermitted" reason. - items: - description: |- - ObjectReference identifies an API object including its namespace. - - The API object must be valid in the cluster; the Group and Kind must - be registered in the cluster for this reference to be valid. - - References to objects with invalid Group and Kind are not valid, and must - be rejected by the implementation, with appropriate Conditions set - on the containing object. - properties: - group: - 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: - description: Kind is kind of the referent. For - example "ConfigMap" or "Service". - 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 referenced object. 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 - required: - - group - - kind - - name - type: object - maxItems: 8 - minItems: 1 - type: array - type: object + x-kubernetes-list-type: atomic mode: default: Terminate description: |- @@ -3768,6 +4403,366 @@ spec: rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + tls: + description: |- + TLS specifies frontend and backend tls configuration for entire gateway. + + Support: Extended + properties: + backend: + description: |- + Backend describes TLS configuration for gateway when connecting + to backends. + + Note that this contains only details for the Gateway as a TLS client, + and does _not_ imply behavior about how to choose which backend should + get a TLS connection. That is determined by the presence of a BackendTLSPolicy. + + Support: Core + properties: + clientCertificateRef: + description: |- + ClientCertificateRef is a reference to an object that contains a Client + Certificate and the associated private key. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + ClientCertificateRef can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core + 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: Secret + description: Kind is kind of the referent. For example + "Secret". + 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 referenced object. 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 + required: + - name + type: object + type: object + frontend: + description: |- + Frontend describes TLS config when client connects to Gateway. + Support: Core + properties: + default: + description: |- + Default specifies the default client certificate validation configuration + for all Listeners handling HTTPS traffic, unless a per-port configuration + is defined. + + support: Core + properties: + validation: + description: |- + Validation holds configuration information for validating the frontend (client). + Setting this field will result in mutual authentication when connecting to the gateway. + In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Core + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one certificate in a ConfigMap + with different keys or more than one reference, or other kinds of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the 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: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + 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 referenced object. 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 + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + mode: + default: AllowValidOnly + description: |- + FrontendValidationMode defines the mode for validating the client certificate. + There are two possible modes: + + - AllowValidOnly: In this mode, the gateway will accept connections only if + the client presents a valid certificate. This certificate must successfully + pass validation against the CA certificates specified in `CACertificateRefs`. + - AllowInsecureFallback: In this mode, the gateway will accept connections + even if the client certificate is not presented or fails verification. + + This approach delegates client authorization to the backend and introduce + a significant security risk. It should be used in testing environments or + on a temporary basis in non-testing environments. + + Defaults to AllowValidOnly. + + Support: Core + enum: + - AllowValidOnly + - AllowInsecureFallback + type: string + required: + - caCertificateRefs + type: object + type: object + perPort: + description: |- + PerPort specifies tls configuration assigned per port. + Per port configuration is optional. Once set this configuration overrides + the default configuration for all Listeners handling HTTPS traffic + that match this port. + Each override port requires a unique TLS configuration. + + support: Core + items: + properties: + port: + description: |- + The Port indicates the Port Number to which the TLS configuration will be + applied. This configuration will be applied to all Listeners handling HTTPS + traffic that match this port. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + tls: + description: |- + TLS store the configuration that will be applied to all Listeners handling + HTTPS traffic and matching given port. + + Support: Core + properties: + validation: + description: |- + Validation holds configuration information for validating the frontend (client). + Setting this field will result in mutual authentication when connecting to the gateway. + In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Core + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one certificate in a ConfigMap + with different keys or more than one reference, or other kinds of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the 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: + description: Kind is kind of the referent. + For example "ConfigMap" or "Service". + 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 referenced object. 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 + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + mode: + default: AllowValidOnly + description: |- + FrontendValidationMode defines the mode for validating the client certificate. + There are two possible modes: + + - AllowValidOnly: In this mode, the gateway will accept connections only if + the client presents a valid certificate. This certificate must successfully + pass validation against the CA certificates specified in `CACertificateRefs`. + - AllowInsecureFallback: In this mode, the gateway will accept connections + even if the client certificate is not presented or fails verification. + + This approach delegates client authorization to the backend and introduce + a significant security risk. It should be used in testing environments or + on a temporary basis in non-testing environments. + + Defaults to AllowValidOnly. + + Support: Core + enum: + - AllowValidOnly + - AllowInsecureFallback + type: string + required: + - caCertificateRefs + type: object + type: object + required: + - port + - tls + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: Port for TLS configuration must be unique within + the Gateway + rule: self.all(t1, self.exists_one(t2, t1.port == t2.port)) + required: + - default + type: object + type: object required: - gatewayClassName - listeners @@ -3788,7 +4783,7 @@ spec: description: Status defines the current state of Gateway. properties: addresses: - description: |+ + description: |- Addresses lists the network addresses that have been bound to the Gateway. @@ -3798,7 +4793,6 @@ spec: * no addresses are specified, all addresses are dynamically assigned * a combination of specified and dynamic addresses are assigned * a specified address was unusable (e.g. already in use) - items: description: GatewayStatusAddress describes a network address that is bound to a Gateway. @@ -3843,6 +4837,7 @@ spec: true' maxItems: 16 type: array + x-kubernetes-list-type: atomic conditions: default: - lastTransitionTime: "1970-01-01T00:00:00Z" @@ -4056,6 +5051,7 @@ spec: type: object maxItems: 8 type: array + x-kubernetes-list-type: atomic required: - attachedRoutes - conditions @@ -4090,9 +5086,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: grpcroutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -4238,8 +5233,9 @@ spec: type: string maxItems: 16 type: array + x-kubernetes-list-type: atomic parentRefs: - description: |+ + description: |- ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means @@ -4301,11 +5297,6 @@ spec: connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. - - - - - items: description: |- ParentReference identifies an API object (usually a Gateway) that can be considered @@ -4455,6 +5446,7 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent @@ -4479,9 +5471,7 @@ spec: || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) rules: - description: |+ - Rules are a list of GRPC matchers, filters and actions. - + description: Rules are a list of GRPC matchers, filters and actions. items: description: |- GRPCRouteRule defines the semantics for matching a gRPC request based on @@ -4527,7 +5517,6 @@ spec: namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. @@ -4542,8 +5531,6 @@ spec: If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. - - properties: filters: description: |- @@ -4629,7 +5616,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -4704,7 +5691,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -4732,7 +5719,7 @@ spec: x-kubernetes-list-type: map type: object requestMirror: - description: |+ + description: |- RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. @@ -4742,7 +5729,6 @@ spec: backends. Support: Extended - properties: backendRef: description: |- @@ -4838,13 +5824,12 @@ spec: rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' fraction: - description: |+ + description: |- Fraction represents the fraction of requests that should be mirrored to BackendRef. Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - properties: denominator: default: 100 @@ -4863,14 +5848,13 @@ spec: to denominator rule: self.numerator <= self.denominator percent: - description: |+ + description: |- Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests). Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - format: int32 maximum: 100 minimum: 0 @@ -4915,7 +5899,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -4990,7 +5974,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -5018,7 +6002,7 @@ spec: x-kubernetes-list-type: map type: object type: - description: |+ + description: |- Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: @@ -5043,7 +6027,6 @@ spec: If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. - enum: - ResponseHeaderModifier - RequestHeaderModifier @@ -5085,6 +6068,7 @@ spec: rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: RequestHeaderModifier filter cannot be repeated rule: self.filter(f, f.type == 'RequestHeaderModifier').size() @@ -5181,6 +6165,7 @@ spec: ? has(self.port) : true' maxItems: 16 type: array + x-kubernetes-list-type: atomic filters: description: |- Filters define the filters that are applied to requests that match @@ -5200,7 +6185,7 @@ spec: Specifying the same filter multiple times is not supported unless explicitly indicated in the filter. - If an implementation can not support a combination of filters, it must clearly + If an implementation cannot support a combination of filters, it must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify @@ -5283,7 +6268,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -5357,7 +6342,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -5385,7 +6370,7 @@ spec: x-kubernetes-list-type: map type: object requestMirror: - description: |+ + description: |- RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. @@ -5395,7 +6380,6 @@ spec: backends. Support: Extended - properties: backendRef: description: |- @@ -5491,13 +6475,12 @@ spec: rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' fraction: - description: |+ + description: |- Fraction represents the fraction of requests that should be mirrored to BackendRef. Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - properties: denominator: default: 100 @@ -5516,14 +6499,13 @@ spec: denominator rule: self.numerator <= self.denominator percent: - description: |+ + description: |- Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests). Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - format: int32 maximum: 100 minimum: 0 @@ -5567,7 +6549,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -5641,7 +6623,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -5669,7 +6651,7 @@ spec: x-kubernetes-list-type: map type: object type: - description: |+ + description: |- Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: @@ -5694,7 +6676,6 @@ spec: If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. - enum: - ResponseHeaderModifier - RequestHeaderModifier @@ -5735,6 +6716,7 @@ spec: rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: RequestHeaderModifier filter cannot be repeated rule: self.filter(f, f.type == 'RequestHeaderModifier').size() @@ -5910,10 +6892,11 @@ spec: has(self.method) ? self.method.matches(r"""^[A-Za-z_][A-Za-z_0-9]*$"""): true' type: object - maxItems: 8 + maxItems: 64 type: array + x-kubernetes-list-type: atomic name: - description: | + description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. Support: Extended @@ -5922,12 +6905,11 @@ spec: pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string sessionPersistence: - description: |+ + description: |- SessionPersistence defines and configures session persistence for the route rule. Support: Extended - properties: absoluteTimeout: description: |- @@ -5962,6 +6944,8 @@ spec: absolute lifetime of the cookie tracked by the gateway and is optional. + Defaults to "Session". + Support: Core for "Session" type Support: Extended for "Permanent" type @@ -6012,6 +6996,7 @@ spec: type: object maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: While 16 rules and 64 matches per rule are allowed, the total number of matches across all rules in a route must be less @@ -6036,6 +7021,24 @@ spec: - message: Rule name must be unique within the route rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string type: object status: description: Status defines the current state of GRPCRoute. @@ -6079,7 +7082,7 @@ spec: There are a number of cases where the "Accepted" condition may not be set due to lack of controller visibility, that includes when: - * The Route refers to a non-existent parent. + * The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to. items: @@ -6300,14 +7303,18 @@ spec: - name type: object required: + - conditions - controllerName - parentRef type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic required: - parents type: object + required: + - spec type: object served: true storage: true @@ -6328,9 +7335,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: httproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -6456,8 +7462,9 @@ spec: type: string maxItems: 16 type: array + x-kubernetes-list-type: atomic parentRefs: - description: |+ + description: |- ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means @@ -6519,11 +7526,6 @@ spec: connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. - - - - - items: description: |- ParentReference identifies an API object (usually a Gateway) that can be considered @@ -6673,6 +7675,7 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent @@ -6702,9 +7705,7 @@ spec: - path: type: PathPrefix value: / - description: |+ - Rules are a list of HTTP matchers, filters and actions. - + description: Rules are a list of HTTP matchers, filters and actions. items: description: |- HTTPRouteRule defines semantics for matching an HTTP request based on @@ -6757,7 +7758,6 @@ spec: namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. @@ -6772,8 +7772,6 @@ spec: If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. - - properties: filters: description: |- @@ -6791,6 +7789,290 @@ spec: authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + When set to true, the gateway will include the `Access-Control-Allow-Credentials` + response header with value true (case-sensitive). + + When set to false or omitted the gateway will omit the header + `Access-Control-Allow-Credentials` entirely (this is the standard CORS + behavior). + + Support: Extended + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is false or omitted. + + When the `AllowCredentials` field is true and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is false or omitted. + + When the `AllowCredentials` field is true and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is false or omitted. + + When the `AllowCredentials` field is true and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The CORSOrigin MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The CORSOrigin MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part, or it should be a single '*' character. + URIs that include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: (^\*$)|(^([a-zA-Z][a-zA-Z0-9+\-.]+):\/\/([^:/?#]+)(:([0-9]{1,5}))?$) + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowOrigins cannot contain '*' alongside + other origins + rule: '!(''*'' in self && self.size() > 1)' + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is false or omitted. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object extensionRef: description: |- ExtensionRef is an optional, implementation-specific extension to the @@ -6826,6 +8108,253 @@ spec: - kind - name type: object + externalAuth: + description: |- + ExternalAuth configures settings related to sending request details + to an external auth service. The external service MUST authenticate + the request, and MAY authorize the request as well. + + If there is any problem communicating with the external service, + this filter MUST fail closed. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef is a reference to a backend to send authorization + requests to. + + The backend must speak the selected protocol (GRPC or HTTP) on the + referenced port. + + If the backend service requires TLS, use BackendTLSPolicy to tell the + implementation to supply the TLS details to be used to connect to that + backend. + 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' + forwardBody: + description: |- + ForwardBody controls if requests to the authorization server should include + the body of the client request; and if so, how big that body is allowed + to be. + + It is expected that implementations will buffer the request body up to + `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a + 4xx series error (413 or 403 are common examples), and fail processing + of the filter. + + If unset, or `forwardBody.maxSize` is set to `0`, then the body will not + be forwarded. + + Feature Name: HTTPRouteExternalAuthForwardBody + properties: + maxSize: + description: |- + MaxSize specifies how large in bytes the largest body that will be buffered + and sent to the authorization server. If the body size is larger than + `maxSize`, then the body sent to the authorization server must be + truncated to `maxSize` bytes. + + Experimental note: This behavior needs to be checked against + various dataplanes; it may need to be changed. + See https://github.com/kubernetes-sigs/gateway-api/pull/4001#discussion_r2291405746 + for more. + + If 0, the body will not be sent to the authorization server. + type: integer + type: object + grpc: + description: |- + GRPCAuthConfig contains configuration for communication with ext_authz + protocol-speaking backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what headers from the client request + will be sent to the authorization server. + + If this list is empty, then all headers must be sent. + + If the list has entries, only those entries must be sent. + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + http: + description: |- + HTTPAuthConfig contains configuration for communication with HTTP-speaking + backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what additional headers from the client request + will be sent to the authorization server. + + The following headers must always be sent to the authorization server, + regardless of this setting: + + * `Host` + * `Method` + * `Path` + * `Content-Length` + * `Authorization` + + If this list is empty, then only those headers must be sent. + + Note that `Content-Length` has a special behavior, in that the length + sent must be correct for the actual request to the external authorization + server - that is, it must reflect the actual number of bytes sent in the + body of the request to the authorization server. + + So if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set + to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set + to anything other than `0`, then the `Content-Length` of the authorization + request must be set to the actual number of bytes forwarded. + items: + type: string + type: array + x-kubernetes-list-type: set + allowedResponseHeaders: + description: |- + AllowedResponseHeaders specifies what headers from the authorization response + will be copied into the request to the backend. + + If this list is empty, then all headers from the authorization server + except Authority or Host must be copied. + items: + type: string + type: array + x-kubernetes-list-type: set + path: + description: |- + Path sets the prefix that paths from the client request will have added + when forwarded to the authorization server. + + When empty or unspecified, no prefix is added. + + Valid values are the same as the "value" regex for path values in the `match` + stanza, and the validation regex will screen out invalid paths in the same way. + Even with the validation, implementations MUST sanitize this input before using it + directly. + maxLength: 1024 + pattern: ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$ + type: string + type: object + protocol: + description: |- + ExternalAuthProtocol describes which protocol to use when communicating with an + ext_authz authorization server. + + When this is set to GRPC, each backend must use the Envoy ext_authz protocol + on the port specified in `backendRefs`. Requests and responses are defined + in the protobufs explained at: + https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto + + When this is set to HTTP, each backend must respond with a `200` status + code in on a successful authorization. Any other code is considered + an authorization failure. + + Feature Names: + GRPC Support - HTTPRouteExternalAuthGRPC + HTTP Support - HTTPRouteExternalAuthHTTP + enum: + - HTTP + - GRPC + type: string + required: + - backendRef + - protocol + type: object + x-kubernetes-validations: + - message: grpc must be specified when protocol + is set to 'GRPC' + rule: 'self.protocol == ''GRPC'' ? has(self.grpc) + : true' + - message: protocol must be 'GRPC' when grpc is + set + rule: 'has(self.grpc) ? self.protocol == ''GRPC'' + : true' + - message: http must be specified when protocol + is set to 'HTTP' + rule: 'self.protocol == ''HTTP'' ? has(self.http) + : true' + - message: protocol must be 'HTTP' when http is + set + rule: 'has(self.http) ? self.protocol == ''HTTP'' + : true' requestHeaderModifier: description: |- RequestHeaderModifier defines a schema for a filter that modifies request @@ -6859,7 +8388,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -6934,7 +8463,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -6962,7 +8491,7 @@ spec: x-kubernetes-list-type: map type: object requestMirror: - description: |+ + description: |- RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. @@ -6972,7 +8501,6 @@ spec: backends. Support: Extended - properties: backendRef: description: |- @@ -7068,13 +8596,12 @@ spec: rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' fraction: - description: |+ + description: |- Fraction represents the fraction of requests that should be mirrored to BackendRef. Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - properties: denominator: default: 100 @@ -7093,14 +8620,13 @@ spec: to denominator rule: self.numerator <= self.denominator percent: - description: |+ + description: |- Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests). Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - format: int32 maximum: 100 minimum: 0 @@ -7298,7 +8824,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -7373,7 +8899,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -7441,6 +8967,8 @@ spec: - RequestRedirect - URLRewrite - ExtensionRef + - CORS + - ExternalAuth type: string urlRewrite: description: |- @@ -7573,13 +9101,21 @@ spec: - message: filter.extensionRef must be specified for ExtensionRef filter.type rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type + is not CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + - message: filter.externalAuth must be nil if the filter.type + is not ExternalAuth + rule: '!(has(self.externalAuth) && self.type != ''ExternalAuth'')' + - message: filter.externalAuth must be specified for + ExternalAuth filter.type + rule: '!(!has(self.externalAuth) && self.type == ''ExternalAuth'')' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - - message: May specify either httpRouteFilterRequestRedirect - or httpRouteFilterRequestRewrite, but not both - rule: '!(self.exists(f, f.type == ''RequestRedirect'') - && self.exists(f, f.type == ''URLRewrite''))' - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both rule: '!(self.exists(f, f.type == ''RequestRedirect'') @@ -7685,6 +9221,7 @@ spec: ? has(self.port) : true' maxItems: 16 type: array + x-kubernetes-list-type: atomic filters: description: |- Filters define the filters that are applied to requests that match @@ -7694,7 +9231,7 @@ spec: they are specified. Implementations MAY choose to implement this ordering strictly, rejecting - any combination or order of filters that can not be supported. If implementations + any combination or order of filters that cannot be supported. If implementations choose a strict interpretation of filter ordering, they MUST clearly document that behavior. @@ -7716,7 +9253,7 @@ spec: All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an - implementation can not support other combinations of filters, they must clearly + implementation cannot support other combinations of filters, they must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify @@ -7732,6 +9269,290 @@ spec: authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + When set to true, the gateway will include the `Access-Control-Allow-Credentials` + response header with value true (case-sensitive). + + When set to false or omitted the gateway will omit the header + `Access-Control-Allow-Credentials` entirely (this is the standard CORS + behavior). + + Support: Extended + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is false or omitted. + + When the `AllowCredentials` field is true and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is false or omitted. + + When the `AllowCredentials` field is true and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is false or omitted. + + When the `AllowCredentials` field is true and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The CORSOrigin MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The CORSOrigin MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part, or it should be a single '*' character. + URIs that include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: (^\*$)|(^([a-zA-Z][a-zA-Z0-9+\-.]+):\/\/([^:/?#]+)(:([0-9]{1,5}))?$) + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowOrigins cannot contain '*' alongside + other origins + rule: '!(''*'' in self && self.size() > 1)' + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is false or omitted. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object extensionRef: description: |- ExtensionRef is an optional, implementation-specific extension to the @@ -7767,6 +9588,251 @@ spec: - kind - name type: object + externalAuth: + description: |- + ExternalAuth configures settings related to sending request details + to an external auth service. The external service MUST authenticate + the request, and MAY authorize the request as well. + + If there is any problem communicating with the external service, + this filter MUST fail closed. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef is a reference to a backend to send authorization + requests to. + + The backend must speak the selected protocol (GRPC or HTTP) on the + referenced port. + + If the backend service requires TLS, use BackendTLSPolicy to tell the + implementation to supply the TLS details to be used to connect to that + backend. + 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' + forwardBody: + description: |- + ForwardBody controls if requests to the authorization server should include + the body of the client request; and if so, how big that body is allowed + to be. + + It is expected that implementations will buffer the request body up to + `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a + 4xx series error (413 or 403 are common examples), and fail processing + of the filter. + + If unset, or `forwardBody.maxSize` is set to `0`, then the body will not + be forwarded. + + Feature Name: HTTPRouteExternalAuthForwardBody + properties: + maxSize: + description: |- + MaxSize specifies how large in bytes the largest body that will be buffered + and sent to the authorization server. If the body size is larger than + `maxSize`, then the body sent to the authorization server must be + truncated to `maxSize` bytes. + + Experimental note: This behavior needs to be checked against + various dataplanes; it may need to be changed. + See https://github.com/kubernetes-sigs/gateway-api/pull/4001#discussion_r2291405746 + for more. + + If 0, the body will not be sent to the authorization server. + type: integer + type: object + grpc: + description: |- + GRPCAuthConfig contains configuration for communication with ext_authz + protocol-speaking backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what headers from the client request + will be sent to the authorization server. + + If this list is empty, then all headers must be sent. + + If the list has entries, only those entries must be sent. + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + http: + description: |- + HTTPAuthConfig contains configuration for communication with HTTP-speaking + backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what additional headers from the client request + will be sent to the authorization server. + + The following headers must always be sent to the authorization server, + regardless of this setting: + + * `Host` + * `Method` + * `Path` + * `Content-Length` + * `Authorization` + + If this list is empty, then only those headers must be sent. + + Note that `Content-Length` has a special behavior, in that the length + sent must be correct for the actual request to the external authorization + server - that is, it must reflect the actual number of bytes sent in the + body of the request to the authorization server. + + So if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set + to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set + to anything other than `0`, then the `Content-Length` of the authorization + request must be set to the actual number of bytes forwarded. + items: + type: string + type: array + x-kubernetes-list-type: set + allowedResponseHeaders: + description: |- + AllowedResponseHeaders specifies what headers from the authorization response + will be copied into the request to the backend. + + If this list is empty, then all headers from the authorization server + except Authority or Host must be copied. + items: + type: string + type: array + x-kubernetes-list-type: set + path: + description: |- + Path sets the prefix that paths from the client request will have added + when forwarded to the authorization server. + + When empty or unspecified, no prefix is added. + + Valid values are the same as the "value" regex for path values in the `match` + stanza, and the validation regex will screen out invalid paths in the same way. + Even with the validation, implementations MUST sanitize this input before using it + directly. + maxLength: 1024 + pattern: ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$ + type: string + type: object + protocol: + description: |- + ExternalAuthProtocol describes which protocol to use when communicating with an + ext_authz authorization server. + + When this is set to GRPC, each backend must use the Envoy ext_authz protocol + on the port specified in `backendRefs`. Requests and responses are defined + in the protobufs explained at: + https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto + + When this is set to HTTP, each backend must respond with a `200` status + code in on a successful authorization. Any other code is considered + an authorization failure. + + Feature Names: + GRPC Support - HTTPRouteExternalAuthGRPC + HTTP Support - HTTPRouteExternalAuthHTTP + enum: + - HTTP + - GRPC + type: string + required: + - backendRef + - protocol + type: object + x-kubernetes-validations: + - message: grpc must be specified when protocol is set + to 'GRPC' + rule: 'self.protocol == ''GRPC'' ? has(self.grpc) : + true' + - message: protocol must be 'GRPC' when grpc is set + rule: 'has(self.grpc) ? self.protocol == ''GRPC'' : + true' + - message: http must be specified when protocol is set + to 'HTTP' + rule: 'self.protocol == ''HTTP'' ? has(self.http) : + true' + - message: protocol must be 'HTTP' when http is set + rule: 'has(self.http) ? self.protocol == ''HTTP'' : + true' requestHeaderModifier: description: |- RequestHeaderModifier defines a schema for a filter that modifies request @@ -7799,7 +9865,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -7873,7 +9939,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -7901,7 +9967,7 @@ spec: x-kubernetes-list-type: map type: object requestMirror: - description: |+ + description: |- RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. @@ -7911,7 +9977,6 @@ spec: backends. Support: Extended - properties: backendRef: description: |- @@ -8007,13 +10072,12 @@ spec: rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' fraction: - description: |+ + description: |- Fraction represents the fraction of requests that should be mirrored to BackendRef. Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - properties: denominator: default: 100 @@ -8032,14 +10096,13 @@ spec: denominator rule: self.numerator <= self.denominator percent: - description: |+ + description: |- Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests). Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - format: int32 maximum: 100 minimum: 0 @@ -8236,7 +10299,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -8310,7 +10373,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -8378,6 +10441,8 @@ spec: - RequestRedirect - URLRewrite - ExtensionRef + - CORS + - ExternalAuth type: string urlRewrite: description: |- @@ -8507,8 +10572,20 @@ spec: - message: filter.extensionRef must be specified for ExtensionRef filter.type rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type is not + CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + - message: filter.externalAuth must be nil if the filter.type + is not ExternalAuth + rule: '!(has(self.externalAuth) && self.type != ''ExternalAuth'')' + - message: filter.externalAuth must be specified for ExternalAuth + filter.type + rule: '!(!has(self.externalAuth) && self.type == ''ExternalAuth'')' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both @@ -8610,7 +10687,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent @@ -8820,8 +10897,9 @@ spec: type: object maxItems: 64 type: array + x-kubernetes-list-type: atomic name: - description: | + description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. Support: Extended @@ -8830,15 +10908,14 @@ spec: pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string retry: - description: |+ + description: |- Retry defines the configuration for when to retry an HTTP request. Support: Extended - properties: attempts: description: |- - Attempts specifies the maxmimum number of times an individual request + Attempts specifies the maximum number of times an individual request from the gateway to a backend should be retried. If the maximum number of retries has been attempted without a successful @@ -8912,20 +10989,18 @@ spec: Implementations MAY support specifying discrete values in the 400-499 range, which are often inadvisable to retry. - - maximum: 599 minimum: 400 type: integer type: array + x-kubernetes-list-type: atomic type: object sessionPersistence: - description: |+ + description: |- SessionPersistence defines and configures session persistence for the route rule. Support: Extended - properties: absoluteTimeout: description: |- @@ -8960,6 +11035,8 @@ spec: absolute lifetime of the cookie tracked by the gateway and is optional. + Defaults to "Session". + Support: Core for "Session" type Support: Extended for "Permanent" type @@ -9112,6 +11189,7 @@ spec: != ''PathPrefix'') ? false : true) : true' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: While 16 rules and 64 matches per rule are allowed, the total number of matches across all rules in a route must be less @@ -9130,6 +11208,24 @@ spec: - message: Rule name must be unique within the route rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string type: object status: description: Status defines the current state of HTTPRoute. @@ -9173,7 +11269,7 @@ spec: There are a number of cases where the "Accepted" condition may not be set due to lack of controller visibility, that includes when: - * The Route refers to a non-existent parent. + * The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to. items: @@ -9394,11 +11490,13 @@ spec: - name type: object required: + - conditions - controllerName - parentRef type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic required: - parents type: object @@ -9522,8 +11620,9 @@ spec: type: string maxItems: 16 type: array + x-kubernetes-list-type: atomic parentRefs: - description: |+ + description: |- ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means @@ -9585,11 +11684,6 @@ spec: connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. - - - - - items: description: |- ParentReference identifies an API object (usually a Gateway) that can be considered @@ -9739,6 +11833,7 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent @@ -9768,9 +11863,7 @@ spec: - path: type: PathPrefix value: / - description: |+ - Rules are a list of HTTP matchers, filters and actions. - + description: Rules are a list of HTTP matchers, filters and actions. items: description: |- HTTPRouteRule defines semantics for matching an HTTP request based on @@ -9823,7 +11916,6 @@ spec: namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. @@ -9838,8 +11930,6 @@ spec: If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. - - properties: filters: description: |- @@ -9857,6 +11947,290 @@ spec: authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + When set to true, the gateway will include the `Access-Control-Allow-Credentials` + response header with value true (case-sensitive). + + When set to false or omitted the gateway will omit the header + `Access-Control-Allow-Credentials` entirely (this is the standard CORS + behavior). + + Support: Extended + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is false or omitted. + + When the `AllowCredentials` field is true and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is false or omitted. + + When the `AllowCredentials` field is true and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is false or omitted. + + When the `AllowCredentials` field is true and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The CORSOrigin MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The CORSOrigin MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part, or it should be a single '*' character. + URIs that include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: (^\*$)|(^([a-zA-Z][a-zA-Z0-9+\-.]+):\/\/([^:/?#]+)(:([0-9]{1,5}))?$) + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowOrigins cannot contain '*' alongside + other origins + rule: '!(''*'' in self && self.size() > 1)' + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is false or omitted. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object extensionRef: description: |- ExtensionRef is an optional, implementation-specific extension to the @@ -9892,6 +12266,253 @@ spec: - kind - name type: object + externalAuth: + description: |- + ExternalAuth configures settings related to sending request details + to an external auth service. The external service MUST authenticate + the request, and MAY authorize the request as well. + + If there is any problem communicating with the external service, + this filter MUST fail closed. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef is a reference to a backend to send authorization + requests to. + + The backend must speak the selected protocol (GRPC or HTTP) on the + referenced port. + + If the backend service requires TLS, use BackendTLSPolicy to tell the + implementation to supply the TLS details to be used to connect to that + backend. + 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' + forwardBody: + description: |- + ForwardBody controls if requests to the authorization server should include + the body of the client request; and if so, how big that body is allowed + to be. + + It is expected that implementations will buffer the request body up to + `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a + 4xx series error (413 or 403 are common examples), and fail processing + of the filter. + + If unset, or `forwardBody.maxSize` is set to `0`, then the body will not + be forwarded. + + Feature Name: HTTPRouteExternalAuthForwardBody + properties: + maxSize: + description: |- + MaxSize specifies how large in bytes the largest body that will be buffered + and sent to the authorization server. If the body size is larger than + `maxSize`, then the body sent to the authorization server must be + truncated to `maxSize` bytes. + + Experimental note: This behavior needs to be checked against + various dataplanes; it may need to be changed. + See https://github.com/kubernetes-sigs/gateway-api/pull/4001#discussion_r2291405746 + for more. + + If 0, the body will not be sent to the authorization server. + type: integer + type: object + grpc: + description: |- + GRPCAuthConfig contains configuration for communication with ext_authz + protocol-speaking backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what headers from the client request + will be sent to the authorization server. + + If this list is empty, then all headers must be sent. + + If the list has entries, only those entries must be sent. + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + http: + description: |- + HTTPAuthConfig contains configuration for communication with HTTP-speaking + backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what additional headers from the client request + will be sent to the authorization server. + + The following headers must always be sent to the authorization server, + regardless of this setting: + + * `Host` + * `Method` + * `Path` + * `Content-Length` + * `Authorization` + + If this list is empty, then only those headers must be sent. + + Note that `Content-Length` has a special behavior, in that the length + sent must be correct for the actual request to the external authorization + server - that is, it must reflect the actual number of bytes sent in the + body of the request to the authorization server. + + So if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set + to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set + to anything other than `0`, then the `Content-Length` of the authorization + request must be set to the actual number of bytes forwarded. + items: + type: string + type: array + x-kubernetes-list-type: set + allowedResponseHeaders: + description: |- + AllowedResponseHeaders specifies what headers from the authorization response + will be copied into the request to the backend. + + If this list is empty, then all headers from the authorization server + except Authority or Host must be copied. + items: + type: string + type: array + x-kubernetes-list-type: set + path: + description: |- + Path sets the prefix that paths from the client request will have added + when forwarded to the authorization server. + + When empty or unspecified, no prefix is added. + + Valid values are the same as the "value" regex for path values in the `match` + stanza, and the validation regex will screen out invalid paths in the same way. + Even with the validation, implementations MUST sanitize this input before using it + directly. + maxLength: 1024 + pattern: ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$ + type: string + type: object + protocol: + description: |- + ExternalAuthProtocol describes which protocol to use when communicating with an + ext_authz authorization server. + + When this is set to GRPC, each backend must use the Envoy ext_authz protocol + on the port specified in `backendRefs`. Requests and responses are defined + in the protobufs explained at: + https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto + + When this is set to HTTP, each backend must respond with a `200` status + code in on a successful authorization. Any other code is considered + an authorization failure. + + Feature Names: + GRPC Support - HTTPRouteExternalAuthGRPC + HTTP Support - HTTPRouteExternalAuthHTTP + enum: + - HTTP + - GRPC + type: string + required: + - backendRef + - protocol + type: object + x-kubernetes-validations: + - message: grpc must be specified when protocol + is set to 'GRPC' + rule: 'self.protocol == ''GRPC'' ? has(self.grpc) + : true' + - message: protocol must be 'GRPC' when grpc is + set + rule: 'has(self.grpc) ? self.protocol == ''GRPC'' + : true' + - message: http must be specified when protocol + is set to 'HTTP' + rule: 'self.protocol == ''HTTP'' ? has(self.http) + : true' + - message: protocol must be 'HTTP' when http is + set + rule: 'has(self.http) ? self.protocol == ''HTTP'' + : true' requestHeaderModifier: description: |- RequestHeaderModifier defines a schema for a filter that modifies request @@ -9925,7 +12546,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -10000,7 +12621,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -10028,7 +12649,7 @@ spec: x-kubernetes-list-type: map type: object requestMirror: - description: |+ + description: |- RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. @@ -10038,7 +12659,6 @@ spec: backends. Support: Extended - properties: backendRef: description: |- @@ -10134,13 +12754,12 @@ spec: rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' fraction: - description: |+ + description: |- Fraction represents the fraction of requests that should be mirrored to BackendRef. Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - properties: denominator: default: 100 @@ -10159,14 +12778,13 @@ spec: to denominator rule: self.numerator <= self.denominator percent: - description: |+ + description: |- Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests). Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - format: int32 maximum: 100 minimum: 0 @@ -10364,7 +12982,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -10439,7 +13057,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -10507,6 +13125,8 @@ spec: - RequestRedirect - URLRewrite - ExtensionRef + - CORS + - ExternalAuth type: string urlRewrite: description: |- @@ -10639,13 +13259,21 @@ spec: - message: filter.extensionRef must be specified for ExtensionRef filter.type rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type + is not CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + - message: filter.externalAuth must be nil if the filter.type + is not ExternalAuth + rule: '!(has(self.externalAuth) && self.type != ''ExternalAuth'')' + - message: filter.externalAuth must be specified for + ExternalAuth filter.type + rule: '!(!has(self.externalAuth) && self.type == ''ExternalAuth'')' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - - message: May specify either httpRouteFilterRequestRedirect - or httpRouteFilterRequestRewrite, but not both - rule: '!(self.exists(f, f.type == ''RequestRedirect'') - && self.exists(f, f.type == ''URLRewrite''))' - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both rule: '!(self.exists(f, f.type == ''RequestRedirect'') @@ -10751,6 +13379,7 @@ spec: ? has(self.port) : true' maxItems: 16 type: array + x-kubernetes-list-type: atomic filters: description: |- Filters define the filters that are applied to requests that match @@ -10760,7 +13389,7 @@ spec: they are specified. Implementations MAY choose to implement this ordering strictly, rejecting - any combination or order of filters that can not be supported. If implementations + any combination or order of filters that cannot be supported. If implementations choose a strict interpretation of filter ordering, they MUST clearly document that behavior. @@ -10782,7 +13411,7 @@ spec: All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an - implementation can not support other combinations of filters, they must clearly + implementation cannot support other combinations of filters, they must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify @@ -10798,6 +13427,290 @@ spec: authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + When set to true, the gateway will include the `Access-Control-Allow-Credentials` + response header with value true (case-sensitive). + + When set to false or omitted the gateway will omit the header + `Access-Control-Allow-Credentials` entirely (this is the standard CORS + behavior). + + Support: Extended + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is false or omitted. + + When the `AllowCredentials` field is true and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is false or omitted. + + When the `AllowCredentials` field is true and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is false or omitted. + + When the `AllowCredentials` field is true and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The CORSOrigin MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The CORSOrigin MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part, or it should be a single '*' character. + URIs that include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: (^\*$)|(^([a-zA-Z][a-zA-Z0-9+\-.]+):\/\/([^:/?#]+)(:([0-9]{1,5}))?$) + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowOrigins cannot contain '*' alongside + other origins + rule: '!(''*'' in self && self.size() > 1)' + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is false or omitted. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object extensionRef: description: |- ExtensionRef is an optional, implementation-specific extension to the @@ -10833,6 +13746,251 @@ spec: - kind - name type: object + externalAuth: + description: |- + ExternalAuth configures settings related to sending request details + to an external auth service. The external service MUST authenticate + the request, and MAY authorize the request as well. + + If there is any problem communicating with the external service, + this filter MUST fail closed. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef is a reference to a backend to send authorization + requests to. + + The backend must speak the selected protocol (GRPC or HTTP) on the + referenced port. + + If the backend service requires TLS, use BackendTLSPolicy to tell the + implementation to supply the TLS details to be used to connect to that + backend. + 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' + forwardBody: + description: |- + ForwardBody controls if requests to the authorization server should include + the body of the client request; and if so, how big that body is allowed + to be. + + It is expected that implementations will buffer the request body up to + `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a + 4xx series error (413 or 403 are common examples), and fail processing + of the filter. + + If unset, or `forwardBody.maxSize` is set to `0`, then the body will not + be forwarded. + + Feature Name: HTTPRouteExternalAuthForwardBody + properties: + maxSize: + description: |- + MaxSize specifies how large in bytes the largest body that will be buffered + and sent to the authorization server. If the body size is larger than + `maxSize`, then the body sent to the authorization server must be + truncated to `maxSize` bytes. + + Experimental note: This behavior needs to be checked against + various dataplanes; it may need to be changed. + See https://github.com/kubernetes-sigs/gateway-api/pull/4001#discussion_r2291405746 + for more. + + If 0, the body will not be sent to the authorization server. + type: integer + type: object + grpc: + description: |- + GRPCAuthConfig contains configuration for communication with ext_authz + protocol-speaking backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what headers from the client request + will be sent to the authorization server. + + If this list is empty, then all headers must be sent. + + If the list has entries, only those entries must be sent. + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + http: + description: |- + HTTPAuthConfig contains configuration for communication with HTTP-speaking + backends. + + If unset, implementations must assume the default behavior for each + included field is intended. + properties: + allowedHeaders: + description: |- + AllowedRequestHeaders specifies what additional headers from the client request + will be sent to the authorization server. + + The following headers must always be sent to the authorization server, + regardless of this setting: + + * `Host` + * `Method` + * `Path` + * `Content-Length` + * `Authorization` + + If this list is empty, then only those headers must be sent. + + Note that `Content-Length` has a special behavior, in that the length + sent must be correct for the actual request to the external authorization + server - that is, it must reflect the actual number of bytes sent in the + body of the request to the authorization server. + + So if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set + to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set + to anything other than `0`, then the `Content-Length` of the authorization + request must be set to the actual number of bytes forwarded. + items: + type: string + type: array + x-kubernetes-list-type: set + allowedResponseHeaders: + description: |- + AllowedResponseHeaders specifies what headers from the authorization response + will be copied into the request to the backend. + + If this list is empty, then all headers from the authorization server + except Authority or Host must be copied. + items: + type: string + type: array + x-kubernetes-list-type: set + path: + description: |- + Path sets the prefix that paths from the client request will have added + when forwarded to the authorization server. + + When empty or unspecified, no prefix is added. + + Valid values are the same as the "value" regex for path values in the `match` + stanza, and the validation regex will screen out invalid paths in the same way. + Even with the validation, implementations MUST sanitize this input before using it + directly. + maxLength: 1024 + pattern: ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$ + type: string + type: object + protocol: + description: |- + ExternalAuthProtocol describes which protocol to use when communicating with an + ext_authz authorization server. + + When this is set to GRPC, each backend must use the Envoy ext_authz protocol + on the port specified in `backendRefs`. Requests and responses are defined + in the protobufs explained at: + https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto + + When this is set to HTTP, each backend must respond with a `200` status + code in on a successful authorization. Any other code is considered + an authorization failure. + + Feature Names: + GRPC Support - HTTPRouteExternalAuthGRPC + HTTP Support - HTTPRouteExternalAuthHTTP + enum: + - HTTP + - GRPC + type: string + required: + - backendRef + - protocol + type: object + x-kubernetes-validations: + - message: grpc must be specified when protocol is set + to 'GRPC' + rule: 'self.protocol == ''GRPC'' ? has(self.grpc) : + true' + - message: protocol must be 'GRPC' when grpc is set + rule: 'has(self.grpc) ? self.protocol == ''GRPC'' : + true' + - message: http must be specified when protocol is set + to 'HTTP' + rule: 'self.protocol == ''HTTP'' ? has(self.http) : + true' + - message: protocol must be 'HTTP' when http is set + rule: 'has(self.http) ? self.protocol == ''HTTP'' : + true' requestHeaderModifier: description: |- RequestHeaderModifier defines a schema for a filter that modifies request @@ -10865,7 +14023,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -10939,7 +14097,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -10967,7 +14125,7 @@ spec: x-kubernetes-list-type: map type: object requestMirror: - description: |+ + description: |- RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. @@ -10977,7 +14135,6 @@ spec: backends. Support: Extended - properties: backendRef: description: |- @@ -11073,13 +14230,12 @@ spec: rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' fraction: - description: |+ + description: |- Fraction represents the fraction of requests that should be mirrored to BackendRef. Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - properties: denominator: default: 100 @@ -11098,14 +14254,13 @@ spec: denominator rule: self.numerator <= self.denominator percent: - description: |+ + description: |- Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests). Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - format: int32 maximum: 100 minimum: 0 @@ -11302,7 +14457,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -11376,7 +14531,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -11444,6 +14599,8 @@ spec: - RequestRedirect - URLRewrite - ExtensionRef + - CORS + - ExternalAuth type: string urlRewrite: description: |- @@ -11573,8 +14730,20 @@ spec: - message: filter.extensionRef must be specified for ExtensionRef filter.type rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type is not + CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + - message: filter.externalAuth must be nil if the filter.type + is not ExternalAuth + rule: '!(has(self.externalAuth) && self.type != ''ExternalAuth'')' + - message: filter.externalAuth must be specified for ExternalAuth + filter.type + rule: '!(!has(self.externalAuth) && self.type == ''ExternalAuth'')' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both @@ -11676,7 +14845,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent @@ -11886,8 +15055,9 @@ spec: type: object maxItems: 64 type: array + x-kubernetes-list-type: atomic name: - description: | + description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. Support: Extended @@ -11896,15 +15066,14 @@ spec: pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string retry: - description: |+ + description: |- Retry defines the configuration for when to retry an HTTP request. Support: Extended - properties: attempts: description: |- - Attempts specifies the maxmimum number of times an individual request + Attempts specifies the maximum number of times an individual request from the gateway to a backend should be retried. If the maximum number of retries has been attempted without a successful @@ -11978,20 +15147,18 @@ spec: Implementations MAY support specifying discrete values in the 400-499 range, which are often inadvisable to retry. - - maximum: 599 minimum: 400 type: integer type: array + x-kubernetes-list-type: atomic type: object sessionPersistence: - description: |+ + description: |- SessionPersistence defines and configures session persistence for the route rule. Support: Extended - properties: absoluteTimeout: description: |- @@ -12026,6 +15193,8 @@ spec: absolute lifetime of the cookie tracked by the gateway and is optional. + Defaults to "Session". + Support: Core for "Session" type Support: Extended for "Permanent" type @@ -12178,6 +15347,7 @@ spec: != ''PathPrefix'') ? false : true) : true' maxItems: 16 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: While 16 rules and 64 matches per rule are allowed, the total number of matches across all rules in a route must be less @@ -12196,6 +15366,24 @@ spec: - message: Rule name must be unique within the route rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string type: object status: description: Status defines the current state of HTTPRoute. @@ -12239,7 +15427,7 @@ spec: There are a number of cases where the "Accepted" condition may not be set due to lack of controller visibility, that includes when: - * The Route refers to a non-existent parent. + * The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to. items: @@ -12460,11 +15648,13 @@ spec: - name type: object required: + - conditions - controllerName - parentRef type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic required: - parents type: object @@ -12490,9 +15680,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: referencegrants.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -12611,6 +15800,7 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic to: description: |- To describes the resources that may be referenced by the resources @@ -12660,6 +15850,7 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic required: - from - to @@ -12683,9 +15874,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: tcproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -12731,7 +15921,7 @@ spec: description: Spec defines the desired state of TCPRoute. properties: parentRefs: - description: |+ + description: |- ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means @@ -12793,11 +15983,6 @@ spec: connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. - - - - - items: description: |- ParentReference identifies an API object (usually a Gateway) that can be considered @@ -12947,6 +16132,7 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent @@ -12971,16 +16157,14 @@ spec: || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) rules: - description: |+ - Rules are a list of TCP matchers and actions. - + description: Rules are a list of TCP matchers and actions. items: description: TCPRouteRule is the configuration for a given rule. properties: backendRefs: description: |- BackendRefs defines the backend(s) where matching requests should be - sent. If unspecified or invalid (refers to a non-existent resource or a + sent. If unspecified or invalid (refers to a nonexistent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of @@ -13003,7 +16187,6 @@ spec: namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. @@ -13019,7 +16202,6 @@ spec: protocol then the backend is considered invalid. Implementations MUST set the "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. - Note that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields @@ -13115,6 +16297,7 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic name: description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. @@ -13124,14 +16307,35 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string + required: + - backendRefs type: object maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: Rule name must be unique within the route rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string required: - rules type: object @@ -13177,7 +16381,7 @@ spec: There are a number of cases where the "Accepted" condition may not be set due to lack of controller visibility, that includes when: - * The Route refers to a non-existent parent. + * The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to. items: @@ -13398,11 +16602,13 @@ spec: - name type: object required: + - conditions - controllerName - parentRef type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic required: - parents type: object @@ -13428,9 +16634,8 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.4.0 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: tlsroutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -13535,8 +16740,9 @@ spec: type: string maxItems: 16 type: array + x-kubernetes-list-type: atomic parentRefs: - description: |+ + description: |- ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means @@ -13598,11 +16804,6 @@ spec: connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. - - - - - items: description: |- ParentReference identifies an API object (usually a Gateway) that can be considered @@ -13752,6 +16953,7 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent @@ -13776,16 +16978,14 @@ spec: || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) rules: - description: |+ - Rules are a list of TLS matchers and actions. - + description: Rules are a list of TLS matchers and actions. items: description: TLSRouteRule is the configuration for a given rule. properties: backendRefs: description: |- BackendRefs defines the backend(s) where matching requests should be - sent. If unspecified or invalid (refers to a non-existent resource or + sent. If unspecified or invalid (refers to a nonexistent resource or a Service with no endpoints), the rule performs no forwarding; if no filters are specified that would result in a response being sent, the underlying implementation must actively reject request attempts to this @@ -13811,7 +17011,6 @@ spec: namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. @@ -13827,7 +17026,6 @@ spec: protocol then the backend is considered invalid. Implementations MUST set the "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. - Note that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields @@ -13923,6 +17121,7 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic name: description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. @@ -13932,14 +17131,35 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string + required: + - backendRefs type: object maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: Rule name must be unique within the route rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string required: - rules type: object @@ -13985,7 +17205,7 @@ spec: There are a number of cases where the "Accepted" condition may not be set due to lack of controller visibility, that includes when: - * The Route refers to a non-existent parent. + * The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to. items: @@ -14206,11 +17426,13 @@ spec: - name type: object required: + - conditions - controllerName - parentRef type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic required: - parents type: object @@ -14218,50 +17440,23 @@ spec: - spec type: object served: true - storage: true + storage: false subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null ---- -# -# config/crd/experimental/gateway.networking.k8s.io_udproutes.yaml -# -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: udproutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: UDPRoute - listKind: UDPRouteList - plural: udproutes - singular: udproute - scope: Namespaced - versions: - additionalPrinterColumns: - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha2 + name: v1alpha3 schema: openAPIV3Schema: description: |- - UDPRoute provides a way to route UDP traffic. When combined with a Gateway - listener, it can be used to forward traffic on the port specified by the - listener to a set of backends specified by the UDPRoute. + The TLSRoute resource is similar to TCPRoute, but can be configured + to match against TLS-specific metadata. This allows more flexibility + in matching streams for a given TLS listener. + + If you need to forward traffic to a single target for a TLS listener, you + could choose to use a TCPRoute with a TLS listener. properties: apiVersion: description: |- @@ -14281,10 +17476,69 @@ spec: metadata: type: object spec: - description: Spec defines the desired state of UDPRoute. + description: Spec defines the desired state of TLSRoute. properties: + hostnames: + description: |- + Hostnames defines a set of SNI hostnames that should match against the + SNI attribute of TLS ClientHello message in TLS handshake. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed in SNI hostnames per RFC 6066. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and TLSRoute, there + must be at least one intersecting hostname for the TLSRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches TLSRoutes + that have specified at least one of `test.example.com` or + `*.example.com`. + * A Listener with `*.example.com` as the hostname matches TLSRoutes + that have specified at least one hostname that matches the Listener + hostname. For example, `test.example.com` and `*.example.com` would both + match. On the other hand, `example.com` and `test.example.net` would not + match. + + If both the Listener and TLSRoute have specified hostnames, any + TLSRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + TLSRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and TLSRoute have specified hostnames, and none + match with the criteria above, then the TLSRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic parentRefs: - description: |+ + description: |- ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means @@ -14346,11 +17600,6 @@ spec: connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. - - - - - items: description: |- ParentReference identifies an API object (usually a Gateway) that can be considered @@ -14500,6 +17749,7 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent @@ -14524,20 +17774,21 @@ spec: || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) rules: - description: |+ - Rules are a list of UDP matchers and actions. - + description: Rules are a list of actions. items: - description: UDPRouteRule is the configuration for a given rule. + description: TLSRouteRule is the configuration for a given rule. properties: backendRefs: description: |- BackendRefs defines the backend(s) where matching requests should be - sent. If unspecified or invalid (refers to a non-existent resource or a - Service with no endpoints), the underlying implementation MUST actively - reject connection attempts to this backend. Packet drops must - respect weight; if an invalid backend is requested to have 80% of - the packets, then 80% of packets must be dropped instead. + sent. If unspecified or invalid (refers to a nonexistent resource or + a Service with no endpoints), the rule performs no forwarding; if no + filters are specified that would result in a response being sent, the + underlying implementation must actively reject request attempts to this + backend, by rejecting the connection or returning a 500 status code. + Request rejections must respect weight; if an invalid backend is + requested to have 80% of requests, then 80% of requests must be rejected + instead. Support: Core for Kubernetes Service @@ -14556,7 +17807,6 @@ spec: namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. @@ -14572,7 +17822,6 @@ spec: protocol then the backend is considered invalid. Implementations MUST set the "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. - Note that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields @@ -14668,6 +17917,7 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-list-type: atomic name: description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. @@ -14677,19 +17927,41 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string + required: + - backendRefs type: object - maxItems: 16 + maxItems: 1 minItems: 1 type: array + x-kubernetes-list-type: atomic x-kubernetes-validations: - message: Rule name must be unique within the route rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string required: + - hostnames - rules type: object status: - description: Status defines the current state of UDPRoute. + description: Status defines the current state of TLSRoute. properties: parents: description: |- @@ -14730,7 +18002,7 @@ spec: There are a number of cases where the "Accepted" condition may not be set due to lack of controller visibility, that includes when: - * The Route refers to a non-existent parent. + * The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to. items: @@ -14951,11 +18223,13 @@ spec: - name type: object required: + - conditions - controllerName - parentRef type: object maxItems: 32 type: array + x-kubernetes-list-type: atomic required: - parents type: object @@ -14972,3 +18246,2416 @@ status: plural: "" conditions: null storedVersions: null +--- +# +# config/crd/experimental/gateway.networking.k8s.io_udproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.4.0 + gateway.networking.k8s.io/channel: experimental + name: udproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: UDPRoute + listKind: UDPRouteList + plural: udproutes + singular: udproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + UDPRoute provides a way to route UDP traffic. When combined with a Gateway + listener, it can be used to forward traffic on the port specified by the + listener to a set of backends specified by the UDPRoute. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of UDPRoute. + properties: + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + 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: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + 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. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of UDP matchers and actions. + items: + description: UDPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. If unspecified or invalid (refers to a nonexistent resource or a + Service with no endpoints), the underlying implementation MUST actively + reject connection attempts to this backend. Packet drops must + respect weight; if an invalid backend is requested to have 80% of + the packets, then 80% of packets must be dropped instead. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Extended + items: + description: |- + BackendRef defines how a Route should forward a request to a Kubernetes + resource. + + 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. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + + + Note that when the BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. See the fields + where this struct is used for more information about the exact behavior. + 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 + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + 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: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - backendRefs + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + useDefaultGateways: + description: |- + UseDefaultGateways indicates the default Gateway scope to use for this + Route. If unset (the default) or set to None, the Route will not be + attached to any default Gateway; if set, it will be attached to any + default Gateway supporting the named scope, subject to the usual rules + about which Routes a Gateway is allowed to claim. + + Think carefully before using this functionality! The set of default + Gateways supporting the requested scope can change over time without + any notice to the Route author, and in many situations it will not be + appropriate to request a default Gateway for a given Route -- for + example, a Route with specific security requirements should almost + certainly not use a default Gateway. + enum: + - All + - None + type: string + required: + - rules + type: object + status: + description: Status defines the current state of UDPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + 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: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + 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. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - conditions + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + x-kubernetes-list-type: atomic + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# +# config/crd/experimental/gateway.networking.x-k8s.io_xbackendtrafficpolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.4.0 + gateway.networking.k8s.io/channel: experimental + labels: + gateway.networking.k8s.io/policy: Direct + name: xbackendtrafficpolicies.gateway.networking.x-k8s.io +spec: + group: gateway.networking.x-k8s.io + names: + categories: + - gateway-api + kind: XBackendTrafficPolicy + listKind: XBackendTrafficPolicyList + plural: xbackendtrafficpolicies + shortNames: + - xbtrafficpolicy + singular: xbackendtrafficpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + XBackendTrafficPolicy defines the configuration for how traffic to a + target backend should be handled. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of BackendTrafficPolicy. + properties: + retryConstraint: + description: |- + RetryConstraint defines the configuration for when to allow or prevent + further retries to a target backend, by dynamically calculating a 'retry + budget'. This budget is calculated based on the percentage of incoming + traffic composed of retries over a given time interval. Once the budget + is exceeded, additional retries will be rejected. + + For example, if the retry budget interval is 10 seconds, there have been + 1000 active requests in the past 10 seconds, and the allowed percentage + of requests that can be retried is 20% (the default), then 200 of those + requests may be composed of retries. Active requests will only be + considered for the duration of the interval when calculating the retry + budget. Retrying the same original request multiple times within the + retry budget interval will lead to each retry being counted towards + calculating the budget. + + Configuring a RetryConstraint in BackendTrafficPolicy is compatible with + HTTPRoute Retry settings for each HTTPRouteRule that targets the same + backend. While the HTTPRouteRule Retry stanza can specify whether a + request will be retried, and the number of retry attempts each client + may perform, RetryConstraint helps prevent cascading failures such as + retry storms during periods of consistent failures. + + After the retry budget has been exceeded, additional retries to the + backend MUST return a 503 response to the client. + + Additional configurations for defining a constraint on retries MAY be + defined in the future. + + Support: Extended + properties: + budget: + default: + interval: 10s + percent: 20 + description: Budget holds the details of the retry budget configuration. + properties: + interval: + default: 10s + description: |- + Interval defines the duration in which requests will be considered + for calculating the budget for retries. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + x-kubernetes-validations: + - message: interval can not be greater than one hour or less + than one second + rule: '!(duration(self) < duration(''1s'') || duration(self) + > duration(''1h''))' + percent: + default: 20 + description: |- + Percent defines the maximum percentage of active requests that may + be made up of retries. + + Support: Extended + maximum: 100 + minimum: 0 + type: integer + type: object + minRetryRate: + default: + count: 10 + interval: 1s + description: |- + MinRetryRate defines the minimum rate of retries that will be allowable + over a specified duration of time. + + The effective overall minimum rate of retries targeting the backend + service may be much higher, as there can be any number of clients which + are applying this setting locally. + + This ensures that requests can still be retried during periods of low + traffic, where the budget for retries may be calculated as a very low + value. + + Support: Extended + properties: + count: + description: |- + Count specifies the number of requests per time interval. + + Support: Extended + maximum: 1000000 + minimum: 1 + type: integer + interval: + description: |- + Interval specifies the divisor of the rate of requests, the amount of + time during which the given count of requests occur. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + x-kubernetes-validations: + - message: interval can not be greater than one hour + rule: '!(duration(self) == duration(''0s'') || duration(self) + > duration(''1h''))' + type: object + type: object + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the backend. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + targetRefs: + description: |- + TargetRefs identifies API object(s) to apply this policy to. + Currently, Backends (A grouping of like endpoints such as Service, + ServiceImport, or any implementation-specific backendRef) are the only + valid API target references. + + Currently, a TargetRef can not be scoped to a specific port on a + Service. + items: + description: |- + LocalPolicyTargetReference identifies an API object to apply a direct or + inherited policy to. This should be used as part of Policy resources + that can target Gateway API resources. For more information on how this + policy attachment model works, and a sample Policy resource, refer to + the policy attachment documentation for Gateway API. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + 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 target resource. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - group + - kind + - name + x-kubernetes-list-type: map + required: + - targetRefs + type: object + status: + description: Status defines the current state of BackendTrafficPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + 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: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + 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. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - conditions + - controllerName + type: object + maxItems: 16 + type: array + x-kubernetes-list-type: atomic + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# +# config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.4.0 + gateway.networking.k8s.io/channel: experimental + name: xlistenersets.gateway.networking.x-k8s.io +spec: + group: gateway.networking.x-k8s.io + names: + categories: + - gateway-api + kind: XListenerSet + listKind: XListenerSetList + plural: xlistenersets + shortNames: + - lset + singular: xlistenerset + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + XListenerSet defines a set of additional listeners to attach to an existing Gateway. + This resource provides a mechanism to merge multiple listeners into a single Gateway. + + The parent Gateway must explicitly allow ListenerSet attachment through its + AllowedListeners configuration. By default, Gateways do not allow ListenerSet + attachment. + + Routes can attach to a ListenerSet by specifying it as a parentRef, and can + optionally target specific listeners using the sectionName field. + + Policy Attachment: + - Policies that attach to a ListenerSet apply to all listeners defined in that resource + - Policies do not impact listeners in the parent Gateway + - Different ListenerSets attached to the same Gateway can have different policies + - If an implementation cannot apply a policy to specific listeners, it should reject the policy + + ReferenceGrant Semantics: + - ReferenceGrants applied to a Gateway are not inherited by child ListenerSets + - ReferenceGrants applied to a ListenerSet do not grant permission to the parent Gateway's listeners + - A ListenerSet can reference secrets/backends in its own namespace without a ReferenceGrant + + Gateway Integration: + - The parent Gateway's status will include an "AttachedListenerSets" condition + - This condition will be: + - True: when AllowedListeners is set and at least one child ListenerSet is attached + - False: when AllowedListeners is set but no valid listeners are attached, or when AllowedListeners is not set or false + - Unknown: when no AllowedListeners config is present + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ListenerSet. + properties: + listeners: + description: |- + Listeners associated with this ListenerSet. Listeners define + logical endpoints that are bound on this referenced parent Gateway's addresses. + + Listeners in a `Gateway` and their attached `ListenerSets` are concatenated + as a list when programming the underlying infrastructure. Each listener + name does not need to be unique across the Gateway and ListenerSets. + See ListenerEntry.Name for more details. + + Implementations MUST treat the parent Gateway as having the merged + list of all listeners from itself and attached ListenerSets using + the following precedence: + + 1. "parent" Gateway + 2. ListenerSet ordered by creation time (oldest first) + 3. ListenerSet ordered alphabetically by "{namespace}/{name}". + + An implementation MAY reject listeners by setting the ListenerEntryStatus + `Accepted` condition to False with the Reason `TooManyListeners` + + If a listener has a conflict, this will be reported in the + Status.ListenerEntryStatus setting the `Conflicted` condition to True. + + Implementations SHOULD be cautious about what information from the + parent or siblings are reported to avoid accidentally leaking + sensitive information that the child would not otherwise have access + to. This can include contents of secrets etc. + items: + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + x-kubernetes-list-type: atomic + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP + protocol layers as described above. If an implementation does not + ensure that both the SNI and Host header match the Listener hostname, + it MUST clearly document that. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + ListenerSet. + + Name is not required to be unique across a Gateway and ListenerSets. + Routes can attach to a Listener by having a ListenerSet as a parentRef + and setting the SectionName + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + default: 0 + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + + If the port is not set or specified as zero, the implementation will assign + a unique port. If the implementation does not support dynamic port + assignment, it MUST set `Accepted` condition to `False` with the + `UnsupportedPort` reason. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + protocol: + description: Protocol specifies the network protocol this listener + expects to receive. + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in ListenerTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + 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: Secret + description: Kind is kind of the referent. For example + "Secret". + 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 referenced object. 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 + required: + - name + type: object + maxItems: 64 + type: array + x-kubernetes-list-type: atomic + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, !has(l1.port) || self.exists_one(l2, has(l2.port) + && l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) + && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) + && !has(l2.hostname))))' + parentRef: + description: ParentRef references the Gateway that the listeners are + attached to. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the referent. + 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: Gateway + description: Kind is kind of the referent. For example "Gateway". + 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 referent. If not present, + the namespace of the referent is assumed to be the same as + the namespace of the referring object. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - listeners + - parentRef + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of ListenerSet. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the ListenerSet. + + Implementations MUST express ListenerSet conditions using the + `ListenerSetConditionType` and `ListenerSetConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe ListenerSet state. + + Known condition types are: + + * "Accepted" + * "Programmed" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: Port is the network port the listener is configured + to listen on. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + x-kubernetes-list-type: atomic + required: + - attachedRoutes + - conditions + - name + - port + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# +# config/crd/experimental/gateway.networking.x-k8s.io_xmeshes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.4.0 + gateway.networking.k8s.io/channel: experimental + name: xmeshes.gateway.networking.x-k8s.io +spec: + group: gateway.networking.x-k8s.io + names: + categories: + - gateway-api + kind: XMesh + listKind: XMeshList + plural: xmeshes + shortNames: + - mesh + singular: xmesh + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: XMesh defines mesh-wide characteristics of a GAMMA-compliant + service mesh. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of XMesh. + properties: + controllerName: + description: |- + ControllerName is the name of a controller that is managing Gateway API + resources for mesh traffic management. The value of this field MUST be a + domain prefixed path. + + Example: "example.com/awesome-mesh". + + This field is not mutable and cannot be empty. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description optionally provides a human-readable description + of a Mesh. + maxLength: 64 + type: string + parametersRef: + description: |- + ParametersRef is an optional reference to a resource that contains + implementation-specific configuration for this Mesh. If no + implementation-specific parameters are needed, this field MUST be + omitted. + + ParametersRef can reference a standard Kubernetes resource, i.e. + ConfigMap, or an implementation-specific custom resource. The resource + can be cluster-scoped or namespace-scoped. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Mesh MUST be rejected + with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + 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 referent. + This field is required when referring to a Namespace-scoped resource and + MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: Status defines the current state of XMesh. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions is the current status from the controller for + this Mesh. + + Controllers should prefer to publish conditions using values + of MeshConditionType for the type of each Condition. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: |- + SupportedFeatures is the set of features the Mesh support. + It MUST be sorted in ascending alphabetical order by the Name key. + items: + properties: + name: + description: |- + FeatureName is used to describe distinct features that are covered by + conformance tests. + type: string + required: + - name + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/integration/fixtures/k8s_gateway_conformance.toml b/integration/fixtures/k8s_gateway_conformance.toml deleted file mode 100644 index 5114f2dfb..000000000 --- a/integration/fixtures/k8s_gateway_conformance.toml +++ /dev/null @@ -1,21 +0,0 @@ -[global] - checkNewVersion = false - sendAnonymousUsage = false - -[log] - level = "DEBUG" - noColor = true - -[api] - insecure = true - -[experimental] - kubernetesGateway = true - -[entryPoints] - [entryPoints.web] - address = ":80" - [entryPoints.websecure] - address = ":443" - -[providers.kubernetesGateway] diff --git a/integration/conformance-reports/v1.3.0/experimental-v3.5-default-report.yaml b/integration/gateway-api-conformance-reports/v1.4.0/experimental-v3.6-default-report.yaml similarity index 93% rename from integration/conformance-reports/v1.3.0/experimental-v3.5-default-report.yaml rename to integration/gateway-api-conformance-reports/v1.4.0/experimental-v3.6-default-report.yaml index 0facbd714..429592e4a 100644 --- a/integration/conformance-reports/v1.3.0/experimental-v3.5-default-report.yaml +++ b/integration/gateway-api-conformance-reports/v1.4.0/experimental-v3.6-default-report.yaml @@ -1,14 +1,14 @@ apiVersion: gateway.networking.k8s.io/v1 date: '-' gatewayAPIChannel: experimental -gatewayAPIVersion: v1.3.0 +gatewayAPIVersion: v1.4.0 implementation: contact: - '@traefik/maintainers' organization: traefik project: traefik url: https://traefik.io/ - version: v3.5 + version: v3.6 kind: ConformanceReport mode: default profiles: @@ -16,7 +16,7 @@ profiles: result: success statistics: Failed: 0 - Passed: 12 + Passed: 13 Skipped: 0 name: GATEWAY-GRPC summary: Core tests succeeded. @@ -52,6 +52,8 @@ profiles: - GatewayStaticAddresses - HTTPRouteBackendRequestHeaderModification - HTTPRouteBackendTimeout + - HTTPRouteCORS + - HTTPRouteNamedRouteRule - HTTPRouteParentRefPort - HTTPRouteRequestMirror - HTTPRouteRequestMultipleMirrors diff --git a/integration/k8s_conformance_test.go b/integration/gateway_api_conformance_test.go similarity index 85% rename from integration/k8s_conformance_test.go rename to integration/gateway_api_conformance_test.go index 22aae5b15..9a25125d6 100644 --- a/integration/k8s_conformance_test.go +++ b/integration/gateway_api_conformance_test.go @@ -1,3 +1,5 @@ +//go:build gatewayAPIConformance + package integration import ( @@ -37,8 +39,8 @@ import ( "sigs.k8s.io/yaml" ) -// K8sConformanceSuite tests suite. -type K8sConformanceSuite struct { +// GatewayAPIConformanceSuite tests suite. +type GatewayAPIConformanceSuite struct { BaseSuite k3sContainer *k3s.K3sContainer @@ -47,15 +49,11 @@ type K8sConformanceSuite struct { clientSet *kclientset.Clientset } -func TestK8sConformanceSuite(t *testing.T) { - suite.Run(t, new(K8sConformanceSuite)) +func TestGatewayAPIConformanceSuite(t *testing.T) { + suite.Run(t, new(GatewayAPIConformanceSuite)) } -func (s *K8sConformanceSuite) SetupSuite() { - if !*k8sConformance { - s.T().Skip("Skip because it can take a long time to execute. To enable pass the `k8sConformance` flag.") - } - +func (s *GatewayAPIConformanceSuite) SetupSuite() { s.BaseSuite.SetupSuite() // Avoid panic. @@ -82,9 +80,9 @@ func (s *K8sConformanceSuite) SetupSuite() { s.k3sContainer, err = k3s.Run(ctx, k3sImage, - k3s.WithManifest("./fixtures/k8s-conformance/00-experimental-v1.3.0.yml"), - k3s.WithManifest("./fixtures/k8s-conformance/01-rbac.yml"), - k3s.WithManifest("./fixtures/k8s-conformance/02-traefik.yml"), + k3s.WithManifest("./fixtures/gateway-api-conformance/00-experimental-v1.4.0.yml"), + k3s.WithManifest("./fixtures/gateway-api-conformance/01-rbac.yml"), + k3s.WithManifest("./fixtures/gateway-api-conformance/02-traefik.yml"), network.WithNetwork(nil, s.network), ) if err != nil { @@ -137,7 +135,7 @@ func (s *K8sConformanceSuite) SetupSuite() { } } -func (s *K8sConformanceSuite) TearDownSuite() { +func (s *GatewayAPIConformanceSuite) TearDownSuite() { ctx := s.T().Context() if s.T().Failed() || *showLog { @@ -163,7 +161,7 @@ func (s *K8sConformanceSuite) TearDownSuite() { s.BaseSuite.TearDownSuite() } -func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() { +func (s *GatewayAPIConformanceSuite) TestK8sGatewayAPIConformance() { // Wait for traefik to start k3sContainerIP, err := s.k3sContainer.ContainerIP(s.T().Context()) require.NoError(s.T(), err) @@ -181,12 +179,12 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() { TimeoutConfig: config.DefaultTimeoutConfig(), ManifestFS: []fs.FS{&conformance.Manifests}, EnableAllSupportedFeatures: false, - RunTest: *k8sConformanceRunTest, + RunTest: *gatewayAPIConformanceRunTest, Implementation: v1.Implementation{ Organization: "traefik", Project: "traefik", URL: "https://traefik.io/", - Version: *k8sConformanceTraefikVersion, + Version: *traefikVersion, Contact: []string{"@traefik/maintainers"}, }, ConformanceProfiles: sets.New( @@ -220,8 +218,8 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() { require.NoError(s.T(), err) s.T().Logf("Conformance report:\n%s", string(rawReport)) - require.NoError(s.T(), os.MkdirAll("./conformance-reports/"+report.GatewayAPIVersion, 0o755)) - outFile := filepath.Join("conformance-reports/"+report.GatewayAPIVersion, fmt.Sprintf("%s-%s-%s-report.yaml", report.GatewayAPIChannel, report.Version, report.Mode)) + require.NoError(s.T(), os.MkdirAll("./gateway-api-conformance-reports/"+report.GatewayAPIVersion, 0o755)) + outFile := filepath.Join("gateway-api-conformance-reports/"+report.GatewayAPIVersion, fmt.Sprintf("%s-%s-%s-report.yaml", report.GatewayAPIChannel, report.Version, report.Mode)) require.NoError(s.T(), os.WriteFile(outFile, rawReport, 0o600)) s.T().Logf("Report written to: %s", outFile) } diff --git a/integration/integration_test.go b/integration/integration_test.go index e1b967c1a..815463498 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -37,9 +37,8 @@ import ( var ( showLog = flag.Bool("tlog", false, "always show Traefik logs") - k8sConformance = flag.Bool("k8sConformance", false, "run K8s Gateway API conformance test") - k8sConformanceRunTest = flag.String("k8sConformanceRunTest", "", "run a specific K8s Gateway API conformance test") - k8sConformanceTraefikVersion = flag.String("k8sConformanceTraefikVersion", "dev", "specify the Traefik version for the K8s Gateway API conformance report") + gatewayAPIConformanceRunTest = flag.String("gatewayAPIConformanceRunTest", "", "runs a specific Gateway API conformance test") + traefikVersion = flag.String("traefikVersion", "dev", "defines the Traefik version") ) const ( diff --git a/pkg/provider/kubernetes/gateway/client.go b/pkg/provider/kubernetes/gateway/client.go index 109a0f9d7..50cf789d3 100644 --- a/pkg/provider/kubernetes/gateway/client.go +++ b/pkg/provider/kubernetes/gateway/client.go @@ -14,7 +14,6 @@ import ( "github.com/traefik/traefik/v3/pkg/types" corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" - kerror "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" @@ -26,7 +25,6 @@ import ( "k8s.io/client-go/util/retry" gatev1 "sigs.k8s.io/gateway-api/apis/v1" gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gatev1alpha3 "sigs.k8s.io/gateway-api/apis/v1alpha3" gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" gateclientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" gateinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions" @@ -187,6 +185,14 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< if err != nil { return nil, err } + _, err = factoryGateway.Gateway().V1().BackendTLSPolicies().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + _, err = factoryKube.Core().V1().ConfigMaps().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } if c.experimentalChannel { _, err = factoryGateway.Gateway().V1alpha2().TCPRoutes().Informer().AddEventHandler(eventHandler) @@ -197,14 +203,6 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< if err != nil { return nil, err } - _, err = factoryGateway.Gateway().V1alpha3().BackendTLSPolicies().Informer().AddEventHandler(eventHandler) - if err != nil { - return nil, err - } - _, err = factoryKube.Core().V1().ConfigMaps().Informer().AddEventHandler(eventHandler) - if err != nil { - return nil, err - } } factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm)) @@ -367,6 +365,72 @@ func (c *clientWrapper) ListGatewayClasses() ([]*gatev1.GatewayClass, error) { return c.factoryGatewayClass.Gateway().V1().GatewayClasses().Lister().List(labels.Everything()) } +// ListEndpointSlicesForService returns the EndpointSlices for the given service name in the given namespace. +func (c *clientWrapper) ListEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("failed to get endpointslices for service %s/%s: namespace is not within watched namespaces", namespace, serviceName) + } + + serviceLabelRequirement, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.Equals, []string{serviceName}) + if err != nil { + return nil, fmt.Errorf("failed to create service label selector requirement: %w", err) + } + serviceSelector := labels.NewSelector() + serviceSelector = serviceSelector.Add(*serviceLabelRequirement) + + return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector) +} + +// ListBackendTLSPoliciesForService returns the BackendTLSPolicy for the given service name in the given namespace. +func (c *clientWrapper) ListBackendTLSPoliciesForService(namespace, serviceName string) ([]*gatev1.BackendTLSPolicy, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("failed to get BackendTLSPolicies for service %s/%s: namespace is not within watched namespaces", namespace, serviceName) + } + + policies, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1().BackendTLSPolicies().Lister().BackendTLSPolicies(namespace).List(labels.Everything()) + if err != nil { + return nil, fmt.Errorf("failed to list BackendTLSPolicies in namespace %s", namespace) + } + + var servicePolicies []*gatev1.BackendTLSPolicy + for _, policy := range policies { + for _, ref := range policy.Spec.TargetRefs { + // The policy does not target the service. + if (ref.Group != "" && ref.Group != groupCore) || ref.Kind != kindService || string(ref.Name) != serviceName { + continue + } + + servicePolicies = append(servicePolicies, policy) + } + } + + return servicePolicies, nil +} + +// GetService returns the named service from the given namespace. +func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("failed to get service %s/%s: namespace is not within watched namespaces", namespace, name) + } + return c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Services().Lister().Services(namespace).Get(name) +} + +// GetSecret returns the named secret from the given namespace. +func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("failed to get secret %s/%s: namespace is not within watched namespaces", namespace, name) + } + return c.factoriesSecret[c.lookupNamespace(namespace)].Core().V1().Secrets().Lister().Secrets(namespace).Get(name) +} + +// GetConfigMap returns the named configMap from the given namespace. +func (c *clientWrapper) GetConfigMap(namespace, name string) (*corev1.ConfigMap, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("failed to get configMap %s/%s: namespace is not within watched namespaces", namespace, name) + } + return c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().ConfigMaps().Lister().ConfigMaps(namespace).Get(name) +} + func (c *clientWrapper) UpdateGatewayClassStatus(ctx context.Context, name string, status gatev1.GatewayClassStatus) error { err := retry.RetryOnConflict(retry.DefaultRetry, func() error { currentGatewayClass, err := c.factoryGatewayClass.Gateway().V1().GatewayClasses().Lister().Get(name) @@ -637,20 +701,20 @@ func (c *clientWrapper) UpdateTLSRouteStatus(ctx context.Context, route ktypes.N return nil } -func (c *clientWrapper) UpdateBackendTLSPolicyStatus(ctx context.Context, policy ktypes.NamespacedName, status gatev1alpha2.PolicyStatus) error { +func (c *clientWrapper) UpdateBackendTLSPolicyStatus(ctx context.Context, policy ktypes.NamespacedName, status gatev1.PolicyStatus) error { if !c.isWatchedNamespace(policy.Namespace) { return fmt.Errorf("updating BackendTLSPolicy status %s/%s: namespace is not within watched namespaces", policy.Namespace, policy.Name) } err := retry.RetryOnConflict(retry.DefaultRetry, func() error { - currentPolicy, err := c.factoriesGateway[c.lookupNamespace(policy.Namespace)].Gateway().V1alpha3().BackendTLSPolicies().Lister().BackendTLSPolicies(policy.Namespace).Get(policy.Name) + currentPolicy, err := c.factoriesGateway[c.lookupNamespace(policy.Namespace)].Gateway().V1().BackendTLSPolicies().Lister().BackendTLSPolicies(policy.Namespace).Get(policy.Name) if err != nil { // We have to return err itself here (not wrapped inside another error) // so that RetryOnConflict can identify it correctly. return err } - ancestorStatuses := make([]gatev1alpha2.PolicyAncestorStatus, len(status.Ancestors)) + ancestorStatuses := make([]gatev1.PolicyAncestorStatus, len(status.Ancestors)) copy(ancestorStatuses, status.Ancestors) // keep statuses added by other gateway controllers, @@ -660,14 +724,6 @@ func (c *clientWrapper) UpdateBackendTLSPolicyStatus(ctx context.Context, policy ancestorStatuses = append(ancestorStatuses, ancestorStatus) continue } - - if slices.ContainsFunc(status.Ancestors, func(status gatev1alpha2.PolicyAncestorStatus) bool { - return reflect.DeepEqual(ancestorStatus.AncestorRef, status.AncestorRef) - }) { - continue - } - - ancestorStatuses = append(ancestorStatuses, ancestorStatus) } if len(ancestorStatuses) > 16 { @@ -680,11 +736,11 @@ func (c *clientWrapper) UpdateBackendTLSPolicyStatus(ctx context.Context, policy } currentPolicy = currentPolicy.DeepCopy() - currentPolicy.Status = gatev1alpha2.PolicyStatus{ + currentPolicy.Status = gatev1.PolicyStatus{ Ancestors: ancestorStatuses, } - if _, err = c.csGateway.GatewayV1alpha3().BackendTLSPolicies(policy.Namespace).UpdateStatus(ctx, currentPolicy, metav1.UpdateOptions{}); err != nil { + if _, err = c.csGateway.GatewayV1().BackendTLSPolicies(policy.Namespace).UpdateStatus(ctx, currentPolicy, metav1.UpdateOptions{}); err != nil { // We have to return err itself here (not wrapped inside another error) // so that RetryOnConflict can identify it correctly. return err @@ -699,85 +755,7 @@ func (c *clientWrapper) UpdateBackendTLSPolicyStatus(ctx context.Context, policy return nil } -// GetService returns the named service from the given namespace. -func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, bool, error) { - if !c.isWatchedNamespace(namespace) { - return nil, false, fmt.Errorf("failed to get service %s/%s: namespace is not within watched namespaces", namespace, name) - } - - service, err := c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Services().Lister().Services(namespace).Get(name) - exist, err := translateNotFoundError(err) - - return service, exist, err -} - -// ListEndpointSlicesForService returns the EndpointSlices for the given service name in the given namespace. -func (c *clientWrapper) ListEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) { - if !c.isWatchedNamespace(namespace) { - return nil, fmt.Errorf("failed to get endpointslices for service %s/%s: namespace is not within watched namespaces", namespace, serviceName) - } - - serviceLabelRequirement, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.Equals, []string{serviceName}) - if err != nil { - return nil, fmt.Errorf("failed to create service label selector requirement: %w", err) - } - serviceSelector := labels.NewSelector() - serviceSelector = serviceSelector.Add(*serviceLabelRequirement) - - return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector) -} - -// ListBackendTLSPoliciesForService returns the BackendTLSPolicy for the given service name in the given namespace. -func (c *clientWrapper) ListBackendTLSPoliciesForService(namespace, serviceName string) ([]*gatev1alpha3.BackendTLSPolicy, error) { - if !c.isWatchedNamespace(namespace) { - return nil, fmt.Errorf("failed to get BackendTLSPolicies for service %s/%s: namespace is not within watched namespaces", namespace, serviceName) - } - - policies, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1alpha3().BackendTLSPolicies().Lister().BackendTLSPolicies(namespace).List(labels.Everything()) - if err != nil { - return nil, fmt.Errorf("failed to list BackendTLSPolicies in namespace %s", namespace) - } - - var servicePolicies []*gatev1alpha3.BackendTLSPolicy - for _, policy := range policies { - for _, ref := range policy.Spec.TargetRefs { - // The policy does not target the service. - if (ref.Group != "" && ref.Group != groupCore) || ref.Kind != kindService || string(ref.Name) != serviceName { - continue - } - - servicePolicies = append(servicePolicies, policy) - } - } - - return servicePolicies, nil -} - -// GetSecret returns the named secret from the given namespace. -func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, bool, error) { - if !c.isWatchedNamespace(namespace) { - return nil, false, fmt.Errorf("failed to get secret %s/%s: namespace is not within watched namespaces", namespace, name) - } - - secret, err := c.factoriesSecret[c.lookupNamespace(namespace)].Core().V1().Secrets().Lister().Secrets(namespace).Get(name) - exist, err := translateNotFoundError(err) - - return secret, exist, err -} - -// GetConfigMap returns the named configMap from the given namespace. -func (c *clientWrapper) GetConfigMap(namespace, name string) (*corev1.ConfigMap, bool, error) { - if !c.isWatchedNamespace(namespace) { - return nil, false, fmt.Errorf("failed to get configMap %s/%s: namespace is not within watched namespaces", namespace, name) - } - - configMap, err := c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().ConfigMaps().Lister().ConfigMaps(namespace).Get(name) - exist, err := translateNotFoundError(err) - - return configMap, exist, err -} - -// lookupNamespace returns the lookup namespace key for the given namespace. +// lookupNamespace returns the lookup namespace listenerKey for the given namespace. // When listening on all namespaces, it returns the client-go identifier ("") // for all-namespaces. Otherwise, it returns the given namespace. // The distinction is necessary because we index all informers on the special @@ -800,28 +778,19 @@ func (c *clientWrapper) isWatchedNamespace(namespace string) bool { return slices.Contains(c.watchedNamespaces, namespace) } -// translateNotFoundError will translate a "not found" error to a boolean return -// value which indicates if the resource exists and a nil error. -func translateNotFoundError(err error) (bool, error) { - if kerror.IsNotFound(err) { - return false, nil - } - return err == nil, err -} - func gatewayStatusEqual(statusA, statusB gatev1.GatewayStatus) bool { return reflect.DeepEqual(statusA.Addresses, statusB.Addresses) && listenersStatusEqual(statusA.Listeners, statusB.Listeners) && conditionsEqual(statusA.Conditions, statusB.Conditions) } -func policyAncestorStatusesEqual(policyAncestorStatusesA, policyAncestorStatusesB []gatev1alpha2.PolicyAncestorStatus) bool { +func policyAncestorStatusesEqual(policyAncestorStatusesA, policyAncestorStatusesB []gatev1.PolicyAncestorStatus) bool { if len(policyAncestorStatusesA) != len(policyAncestorStatusesB) { return false } for _, sA := range policyAncestorStatusesA { - if !slices.ContainsFunc(policyAncestorStatusesB, func(sB gatev1alpha2.PolicyAncestorStatus) bool { + if !slices.ContainsFunc(policyAncestorStatusesB, func(sB gatev1.PolicyAncestorStatus) bool { return policyAncestorStatusEqual(sB, sA) }) { return false @@ -829,7 +798,7 @@ func policyAncestorStatusesEqual(policyAncestorStatusesA, policyAncestorStatuses } for _, sB := range policyAncestorStatusesB { - if !slices.ContainsFunc(policyAncestorStatusesA, func(sA gatev1alpha2.PolicyAncestorStatus) bool { + if !slices.ContainsFunc(policyAncestorStatusesA, func(sA gatev1.PolicyAncestorStatus) bool { return policyAncestorStatusEqual(sA, sB) }) { return false @@ -839,7 +808,7 @@ func policyAncestorStatusesEqual(policyAncestorStatusesA, policyAncestorStatuses return true } -func policyAncestorStatusEqual(sA, sB gatev1alpha2.PolicyAncestorStatus) bool { +func policyAncestorStatusEqual(sA, sB gatev1.PolicyAncestorStatus) bool { return sA.ControllerName == sB.ControllerName && reflect.DeepEqual(sA.AncestorRef, sB.AncestorRef) && conditionsEqual(sA.Conditions, sB.Conditions) diff --git a/pkg/provider/kubernetes/gateway/features.go b/pkg/provider/kubernetes/gateway/features.go index eb3a433d8..88dedd573 100644 --- a/pkg/provider/kubernetes/gateway/features.go +++ b/pkg/provider/kubernetes/gateway/features.go @@ -1,24 +1,48 @@ package gateway -import "sigs.k8s.io/gateway-api/pkg/features" +import ( + "sync" -func SupportedFeatures() []features.FeatureName { - return []features.FeatureName{ - features.GatewayFeature.Name, - features.GatewayPort8080Feature.Name, - features.GRPCRouteFeature.Name, - features.HTTPRouteFeature.Name, - features.HTTPRouteQueryParamMatchingFeature.Name, - features.HTTPRouteMethodMatchingFeature.Name, - features.HTTPRoutePortRedirectFeature.Name, - features.HTTPRouteSchemeRedirectFeature.Name, - features.HTTPRouteHostRewriteFeature.Name, - features.HTTPRoutePathRewriteFeature.Name, - features.HTTPRoutePathRedirectFeature.Name, - features.HTTPRouteResponseHeaderModificationFeature.Name, - features.HTTPRouteBackendProtocolH2CFeature.Name, - features.HTTPRouteBackendProtocolWebSocketFeature.Name, - features.HTTPRouteDestinationPortMatchingFeature.Name, - features.TLSRouteFeature.Name, + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/gateway-api/pkg/features" +) + +var SupportedFeatures = sync.OnceValue(func() []features.FeatureName { + featureSet := sets.New[features.Feature](). + Insert(features.GatewayCoreFeatures.UnsortedList()...). + Insert(features.GatewayExtendedFeatures.Intersection(extendedGatewayFeatures()).UnsortedList()...). + Insert(features.HTTPRouteCoreFeatures.UnsortedList()...). + Insert(features.HTTPRouteExtendedFeatures.Intersection(extendedHTTPRouteFeatures()).UnsortedList()...). + Insert(features.ReferenceGrantCoreFeatures.UnsortedList()...). + Insert(features.BackendTLSPolicyCoreFeatures.UnsortedList()...). + Insert(features.GRPCRouteCoreFeatures.UnsortedList()...). + Insert(features.TLSRouteCoreFeatures.UnsortedList()...) + + featureNames := make([]features.FeatureName, 0, featureSet.Len()) + for f := range featureSet { + featureNames = append(featureNames, f.Name) } + return featureNames +}) + +// extendedGatewayFeatures returns the supported extended Gateway features. +func extendedGatewayFeatures() sets.Set[features.Feature] { + return sets.New(features.GatewayPort8080Feature) +} + +// extendedHTTPRouteFeatures returns the supported extended HTTP Route features. +func extendedHTTPRouteFeatures() sets.Set[features.Feature] { + return sets.New( + features.HTTPRouteQueryParamMatchingFeature, + features.HTTPRouteMethodMatchingFeature, + features.HTTPRoutePortRedirectFeature, + features.HTTPRouteSchemeRedirectFeature, + features.HTTPRouteHostRewriteFeature, + features.HTTPRoutePathRewriteFeature, + features.HTTPRoutePathRedirectFeature, + features.HTTPRouteResponseHeaderModificationFeature, + features.HTTPRouteBackendProtocolH2CFeature, + features.HTTPRouteBackendProtocolWebSocketFeature, + features.HTTPRouteDestinationPortMatchingFeature, + ) } diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy.yml index 7748aee47..f183c92d8 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy.yml @@ -52,7 +52,7 @@ spec: --- kind: BackendTLSPolicy -apiVersion: gateway.networking.k8s.io/v1alpha3 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: policy-1 namespace: default @@ -78,7 +78,7 @@ metadata: name: ca-file namespace: default data: - ca.crt: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=" + ca.crt: "CA1" --- apiVersion: v1 @@ -87,4 +87,4 @@ metadata: name: ca-file-2 namespace: default data: - ca.crt: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=" + ca.crt: "CA2" diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy_system.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy_system.yml index cc36c0468..05b28bd9c 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy_system.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy_system.yml @@ -52,7 +52,7 @@ spec: --- kind: BackendTLSPolicy -apiVersion: gateway.networking.k8s.io/v1alpha3 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: policy-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https.yml index cd4abdec1..bb160e469 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_with_tls_passthrough.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_with_tls_passthrough.yml index 9804bc80d..73cbc8e82 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_with_tls_passthrough.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_with_tls_passthrough.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tls.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tls.yml index bace669a0..552e32c95 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tls.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tls.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_gateways_one_httproute.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_gateways_one_httproute.yml index 934b67f82..b5c5c832f 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_gateways_one_httproute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_gateways_one_httproute.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_listeners_one_httproute.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_listeners_one_httproute.yml index ca1c1a90e..f86f94346 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_listeners_one_httproute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_listeners_one_httproute.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml index 0fd6ee36e..35bd833e4 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_core_group.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_core_group.yml index dcef7966d..3294e3a73 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_core_group.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_core_group.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_listeners_using_same_hostname_port_protocol.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_listeners_using_same_hostname_port_protocol.yml index 272e363d7..cb0e19a60 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_listeners_using_same_hostname_port_protocol.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_listeners_using_same_hostname_port_protocol.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_all.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_all.yml index c2b525044..263302204 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_all.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_all.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_same.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_same.yml index 8d470e749..6bf1df73b 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_same.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_same.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_selector.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_selector.yml index a46e3dab6..7fa26a504 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_selector.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_selector.yml @@ -14,8 +14,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret.yml b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret.yml index ba5f1a9fb..d76fb2065 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret.yml @@ -20,8 +20,8 @@ metadata: namespace: secret-namespace data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_missing.yml b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_missing.yml index 660ff18b6..e73e55a24 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_missing.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_missing.yml @@ -6,8 +6,8 @@ metadata: namespace: secret-namespace data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_from.yml b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_from.yml index 47aba19d8..e3662e9f2 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_from.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_from.yml @@ -20,8 +20,8 @@ metadata: namespace: secret-namespace data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_to.yml b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_to.yml index e08fa7a42..2b336f6ea 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_to.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/referencegrant/for_secret_not_matching_to.yml @@ -21,8 +21,8 @@ metadata: namespace: secret-namespace data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_https.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_https.yml index ceb2f0bb0..5bbebbfca 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_https.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_https.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_tls.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_tls.yml index 05e3fc5ae..ac6b60bee 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_tls.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_tls.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute_tls_protocol.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute_tls_protocol.yml index 67e8f584d..23a1860b4 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute_tls_protocol.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute_tls_protocol.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/gatewayclass_with_unknown_controller.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/gatewayclass_with_unknown_controller.yml index 0e9c8f448..b5ce33ede 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/gatewayclass_with_unknown_controller.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/gatewayclass_with_unknown_controller.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute.yml index 4a2dc107f..7f30bf92d 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TLSRoute.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TLSRoute.yml index e41a60aa7..b908be3f0 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TLSRoute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TLSRoute.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_cross_provider.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_cross_provider.yml index 46c588590..636673c68 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_cross_provider.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_cross_provider.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_nativelb.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_nativelb.yml index 253e59b9b..fde70d51e 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_nativelb.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_nativelb.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml index c88672329..3c6afe86e 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough_tls.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough_tls.yml index e04d4e362..9ffc3a200 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough_tls.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough_tls.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_https.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_https.yml index c5d904baa..2b424d065 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_https.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_https.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_wrong_service_port.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_wrong_service_port.yml index 5d296c248..37fafa2ad 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_wrong_service_port.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_wrong_service_port.yml @@ -6,8 +6,8 @@ metadata: namespace: default data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJxRENDQVU2Z0F3SUJBZ0lVWU9zcjBRZ0hPQnE0a1lSQ0w1K1REZFZ0NmJRd0NnWUlLb1pJemowRUF3SXcKRmpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzWlM1amIyMHdIaGNOTWpVeE1ERXdNRGN4TnpNd1doY05NelV4TURBNApNRGN4TnpNd1dqQVdNUlF3RWdZRFZRUUREQXRsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJET3JpdzNaUTd3SWhXcmJQUzZKRlFUM2JUb05DRjAwdlNWNWZhYjZUYlh5TDh0bHNHcmUKVFJJRjJFd2dzdGVNT2t4R0tLU2xEdnVhRHdxOHAvcVYrMHVqZWpCNE1CMEdBMVVkRGdRV0JCUk1Fa3VleFhRaApVdERnUmcxS0J2NzJDRHErRXpBZkJnTlZIU01FR0RBV2dCUk1Fa3VleFhRaFV0RGdSZzFLQnY3MkNEcStFekFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUNVR0ExVWRFUVFlTUJ5Q0MyVjRZVzF3YkdVdVkyOXRnZzBxTG1WNFlXMXcKYkdVdVkyOXRNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUURzODdWazBzd0E2SGdPSmpST3llMW14RDgzcWNHeQpwZUZnb3hWOTNEeStjd0lnVjBNTUVKSmJWc1R5WkszRVErK1hjNXJFTDc4bnJKK1lJRVYrckNVV2o1VT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ253Z0w1RFk0VUIxNHNNNmYKRGlrUWR0cWgyUVcxQXJmRjRmYzFVRnppZmRHaFJBTkNBQVF6cTRzTjJVTzhDSVZxMnowdWlSVUU5MjA2RFFoZApOTDBsZVgybStrMjE4aS9MWmJCcTNrMFNCZGhNSUxMWGpEcE1SaWlrcFE3N21nOEt2S2Y2bGZ0TAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t --- kind: GatewayClass diff --git a/pkg/provider/kubernetes/gateway/httproute.go b/pkg/provider/kubernetes/gateway/httproute.go index ebee5fd1f..863c2f093 100644 --- a/pkg/provider/kubernetes/gateway/httproute.go +++ b/pkg/provider/kubernetes/gateway/httproute.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "regexp" + "slices" "strconv" "strings" @@ -19,8 +20,6 @@ import ( ktypes "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" gatev1 "sigs.k8s.io/gateway-api/apis/v1" - gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gatev1alpha3 "sigs.k8s.io/gateway-api/apis/v1alpha3" ) func (p *Provider) loadHTTPRoutes(ctx context.Context, gatewayListeners []gatewayListener, conf *dynamic.Configuration) { @@ -412,85 +411,125 @@ func (p *Provider) loadHTTPServers(ctx context.Context, namespace string, route } } - var st *dynamic.ServersTransport - var protocol string - if p.ExperimentalChannel { - servicePolicies, err := p.client.ListBackendTLSPoliciesForService(namespace, string(backendRef.Name)) - if err != nil { - return nil, nil, &metav1.Condition{ - Type: string(gatev1.RouteConditionResolvedRefs), - Status: metav1.ConditionFalse, - ObservedGeneration: route.Generation, - LastTransitionTime: metav1.Now(), - Reason: string(gatev1.RouteReasonRefNotPermitted), - Message: fmt.Sprintf("Cannot list BackendTLSPolicies for Service %s/%s: %s", namespace, string(backendRef.Name), err), - } + backendTLSPolicies, err := p.client.ListBackendTLSPoliciesForService(namespace, string(backendRef.Name)) + if err != nil { + return nil, nil, &metav1.Condition{ + Type: string(gatev1.RouteConditionResolvedRefs), + Status: metav1.ConditionFalse, + ObservedGeneration: route.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.RouteReasonRefNotPermitted), + Message: fmt.Sprintf("Cannot list BackendTLSPolicies for Service %s/%s: %s", namespace, string(backendRef.Name), err), } + } - var matchedPolicy *gatev1alpha3.BackendTLSPolicy - for _, policy := range servicePolicies { - matched := false - for _, targetRef := range policy.Spec.TargetRefs { - if targetRef.SectionName == nil || svcPort.Name == string(*targetRef.SectionName) { - matchedPolicy = policy - matched = true - break - } + // Sort BackendTLSPolicies by creation timestamp, then by name to match the BackendTLSPolicy requirements. + slices.SortStableFunc(backendTLSPolicies, func(a, b *gatev1.BackendTLSPolicy) int { + cmpTime := a.CreationTimestamp.Time.Compare(b.CreationTimestamp.Time) + if cmpTime == 0 { + return strings.Compare(a.Name, b.Name) + } + return cmpTime + }) + + var serversTransport *dynamic.ServersTransport + for _, policy := range backendTLSPolicies { + for _, targetRef := range policy.Spec.TargetRefs { + if targetRef.SectionName != nil && svcPort.Name != string(*targetRef.SectionName) { + continue } - // If the policy targets the service, but doesn't match any port. - if !matched { - // update policy status - status := gatev1alpha2.PolicyStatus{ - Ancestors: []gatev1alpha2.PolicyAncestorStatus{{ - AncestorRef: gatev1alpha2.ParentReference{ - Group: ptr.To(gatev1.Group(groupGateway)), - Kind: ptr.To(gatev1.Kind(kindGateway)), - Namespace: ptr.To(gatev1.Namespace(namespace)), - Name: gatev1.ObjectName(listener.GWName), - SectionName: ptr.To(gatev1.SectionName(listener.Name)), - }, - ControllerName: controllerName, - Conditions: []metav1.Condition{{ - Type: string(gatev1.RouteConditionResolvedRefs), - Status: metav1.ConditionFalse, - ObservedGeneration: route.Generation, - LastTransitionTime: metav1.Now(), - Reason: string(gatev1.RouteReasonBackendNotFound), - Message: fmt.Sprintf("BackendTLSPolicy has no valid TargetRef for Service %s/%s", namespace, string(backendRef.Name)), - }}, - }}, - } + policyAncestorStatus := gatev1.PolicyAncestorStatus{ + AncestorRef: gatev1.ParentReference{ + Group: ptr.To(gatev1.Group(groupGateway)), + Kind: ptr.To(gatev1.Kind(kindGateway)), + Namespace: ptr.To(gatev1.Namespace(namespace)), + Name: gatev1.ObjectName(listener.GWName), + SectionName: ptr.To(gatev1.SectionName(listener.Name)), + }, + ControllerName: controllerName, + } + // Multiple BackendTLSPolicies can match the same service port, meaning that there is a conflict. + if serversTransport != nil { + policyAncestorStatus.Conditions = append(policyAncestorStatus.Conditions, + metav1.Condition{ + Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs), + Status: metav1.ConditionFalse, + ObservedGeneration: policy.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.BackendTLSPolicyReasonResolvedRefs), + }, + metav1.Condition{ + Type: string(gatev1.PolicyConditionAccepted), + Status: metav1.ConditionFalse, + ObservedGeneration: policy.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.PolicyReasonConflicted), + }, + ) + + status := gatev1.PolicyStatus{ + Ancestors: []gatev1.PolicyAncestorStatus{policyAncestorStatus}, + } if err := p.client.UpdateBackendTLSPolicyStatus(ctx, ktypes.NamespacedName{Namespace: policy.Namespace, Name: policy.Name}, status); err != nil { - log.Ctx(ctx).Warn().Err(err). - Msg("Unable to update BackendTLSPolicy status") + log.Ctx(ctx).Warn().Err(err).Msg("Unable to update conflicting BackendTLSPolicy status") } - } - } - if matchedPolicy != nil { - st, err = p.loadServersTransport(namespace, *matchedPolicy) - if err != nil { + continue + } + + var resolvedRefCondition metav1.Condition + serversTransport, resolvedRefCondition = p.loadServersTransport(namespace, policy) + + policyAncestorStatus.Conditions = append(policyAncestorStatus.Conditions, resolvedRefCondition) + if resolvedRefCondition.Status == metav1.ConditionFalse { + policyAncestorStatus.Conditions = append(policyAncestorStatus.Conditions, metav1.Condition{ + Type: string(gatev1.PolicyConditionAccepted), + Status: metav1.ConditionFalse, + ObservedGeneration: policy.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.BackendTLSPolicyReasonNoValidCACertificate), + }) + } else { + policyAncestorStatus.Conditions = append(policyAncestorStatus.Conditions, metav1.Condition{ + Type: string(gatev1.PolicyConditionAccepted), + Status: metav1.ConditionTrue, + ObservedGeneration: policy.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.PolicyReasonAccepted), + }) + } + + status := gatev1.PolicyStatus{ + Ancestors: []gatev1.PolicyAncestorStatus{policyAncestorStatus}, + } + if err := p.client.UpdateBackendTLSPolicyStatus(ctx, ktypes.NamespacedName{Namespace: policy.Namespace, Name: policy.Name}, status); err != nil { + log.Ctx(ctx).Warn().Err(err).Msg("Unable to update BackendTLSPolicy status") + } + + // When something wen wrong during the loading of a ServersTransport, + // we stop here and return a route condition error. + if resolvedRefCondition.Status == metav1.ConditionFalse { return nil, nil, &metav1.Condition{ Type: string(gatev1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, ObservedGeneration: route.Generation, LastTransitionTime: metav1.Now(), Reason: string(gatev1.RouteReasonRefNotPermitted), - Message: fmt.Sprintf("Cannot apply BackendTLSPolicy for Service %s/%s: %s", namespace, string(backendRef.Name), err), + Message: fmt.Sprintf("Cannot apply BackendTLSPolicy for Service %s/%s: %s", namespace, string(backendRef.Name), resolvedRefCondition.Message), } } - // A backend TLS policy has been found for the service, a serversTransport configuration has been created, use/force HTTPS. - protocol = "https" } } lb := &dynamic.ServersLoadBalancer{} lb.SetDefaults() - // Guess the protocol from the service port if not set by the backend TLS policy - if protocol == "" { + // If a ServersTransport is set, it means a BackendTLSPolicy matched the service port, and we can safely assume the protocol is HTTPS. + // When no ServersTransport is set, we need to determine the protocol based on the service port. + protocol := "https" + if serversTransport == nil { protocol, err = getHTTPServiceProtocol(svcPort) if err != nil { return nil, nil, &metav1.Condition{ @@ -509,40 +548,70 @@ func (p *Provider) loadHTTPServers(ctx context.Context, namespace string, route URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(ba.IP, strconv.Itoa(int(ba.Port)))), }) } - return lb, st, nil + return lb, serversTransport, nil } -func (p *Provider) loadServersTransport(namespace string, policy gatev1alpha3.BackendTLSPolicy) (*dynamic.ServersTransport, error) { +func (p *Provider) loadServersTransport(namespace string, policy *gatev1.BackendTLSPolicy) (*dynamic.ServersTransport, metav1.Condition) { st := &dynamic.ServersTransport{ ServerName: string(policy.Spec.Validation.Hostname), } if policy.Spec.Validation.WellKnownCACertificates != nil { - return st, nil + return st, metav1.Condition{ + Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs), + Status: metav1.ConditionTrue, + ObservedGeneration: policy.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.BackendTLSPolicyReasonResolvedRefs), + } } for _, caCertRef := range policy.Spec.Validation.CACertificateRefs { if (caCertRef.Group != "" && caCertRef.Group != groupCore) || caCertRef.Kind != "ConfigMap" { - continue + return nil, metav1.Condition{ + Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs), + Status: metav1.ConditionFalse, + ObservedGeneration: policy.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.BackendTLSPolicyReasonInvalidKind), + Message: "Only ConfigMaps are supported", + } } - configMap, exists, err := p.client.GetConfigMap(namespace, string(caCertRef.Name)) + configMap, err := p.client.GetConfigMap(namespace, string(caCertRef.Name)) if err != nil { - return nil, fmt.Errorf("getting configmap: %w", err) - } - if !exists { - return nil, fmt.Errorf("configmap %s/%s not found", namespace, string(caCertRef.Name)) + return nil, metav1.Condition{ + Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs), + Status: metav1.ConditionFalse, + ObservedGeneration: policy.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.BackendTLSPolicyReasonInvalidCACertificateRef), + Message: fmt.Sprintf("getting configmap %s/%s: %s", namespace, string(caCertRef.Name), err), + } } caCRT, ok := configMap.Data["ca.crt"] if !ok { - return nil, fmt.Errorf("configmap %s/%s does not have ca.crt", namespace, string(caCertRef.Name)) + return nil, metav1.Condition{ + Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs), + Status: metav1.ConditionFalse, + ObservedGeneration: policy.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.BackendTLSPolicyReasonInvalidCACertificateRef), + Message: fmt.Sprintf("configmap %s/%s does not have a ca.crt", namespace, string(caCertRef.Name)), + } } st.RootCAs = append(st.RootCAs, types.FileOrContent(caCRT)) } - return st, nil + return st, metav1.Condition{ + Type: string(gatev1.BackendTLSPolicyConditionResolvedRefs), + Status: metav1.ConditionTrue, + ObservedGeneration: policy.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.BackendTLSPolicyReasonResolvedRefs), + } } func buildHostRule(hostnames []gatev1.Hostname) (string, int) { @@ -746,7 +815,7 @@ func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch g port = ptr.To("") } if filter.Port != nil { - port = ptr.To(fmt.Sprintf("%d", *filter.Port)) + port = ptr.To(strconv.Itoa(int(*filter.Port))) } var path *string diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index 3010f6bc0..0c50b76a8 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -121,7 +121,7 @@ type gatewayListener struct { Port gatev1.PortNumber Protocol gatev1.ProtocolType - TLS *gatev1.GatewayTLSConfig + TLS *gatev1.ListenerTLSConfig Hostname *gatev1.Hostname Status *gatev1.ListenerStatus AllowedNamespaces []string @@ -325,14 +325,12 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.C } var supportedFeatures []gatev1.SupportedFeature - if p.ExperimentalChannel { - for _, feature := range SupportedFeatures() { - supportedFeatures = append(supportedFeatures, gatev1.SupportedFeature{Name: gatev1.FeatureName(feature)}) - } - slices.SortFunc(supportedFeatures, func(a, b gatev1.SupportedFeature) int { - return strings.Compare(string(a.Name), string(b.Name)) - }) + for _, feature := range SupportedFeatures() { + supportedFeatures = append(supportedFeatures, gatev1.SupportedFeature{Name: gatev1.FeatureName(feature)}) } + slices.SortFunc(supportedFeatures, func(a, b gatev1.SupportedFeature) int { + return strings.Compare(string(a.Name), string(b.Name)) + }) gatewayClassNames := map[string]struct{}{} for _, gatewayClass := range gatewayClasses { @@ -768,12 +766,9 @@ func (p *Provider) gatewayAddresses() ([]gatev1.GatewayStatusAddress, error) { svcRef := p.StatusAddress.Service if svcRef.Name != "" && svcRef.Namespace != "" { - svc, exists, err := p.client.GetService(svcRef.Namespace, svcRef.Name) + svc, err := p.client.GetService(svcRef.Namespace, svcRef.Name) if err != nil { - return nil, fmt.Errorf("unable to get service: %w", err) - } - if !exists { - return nil, fmt.Errorf("could not find a service with name %s in namespace %s", svcRef.Name, svcRef.Namespace) + return nil, fmt.Errorf("getting service: %w", err) } var addresses []gatev1.GatewayStatusAddress @@ -836,25 +831,27 @@ func (p *Provider) isReferenceGranted(fromKind, fromNamespace, toGroup, toKind, } func (p *Provider) getTLS(secretName gatev1.ObjectName, namespace string) (*tls.CertAndStores, error) { - secret, exists, err := p.client.GetSecret(namespace, string(secretName)) + secret, err := p.client.GetSecret(namespace, string(secretName)) if err != nil { - return nil, fmt.Errorf("failed to fetch secret %s/%s: %w", namespace, secretName, err) - } - if !exists { - return nil, fmt.Errorf("secret %s/%s does not exist", namespace, secretName) + return nil, fmt.Errorf("getting secret: %w", err) } cert, key, err := getCertificateBlocks(secret, namespace, string(secretName)) if err != nil { - return nil, err + return nil, fmt.Errorf("getting certificate blocks: %w", err) } - return &tls.CertAndStores{ + certAndStore := &tls.CertAndStores{ Certificate: tls.Certificate{ CertFile: types.FileOrContent(cert), KeyFile: types.FileOrContent(key), }, - }, nil + } + if _, err := certAndStore.GetCertificate(); err != nil { + return nil, fmt.Errorf("validating certificate: %w", err) + } + + return certAndStore, nil } func (p *Provider) allowedNamespaces(gatewayNamespace string, routeNamespaces *gatev1.RouteNamespaces) ([]string, error) { @@ -891,20 +888,17 @@ func (p *Provider) getBackendAddresses(namespace string, ref gatev1.BackendRef) return nil, corev1.ServicePort{}, errors.New("port is required for Kubernetes Service reference") } - service, exists, err := p.client.GetService(namespace, string(ref.Name)) + service, err := p.client.GetService(namespace, string(ref.Name)) if err != nil { return nil, corev1.ServicePort{}, fmt.Errorf("getting service: %w", err) } - if !exists { - return nil, corev1.ServicePort{}, errors.New("service not found") - } if service.Spec.Type == corev1.ServiceTypeExternalName { return nil, corev1.ServicePort{}, errors.New("type ExternalName is not supported for Kubernetes Service reference") } var svcPort *corev1.ServicePort for _, p := range service.Spec.Ports { - if p.Port == int32(*ref.Port) { + if p.Port == *ref.Port { svcPort = &p break } diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 90e93f64f..f08a58fc3 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -49,6 +49,25 @@ func init() { } } +const ( + listenerCert string = `-----BEGIN CERTIFICATE----- +MIIBqDCCAU6gAwIBAgIUYOsr0QgHOBq4kYRCL5+TDdVt6bQwCgYIKoZIzj0EAwIw +FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wHhcNMjUxMDEwMDcxNzMwWhcNMzUxMDA4 +MDcxNzMwWjAWMRQwEgYDVQQDDAtleGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABDOriw3ZQ7wIhWrbPS6JFQT3bToNCF00vSV5fab6TbXyL8tlsGre +TRIF2EwgsteMOkxGKKSlDvuaDwq8p/qV+0ujejB4MB0GA1UdDgQWBBRMEkuexXQh +UtDgRg1KBv72CDq+EzAfBgNVHSMEGDAWgBRMEkuexXQhUtDgRg1KBv72CDq+EzAP +BgNVHRMBAf8EBTADAQH/MCUGA1UdEQQeMByCC2V4YW1wbGUuY29tgg0qLmV4YW1w +bGUuY29tMAoGCCqGSM49BAMCA0gAMEUCIQDs87Vk0swA6HgOJjROye1mxD83qcGy +peFgoxV93Dy+cwIgV0MMEJJbVsTyZK3EQ++Xc5rEL78nrJ+YIEV+rCUWj5U= +-----END CERTIFICATE-----` + listenerKey string = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgnwgL5DY4UB14sM6f +DikQdtqh2QW1ArfF4fc1UFzifdGhRANCAAQzq4sN2UO8CIVq2z0uiRUE9206DQhd +NL0leX2m+k218i/LZbBq3k0SBdhMILLXjDpMRiikpQ77mg8KvKf6lftL +-----END PRIVATE KEY-----` +) + func TestGatewayClassLabelSelector(t *testing.T) { k8sObjects, gwObjects := readResources(t, []string{"gatewayclass_labelselector.yaml"}) @@ -561,8 +580,8 @@ func TestLoadHTTPRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -755,8 +774,8 @@ func TestLoadHTTPRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -1214,8 +1233,8 @@ func TestLoadHTTPRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -1308,8 +1327,8 @@ func TestLoadHTTPRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -2202,74 +2221,11 @@ func TestLoadHTTPRoutes(t *testing.T) { }, }, { - desc: "Simple HTTPRoute and BackendTLSPolicy, experimental channel disabled", + desc: "Simple HTTPRoute and BackendTLSPolicy with CA certificate", paths: []string{"services.yml", "httproute/with_backend_tls_policy.yml"}, entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, - expected: &dynamic.Configuration{ - UDP: &dynamic.UDPConfiguration{ - Routers: map[string]*dynamic.UDPRouter{}, - Services: map[string]*dynamic.UDPService{}, - }, - TCP: &dynamic.TCPConfiguration{ - Routers: map[string]*dynamic.TCPRouter{}, - Middlewares: map[string]*dynamic.TCPMiddleware{}, - Services: map[string]*dynamic.TCPService{}, - ServersTransports: map[string]*dynamic.TCPServersTransport{}, - }, - HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{ - "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06": { - EntryPoints: []string{"web"}, - Service: "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06-wrr", - Rule: "Host(`foo.com`) && Path(`/bar`)", - Priority: 100008, - RuleSyntax: "default", - }, - }, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{ - "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06-wrr": { - Weighted: &dynamic.WeightedRoundRobin{ - Services: []dynamic.WRRService{ - { - Name: "default-whoami-http-80", - Weight: ptr.To(1), - }, - }, - }, - }, - "default-whoami-http-80": { - LoadBalancer: &dynamic.ServersLoadBalancer{ - Strategy: dynamic.BalancerStrategyWRR, - Servers: []dynamic.Server{ - { - URL: "http://10.10.0.1:80", - }, - { - URL: "http://10.10.0.2:80", - }, - }, - PassHostHeader: ptr.To(true), - ResponseForwarding: &dynamic.ResponseForwarding{ - FlushInterval: ptypes.Duration(100 * time.Millisecond), - }, - }, - }, - }, - ServersTransports: map[string]*dynamic.ServersTransport{}, - }, - TLS: &dynamic.TLSConfiguration{}, - }, - }, - { - desc: "Simple HTTPRoute and BackendTLSPolicy with CA certificate, experimental channel enabled", - paths: []string{"services.yml", "httproute/with_backend_tls_policy.yml"}, - entryPoints: map[string]Entrypoint{"web": { - Address: ":80", - }}, - experimentalChannel: true, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -2326,8 +2282,8 @@ func TestLoadHTTPRoutes(t *testing.T) { "default-whoami-http-80": { ServerName: "whoami", RootCAs: []types.FileOrContent{ - "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=", - "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=", + "CA1", + "CA2", }, }, }, @@ -2336,12 +2292,11 @@ func TestLoadHTTPRoutes(t *testing.T) { }, }, { - desc: "Simple HTTPRoute and BackendTLSPolicy with System CA, experimental channel enabled", + desc: "Simple HTTPRoute and BackendTLSPolicy with System CA", paths: []string{"services.yml", "httproute/with_backend_tls_policy_system.yml"}, entryPoints: map[string]Entrypoint{"web": { Address: ":80", }}, - experimentalChannel: true, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -4217,8 +4172,8 @@ func TestLoadTCPRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -4809,8 +4764,8 @@ func TestLoadTLSRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -4909,8 +4864,8 @@ func TestLoadTLSRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -5127,8 +5082,8 @@ func TestLoadTLSRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -5197,8 +5152,8 @@ func TestLoadTLSRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -6165,8 +6120,8 @@ func TestLoadMixedRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -6354,8 +6309,8 @@ func TestLoadMixedRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -6612,8 +6567,8 @@ func TestLoadMixedRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -6773,8 +6728,8 @@ func TestLoadMixedRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -6913,8 +6868,8 @@ func TestLoadMixedRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, @@ -7125,8 +7080,8 @@ func TestLoadRoutesWithReferenceGrants(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent(listenerCert), + KeyFile: types.FileOrContent(listenerKey), }, }, }, From 463ffadb6a2d8ab16bbea102cf6e4c98f3640676 Mon Sep 17 00:00:00 2001 From: Artur Melanchyk Date: Mon, 13 Oct 2025 09:46:05 +0200 Subject: [PATCH 012/134] Avoid allocations in readLoop by using sync.Pool --- .golangci.yml | 6 ++++-- pkg/udp/conn.go | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 62e29ed6d..30673cca6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -239,8 +239,6 @@ linters: text: ' always receives ' linters: - unparam - - path: pkg/server/service/bufferpool.go - text: 'SA6002: argument should be pointer-like to avoid allocations' - path: pkg/server/middleware/middlewares.go text: Function 'buildConstructor' has too many statements linters: @@ -316,8 +314,12 @@ linters: text: 'the methods of "wasmMiddlewareBuilder" use pointer receiver and non-pointer receiver.' linters: - recvcheck + - path: pkg/server/service/bufferpool.go + text: 'SA6002: argument should be pointer-like to avoid allocations' - path: pkg/proxy/httputil/bufferpool.go text: 'SA6002: argument should be pointer-like to avoid allocations' + - path: pkg/udp/conn.go + text: 'SA6002: argument should be pointer-like to avoid allocations' - path: integration/integration_test.go text: 'var (gatewayAPIConformanceRunTest|traefikVersion) is unused' paths: diff --git a/pkg/udp/conn.go b/pkg/udp/conn.go index 36778f648..2a53b0a6b 100644 --- a/pkg/udp/conn.go +++ b/pkg/udp/conn.go @@ -32,6 +32,9 @@ type Listener struct { // timeout defines how long to wait on an idle session, // before releasing its related resources. timeout time.Duration + + // readBufferPool is a pool of byte slices for UDP packet reading. + readBufferPool sync.Pool } // ListenPacketConn creates a new listener from PacketConn. @@ -51,6 +54,11 @@ func ListenPacketConn(packetConn net.PacketConn, timeout time.Duration) (*Listen conns: make(map[string]*Conn), accepting: true, timeout: timeout, + readBufferPool: sync.Pool{ + New: func() interface{} { + return make([]byte, maxDatagramSize) + }, + }, } go l.readLoop() @@ -152,21 +160,26 @@ func (l *Listener) readLoop() { for { // Allocating a new buffer for every read avoids // overwriting data in c.msgs in case the next packet is received - // before c.msgs is emptied via Read() - buf := make([]byte, maxDatagramSize) + // before c.msgs is emptied via Read(). + // Reuses buffers via the readBufferPool sync.Pool. + buf := l.readBufferPool.Get().([]byte) n, raddr, err := l.pConn.ReadFrom(buf) if err != nil { + l.readBufferPool.Put(buf) return } conn, err := l.getConn(raddr) if err != nil { + l.readBufferPool.Put(buf) continue } select { + // Receiver must call releaseReadBuffer() when done reading the data. case conn.receiveCh <- buf[:n]: case <-conn.doneCh: + l.readBufferPool.Put(buf) continue } } @@ -211,15 +224,15 @@ type Conn struct { listener *Listener rAddr net.Addr - receiveCh chan []byte // to receive the data from the listener's readLoop - readCh chan []byte // to receive the buffer into which we should Read - sizeCh chan int // to synchronize with the end of a Read - msgs [][]byte // to store data from listener, to be consumed by Reads + receiveCh chan []byte // to receive the data from the listener's readLoop. + readCh chan []byte // to receive the buffer into which we should Read. + sizeCh chan int // to synchronize with the end of a Read. + msgs [][]byte // to store data from listener, to be consumed by Reads. muActivity sync.RWMutex - lastActivity time.Time // the last time the session saw either read or write activity + lastActivity time.Time // the last time the session saw either read or write activity. - timeout time.Duration // for timeouts + timeout time.Duration // for timeouts. doneOnce sync.Once doneCh chan struct{} } @@ -254,6 +267,8 @@ func (c *Conn) readLoop() { msg := c.msgs[0] c.msgs = c.msgs[1:] n := copy(cBuf, msg) + // Return buffer to sync.Pool once done reading from it. + c.listener.readBufferPool.Put(msg) c.sizeCh <- n case msg := <-c.receiveCh: c.msgs = append(c.msgs, msg) @@ -299,6 +314,11 @@ func (c *Conn) Write(p []byte) (n int, err error) { func (c *Conn) close() { c.doneOnce.Do(func() { + // Release any buffered data before closing. + for _, msg := range c.msgs { + c.listener.readBufferPool.Put(msg) + } + c.msgs = nil close(c.doneCh) }) } From d1ab6ed4899b3bee379d81c52c9e08853e7449cb Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 Oct 2025 17:46:05 +0200 Subject: [PATCH 013/134] Support syscall --- .../configuration-options.md | 4 +- .../reference/static-configuration/cli-ref.md | 4 +- .../reference/static-configuration/env-ref.md | 4 +- .../fixtures/src/testpluginsafe/go.mod | 3 + .../src/testpluginsafe/testpluginsafe.go | 21 +++ .../fixtures/src/testpluginsyscall/go.mod | 3 + .../testpluginsyscall/testpluginsyscall.go | 29 ++++ .../fixtures/src/testpluginunsafe/go.mod | 3 + .../src/testpluginunsafe/testpluginunsafe.go | 26 +++ pkg/plugins/middlewareyaegi.go | 8 +- pkg/plugins/middlewareyaegi_test.go | 148 ++++++++++++++++++ pkg/plugins/types.go | 2 +- 12 files changed, 247 insertions(+), 8 deletions(-) create mode 100644 pkg/plugins/fixtures/src/testpluginsafe/go.mod create mode 100644 pkg/plugins/fixtures/src/testpluginsafe/testpluginsafe.go create mode 100644 pkg/plugins/fixtures/src/testpluginsyscall/go.mod create mode 100644 pkg/plugins/fixtures/src/testpluginsyscall/testpluginsyscall.go create mode 100644 pkg/plugins/fixtures/src/testpluginunsafe/go.mod create mode 100644 pkg/plugins/fixtures/src/testpluginunsafe/testpluginunsafe.go create mode 100644 pkg/plugins/middlewareyaegi_test.go diff --git a/docs/content/reference/install-configuration/configuration-options.md b/docs/content/reference/install-configuration/configuration-options.md index 3bd7e708e..3da346ee6 100644 --- a/docs/content/reference/install-configuration/configuration-options.md +++ b/docs/content/reference/install-configuration/configuration-options.md @@ -131,14 +131,14 @@ THIS FILE MUST NOT BE EDITED BY HAND | experimental.localplugins._name_.settings | Plugin's settings (works only for wasm plugins). | | | experimental.localplugins._name_.settings.envs | Environment variables to forward to the wasm guest. | | | experimental.localplugins._name_.settings.mounts | Directory to mount to the wasm guest. | | -| experimental.localplugins._name_.settings.useunsafe | Allow the plugin to use unsafe package. | false | +| experimental.localplugins._name_.settings.useunsafe | Allow the plugin to use unsafe and syscall packages. | false | | experimental.otlplogs | Enables the OpenTelemetry logs integration. | false | | experimental.plugins._name_.hash | plugin's hash to validate' | | | experimental.plugins._name_.modulename | plugin's module name. | | | experimental.plugins._name_.settings | Plugin's settings (works only for wasm plugins). | | | experimental.plugins._name_.settings.envs | Environment variables to forward to the wasm guest. | | | experimental.plugins._name_.settings.mounts | Directory to mount to the wasm guest. | | -| experimental.plugins._name_.settings.useunsafe | Allow the plugin to use unsafe package. | false | +| experimental.plugins._name_.settings.useunsafe | Allow the plugin to use unsafe and syscall packages. | false | | experimental.plugins._name_.version | plugin's version. | | | global.checknewversion | Periodically check if a new version has been released. | true | | global.sendanonymoususage | Periodically send anonymous usage statistics. If the option is not specified, it will be disabled by default. | false | diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 6c9a2657a..ec0d3cc58 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -367,7 +367,7 @@ Environment variables to forward to the wasm guest. Directory to mount to the wasm guest. `--experimental.localplugins..settings.useunsafe`: -Allow the plugin to use unsafe package. (Default: ```false```) +Allow the plugin to use unsafe and syscall packages. (Default: ```false```) `--experimental.otlplogs`: Enables the OpenTelemetry logs integration. (Default: ```false```) @@ -385,7 +385,7 @@ Environment variables to forward to the wasm guest. Directory to mount to the wasm guest. `--experimental.plugins..settings.useunsafe`: -Allow the plugin to use unsafe package. (Default: ```false```) +Allow the plugin to use unsafe and syscall packages. (Default: ```false```) `--experimental.plugins..version`: plugin's version. diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index fdf963498..0148de541 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -367,7 +367,7 @@ Environment variables to forward to the wasm guest. Directory to mount to the wasm guest. `TRAEFIK_EXPERIMENTAL_LOCALPLUGINS__SETTINGS_USEUNSAFE`: -Allow the plugin to use unsafe package. (Default: ```false```) +Allow the plugin to use unsafe and syscall packages. (Default: ```false```) `TRAEFIK_EXPERIMENTAL_OTLPLOGS`: Enables the OpenTelemetry logs integration. (Default: ```false```) @@ -385,7 +385,7 @@ Environment variables to forward to the wasm guest. Directory to mount to the wasm guest. `TRAEFIK_EXPERIMENTAL_PLUGINS__SETTINGS_USEUNSAFE`: -Allow the plugin to use unsafe package. (Default: ```false```) +Allow the plugin to use unsafe and syscall packages. (Default: ```false```) `TRAEFIK_EXPERIMENTAL_PLUGINS__VERSION`: plugin's version. diff --git a/pkg/plugins/fixtures/src/testpluginsafe/go.mod b/pkg/plugins/fixtures/src/testpluginsafe/go.mod new file mode 100644 index 000000000..ffc61e1ba --- /dev/null +++ b/pkg/plugins/fixtures/src/testpluginsafe/go.mod @@ -0,0 +1,3 @@ +module testpluginsafe + +go 1.23.0 diff --git a/pkg/plugins/fixtures/src/testpluginsafe/testpluginsafe.go b/pkg/plugins/fixtures/src/testpluginsafe/testpluginsafe.go new file mode 100644 index 000000000..d9ca33cde --- /dev/null +++ b/pkg/plugins/fixtures/src/testpluginsafe/testpluginsafe.go @@ -0,0 +1,21 @@ +package testpluginsafe + +import ( + "context" + "net/http" +) + +type Config struct { + Message string +} + +func CreateConfig() *Config { + return &Config{Message: "safe plugin"} +} + +func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("X-Test-Plugin", "safe") + next.ServeHTTP(rw, req) + }), nil +} diff --git a/pkg/plugins/fixtures/src/testpluginsyscall/go.mod b/pkg/plugins/fixtures/src/testpluginsyscall/go.mod new file mode 100644 index 000000000..871d361b1 --- /dev/null +++ b/pkg/plugins/fixtures/src/testpluginsyscall/go.mod @@ -0,0 +1,3 @@ +module testpluginsyscall + +go 1.23.0 \ No newline at end of file diff --git a/pkg/plugins/fixtures/src/testpluginsyscall/testpluginsyscall.go b/pkg/plugins/fixtures/src/testpluginsyscall/testpluginsyscall.go new file mode 100644 index 000000000..41272fb27 --- /dev/null +++ b/pkg/plugins/fixtures/src/testpluginsyscall/testpluginsyscall.go @@ -0,0 +1,29 @@ +package testpluginsyscall + +import ( + "context" + "net/http" + "syscall" + "unsafe" +) + +type Config struct { + Message string +} + +func CreateConfig() *Config { + return &Config{Message: "syscall plugin"} +} + +func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) { + // Use syscall and unsafe to test they're available + pid := syscall.Getpid() + size := unsafe.Sizeof(int(0)) + + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("X-Test-Plugin", "syscall") + rw.Header().Set("X-Test-PID", string(rune(pid))) + rw.Header().Set("X-Test-Size", string(rune(size))) + next.ServeHTTP(rw, req) + }), nil +} diff --git a/pkg/plugins/fixtures/src/testpluginunsafe/go.mod b/pkg/plugins/fixtures/src/testpluginunsafe/go.mod new file mode 100644 index 000000000..ef0bee089 --- /dev/null +++ b/pkg/plugins/fixtures/src/testpluginunsafe/go.mod @@ -0,0 +1,3 @@ +module testpluginunsafe + +go 1.23.0 \ No newline at end of file diff --git a/pkg/plugins/fixtures/src/testpluginunsafe/testpluginunsafe.go b/pkg/plugins/fixtures/src/testpluginunsafe/testpluginunsafe.go new file mode 100644 index 000000000..1a58901dd --- /dev/null +++ b/pkg/plugins/fixtures/src/testpluginunsafe/testpluginunsafe.go @@ -0,0 +1,26 @@ +package testpluginunsafe + +import ( + "context" + "net/http" + "unsafe" +) + +type Config struct { + Message string +} + +func CreateConfig() *Config { + return &Config{Message: "unsafe only plugin"} +} + +func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) { + // Use ONLY unsafe to test it's available + size := unsafe.Sizeof(int(0)) + + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("X-Test-Plugin", "unsafe-only") + rw.Header().Set("X-Test-Unsafe-Size", string(rune(size))) + next.ServeHTTP(rw, req) + }), nil +} diff --git a/pkg/plugins/middlewareyaegi.go b/pkg/plugins/middlewareyaegi.go index 590938044..41d120158 100644 --- a/pkg/plugins/middlewareyaegi.go +++ b/pkg/plugins/middlewareyaegi.go @@ -16,6 +16,7 @@ import ( "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/yaegi/interp" "github.com/traefik/yaegi/stdlib" + "github.com/traefik/yaegi/stdlib/syscall" "github.com/traefik/yaegi/stdlib/unsafe" ) @@ -135,7 +136,7 @@ func newInterpreter(ctx context.Context, goPath string, manifest *Manifest, sett } if manifest.UseUnsafe && !settings.UseUnsafe { - return nil, errors.New("this plugin uses unsafe import. If you want to use it, you need to allow useUnsafe in the settings") + return nil, errors.New("this plugin uses restricted imports. If you want to use it, you need to allow useUnsafe in the settings") } if settings.UseUnsafe && manifest.UseUnsafe { @@ -143,6 +144,11 @@ func newInterpreter(ctx context.Context, goPath string, manifest *Manifest, sett if err != nil { return nil, fmt.Errorf("failed to load unsafe symbols: %w", err) } + + err = i.Use(syscall.Symbols) + if err != nil { + return nil, fmt.Errorf("failed to load syscall symbols: %w", err) + } } err = i.Use(ppSymbols()) diff --git a/pkg/plugins/middlewareyaegi_test.go b/pkg/plugins/middlewareyaegi_test.go new file mode 100644 index 000000000..f9df8232d --- /dev/null +++ b/pkg/plugins/middlewareyaegi_test.go @@ -0,0 +1,148 @@ +package plugins + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/yaegi/interp" +) + +// TestNewInterpreter_SyscallErrorCase - Tests the security gate logic +func TestNewInterpreter_SyscallErrorCase(t *testing.T) { + manifest := &Manifest{ + Import: "does-not-matter-will-error-before-import", + UseUnsafe: true, // Plugin wants unsafe access + } + settings := Settings{ + UseUnsafe: false, // But admin doesn't allow it + } + + ctx := t.Context() + _, err := newInterpreter(ctx, "/tmp", manifest, settings) + + // This proves our security gate logic works + require.Error(t, err) + assert.Contains(t, err.Error(), "restricted imports", "Our error message should be returned") +} + +// TestNewYaegiMiddlewareBuilder_WithSyscallSupport - Tests the ACTUAL production code! +func TestNewYaegiMiddlewareBuilder_WithSyscallSupport(t *testing.T) { + tests := []struct { + name string + pluginType string + manifestUnsafe bool + settingsUnsafe bool + shouldSucceed bool + expectedError string + }{ + { + name: "Should work with safe plugin when useUnsafe disabled", + pluginType: "safe", + manifestUnsafe: false, + settingsUnsafe: false, + shouldSucceed: true, + }, + { + name: "Should work with unsafe-only plugin when useUnsafe enabled", + pluginType: "unsafe-only", + manifestUnsafe: true, + settingsUnsafe: true, + shouldSucceed: true, + }, + { + name: "Should work with unsafe+syscall plugin when useUnsafe enabled", + pluginType: "unsafe+syscall", + manifestUnsafe: true, + settingsUnsafe: true, + shouldSucceed: true, + }, + { + name: "Should fail when plugin needs unsafe but setting disabled", + pluginType: "unsafe-only", + manifestUnsafe: true, + settingsUnsafe: false, + shouldSucceed: false, + expectedError: "restricted imports", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := t.Context() + + // Set GOPATH to include our fixtures directory + goPath := "fixtures" + + // Create interpreter using our ACTUAL newInterpreter function + // This will automatically import the real test plugin! + interpreter, err := createInterpreterForTesting(ctx, goPath, tc.pluginType, tc.manifestUnsafe, tc.settingsUnsafe) + + if tc.shouldSucceed { + require.NoError(t, err) + require.NotNil(t, interpreter) + + // Test actual middleware building using newYaegiMiddlewareBuilder + // The plugin is already loaded by newInterpreter! + basePkg := getPluginPackage(tc.pluginType) + + builder, err := newYaegiMiddlewareBuilder(interpreter, basePkg, basePkg) + require.NoError(t, err) + require.NotNil(t, builder) + + // Verify that unsafe/syscall functions actually work if the plugin uses them + if tc.pluginType != "safe" { + verifyMiddlewareWorks(t, builder) + } + } else { + assert.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedError) + } + }) + } +} + +// Helper that uses the ACTUAL newInterpreter function with real test plugins +func createInterpreterForTesting(ctx context.Context, goPath, pluginType string, manifestUnsafe, settingsUnsafe bool) (*interp.Interpreter, error) { + pluginImport := getPluginPackage(pluginType) + + manifest := &Manifest{ + Import: pluginImport, + UseUnsafe: manifestUnsafe, + } + settings := Settings{ + UseUnsafe: settingsUnsafe, + } + + // Call the ACTUAL production newInterpreter function - no workarounds needed! + return newInterpreter(ctx, goPath, manifest, settings) +} + +// Helper to get the correct plugin package name based on type +func getPluginPackage(pluginType string) string { + switch pluginType { + case "safe": + return "testpluginsafe" + case "unsafe-only": + return "testpluginunsafe" + case "unsafe+syscall": + return "testpluginsyscall" + default: + return "testpluginsafe" + } +} + +// Helper to verify that unsafe/syscall functions actually work by invoking the middleware +func verifyMiddlewareWorks(t *testing.T, builder *yaegiMiddlewareBuilder) { + t.Helper() + // Create a middleware instance - this will call the plugin's New() function + // which uses unsafe/syscall, proving they work + middleware, err := builder.newMiddleware(map[string]interface{}{ + "message": "test", + }, "test-middleware") + require.NoError(t, err, "Should be able to create middleware that uses unsafe/syscall") + require.NotNil(t, middleware, "Middleware should not be nil") + + // The fact that we got here without crashing proves unsafe/syscall work! +} diff --git a/pkg/plugins/types.go b/pkg/plugins/types.go index 75bb589b3..aa05cc320 100644 --- a/pkg/plugins/types.go +++ b/pkg/plugins/types.go @@ -13,7 +13,7 @@ const ( type Settings struct { Envs []string `description:"Environment variables to forward to the wasm guest." json:"envs,omitempty" toml:"envs,omitempty" yaml:"envs,omitempty"` Mounts []string `description:"Directory to mount to the wasm guest." json:"mounts,omitempty" toml:"mounts,omitempty" yaml:"mounts,omitempty"` - UseUnsafe bool `description:"Allow the plugin to use unsafe package." json:"useUnsafe,omitempty" toml:"useUnsafe,omitempty" yaml:"useUnsafe,omitempty"` + UseUnsafe bool `description:"Allow the plugin to use unsafe and syscall packages." json:"useUnsafe,omitempty" toml:"useUnsafe,omitempty" yaml:"useUnsafe,omitempty"` } // Descriptor The static part of a plugin configuration. From 8392503df7768f45aa1e7ac359766043279574b6 Mon Sep 17 00:00:00 2001 From: Douglas De Toni Machado Date: Wed, 22 Oct 2025 06:42:05 -0300 Subject: [PATCH 014/134] Add TCP Healthcheck --- .../other-providers/file.toml | 8 + .../other-providers/file.yaml | 8 + .../routing-configuration/tcp/service.md | 148 +++- .../fixtures/tcp_healthcheck/simple.toml | 52 ++ integration/k8s_test.go | 6 +- .../resources/compose/tcp_healthcheck.yml | 12 + integration/tcp_healthcheck_test.go | 114 +++ integration/testdata/rawdata-crd.json | 11 +- integration/testdata/rawdata-gateway.json | 6 +- pkg/api/handler.go | 31 +- pkg/api/handler_http.go | 4 +- pkg/api/handler_tcp.go | 8 +- pkg/api/handler_tcp_test.go | 346 ++++---- pkg/api/testdata/tcpservice-bar.json | 3 + .../testdata/tcpservice-foo-slash-bar.json | 3 + .../testdata/tcpservices-filtered-search.json | 3 + .../testdata/tcpservices-filtered-status.json | 3 + pkg/api/testdata/tcpservices-page2.json | 3 + pkg/api/testdata/tcpservices.json | 11 +- pkg/config/dynamic/tcp_config.go | 25 +- pkg/config/dynamic/zz_generated.deepcopy.go | 31 + pkg/config/runtime/runtime_tcp.go | 31 + pkg/healthcheck/healthcheck_tcp.go | 212 +++++ pkg/healthcheck/healthcheck_tcp_test.go | 754 ++++++++++++++++++ pkg/healthcheck/healthcheck_test.go | 2 + pkg/server/routerfactory.go | 2 + pkg/server/service/loadbalancer/hrw/hrw.go | 1 + pkg/server/service/loadbalancer/p2c/p2c.go | 1 + pkg/server/service/loadbalancer/wrr/wrr.go | 1 + pkg/server/service/service.go | 20 +- pkg/server/service/tcp/service.go | 73 +- pkg/server/service/tcp/service_test.go | 43 + pkg/tcp/dialer.go | 16 +- pkg/tcp/wrr_load_balancer.go | 171 ++-- pkg/tcp/wrr_load_balancer_test.go | 242 ++++-- webui/src/pages/tcp/TcpService.spec.tsx | 92 ++- webui/src/pages/tcp/TcpService.tsx | 226 +++++- 37 files changed, 2416 insertions(+), 307 deletions(-) create mode 100644 integration/fixtures/tcp_healthcheck/simple.toml create mode 100644 integration/resources/compose/tcp_healthcheck.yml create mode 100644 integration/tcp_healthcheck_test.go create mode 100644 pkg/healthcheck/healthcheck_tcp.go create mode 100644 pkg/healthcheck/healthcheck_tcp_test.go diff --git a/docs/content/reference/routing-configuration/other-providers/file.toml b/docs/content/reference/routing-configuration/other-providers/file.toml index e0e2139e5..907cf02e1 100644 --- a/docs/content/reference/routing-configuration/other-providers/file.toml +++ b/docs/content/reference/routing-configuration/other-providers/file.toml @@ -478,6 +478,13 @@ tls = true [tcp.services.TCPService01.loadBalancer.proxyProtocol] version = 42 + [tcp.services.TCPService01.loadBalancer.healthCheck] + port = 42 + send = "foobar" + expect = "foobar" + interval = "42s" + unhealthyInterval = "42s" + timeout = "42s" [tcp.services.TCPService02] [tcp.services.TCPService02.weighted] @@ -488,6 +495,7 @@ [[tcp.services.TCPService02.weighted.services]] name = "foobar" weight = 42 + [tcp.services.TCPService02.weighted.healthCheck] [tcp.middlewares] [tcp.middlewares.TCPMiddleware01] [tcp.middlewares.TCPMiddleware01.ipAllowList] diff --git a/docs/content/reference/routing-configuration/other-providers/file.yaml b/docs/content/reference/routing-configuration/other-providers/file.yaml index e2ab16e54..438a74a65 100644 --- a/docs/content/reference/routing-configuration/other-providers/file.yaml +++ b/docs/content/reference/routing-configuration/other-providers/file.yaml @@ -538,6 +538,13 @@ tcp: proxyProtocol: version: 42 terminationDelay: 42 + healthCheck: + port: 42 + send: foobar + expect: foobar + interval: 42s + unhealthyInterval: 42s + timeout: 42s TCPService02: weighted: services: @@ -545,6 +552,7 @@ tcp: weight: 42 - name: foobar weight: 42 + healthCheck: {} middlewares: TCPMiddleware01: ipAllowList: diff --git a/docs/content/reference/routing-configuration/tcp/service.md b/docs/content/reference/routing-configuration/tcp/service.md index d85a75102..348d83d9a 100644 --- a/docs/content/reference/routing-configuration/tcp/service.md +++ b/docs/content/reference/routing-configuration/tcp/service.md @@ -23,6 +23,12 @@ tcp: servers: - address: "xx.xx.xx.xx:xx" - address: "xx.xx.xx.xx:xx" + healthCheck: + send: "PING" + expect: "PONG" + interval: "10s" + timeout: "3s" + serversTransport: "customTransport@file" ``` ```toml tab="Structured (TOML)" @@ -32,26 +38,79 @@ tcp: address = "xx.xx.xx.xx:xx" [[tcp.services.my-service.loadBalancer.servers]] address = "xx.xx.xx.xx:xx" + + [tcp.services.my-service.loadBalancer.healthCheck] + send = "PING" + expect = "PONG" + interval = "10s" + timeout = "3s" + + serversTransport = "customTransport@file" ``` -## Configuration Options +```yaml tab="Labels" +labels: + - "traefik.tcp.services.my-service.loadBalancer.servers[0].address=xx.xx.xx.xx:xx" + - "traefik.tcp.services.my-service.loadBalancer.servers[1].address=xx.xx.xx.xx:xx" + - "traefik.tcp.services.my-service.loadBalancer.healthCheck.send=PING" + - "traefik.tcp.services.my-service.loadBalancer.healthCheck.expect=PONG" + - "traefik.tcp.services.my-service.loadBalancer.healthCheck.interval=10s" + - "traefik.tcp.services.my-service.loadBalancer.healthCheck.timeout=3s" + - "traefik.tcp.services.my-service.loadBalancer.serversTransport=customTransport@file" +``` + +```json tab="Tags" +{ + "Tags": [ + "traefik.tcp.services.my-service.loadBalancer.servers[0].address=xx.xx.xx.xx:xx", + "traefik.tcp.services.my-service.loadBalancer.servers[1].address=xx.xx.xx.xx:xx", + "traefik.tcp.services.my-service.loadBalancer.healthCheck.send=PING", + "traefik.tcp.services.my-service.loadBalancer.healthCheck.expect=PONG", + "traefik.tcp.services.my-service.loadBalancer.healthCheck.interval=10s", + "traefik.tcp.services.my-service.loadBalancer.healthCheck.timeout=3s", + "traefik.tcp.services.my-service.loadBalancer.serversTransport=customTransport@file" + ] +} +``` + +### Configuration Options | Field | Description | Default | |----------|------------------------------------------|--------- | | `servers` | Servers declare a single instance of your program. | "" | | `servers.address` | The address option (IP:Port) point to a specific instance. | "" | | `servers.tls` | The `tls` option determines whether to use TLS when dialing with the backend. | false | -| `servers.serversTransport` | `serversTransport` allows to reference a TCP [ServersTransport](./serverstransport.md configuration for the communication between Traefik and your servers. If no serversTransport is specified, the default@internal will be used. | "" | +| `serversTransport` | `serversTransport` allows to reference a TCP [ServersTransport](./serverstransport.md) configuration for the communication between Traefik and your servers. If no serversTransport is specified, the default@internal will be used. | "" | +| `healthCheck` | Configures health check to remove unhealthy servers from the load balancing rotation. See [HealthCheck](#health-check) for details. | | No | + +### Health Check + +The `healthCheck` option configures health check to remove unhealthy servers from the load balancing rotation. +Traefik will consider TCP servers healthy as long as the connection to the target server succeeds. +For advanced health checks, you can configure TCP payload exchange by specifying `send` and `expect` parameters. + +To propagate status changes (e.g. all servers of this service are down) upwards, HealthCheck must also be enabled on the parent(s) of this service. + +Below are the available options for the health check mechanism: + +| Field | Description | Default | Required | +|-------|-------------|---------|----------| +| `port` | Replaces the server address port for the health check endpoint. | | No | +| `send` | Defines the payload to send to the server during the health check. | "" | No | +| `expect` | Defines the expected response payload from the server. | "" | No | +| `interval` | Defines the frequency of the health check calls for healthy targets. | 30s | No | +| `unhealthyInterval` | Defines the frequency of the health check calls for unhealthy targets. When not defined, it defaults to the `interval` value. | 30s | No | +| `timeout` | Defines the maximum duration Traefik will wait for a health check connection before considering the server unhealthy. | 5s | No | ## Weighted Round Robin -The Weighted Round Robin (alias `WRR`) load-balancer of services is in charge of balancing the requests between multiple services based on provided weights. +The Weighted Round Robin (alias `WRR`) load-balancer of services is in charge of balancing the connections between multiple services based on provided weights. This strategy is only available to load balance between [services](./service.md) and not between servers. !!! info "Supported Providers" - This strategy can be defined currently with the [File](../../install-configuration/providers/others/file.md) or [IngressRoute](../../install-configuration/providers/kubernetes/kubernetes-crd.md) providers. + This strategy can be defined currently with the [File provider](../../install-configuration/providers/others/file.md). ```yaml tab="Structured (YAML)" tcp: @@ -95,4 +154,83 @@ tcp: [[tcp.services.appv2.loadBalancer.servers]] address = "private-ip-server-2:8080/" ``` - + +### Health Check + +HealthCheck enables automatic self-healthcheck for this service, i.e. whenever one of its children is reported as down, +this service becomes aware of it, and takes it into account (i.e. it ignores the down child) when running the load-balancing algorithm. +In addition, if the parent of this service also has HealthCheck enabled, this service reports to its parent any status change. + +!!! note "Behavior" + + If HealthCheck is enabled for a given service and any of its descendants does not have it enabled, the creation of the service will fail. + + HealthCheck on Weighted services can be defined currently only with the [File provider](../../../install-configuration/providers/others/file.md). + +```yaml tab="Structured (YAML)" +## Dynamic configuration +tcp: + services: + app: + weighted: + healthCheck: {} + services: + - name: appv1 + weight: 3 + - name: appv2 + weight: 1 + + appv1: + loadBalancer: + healthCheck: + send: "PING" + expect: "PONG" + interval: 10s + timeout: 3s + servers: + - address: "192.168.1.10:6379" + + appv2: + loadBalancer: + healthCheck: + send: "PING" + expect: "PONG" + interval: 10s + timeout: 3s + servers: + - address: "192.168.1.11:6379" +``` + +```toml tab="Structured (TOML)" +## Dynamic configuration +[tcp.services] + [tcp.services.app] + [tcp.services.app.weighted.healthCheck] + [[tcp.services.app.weighted.services]] + name = "appv1" + weight = 3 + [[tcp.services.app.weighted.services]] + name = "appv2" + weight = 1 + + [tcp.services.appv1] + [tcp.services.appv1.loadBalancer] + [tcp.services.appv1.loadBalancer.healthCheck] + send = "PING" + expect = "PONG" + interval = "10s" + timeout = "3s" + [[tcp.services.appv1.loadBalancer.servers]] + address = "192.168.1.10:6379" + + [tcp.services.appv2] + [tcp.services.appv2.loadBalancer] + [tcp.services.appv2.loadBalancer.healthCheck] + send = "PING" + expect = "PONG" + interval = "10s" + timeout = "3s" + [[tcp.services.appv2.loadBalancer.servers]] + address = "192.168.1.11:6379" +``` + diff --git a/integration/fixtures/tcp_healthcheck/simple.toml b/integration/fixtures/tcp_healthcheck/simple.toml new file mode 100644 index 000000000..4897052e9 --- /dev/null +++ b/integration/fixtures/tcp_healthcheck/simple.toml @@ -0,0 +1,52 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + noColor = true + +[entryPoints] + [entryPoints.tcp] + address = ":8093" + +[api] + insecure = true + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[tcp.routers] + [tcp.routers.router1] + rule = "HostSNI(`*`)" + service = "weightedservice" + +[tcp.services] + [tcp.services.weightedservice.weighted] + [tcp.services.weightedservice.weighted.healthCheck] + [[tcp.services.weightedservice.weighted.services]] + name = "service1" + weight = 1 + [[tcp.services.weightedservice.weighted.services]] + name = "service2" + weight = 1 + + [tcp.services.service1.loadBalancer] + [tcp.services.service1.loadBalancer.healthCheck] + interval = "500ms" + timeout = "500ms" + send = "PING" + expect = "Received: PING" + [[tcp.services.service1.loadBalancer.servers]] + address = "{{.Server1}}:8080" + + [tcp.services.service2.loadBalancer] + [tcp.services.service2.loadBalancer.healthCheck] + interval = "500ms" + timeout = "500ms" + send = "PING" + expect = "Received: PING" + [[tcp.services.service2.loadBalancer.servers]] + address = "{{.Server2}}:8080" diff --git a/integration/k8s_test.go b/integration/k8s_test.go index 396913538..3e81d8b0b 100644 --- a/integration/k8s_test.go +++ b/integration/k8s_test.go @@ -199,9 +199,9 @@ func matchesConfig(wantConfig string, buf *bytes.Buffer) try.ResponseCondition { sanitizedExpected := rxURL.ReplaceAll(bytes.TrimSpace(expected), []byte(`"$1": "XXXX"`)) sanitizedGot := rxURL.ReplaceAll(got, []byte(`"$1": "XXXX"`)) - rxServerStatus := regexp.MustCompile(`"http://.*?":\s+(".*")`) - sanitizedExpected = rxServerStatus.ReplaceAll(sanitizedExpected, []byte(`"http://XXXX": $1`)) - sanitizedGot = rxServerStatus.ReplaceAll(sanitizedGot, []byte(`"http://XXXX": $1`)) + rxServerStatus := regexp.MustCompile(`"(http://)?.*?":\s+(".*")`) + sanitizedExpected = rxServerStatus.ReplaceAll(sanitizedExpected, []byte(`"XXXX": $1`)) + sanitizedGot = rxServerStatus.ReplaceAll(sanitizedGot, []byte(`"XXXX": $1`)) if bytes.Equal(sanitizedExpected, sanitizedGot) { return nil diff --git a/integration/resources/compose/tcp_healthcheck.yml b/integration/resources/compose/tcp_healthcheck.yml new file mode 100644 index 000000000..fe7d93b68 --- /dev/null +++ b/integration/resources/compose/tcp_healthcheck.yml @@ -0,0 +1,12 @@ +services: + whoamitcp1: + image: traefik/whoamitcp + command: + - -name + - whoamitcp1 + + whoamitcp2: + image: traefik/whoamitcp + command: + - -name + - whoamitcp2 diff --git a/integration/tcp_healthcheck_test.go b/integration/tcp_healthcheck_test.go new file mode 100644 index 000000000..d79fb610b --- /dev/null +++ b/integration/tcp_healthcheck_test.go @@ -0,0 +1,114 @@ +package integration + +import ( + "net" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/traefik/traefik/v3/integration/try" +) + +// TCPHealthCheckSuite test suite for TCP health checks. +type TCPHealthCheckSuite struct { + BaseSuite + whoamitcp1IP string + whoamitcp2IP string +} + +func TestTCPHealthCheckSuite(t *testing.T) { + suite.Run(t, new(TCPHealthCheckSuite)) +} + +func (s *TCPHealthCheckSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("tcp_healthcheck") + s.composeUp() + + s.whoamitcp1IP = s.getComposeServiceIP("whoamitcp1") + s.whoamitcp2IP = s.getComposeServiceIP("whoamitcp2") +} + +func (s *TCPHealthCheckSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *TCPHealthCheckSuite) TestSimpleConfiguration() { + file := s.adaptFile("fixtures/tcp_healthcheck/simple.toml", struct { + Server1 string + Server2 string + }{s.whoamitcp1IP, s.whoamitcp2IP}) + + s.traefikCmd(withConfigFile(file)) + + // Wait for Traefik. + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("HostSNI(`*`)")) + require.NoError(s.T(), err) + + // Test that we can consistently reach servers through load balancing. + var ( + successfulConnectionsWhoamitcp1 int + successfulConnectionsWhoamitcp2 int + ) + for range 4 { + out := s.whoIs("127.0.0.1:8093") + require.NoError(s.T(), err) + + if strings.Contains(out, "whoamitcp1") { + successfulConnectionsWhoamitcp1++ + } + if strings.Contains(out, "whoamitcp2") { + successfulConnectionsWhoamitcp2++ + } + } + + assert.Equal(s.T(), 2, successfulConnectionsWhoamitcp1) + assert.Equal(s.T(), 2, successfulConnectionsWhoamitcp2) + + // Stop one whoamitcp2 containers to simulate health check failure. + conn, err := net.DialTimeout("tcp", s.whoamitcp2IP+":8080", time.Second) + require.NoError(s.T(), err) + + s.T().Cleanup(func() { + _ = conn.Close() + }) + + s.composeStop("whoamitcp2") + + // Wait for the health check to detect the failure. + time.Sleep(1 * time.Second) + + // Verify that the remaining server still responds. + for range 3 { + out := s.whoIs("127.0.0.1:8093") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoamitcp1") + } +} + +// connectTCP connects to the given TCP address and returns the response. +func (s *TCPHealthCheckSuite) whoIs(addr string) string { + s.T().Helper() + + conn, err := net.DialTimeout("tcp", addr, time.Second) + require.NoError(s.T(), err) + + s.T().Cleanup(func() { + _ = conn.Close() + }) + + _, err = conn.Write([]byte("WHO")) + require.NoError(s.T(), err) + + _ = conn.SetReadDeadline(time.Now().Add(2 * time.Second)) + + buffer := make([]byte, 1024) + n, err := conn.Read(buffer) + require.NoError(s.T(), err) + + return string(buffer[:n]) +} diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index 4f9c63322..ece3a7dd3 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -362,7 +362,10 @@ } ] }, - "status": "enabled" + "status": "enabled", + "serverStatus": { + "domain.com:9090": "UP" + } }, "default-test3.route-673acf455cb2dab0b43a-whoamitcp-8080@kubernetescrd": { "loadBalancer": { @@ -375,7 +378,11 @@ } ] }, - "status": "enabled" + "status": "enabled", + "serverStatus": { + "10.42.0.2:8080": "UP", + "10.42.0.6:8080": "UP" + } }, "default-test3.route-673acf455cb2dab0b43a@kubernetescrd": { "weighted": { diff --git a/integration/testdata/rawdata-gateway.json b/integration/testdata/rawdata-gateway.json index 8a6e1bc8c..0b1f9fc20 100644 --- a/integration/testdata/rawdata-gateway.json +++ b/integration/testdata/rawdata-gateway.json @@ -233,7 +233,11 @@ } ] }, - "status": "enabled" + "status": "enabled", + "serverStatus": { + "10.42.0.2:8080": "UP", + "10.42.0.6:8080": "UP" + } }, "tcproute-default-tcp-app-1-gw-default-my-tcp-gateway-ep-footcp-0-e3b0c44298fc1c149afb-wrr@kubernetesgateway": { "weighted": { diff --git a/pkg/api/handler.go b/pkg/api/handler.go index 6c99ca12e..0165da9e8 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -33,16 +33,21 @@ type serviceInfoRepresentation struct { ServerStatus map[string]string `json:"serverStatus,omitempty"` } +type tcpServiceInfoRepresentation struct { + *runtime.TCPServiceInfo + ServerStatus map[string]string `json:"serverStatus,omitempty"` +} + // RunTimeRepresentation is the configuration information exposed by the API handler. type RunTimeRepresentation struct { - Routers map[string]*runtime.RouterInfo `json:"routers,omitempty"` - Middlewares map[string]*runtime.MiddlewareInfo `json:"middlewares,omitempty"` - Services map[string]*serviceInfoRepresentation `json:"services,omitempty"` - TCPRouters map[string]*runtime.TCPRouterInfo `json:"tcpRouters,omitempty"` - TCPMiddlewares map[string]*runtime.TCPMiddlewareInfo `json:"tcpMiddlewares,omitempty"` - TCPServices map[string]*runtime.TCPServiceInfo `json:"tcpServices,omitempty"` - UDPRouters map[string]*runtime.UDPRouterInfo `json:"udpRouters,omitempty"` - UDPServices map[string]*runtime.UDPServiceInfo `json:"udpServices,omitempty"` + Routers map[string]*runtime.RouterInfo `json:"routers,omitempty"` + Middlewares map[string]*runtime.MiddlewareInfo `json:"middlewares,omitempty"` + Services map[string]*serviceInfoRepresentation `json:"services,omitempty"` + TCPRouters map[string]*runtime.TCPRouterInfo `json:"tcpRouters,omitempty"` + TCPMiddlewares map[string]*runtime.TCPMiddlewareInfo `json:"tcpMiddlewares,omitempty"` + TCPServices map[string]*tcpServiceInfoRepresentation `json:"tcpServices,omitempty"` + UDPRouters map[string]*runtime.UDPRouterInfo `json:"udpRouters,omitempty"` + UDPServices map[string]*runtime.UDPServiceInfo `json:"udpServices,omitempty"` } // Handler serves the configuration and status of Traefik on API endpoints. @@ -127,13 +132,21 @@ func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.R } } + tcpSIRepr := make(map[string]*tcpServiceInfoRepresentation, len(h.runtimeConfiguration.Services)) + for k, v := range h.runtimeConfiguration.TCPServices { + tcpSIRepr[k] = &tcpServiceInfoRepresentation{ + TCPServiceInfo: v, + ServerStatus: v.GetAllStatus(), + } + } + result := RunTimeRepresentation{ Routers: h.runtimeConfiguration.Routers, Middlewares: h.runtimeConfiguration.Middlewares, Services: siRepr, TCPRouters: h.runtimeConfiguration.TCPRouters, TCPMiddlewares: h.runtimeConfiguration.TCPMiddlewares, - TCPServices: h.runtimeConfiguration.TCPServices, + TCPServices: tcpSIRepr, UDPRouters: h.runtimeConfiguration.UDPRouters, UDPServices: h.runtimeConfiguration.UDPServices, } diff --git a/pkg/api/handler_http.go b/pkg/api/handler_http.go index 9439d8ab8..a0ca84ce7 100644 --- a/pkg/api/handler_http.go +++ b/pkg/api/handler_http.go @@ -34,10 +34,10 @@ func newRouterRepresentation(name string, rt *runtime.RouterInfo) routerRepresen type serviceRepresentation struct { *runtime.ServiceInfo - ServerStatus map[string]string `json:"serverStatus,omitempty"` Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` Type string `json:"type,omitempty"` + ServerStatus map[string]string `json:"serverStatus,omitempty"` } func newServiceRepresentation(name string, si *runtime.ServiceInfo) serviceRepresentation { @@ -45,8 +45,8 @@ func newServiceRepresentation(name string, si *runtime.ServiceInfo) serviceRepre ServiceInfo: si, Name: name, Provider: getProviderName(name), - ServerStatus: si.GetAllStatus(), Type: strings.ToLower(extractType(si.Service)), + ServerStatus: si.GetAllStatus(), } } diff --git a/pkg/api/handler_tcp.go b/pkg/api/handler_tcp.go index 3ad0fb5b7..cc8f9aeed 100644 --- a/pkg/api/handler_tcp.go +++ b/pkg/api/handler_tcp.go @@ -29,9 +29,10 @@ func newTCPRouterRepresentation(name string, rt *runtime.TCPRouterInfo) tcpRoute type tcpServiceRepresentation struct { *runtime.TCPServiceInfo - Name string `json:"name,omitempty"` - Provider string `json:"provider,omitempty"` - Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Provider string `json:"provider,omitempty"` + Type string `json:"type,omitempty"` + ServerStatus map[string]string `json:"serverStatus,omitempty"` } func newTCPServiceRepresentation(name string, si *runtime.TCPServiceInfo) tcpServiceRepresentation { @@ -40,6 +41,7 @@ func newTCPServiceRepresentation(name string, si *runtime.TCPServiceInfo) tcpSer Name: name, Provider: getProviderName(name), Type: strings.ToLower(extractType(si.TCPService)), + ServerStatus: si.GetAllStatus(), } } diff --git a/pkg/api/handler_tcp_test.go b/pkg/api/handler_tcp_test.go index 387c39050..b0ee4cc2f 100644 --- a/pkg/api/handler_tcp_test.go +++ b/pkg/api/handler_tcp_test.go @@ -355,45 +355,57 @@ func TestHandler_TCP(t *testing.T) { path: "/api/tcp/services", conf: runtime.Configuration{ TCPServices: map[string]*runtime.TCPServiceInfo{ - "bar@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.1:2345", + "bar@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider", "test@myprovider"}, - Status: runtime.StatusEnabled, - }, - "baz@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.2:2345", + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + Status: runtime.StatusEnabled, + } + si.UpdateServerStatus("127.0.0.1:2345", "UP") + return si + }(), + "baz@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.2:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider"}, - Status: runtime.StatusWarning, - }, - "foz@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.2:2345", + UsedBy: []string{"foo@myprovider"}, + Status: runtime.StatusWarning, + } + si.UpdateServerStatus("127.0.0.2:2345", "UP") + return si + }(), + "foz@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.3:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider"}, - Status: runtime.StatusDisabled, - }, + UsedBy: []string{"foo@myprovider"}, + Status: runtime.StatusDisabled, + } + si.UpdateServerStatus("127.0.0.3:2345", "UP") + return si + }(), }, }, expected: expected{ @@ -407,45 +419,57 @@ func TestHandler_TCP(t *testing.T) { path: "/api/tcp/services?status=enabled", conf: runtime.Configuration{ TCPServices: map[string]*runtime.TCPServiceInfo{ - "bar@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.1:2345", + "bar@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider", "test@myprovider"}, - Status: runtime.StatusEnabled, - }, - "baz@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.2:2345", + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + Status: runtime.StatusEnabled, + } + si.UpdateServerStatus("127.0.0.1:2345", "UP") + return si + }(), + "baz@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.2:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider"}, - Status: runtime.StatusWarning, - }, - "foz@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.2:2345", + UsedBy: []string{"foo@myprovider"}, + Status: runtime.StatusWarning, + } + si.UpdateServerStatus("127.0.0.2:2345", "UP") + return si + }(), + "foz@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.3:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider"}, - Status: runtime.StatusDisabled, - }, + UsedBy: []string{"foo@myprovider"}, + Status: runtime.StatusDisabled, + } + si.UpdateServerStatus("127.0.0.3:2345", "UP") + return si + }(), }, }, expected: expected{ @@ -459,45 +483,57 @@ func TestHandler_TCP(t *testing.T) { path: "/api/tcp/services?search=baz@my", conf: runtime.Configuration{ TCPServices: map[string]*runtime.TCPServiceInfo{ - "bar@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.1:2345", + "bar@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider", "test@myprovider"}, - Status: runtime.StatusEnabled, - }, - "baz@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.2:2345", + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + Status: runtime.StatusEnabled, + } + si.UpdateServerStatus("127.0.0.1:2345", "UP") + return si + }(), + "baz@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.2:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider"}, - Status: runtime.StatusWarning, - }, - "foz@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.2:2345", + UsedBy: []string{"foo@myprovider"}, + Status: runtime.StatusWarning, + } + si.UpdateServerStatus("127.0.0.2:2345", "UP") + return si + }(), + "foz@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.3:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider"}, - Status: runtime.StatusDisabled, - }, + UsedBy: []string{"foo@myprovider"}, + Status: runtime.StatusDisabled, + } + si.UpdateServerStatus("127.0.0.3:2345", "UP") + return si + }(), }, }, expected: expected{ @@ -511,41 +547,53 @@ func TestHandler_TCP(t *testing.T) { path: "/api/tcp/services?page=2&per_page=1", conf: runtime.Configuration{ TCPServices: map[string]*runtime.TCPServiceInfo{ - "bar@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.1:2345", + "bar@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider", "test@myprovider"}, - }, - "baz@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.2:2345", + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + } + si.UpdateServerStatus("127.0.0.1:2345", "UP") + return si + }(), + "baz@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.2:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider"}, - }, - "test@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.3:2345", + UsedBy: []string{"foo@myprovider"}, + } + si.UpdateServerStatus("127.0.0.2:2345", "UP") + return si + }(), + "test@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.3:2345", + }, }, }, }, - }, - }, + } + si.UpdateServerStatus("127.0.0.3:2345", "UP") + return si + }(), }, }, expected: expected{ @@ -559,18 +607,22 @@ func TestHandler_TCP(t *testing.T) { path: "/api/tcp/services/bar@myprovider", conf: runtime.Configuration{ TCPServices: map[string]*runtime.TCPServiceInfo{ - "bar@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.1:2345", + "bar@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider", "test@myprovider"}, - }, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + } + si.UpdateServerStatus("127.0.0.1:2345", "UP") + return si + }(), }, }, expected: expected{ @@ -583,18 +635,22 @@ func TestHandler_TCP(t *testing.T) { path: "/api/tcp/services/" + url.PathEscape("foo / bar@myprovider"), conf: runtime.Configuration{ TCPServices: map[string]*runtime.TCPServiceInfo{ - "foo / bar@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.1:2345", + "foo / bar@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider", "test@myprovider"}, - }, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + } + si.UpdateServerStatus("127.0.0.1:2345", "UP") + return si + }(), }, }, expected: expected{ @@ -607,18 +663,22 @@ func TestHandler_TCP(t *testing.T) { path: "/api/tcp/services/nono@myprovider", conf: runtime.Configuration{ TCPServices: map[string]*runtime.TCPServiceInfo{ - "bar@myprovider": { - TCPService: &dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ - { - Address: "127.0.0.1:2345", + "bar@myprovider": func() *runtime.TCPServiceInfo { + si := &runtime.TCPServiceInfo{ + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:2345", + }, }, }, }, - }, - UsedBy: []string{"foo@myprovider", "test@myprovider"}, - }, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, + } + si.UpdateServerStatus("127.0.0.1:2345", "UP") + return si + }(), }, }, expected: expected{ diff --git a/pkg/api/testdata/tcpservice-bar.json b/pkg/api/testdata/tcpservice-bar.json index ade480a92..1c0476407 100644 --- a/pkg/api/testdata/tcpservice-bar.json +++ b/pkg/api/testdata/tcpservice-bar.json @@ -8,6 +8,9 @@ }, "name": "bar@myprovider", "provider": "myprovider", + "serverStatus": { + "127.0.0.1:2345": "UP" + }, "status": "enabled", "type": "loadbalancer", "usedBy": [ diff --git a/pkg/api/testdata/tcpservice-foo-slash-bar.json b/pkg/api/testdata/tcpservice-foo-slash-bar.json index b250966d5..ee36ad8b8 100644 --- a/pkg/api/testdata/tcpservice-foo-slash-bar.json +++ b/pkg/api/testdata/tcpservice-foo-slash-bar.json @@ -8,6 +8,9 @@ }, "name": "foo / bar@myprovider", "provider": "myprovider", + "serverStatus": { + "127.0.0.1:2345": "UP" + }, "status": "enabled", "type": "loadbalancer", "usedBy": [ diff --git a/pkg/api/testdata/tcpservices-filtered-search.json b/pkg/api/testdata/tcpservices-filtered-search.json index 130d5eace..653cfb884 100644 --- a/pkg/api/testdata/tcpservices-filtered-search.json +++ b/pkg/api/testdata/tcpservices-filtered-search.json @@ -9,6 +9,9 @@ }, "name": "baz@myprovider", "provider": "myprovider", + "serverStatus": { + "127.0.0.2:2345": "UP" + }, "status": "warning", "type": "loadbalancer", "usedBy": [ diff --git a/pkg/api/testdata/tcpservices-filtered-status.json b/pkg/api/testdata/tcpservices-filtered-status.json index 03ec085a0..61fe14b45 100644 --- a/pkg/api/testdata/tcpservices-filtered-status.json +++ b/pkg/api/testdata/tcpservices-filtered-status.json @@ -9,6 +9,9 @@ }, "name": "bar@myprovider", "provider": "myprovider", + "serverStatus": { + "127.0.0.1:2345": "UP" + }, "status": "enabled", "type": "loadbalancer", "usedBy": [ diff --git a/pkg/api/testdata/tcpservices-page2.json b/pkg/api/testdata/tcpservices-page2.json index 414e0f37d..ff3025fc7 100644 --- a/pkg/api/testdata/tcpservices-page2.json +++ b/pkg/api/testdata/tcpservices-page2.json @@ -9,6 +9,9 @@ }, "name": "baz@myprovider", "provider": "myprovider", + "serverStatus": { + "127.0.0.2:2345": "UP" + }, "status": "enabled", "type": "loadbalancer", "usedBy": [ diff --git a/pkg/api/testdata/tcpservices.json b/pkg/api/testdata/tcpservices.json index c3f9f7ea6..e741f119e 100644 --- a/pkg/api/testdata/tcpservices.json +++ b/pkg/api/testdata/tcpservices.json @@ -9,6 +9,9 @@ }, "name": "bar@myprovider", "provider": "myprovider", + "serverStatus": { + "127.0.0.1:2345": "UP" + }, "status": "enabled", "type": "loadbalancer", "usedBy": [ @@ -26,6 +29,9 @@ }, "name": "baz@myprovider", "provider": "myprovider", + "serverStatus": { + "127.0.0.2:2345": "UP" + }, "status": "warning", "type": "loadbalancer", "usedBy": [ @@ -36,12 +42,15 @@ "loadBalancer": { "servers": [ { - "address": "127.0.0.2:2345" + "address": "127.0.0.3:2345" } ] }, "name": "foz@myprovider", "provider": "myprovider", + "serverStatus": { + "127.0.0.3:2345": "UP" + }, "status": "disabled", "type": "loadbalancer", "usedBy": [ diff --git a/pkg/config/dynamic/tcp_config.go b/pkg/config/dynamic/tcp_config.go index 207c90045..408516a9f 100644 --- a/pkg/config/dynamic/tcp_config.go +++ b/pkg/config/dynamic/tcp_config.go @@ -39,7 +39,8 @@ type TCPService struct { // TCPWeightedRoundRobin is a weighted round robin tcp load-balancer of services. type TCPWeightedRoundRobin struct { - Services []TCPWRRService `json:"services,omitempty" toml:"services,omitempty" yaml:"services,omitempty" export:"true"` + Services []TCPWRRService `json:"services,omitempty" toml:"services,omitempty" yaml:"services,omitempty" export:"true"` + HealthCheck *HealthCheck `json:"healthCheck,omitempty" toml:"healthCheck,omitempty" yaml:"healthCheck,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` } // +k8s:deepcopy-gen=true @@ -86,7 +87,6 @@ type RouterTCPTLSConfig struct { type TCPServersLoadBalancer struct { Servers []TCPServer `json:"servers,omitempty" toml:"servers,omitempty" yaml:"servers,omitempty" label-slice-as-struct:"server" export:"true"` ServersTransport string `json:"serversTransport,omitempty" toml:"serversTransport,omitempty" yaml:"serversTransport,omitempty" export:"true"` - // ProxyProtocol holds the PROXY Protocol configuration. // Deprecated: use ServersTransport to configure ProxyProtocol instead. ProxyProtocol *ProxyProtocol `json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` @@ -96,7 +96,8 @@ type TCPServersLoadBalancer struct { // connection. It is a duration in milliseconds, defaulting to 100. A negative value // means an infinite deadline (i.e. the reading capability is never closed). // Deprecated: use ServersTransport to configure the TerminationDelay instead. - TerminationDelay *int `json:"terminationDelay,omitempty" toml:"terminationDelay,omitempty" yaml:"terminationDelay,omitempty" export:"true"` + TerminationDelay *int `json:"terminationDelay,omitempty" toml:"terminationDelay,omitempty" yaml:"terminationDelay,omitempty" export:"true"` + HealthCheck *TCPServerHealthCheck `json:"healthCheck,omitempty" toml:"healthCheck,omitempty" yaml:"healthCheck,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` } // Mergeable tells if the given service is mergeable. @@ -176,3 +177,21 @@ type TLSClientConfig struct { PeerCertURI string `description:"Defines the URI used to match against SAN URI during the peer certificate verification." json:"peerCertURI,omitempty" toml:"peerCertURI,omitempty" yaml:"peerCertURI,omitempty" export:"true"` Spiffe *Spiffe `description:"Defines the SPIFFE TLS configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } + +// +k8s:deepcopy-gen=true + +// TCPServerHealthCheck holds the HealthCheck configuration. +type TCPServerHealthCheck struct { + Port int `json:"port,omitempty" toml:"port,omitempty,omitzero" yaml:"port,omitempty" export:"true"` + Send string `json:"send,omitempty" toml:"send,omitempty" yaml:"send,omitempty" export:"true"` + Expect string `json:"expect,omitempty" toml:"expect,omitempty" yaml:"expect,omitempty" export:"true"` + Interval ptypes.Duration `json:"interval,omitempty" toml:"interval,omitempty" yaml:"interval,omitempty" export:"true"` + UnhealthyInterval *ptypes.Duration `json:"unhealthyInterval,omitempty" toml:"unhealthyInterval,omitempty" yaml:"unhealthyInterval,omitempty" export:"true"` + Timeout ptypes.Duration `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty" export:"true"` +} + +// SetDefaults sets the default values for a TCPServerHealthCheck. +func (t *TCPServerHealthCheck) SetDefaults() { + t.Interval = DefaultHealthCheckInterval + t.Timeout = DefaultHealthCheckTimeout +} diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index 0d4dbca75..9ffb5d34c 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -2001,6 +2001,27 @@ func (in *TCPServer) DeepCopy() *TCPServer { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TCPServerHealthCheck) DeepCopyInto(out *TCPServerHealthCheck) { + *out = *in + if in.UnhealthyInterval != nil { + in, out := &in.UnhealthyInterval, &out.UnhealthyInterval + *out = new(paersertypes.Duration) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPServerHealthCheck. +func (in *TCPServerHealthCheck) DeepCopy() *TCPServerHealthCheck { + if in == nil { + return nil + } + out := new(TCPServerHealthCheck) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TCPServersLoadBalancer) DeepCopyInto(out *TCPServersLoadBalancer) { *out = *in @@ -2019,6 +2040,11 @@ func (in *TCPServersLoadBalancer) DeepCopyInto(out *TCPServersLoadBalancer) { *out = new(int) **out = **in } + if in.HealthCheck != nil { + in, out := &in.HealthCheck, &out.HealthCheck + *out = new(TCPServerHealthCheck) + (*in).DeepCopyInto(*out) + } return } @@ -2115,6 +2141,11 @@ func (in *TCPWeightedRoundRobin) DeepCopyInto(out *TCPWeightedRoundRobin) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.HealthCheck != nil { + in, out := &in.HealthCheck, &out.HealthCheck + *out = new(HealthCheck) + **out = **in + } return } diff --git a/pkg/config/runtime/runtime_tcp.go b/pkg/config/runtime/runtime_tcp.go index b8bc08981..07473b51e 100644 --- a/pkg/config/runtime/runtime_tcp.go +++ b/pkg/config/runtime/runtime_tcp.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "slices" + "sync" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" @@ -87,6 +88,9 @@ type TCPServiceInfo struct { // It is the caller's responsibility to set the initial status. Status string `json:"status,omitempty"` UsedBy []string `json:"usedBy,omitempty"` // list of routers using that service + + serverStatusMu sync.RWMutex + serverStatus map[string]string // keyed by server address } // AddError adds err to s.Err, if it does not already exist. @@ -110,6 +114,33 @@ func (s *TCPServiceInfo) AddError(err error, critical bool) { } } +// UpdateServerStatus sets the status of the server in the TCPServiceInfo. +func (s *TCPServiceInfo) UpdateServerStatus(server, status string) { + s.serverStatusMu.Lock() + defer s.serverStatusMu.Unlock() + + if s.serverStatus == nil { + s.serverStatus = make(map[string]string) + } + s.serverStatus[server] = status +} + +// GetAllStatus returns all the statuses of all the servers in TCPServiceInfo. +func (s *TCPServiceInfo) GetAllStatus() map[string]string { + s.serverStatusMu.RLock() + defer s.serverStatusMu.RUnlock() + + if len(s.serverStatus) == 0 { + return nil + } + + allStatus := make(map[string]string, len(s.serverStatus)) + for k, v := range s.serverStatus { + allStatus[k] = v + } + return allStatus +} + // TCPMiddlewareInfo holds information about a currently running middleware. type TCPMiddlewareInfo struct { *dynamic.TCPMiddleware // dynamic configuration diff --git a/pkg/healthcheck/healthcheck_tcp.go b/pkg/healthcheck/healthcheck_tcp.go new file mode 100644 index 000000000..4bab5c386 --- /dev/null +++ b/pkg/healthcheck/healthcheck_tcp.go @@ -0,0 +1,212 @@ +package healthcheck + +import ( + "context" + "errors" + "fmt" + "net" + "strconv" + "time" + + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/config/runtime" + "github.com/traefik/traefik/v3/pkg/tcp" +) + +// maxPayloadSize is the maximum payload size that can be sent during health checks. +const maxPayloadSize = 65535 + +type TCPHealthCheckTarget struct { + Address string + TLS bool + Dialer tcp.Dialer +} +type ServiceTCPHealthChecker struct { + balancer StatusSetter + info *runtime.TCPServiceInfo + + config *dynamic.TCPServerHealthCheck + interval time.Duration + unhealthyInterval time.Duration + timeout time.Duration + + healthyTargets chan *TCPHealthCheckTarget + unhealthyTargets chan *TCPHealthCheckTarget + + serviceName string +} + +func NewServiceTCPHealthChecker(ctx context.Context, config *dynamic.TCPServerHealthCheck, service StatusSetter, info *runtime.TCPServiceInfo, targets []TCPHealthCheckTarget, serviceName string) *ServiceTCPHealthChecker { + logger := log.Ctx(ctx) + interval := time.Duration(config.Interval) + if interval <= 0 { + logger.Error().Msg("Health check interval smaller than zero, default value will be used instead.") + interval = time.Duration(dynamic.DefaultHealthCheckInterval) + } + + // If the unhealthyInterval option is not set, we use the interval option value, + // to check the unhealthy targets as often as the healthy ones. + var unhealthyInterval time.Duration + if config.UnhealthyInterval == nil { + unhealthyInterval = interval + } else { + unhealthyInterval = time.Duration(*config.UnhealthyInterval) + if unhealthyInterval <= 0 { + logger.Error().Msg("Health check unhealthy interval smaller than zero, default value will be used instead.") + unhealthyInterval = time.Duration(dynamic.DefaultHealthCheckInterval) + } + } + + timeout := time.Duration(config.Timeout) + if timeout <= 0 { + logger.Error().Msg("Health check timeout smaller than zero, default value will be used instead.") + timeout = time.Duration(dynamic.DefaultHealthCheckTimeout) + } + + if config.Send != "" && len(config.Send) > maxPayloadSize { + logger.Error().Msgf("Health check payload size exceeds maximum allowed size of %d bytes, falling back to connect only check.", maxPayloadSize) + config.Send = "" + } + + if config.Expect != "" && len(config.Expect) > maxPayloadSize { + logger.Error().Msgf("Health check expected response size exceeds maximum allowed size of %d bytes, falling back to close without response.", maxPayloadSize) + config.Expect = "" + } + + healthyTargets := make(chan *TCPHealthCheckTarget, len(targets)) + for _, target := range targets { + healthyTargets <- &target + } + unhealthyTargets := make(chan *TCPHealthCheckTarget, len(targets)) + + return &ServiceTCPHealthChecker{ + balancer: service, + info: info, + config: config, + interval: interval, + unhealthyInterval: unhealthyInterval, + timeout: timeout, + healthyTargets: healthyTargets, + unhealthyTargets: unhealthyTargets, + serviceName: serviceName, + } +} + +func (thc *ServiceTCPHealthChecker) Launch(ctx context.Context) { + go thc.healthcheck(ctx, thc.unhealthyTargets, thc.unhealthyInterval) + + thc.healthcheck(ctx, thc.healthyTargets, thc.interval) +} + +func (thc *ServiceTCPHealthChecker) healthcheck(ctx context.Context, targets chan *TCPHealthCheckTarget, interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + + case <-ticker.C: + // We collect the targets to check once for all, + // to avoid rechecking a target that has been moved during the health check. + var targetsToCheck []*TCPHealthCheckTarget + hasMoreTargets := true + for hasMoreTargets { + select { + case <-ctx.Done(): + return + case target := <-targets: + targetsToCheck = append(targetsToCheck, target) + default: + hasMoreTargets = false + } + } + + // Now we can check the targets. + for _, target := range targetsToCheck { + select { + case <-ctx.Done(): + return + default: + } + + up := true + + if err := thc.executeHealthCheck(ctx, thc.config, target); err != nil { + // The context is canceled when the dynamic configuration is refreshed. + if errors.Is(err, context.Canceled) { + return + } + + log.Ctx(ctx).Warn(). + Str("targetAddress", target.Address). + Err(err). + Msg("Health check failed.") + + up = false + } + + thc.balancer.SetStatus(ctx, target.Address, up) + + var statusStr string + if up { + statusStr = runtime.StatusUp + thc.healthyTargets <- target + } else { + statusStr = runtime.StatusDown + thc.unhealthyTargets <- target + } + + thc.info.UpdateServerStatus(target.Address, statusStr) + + // TODO: add a TCP server up metric (like for HTTP). + } + } + } +} + +func (thc *ServiceTCPHealthChecker) executeHealthCheck(ctx context.Context, config *dynamic.TCPServerHealthCheck, target *TCPHealthCheckTarget) error { + addr := target.Address + if config.Port != 0 { + host, _, err := net.SplitHostPort(target.Address) + if err != nil { + return fmt.Errorf("parsing address %q: %w", target.Address, err) + } + + addr = net.JoinHostPort(host, strconv.Itoa(config.Port)) + } + + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Duration(config.Timeout))) + defer cancel() + + conn, err := target.Dialer.DialContext(ctx, "tcp", addr, nil) + if err != nil { + return fmt.Errorf("connecting to %s: %w", addr, err) + } + defer conn.Close() + + if err := conn.SetDeadline(time.Now().Add(thc.timeout)); err != nil { + return fmt.Errorf("setting timeout to %s: %w", thc.timeout, err) + } + + if config.Send != "" { + if _, err = conn.Write([]byte(config.Send)); err != nil { + return fmt.Errorf("sending to %s: %w", addr, err) + } + } + + if config.Expect != "" { + buf := make([]byte, len(config.Expect)) + if _, err = conn.Read(buf); err != nil { + return fmt.Errorf("reading from %s: %w", addr, err) + } + + if string(buf) != config.Expect { + return errors.New("unexpected heath check response") + } + } + + return nil +} diff --git a/pkg/healthcheck/healthcheck_tcp_test.go b/pkg/healthcheck/healthcheck_tcp_test.go new file mode 100644 index 000000000..c1f1917ec --- /dev/null +++ b/pkg/healthcheck/healthcheck_tcp_test.go @@ -0,0 +1,754 @@ +package healthcheck + +import ( + "context" + "crypto/tls" + "crypto/x509" + "net" + "strings" + "sync" + "testing" + "time" + + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + ptypes "github.com/traefik/paerser/types" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + truntime "github.com/traefik/traefik/v3/pkg/config/runtime" + "github.com/traefik/traefik/v3/pkg/tcp" +) + +var localhostCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDJzCCAg+gAwIBAgIUe3vnWg3cTbflL6kz2TyPUxmV8Y4wDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wIBcNMjUwMzA1MjAwOTM4WhgPMjA1 +NTAyMjYyMDA5MzhaMBYxFDASBgNVBAMMC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4Mm4Sp6xzJvFZJWAv/KVmI1krywiuef8Fhlf +JR2M0caKixjBcNt4U8KwrzIrqL+8nilbps1QuwpQ09+6ztlbUXUL6DqR8ZC+4oCp +gOZ3yyVX2vhMigkATbQyJrX/WVjWSHD5rIUBP2BrsaYLt1qETnFP9wwQ3YEi7V4l +c4+jDrZOtJvrv+tRClt9gQJVgkr7Y30X+dx+rsh+ROaA2+/VTDX0qtoqd/4fjhcJ +OY9VLm0eU66VUMyOTNeUm6ZAXRBp/EonIM1FXOlj82S0pZQbPrvyWWqWoAjtPvLU +qRzqp/BQJqx3EHz1dP6s+xUjP999B+7jhiHoFhZ/bfVVlx8XkwIDAQABo2swaTAd +BgNVHQ4EFgQUhJiJ37LW6RODCpBPAApG1zQxFtAwHwYDVR0jBBgwFoAUhJiJ37LW +6RODCpBPAApG1zQxFtAwDwYDVR0TAQH/BAUwAwEB/zAWBgNVHREEDzANggtleGFt +cGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAfnDPHllA1TFlQ6zY46tqM20d68bR +kXeGMKLoaATFPbDea5H8/GM5CU6CPD7RUuEB9CvxvaM0aOInxkgstozG7BOr8hcs +WS9fMgM0oO5yGiSOv+Qa0Rc0BFb6A1fUJRta5MI5DTdTJLoyoRX/5aocSI34T67x +ULbkJvVXw6hnx/KZ65apNobfmVQSy7DR8Fo82eB4hSoaLpXyUUTLmctGgrRCoKof +GVUJfKsDJ4Ts8WIR1np74flSoxksWSHEOYk79AZOPANYgJwPMMiiZKsKm17GBoGu +DxI0om4eX8GaSSZAtG6TOt3O3v1oCjKNsAC+u585HN0x0MFA33TUzC15NA== +-----END CERTIFICATE-----`) + +var localhostKey = []byte(`-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDgybhKnrHMm8Vk +lYC/8pWYjWSvLCK55/wWGV8lHYzRxoqLGMFw23hTwrCvMiuov7yeKVumzVC7ClDT +37rO2VtRdQvoOpHxkL7igKmA5nfLJVfa+EyKCQBNtDImtf9ZWNZIcPmshQE/YGux +pgu3WoROcU/3DBDdgSLtXiVzj6MOtk60m+u/61EKW32BAlWCSvtjfRf53H6uyH5E +5oDb79VMNfSq2ip3/h+OFwk5j1UubR5TrpVQzI5M15SbpkBdEGn8SicgzUVc6WPz +ZLSllBs+u/JZapagCO0+8tSpHOqn8FAmrHcQfPV0/qz7FSM/330H7uOGIegWFn9t +9VWXHxeTAgMBAAECggEALinfGhv7Iaz/3cdCOKlGBZ1MBxmGTC2TPKqbOpEWAWLH +wwcjetznmjQKewBPrQkrYEPYGapioPbeYJS61Y4XzeO+vUOCA10ZhoSrytgJ1ANo +RoTlmxd8I3kVL5QCy8ONxjTFYaOy/OP9We9iypXhRAbLSE4HDKZfmOXTxSbDctql +Kq7uV3LX1KCfr9C6M8d79a0Rdr4p8IXp8MOg3tXq6n75vZbepRFyAujhg7o/kkTp +lgv87h89lrK97K+AjqtvCIT3X3VXfA+LYp3AoQFdOluKgyJT221MyHkTeI/7gggt +Z57lVGD71UJH/LGUJWrraJqXd9uDxZWprD/s66BIAQKBgQD8CtHUJ/VuS7gP0ebN +688zrmRtENj6Gqi+URm/Pwgr9b7wKKlf9jjhg5F/ue+BgB7/nK6N7yJ4Xx3JJ5ox +LqsRGLFa4fDBxogF/FN27obD8naOxe2wS1uTjM6LSrvdJ+HjeNEwHYhjuDjTAHj5 +VVEMagZWgkE4jBiFUYefiYLsAQKBgQDkUVdW8cXaYri5xxDW86JNUzI1tUPyd6I+ +AkOHV/V0y2zpwTHVLcETRpdVGpc5TH3J5vWf+5OvSz6RDTGjv7blDb8vB/kVkFmn +uXTi0dB9P+SYTsm+X3V7hOAFsyVYZ1D9IFsKUyMgxMdF+qgERjdPKx5IdLV/Jf3q +P9pQ922TkwKBgCKllhyU9Z8Y14+NKi4qeUxAb9uyUjFnUsT+vwxULNpmKL44yLfB +UCZoAKtPMwZZR2mZ70Dhm5pycNTDFeYm5Ssvesnkf0UT9oTkH9EcjvgGr5eGy9rN +MSSCWa46MsL/BYVQiWkU1jfnDiCrUvXrbX3IYWCo/TA5yfEhuQQMUiwBAoGADyzo +5TqEsBNHu/FjSSZAb2tMNw2pSoBxJDX6TxClm/G5d4AD0+uKncFfZaSy0HgpFDZp +tQx/sHML4ZBC8GNZwLe9MV8SS0Cg9Oj6v+i6Ntj8VLNH7YNix6b5TOevX8TeOTTh +WDpWZ2Ms65XRfRc9reFrzd0UAzN/QQaleCQ6AEkCgYBe4Ucows7JGbv7fNkz3nb1 +kyH+hk9ecnq/evDKX7UUxKO1wwTi74IYKgcRB2uPLpHKL35gPz+LAfCphCW5rwpR +lvDhS+Pi/1KCBJxLHMv+V/WrckDRgHFnAhDaBZ+2vI/s09rKDnpjcTzV7x22kL0b +XIJCEEE8JZ4AXIZ+IcB6LA== +-----END PRIVATE KEY-----`) + +func TestNewServiceTCPHealthChecker(t *testing.T) { + testCases := []struct { + desc string + config *dynamic.TCPServerHealthCheck + expectedInterval time.Duration + expectedTimeout time.Duration + }{ + { + desc: "default values", + config: &dynamic.TCPServerHealthCheck{}, + expectedInterval: time.Duration(dynamic.DefaultHealthCheckInterval), + expectedTimeout: time.Duration(dynamic.DefaultHealthCheckTimeout), + }, + { + desc: "out of range values", + config: &dynamic.TCPServerHealthCheck{ + Interval: ptypes.Duration(-time.Second), + Timeout: ptypes.Duration(-time.Second), + }, + expectedInterval: time.Duration(dynamic.DefaultHealthCheckInterval), + expectedTimeout: time.Duration(dynamic.DefaultHealthCheckTimeout), + }, + { + desc: "custom durations", + config: &dynamic.TCPServerHealthCheck{ + Interval: ptypes.Duration(time.Second * 10), + Timeout: ptypes.Duration(time.Second * 5), + }, + expectedInterval: time.Second * 10, + expectedTimeout: time.Second * 5, + }, + { + desc: "interval shorter than timeout", + config: &dynamic.TCPServerHealthCheck{ + Interval: ptypes.Duration(time.Second), + Timeout: ptypes.Duration(time.Second * 5), + }, + expectedInterval: time.Second, + expectedTimeout: time.Second * 5, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + healthChecker := NewServiceTCPHealthChecker(t.Context(), test.config, nil, nil, nil, "") + assert.Equal(t, test.expectedInterval, healthChecker.interval) + assert.Equal(t, test.expectedTimeout, healthChecker.timeout) + }) + } +} + +func TestServiceTCPHealthChecker_executeHealthCheck_connection(t *testing.T) { + testCases := []struct { + desc string + address string + config *dynamic.TCPServerHealthCheck + expectedAddress string + }{ + { + desc: "no port override - uses original address", + address: "127.0.0.1:8080", + config: &dynamic.TCPServerHealthCheck{Port: 0}, + expectedAddress: "127.0.0.1:8080", + }, + { + desc: "port override - uses overridden port", + address: "127.0.0.1:8080", + config: &dynamic.TCPServerHealthCheck{Port: 9090}, + expectedAddress: "127.0.0.1:9090", + }, + { + desc: "IPv6 address with port override", + address: "[::1]:8080", + config: &dynamic.TCPServerHealthCheck{Port: 9090}, + expectedAddress: "[::1]:9090", + }, + { + desc: "successful connection without port override", + address: "localhost:3306", + config: &dynamic.TCPServerHealthCheck{Port: 0}, + expectedAddress: "localhost:3306", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + // Create a mock dialer that records the address it was asked to dial. + var gotAddress string + mockDialer := &dialerMock{ + onDial: func(network, addr string) (net.Conn, error) { + gotAddress = addr + return &connMock{}, nil + }, + } + + targets := []TCPHealthCheckTarget{{ + Address: test.address, + Dialer: mockDialer, + }} + healthChecker := NewServiceTCPHealthChecker(t.Context(), test.config, nil, nil, targets, "test") + + // Execute a health check to see what address it tries to connect to. + err := healthChecker.executeHealthCheck(t.Context(), test.config, &targets[0]) + require.NoError(t, err) + + // Verify that the health check attempted to connect to the expected address. + assert.Equal(t, test.expectedAddress, gotAddress) + }) + } +} + +func TestServiceTCPHealthChecker_executeHealthCheck_payloadHandling(t *testing.T) { + testCases := []struct { + desc string + config *dynamic.TCPServerHealthCheck + mockResponse string + expectedSentData string + expectedSuccess bool + }{ + { + desc: "successful send and expect", + config: &dynamic.TCPServerHealthCheck{ + Send: "PING", + Expect: "PONG", + }, + mockResponse: "PONG", + expectedSentData: "PING", + expectedSuccess: true, + }, + { + desc: "send without expect", + config: &dynamic.TCPServerHealthCheck{ + Send: "STATUS", + Expect: "", + }, + expectedSentData: "STATUS", + expectedSuccess: true, + }, + { + desc: "send without expect, ignores response", + config: &dynamic.TCPServerHealthCheck{ + Send: "STATUS", + }, + mockResponse: strings.Repeat("A", maxPayloadSize+1), + expectedSentData: "STATUS", + expectedSuccess: true, + }, + { + desc: "expect without send", + config: &dynamic.TCPServerHealthCheck{ + Expect: "READY", + }, + mockResponse: "READY", + expectedSuccess: true, + }, + { + desc: "wrong response received", + config: &dynamic.TCPServerHealthCheck{ + Send: "PING", + Expect: "PONG", + }, + mockResponse: "WRONG", + expectedSentData: "PING", + expectedSuccess: false, + }, + { + desc: "send payload too large - gets truncated", + config: &dynamic.TCPServerHealthCheck{ + Send: strings.Repeat("A", maxPayloadSize+1), // Will be truncated to empty + Expect: "OK", + }, + mockResponse: "OK", + expectedSuccess: true, + }, + { + desc: "expect payload too large - gets truncated", + config: &dynamic.TCPServerHealthCheck{ + Send: "PING", + Expect: strings.Repeat("B", maxPayloadSize+1), // Will be truncated to empty + }, + expectedSentData: "PING", + expectedSuccess: true, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + var sentData []byte + mockConn := &connMock{ + writeFunc: func(data []byte) (int, error) { + sentData = append([]byte{}, data...) + return len(data), nil + }, + readFunc: func(buf []byte) (int, error) { + return copy(buf, test.mockResponse), nil + }, + } + + mockDialer := &dialerMock{ + onDial: func(network, addr string) (net.Conn, error) { + return mockConn, nil + }, + } + + targets := []TCPHealthCheckTarget{{ + Address: "127.0.0.1:8080", + TLS: false, + Dialer: mockDialer, + }} + + healthChecker := NewServiceTCPHealthChecker(t.Context(), test.config, nil, nil, targets, "test") + + err := healthChecker.executeHealthCheck(t.Context(), test.config, &targets[0]) + + if test.expectedSuccess { + assert.NoError(t, err, "Health check should succeed") + } else { + assert.Error(t, err, "Health check should fail") + } + + assert.Equal(t, test.expectedSentData, string(sentData), "Should send the expected data") + }) + } +} + +func TestServiceTCPHealthChecker_Launch(t *testing.T) { + testCases := []struct { + desc string + server *sequencedTCPServer + config *dynamic.TCPServerHealthCheck + expNumRemovedServers int + expNumUpsertedServers int + targetStatus string + }{ + { + desc: "connection-only healthy server staying healthy", + server: newTCPServer(t, + false, + tcpMockSequence{accept: true}, + tcpMockSequence{accept: true}, + tcpMockSequence{accept: true}, + ), + config: &dynamic.TCPServerHealthCheck{ + Interval: ptypes.Duration(time.Millisecond * 50), + Timeout: ptypes.Duration(time.Millisecond * 40), + }, + expNumRemovedServers: 0, + expNumUpsertedServers: 3, // 3 health check sequences + targetStatus: truntime.StatusUp, + }, + { + desc: "connection-only healthy server becoming unhealthy", + server: newTCPServer(t, + false, + tcpMockSequence{accept: true}, + tcpMockSequence{accept: false}, + ), + config: &dynamic.TCPServerHealthCheck{ + Interval: ptypes.Duration(time.Millisecond * 50), + Timeout: ptypes.Duration(time.Millisecond * 40), + }, + expNumRemovedServers: 1, + expNumUpsertedServers: 1, + targetStatus: truntime.StatusDown, + }, + { + desc: "connection-only server toggling unhealthy to healthy", + server: newTCPServer(t, + false, + tcpMockSequence{accept: false}, + tcpMockSequence{accept: true}, + ), + config: &dynamic.TCPServerHealthCheck{ + Interval: ptypes.Duration(time.Millisecond * 50), + Timeout: ptypes.Duration(time.Millisecond * 40), + }, + expNumRemovedServers: 1, // 1 failure call + expNumUpsertedServers: 1, // 1 success call + targetStatus: truntime.StatusUp, + }, + { + desc: "connection-only server toggling healthy to unhealthy to healthy", + server: newTCPServer(t, + false, + tcpMockSequence{accept: true}, + tcpMockSequence{accept: false}, + tcpMockSequence{accept: true}, + ), + config: &dynamic.TCPServerHealthCheck{ + Interval: ptypes.Duration(time.Millisecond * 50), + Timeout: ptypes.Duration(time.Millisecond * 40), + }, + expNumRemovedServers: 1, + expNumUpsertedServers: 2, + targetStatus: truntime.StatusUp, + }, + { + desc: "send/expect healthy server staying healthy", + server: newTCPServer(t, + false, + tcpMockSequence{accept: true, payloadIn: "PING", payloadOut: "PONG"}, + tcpMockSequence{accept: true, payloadIn: "PING", payloadOut: "PONG"}, + ), + config: &dynamic.TCPServerHealthCheck{ + Send: "PING", + Expect: "PONG", + Interval: ptypes.Duration(time.Millisecond * 50), + Timeout: ptypes.Duration(time.Millisecond * 40), + }, + expNumRemovedServers: 0, + expNumUpsertedServers: 2, // 2 successful health checks + targetStatus: truntime.StatusUp, + }, + { + desc: "send/expect server with wrong response", + server: newTCPServer(t, + false, + tcpMockSequence{accept: true, payloadIn: "PING", payloadOut: "PONG"}, + tcpMockSequence{accept: true, payloadIn: "PING", payloadOut: "WRONG"}, + ), + config: &dynamic.TCPServerHealthCheck{ + Send: "PING", + Expect: "PONG", + Interval: ptypes.Duration(time.Millisecond * 50), + Timeout: ptypes.Duration(time.Millisecond * 40), + }, + expNumRemovedServers: 1, + expNumUpsertedServers: 1, + targetStatus: truntime.StatusDown, + }, + { + desc: "TLS healthy server staying healthy", + server: newTCPServer(t, + true, + tcpMockSequence{accept: true, payloadIn: "HELLO", payloadOut: "WORLD"}, + ), + config: &dynamic.TCPServerHealthCheck{ + Send: "HELLO", + Expect: "WORLD", + Interval: ptypes.Duration(time.Millisecond * 500), + Timeout: ptypes.Duration(time.Millisecond * 2000), // Even longer timeout for TLS handshake + }, + expNumRemovedServers: 0, + expNumUpsertedServers: 1, // 1 TLS health check sequence + targetStatus: truntime.StatusUp, + }, + { + desc: "send-only healthcheck (no expect)", + server: newTCPServer(t, + false, + tcpMockSequence{accept: true, payloadIn: "STATUS"}, + tcpMockSequence{accept: true, payloadIn: "STATUS"}, + ), + config: &dynamic.TCPServerHealthCheck{ + Send: "STATUS", + Interval: ptypes.Duration(time.Millisecond * 50), + Timeout: ptypes.Duration(time.Millisecond * 40), + }, + expNumRemovedServers: 0, + expNumUpsertedServers: 2, + targetStatus: truntime.StatusUp, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(log.Logger.WithContext(t.Context())) + defer cancel() + + test.server.Start(t) + + dialerManager := tcp.NewDialerManager(nil) + dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": { + TLS: &dynamic.TLSClientConfig{ + InsecureSkipVerify: true, + ServerName: "example.com", + }, + }}) + + dialer, err := dialerManager.Build(&dynamic.TCPServersLoadBalancer{}, test.server.TLS) + require.NoError(t, err) + + targets := []TCPHealthCheckTarget{ + { + Address: test.server.Addr.String(), + TLS: test.server.TLS, + Dialer: dialer, + }, + } + + lb := &testLoadBalancer{} + serviceInfo := &truntime.TCPServiceInfo{} + + service := NewServiceTCPHealthChecker(ctx, test.config, lb, serviceInfo, targets, "serviceName") + + go service.Launch(ctx) + + // How much time to wait for the health check to actually complete. + deadline := time.Now().Add(200 * time.Millisecond) + // TLS handshake can take much longer. + if test.server.TLS { + deadline = time.Now().Add(1000 * time.Millisecond) + } + + // Wait for all health checks to complete deterministically + for range test.server.StatusSequence { + test.server.Next() + + initialUpserted := lb.numUpsertedServers + initialRemoved := lb.numRemovedServers + + for time.Now().Before(deadline) { + time.Sleep(5 * time.Millisecond) + if lb.numUpsertedServers > initialUpserted || lb.numRemovedServers > initialRemoved { + break + } + } + } + + assert.Equal(t, test.expNumRemovedServers, lb.numRemovedServers, "removed servers") + assert.Equal(t, test.expNumUpsertedServers, lb.numUpsertedServers, "upserted servers") + assert.Equal(t, map[string]string{test.server.Addr.String(): test.targetStatus}, serviceInfo.GetAllStatus()) + }) + } +} + +func TestServiceTCPHealthChecker_differentIntervals(t *testing.T) { + // Test that unhealthy servers are checked more frequently than healthy servers + // when UnhealthyInterval is set to a lower value than Interval + ctx, cancel := context.WithCancel(t.Context()) + t.Cleanup(cancel) + + // Create a healthy TCP server that always accepts connections + healthyServer := newTCPServer(t, false, + tcpMockSequence{accept: true}, tcpMockSequence{accept: true}, tcpMockSequence{accept: true}, + tcpMockSequence{accept: true}, tcpMockSequence{accept: true}, + ) + healthyServer.Start(t) + + // Create an unhealthy TCP server that always rejects connections + unhealthyServer := newTCPServer(t, false, + tcpMockSequence{accept: false}, tcpMockSequence{accept: false}, tcpMockSequence{accept: false}, + tcpMockSequence{accept: false}, tcpMockSequence{accept: false}, tcpMockSequence{accept: false}, + tcpMockSequence{accept: false}, tcpMockSequence{accept: false}, tcpMockSequence{accept: false}, + tcpMockSequence{accept: false}, + ) + unhealthyServer.Start(t) + + lb := &testLoadBalancer{RWMutex: &sync.RWMutex{}} + + // Set normal interval to 500ms but unhealthy interval to 50ms + // This means unhealthy servers should be checked 10x more frequently + config := &dynamic.TCPServerHealthCheck{ + Interval: ptypes.Duration(500 * time.Millisecond), + UnhealthyInterval: pointer(ptypes.Duration(50 * time.Millisecond)), + Timeout: ptypes.Duration(100 * time.Millisecond), + } + + // Set up dialer manager + dialerManager := tcp.NewDialerManager(nil) + dialerManager.Update(map[string]*dynamic.TCPServersTransport{ + "default@internal": { + DialTimeout: ptypes.Duration(100 * time.Millisecond), + DialKeepAlive: ptypes.Duration(100 * time.Millisecond), + }, + }) + + // Get dialer for targets + dialer, err := dialerManager.Build(&dynamic.TCPServersLoadBalancer{}, false) + require.NoError(t, err) + + targets := []TCPHealthCheckTarget{ + {Address: healthyServer.Addr.String(), TLS: false, Dialer: dialer}, + {Address: unhealthyServer.Addr.String(), TLS: false, Dialer: dialer}, + } + + serviceInfo := &truntime.TCPServiceInfo{} + hc := NewServiceTCPHealthChecker(ctx, config, lb, serviceInfo, targets, "test-service") + + wg := sync.WaitGroup{} + wg.Add(1) + + go func() { + hc.Launch(ctx) + wg.Done() + }() + + // Let it run for 2 seconds to see the different check frequencies + select { + case <-time.After(2 * time.Second): + cancel() + case <-ctx.Done(): + } + + wg.Wait() + + lb.Lock() + defer lb.Unlock() + + // The unhealthy server should be checked more frequently (50ms interval) + // compared to the healthy server (500ms interval), so we should see + // significantly more "removed" events than "upserted" events + assert.Greater(t, lb.numRemovedServers, lb.numUpsertedServers, "unhealthy servers checked more frequently") +} + +type tcpMockSequence struct { + accept bool + payloadIn string + payloadOut string +} + +type sequencedTCPServer struct { + Addr *net.TCPAddr + StatusSequence []tcpMockSequence + TLS bool + release chan struct{} +} + +func newTCPServer(t *testing.T, tlsEnabled bool, statusSequence ...tcpMockSequence) *sequencedTCPServer { + t.Helper() + + addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + require.NoError(t, err) + listener, err := net.ListenTCP("tcp", addr) + require.NoError(t, err) + + tcpAddr, ok := listener.Addr().(*net.TCPAddr) + require.True(t, ok) + + listener.Close() + + return &sequencedTCPServer{ + Addr: tcpAddr, + TLS: tlsEnabled, + StatusSequence: statusSequence, + release: make(chan struct{}), + } +} + +func (s *sequencedTCPServer) Next() { + s.release <- struct{}{} +} + +func (s *sequencedTCPServer) Start(t *testing.T) { + t.Helper() + + go func() { + var listener net.Listener + + for _, seq := range s.StatusSequence { + <-s.release + if listener != nil { + listener.Close() + } + + if !seq.accept { + continue + } + + lis, err := net.ListenTCP("tcp", s.Addr) + require.NoError(t, err) + + listener = lis + + if s.TLS { + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + require.NoError(t, err) + + x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) + require.NoError(t, err) + + certpool := x509.NewCertPool() + certpool.AddCert(x509Cert) + + listener = tls.NewListener( + listener, + &tls.Config{ + RootCAs: certpool, + Certificates: []tls.Certificate{cert}, + InsecureSkipVerify: true, + ServerName: "example.com", + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + ClientAuth: tls.VerifyClientCertIfGiven, + ClientCAs: certpool, + }, + ) + } + + conn, err := listener.Accept() + require.NoError(t, err) + t.Cleanup(func() { + _ = conn.Close() + }) + + // For TLS connections, perform handshake first + if s.TLS { + if tlsConn, ok := conn.(*tls.Conn); ok { + if err := tlsConn.Handshake(); err != nil { + continue // Skip this sequence on handshake failure + } + } + } + + if seq.payloadIn == "" { + continue + } + + buf := make([]byte, len(seq.payloadIn)) + n, err := conn.Read(buf) + require.NoError(t, err) + + recv := strings.TrimSpace(string(buf[:n])) + + switch recv { + case seq.payloadIn: + if _, err := conn.Write([]byte(seq.payloadOut)); err != nil { + t.Errorf("failed to write payload: %v", err) + } + default: + if _, err := conn.Write([]byte("FAULT\n")); err != nil { + t.Errorf("failed to write payload: %v", err) + } + } + } + + defer close(s.release) + }() +} + +type dialerMock struct { + onDial func(network, addr string) (net.Conn, error) +} + +func (dm *dialerMock) Dial(network, addr string, _ tcp.ClientConn) (net.Conn, error) { + return dm.onDial(network, addr) +} + +func (dm *dialerMock) DialContext(_ context.Context, network, addr string, _ tcp.ClientConn) (net.Conn, error) { + return dm.onDial(network, addr) +} + +func (dm *dialerMock) TerminationDelay() time.Duration { + return 0 +} + +type connMock struct { + writeFunc func([]byte) (int, error) + readFunc func([]byte) (int, error) +} + +func (cm *connMock) Read(b []byte) (n int, err error) { + if cm.readFunc != nil { + return cm.readFunc(b) + } + return 0, nil +} + +func (cm *connMock) Write(b []byte) (n int, err error) { + if cm.writeFunc != nil { + return cm.writeFunc(b) + } + return len(b), nil +} + +func (cm *connMock) Close() error { return nil } + +func (cm *connMock) LocalAddr() net.Addr { return &net.TCPAddr{} } + +func (cm *connMock) RemoteAddr() net.Addr { return &net.TCPAddr{} } + +func (cm *connMock) SetDeadline(_ time.Time) error { return nil } + +func (cm *connMock) SetReadDeadline(_ time.Time) error { return nil } + +func (cm *connMock) SetWriteDeadline(_ time.Time) error { return nil } diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go index 056a62a49..cf2bdf2a4 100644 --- a/pkg/healthcheck/healthcheck_test.go +++ b/pkg/healthcheck/healthcheck_test.go @@ -66,6 +66,8 @@ func TestNewServiceHealthChecker_durations(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { + t.Parallel() + healthChecker := NewServiceHealthChecker(t.Context(), nil, test.config, nil, nil, http.DefaultTransport, nil, "") assert.Equal(t, test.expInterval, healthChecker.interval) assert.Equal(t, test.expTimeout, healthChecker.timeout) diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go index f4e0a1102..c14e0536d 100644 --- a/pkg/server/routerfactory.go +++ b/pkg/server/routerfactory.go @@ -124,6 +124,8 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string } } + svcTCPManager.LaunchHealthCheck(ctx) + // UDP svcUDPManager := udpsvc.NewManager(rtConf) rtUDPManager := udprouter.NewManager(rtConf, svcUDPManager) diff --git a/pkg/server/service/loadbalancer/hrw/hrw.go b/pkg/server/service/loadbalancer/hrw/hrw.go index 475873ece..f213b326a 100644 --- a/pkg/server/service/loadbalancer/hrw/hrw.go +++ b/pkg/server/service/loadbalancer/hrw/hrw.go @@ -39,6 +39,7 @@ type Balancer struct { status map[string]struct{} // updaters is the list of hooks that are run (to update the Balancer // parent(s)), whenever the Balancer status changes. + // No mutex is needed, as it is modified only during the configuration build. updaters []func(bool) // fenced is the list of terminating yet still serving child services. fenced map[string]struct{} diff --git a/pkg/server/service/loadbalancer/p2c/p2c.go b/pkg/server/service/loadbalancer/p2c/p2c.go index 3bf06a512..d41de0a2e 100644 --- a/pkg/server/service/loadbalancer/p2c/p2c.go +++ b/pkg/server/service/loadbalancer/p2c/p2c.go @@ -56,6 +56,7 @@ type Balancer struct { // updaters is the list of hooks that are run (to update the Balancer // parent(s)), whenever the Balancer status changes. + // No mutex is needed, as it is modified only during the configuration build. updaters []func(bool) sticky *loadbalancer.Sticky diff --git a/pkg/server/service/loadbalancer/wrr/wrr.go b/pkg/server/service/loadbalancer/wrr/wrr.go index 2c9bff0ee..b2665b56d 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr.go +++ b/pkg/server/service/loadbalancer/wrr/wrr.go @@ -40,6 +40,7 @@ type Balancer struct { // updaters is the list of hooks that are run (to update the Balancer // parent(s)), whenever the Balancer status changes. + // No mutex is needed, as it is modified only during the configuration build. updaters []func(bool) sticky *loadbalancer.Sticky diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 427cf9bc9..c49bd8809 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -266,19 +266,18 @@ func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, continue } - childName := service.Name updater, ok := serviceHandler.(healthcheck.StatusUpdater) if !ok { - return nil, fmt.Errorf("child service %v of %v not a healthcheck.StatusUpdater (%T)", childName, serviceName, serviceHandler) + return nil, fmt.Errorf("child service %v of %v not a healthcheck.StatusUpdater (%T)", service.Name, serviceName, serviceHandler) } if err := updater.RegisterStatusUpdater(func(up bool) { - balancer.SetStatus(ctx, childName, up) + balancer.SetStatus(ctx, service.Name, up) }); err != nil { - return nil, fmt.Errorf("cannot register %v as updater for %v: %w", childName, serviceName, err) + return nil, fmt.Errorf("cannot register %v as updater for %v: %w", service.Name, serviceName, err) } - log.Ctx(ctx).Debug().Str("parent", serviceName).Str("child", childName). + log.Ctx(ctx).Debug().Str("parent", serviceName).Str("child", service.Name). Msg("Child service will update parent on status change") } @@ -342,19 +341,18 @@ func (m *Manager) getHRWServiceHandler(ctx context.Context, serviceName string, continue } - childName := service.Name updater, ok := serviceHandler.(healthcheck.StatusUpdater) if !ok { - return nil, fmt.Errorf("child service %v of %v not a healthcheck.StatusUpdater (%T)", childName, serviceName, serviceHandler) + return nil, fmt.Errorf("child service %v of %v not a healthcheck.StatusUpdater (%T)", service.Name, serviceName, serviceHandler) } if err := updater.RegisterStatusUpdater(func(up bool) { - balancer.SetStatus(ctx, childName, up) + balancer.SetStatus(ctx, service.Name, up) }); err != nil { - return nil, fmt.Errorf("cannot register %v as updater for %v: %w", childName, serviceName, err) + return nil, fmt.Errorf("cannot register %v as updater for %v: %w", service.Name, serviceName, err) } - log.Ctx(ctx).Debug().Str("parent", serviceName).Str("child", childName). + log.Ctx(ctx).Debug().Str("parent", serviceName).Str("child", service.Name). Msg("Child service will update parent on status change") } @@ -466,7 +464,7 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName lb.AddServer(server.URL, proxy, server) - // servers are considered UP by default. + // Servers are considered UP by default. info.UpdateServerStatus(target.String(), runtime.StatusUp) healthCheckTargets[server.URL] = target diff --git a/pkg/server/service/tcp/service.go b/pkg/server/service/tcp/service.go index 9ff454fad..7928eb3b5 100644 --- a/pkg/server/service/tcp/service.go +++ b/pkg/server/service/tcp/service.go @@ -4,12 +4,15 @@ import ( "context" "errors" "fmt" + "maps" "math/rand" "net" + "slices" "time" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" + "github.com/traefik/traefik/v3/pkg/healthcheck" "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/tcp" @@ -17,17 +20,19 @@ import ( // Manager is the TCPHandlers factory. type Manager struct { - dialerManager *tcp.DialerManager - configs map[string]*runtime.TCPServiceInfo - rand *rand.Rand // For the initial shuffling of load-balancers. + dialerManager *tcp.DialerManager + configs map[string]*runtime.TCPServiceInfo + rand *rand.Rand // For the initial shuffling of load-balancers. + healthCheckers map[string]*healthcheck.ServiceTCPHealthChecker } // NewManager creates a new manager. func NewManager(conf *runtime.Configuration, dialerManager *tcp.DialerManager) *Manager { return &Manager{ - dialerManager: dialerManager, - configs: conf.TCPServices, - rand: rand.New(rand.NewSource(time.Now().UnixNano())), + dialerManager: dialerManager, + healthCheckers: make(map[string]*healthcheck.ServiceTCPHealthChecker), + configs: conf.TCPServices, + rand: rand.New(rand.NewSource(time.Now().UnixNano())), } } @@ -51,7 +56,7 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han switch { case conf.LoadBalancer != nil: - loadBalancer := tcp.NewWRRLoadBalancer() + loadBalancer := tcp.NewWRRLoadBalancer(conf.LoadBalancer.HealthCheck != nil) if conf.LoadBalancer.TerminationDelay != nil { log.Ctx(ctx).Warn().Msgf("Service %q load balancer uses `TerminationDelay`, but this option is deprecated, please use ServersTransport configuration instead.", serviceName) @@ -65,6 +70,8 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han conf.LoadBalancer.ServersTransport = provider.GetQualifiedName(ctx, conf.LoadBalancer.ServersTransport) } + uniqHealthCheckTargets := make(map[string]healthcheck.TCPHealthCheckTarget, len(conf.LoadBalancer.Servers)) + for index, server := range shuffle(conf.LoadBalancer.Servers, m.rand) { srvLogger := logger.With(). Int(logs.ServerIndex, index). @@ -86,14 +93,34 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han continue } - loadBalancer.AddServer(handler) + loadBalancer.Add(server.Address, handler, nil) + + // Servers are considered UP by default. + conf.UpdateServerStatus(server.Address, runtime.StatusUp) + + uniqHealthCheckTargets[server.Address] = healthcheck.TCPHealthCheckTarget{ + Address: server.Address, + TLS: server.TLS, + Dialer: dialer, + } + logger.Debug().Msg("Creating TCP server") } + if conf.LoadBalancer.HealthCheck != nil { + m.healthCheckers[serviceName] = healthcheck.NewServiceTCPHealthChecker( + ctx, + conf.LoadBalancer.HealthCheck, + loadBalancer, + conf, + slices.Collect(maps.Values(uniqHealthCheckTargets)), + serviceQualifiedName) + } + return loadBalancer, nil case conf.Weighted != nil: - loadBalancer := tcp.NewWRRLoadBalancer() + loadBalancer := tcp.NewWRRLoadBalancer(conf.Weighted.HealthCheck != nil) for _, service := range shuffle(conf.Weighted.Services, m.rand) { handler, err := m.BuildTCP(ctx, service.Name) @@ -102,7 +129,25 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han return nil, err } - loadBalancer.AddWeightServer(handler, service.Weight) + loadBalancer.Add(service.Name, handler, service.Weight) + + if conf.Weighted.HealthCheck == nil { + continue + } + + updater, ok := handler.(healthcheck.StatusUpdater) + if !ok { + return nil, fmt.Errorf("child service %v of %v not a healthcheck.StatusUpdater (%T)", service.Name, serviceName, handler) + } + + if err := updater.RegisterStatusUpdater(func(up bool) { + loadBalancer.SetStatus(ctx, service.Name, up) + }); err != nil { + return nil, fmt.Errorf("cannot register %v as updater for %v: %w", service.Name, serviceName, err) + } + + log.Ctx(ctx).Debug().Str("parent", serviceName).Str("child", service.Name). + Msg("Child service will update parent on status change") } return loadBalancer, nil @@ -114,6 +159,14 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han } } +// LaunchHealthCheck launches the health checks. +func (m *Manager) LaunchHealthCheck(ctx context.Context) { + for serviceName, hc := range m.healthCheckers { + logger := log.Ctx(ctx).With().Str(logs.ServiceName, serviceName).Logger() + go hc.Launch(logger.WithContext(ctx)) + } +} + func shuffle[T any](values []T, r *rand.Rand) []T { shuffled := make([]T, len(values)) copy(shuffled, values) diff --git a/pkg/server/service/tcp/service_test.go b/pkg/server/service/tcp/service_test.go index 8dd0b7393..bcdfe4bd8 100644 --- a/pkg/server/service/tcp/service_test.go +++ b/pkg/server/service/tcp/service_test.go @@ -233,6 +233,49 @@ func TestManager_BuildTCP(t *testing.T) { providerName: "provider-1", expectedError: "no transport configuration found for \"myServersTransport@provider-1\"", }, + { + desc: "WRR with healthcheck enabled", + stConfigs: map[string]*dynamic.TCPServersTransport{"default@internal": {}}, + serviceName: "serviceName", + configs: map[string]*runtime.TCPServiceInfo{ + "serviceName@provider-1": { + TCPService: &dynamic.TCPService{ + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + {Name: "foobar@provider-1", Weight: new(int)}, + {Name: "foobar2@provider-1", Weight: new(int)}, + }, + HealthCheck: &dynamic.HealthCheck{}, + }, + }, + }, + "foobar@provider-1": { + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "192.168.0.12:80", + }, + }, + HealthCheck: &dynamic.TCPServerHealthCheck{}, + }, + }, + }, + "foobar2@provider-1": { + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "192.168.0.13:80", + }, + }, + HealthCheck: &dynamic.TCPServerHealthCheck{}, + }, + }, + }, + }, + providerName: "provider-1", + }, } for _, test := range testCases { diff --git a/pkg/tcp/dialer.go b/pkg/tcp/dialer.go index bc4855b5b..da9a24569 100644 --- a/pkg/tcp/dialer.go +++ b/pkg/tcp/dialer.go @@ -1,6 +1,7 @@ package tcp import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -33,6 +34,7 @@ type ClientConn interface { // Dialer is an interface to dial a network connection, with support for PROXY protocol and termination delay. type Dialer interface { Dial(network, addr string, clientConn ClientConn) (c net.Conn, err error) + DialContext(ctx context.Context, network, addr string, clientConn ClientConn) (c net.Conn, err error) TerminationDelay() time.Duration } @@ -49,7 +51,12 @@ func (d tcpDialer) TerminationDelay() time.Duration { // Dial dials a network connection and optionally sends a PROXY protocol header. func (d tcpDialer) Dial(network, addr string, clientConn ClientConn) (net.Conn, error) { - conn, err := d.dialer.Dial(network, addr) + return d.DialContext(context.Background(), network, addr, clientConn) +} + +// DialContext dials a network connection and optionally sends a PROXY protocol header, with context. +func (d tcpDialer) DialContext(ctx context.Context, network, addr string, clientConn ClientConn) (net.Conn, error) { + conn, err := d.dialer.DialContext(ctx, network, addr) if err != nil { return nil, err } @@ -72,7 +79,12 @@ type tcpTLSDialer struct { // Dial dials a network connection with the wrapped tcpDialer and performs a TLS handshake. func (d tcpTLSDialer) Dial(network, addr string, clientConn ClientConn) (net.Conn, error) { - conn, err := d.tcpDialer.Dial(network, addr, clientConn) + return d.DialContext(context.Background(), network, addr, clientConn) +} + +// DialContext dials a network connection with the wrapped tcpDialer and performs a TLS handshake, with context. +func (d tcpTLSDialer) DialContext(ctx context.Context, network, addr string, clientConn ClientConn) (net.Conn, error) { + conn, err := d.tcpDialer.DialContext(ctx, network, addr, clientConn) if err != nil { return nil, err } diff --git a/pkg/tcp/wrr_load_balancer.go b/pkg/tcp/wrr_load_balancer.go index 93d43a4fb..c836af17d 100644 --- a/pkg/tcp/wrr_load_balancer.go +++ b/pkg/tcp/wrr_load_balancer.go @@ -1,6 +1,7 @@ package tcp import ( + "context" "errors" "sync" @@ -11,30 +12,42 @@ var errNoServersInPool = errors.New("no servers in the pool") type server struct { Handler + name string weight int } // WRRLoadBalancer is a naive RoundRobin load balancer for TCP services. type WRRLoadBalancer struct { - servers []server - lock sync.Mutex - currentWeight int - index int + // serversMu is a mutex to protect the handlers slice and the status. + serversMu sync.Mutex + servers []server + // status is a record of which child services of the Balancer are healthy, keyed + // by name of child service. A service is initially added to the map when it is + // created via Add, and it is later removed or added to the map as needed, + // through the SetStatus method. + status map[string]struct{} + + // updaters is the list of hooks that are run (to update the Balancer parent(s)), whenever the Balancer status changes. + // No mutex is needed, as it is modified only during the configuration build. + updaters []func(bool) + + index int + currentWeight int + wantsHealthCheck bool } // NewWRRLoadBalancer creates a new WRRLoadBalancer. -func NewWRRLoadBalancer() *WRRLoadBalancer { +func NewWRRLoadBalancer(wantsHealthCheck bool) *WRRLoadBalancer { return &WRRLoadBalancer{ - index: -1, + status: make(map[string]struct{}), + index: -1, + wantsHealthCheck: wantsHealthCheck, } } // ServeTCP forwards the connection to the right service. func (b *WRRLoadBalancer) ServeTCP(conn WriteCloser) { - b.lock.Lock() - next, err := b.next() - b.lock.Unlock() - + next, err := b.nextServer() if err != nil { if !errors.Is(err, errNoServersInPool) { log.Error().Err(err).Msg("Error during load balancing") @@ -46,22 +59,103 @@ func (b *WRRLoadBalancer) ServeTCP(conn WriteCloser) { next.ServeTCP(conn) } -// AddServer appends a server to the existing list. -func (b *WRRLoadBalancer) AddServer(serverHandler Handler) { - w := 1 - b.AddWeightServer(serverHandler, &w) -} - -// AddWeightServer appends a server to the existing list with a weight. -func (b *WRRLoadBalancer) AddWeightServer(serverHandler Handler, weight *int) { - b.lock.Lock() - defer b.lock.Unlock() - +// Add appends a server to the existing list with a name and weight. +func (b *WRRLoadBalancer) Add(name string, handler Handler, weight *int) { w := 1 if weight != nil { w = *weight } - b.servers = append(b.servers, server{Handler: serverHandler, weight: w}) + + b.serversMu.Lock() + b.servers = append(b.servers, server{Handler: handler, name: name, weight: w}) + b.status[name] = struct{}{} + b.serversMu.Unlock() +} + +// SetStatus sets status (UP or DOWN) of a target server. +func (b *WRRLoadBalancer) SetStatus(ctx context.Context, childName string, up bool) { + b.serversMu.Lock() + defer b.serversMu.Unlock() + + upBefore := len(b.status) > 0 + + status := "DOWN" + if up { + status = "UP" + } + + log.Ctx(ctx).Debug().Msgf("Setting status of %s to %v", childName, status) + + if up { + b.status[childName] = struct{}{} + } else { + delete(b.status, childName) + } + + upAfter := len(b.status) > 0 + status = "DOWN" + if upAfter { + status = "UP" + } + + // No Status Change + if upBefore == upAfter { + // We're still with the same status, no need to propagate + log.Ctx(ctx).Debug().Msgf("Still %s, no need to propagate", status) + return + } + + // Status Change + log.Ctx(ctx).Debug().Msgf("Propagating new %s status", status) + for _, fn := range b.updaters { + fn(upAfter) + } +} + +func (b *WRRLoadBalancer) RegisterStatusUpdater(fn func(up bool)) error { + if !b.wantsHealthCheck { + return errors.New("healthCheck not enabled in config for this weighted service") + } + + b.updaters = append(b.updaters, fn) + return nil +} + +func (b *WRRLoadBalancer) nextServer() (Handler, error) { + b.serversMu.Lock() + defer b.serversMu.Unlock() + + if len(b.servers) == 0 || len(b.status) == 0 { + return nil, errNoServersInPool + } + + // The algo below may look messy, but is actually very simple + // it calculates the GCD and subtracts it on every iteration, what interleaves servers + // and allows us not to build an iterator every time we readjust weights. + + // Maximum weight across all enabled servers. + maximum := b.maxWeight() + if maximum == 0 { + return nil, errors.New("all servers have 0 weight") + } + + // GCD across all enabled servers + gcd := b.weightGcd() + + for { + b.index = (b.index + 1) % len(b.servers) + if b.index == 0 { + b.currentWeight -= gcd + if b.currentWeight <= 0 { + b.currentWeight = maximum + } + } + srv := b.servers[b.index] + + if _, ok := b.status[srv.name]; ok && srv.weight >= b.currentWeight { + return srv, nil + } + } } func (b *WRRLoadBalancer) maxWeight() int { @@ -92,36 +186,3 @@ func gcd(a, b int) int { } return a } - -func (b *WRRLoadBalancer) next() (Handler, error) { - if len(b.servers) == 0 { - return nil, errNoServersInPool - } - - // The algo below may look messy, but is actually very simple - // it calculates the GCD and subtracts it on every iteration, what interleaves servers - // and allows us not to build an iterator every time we readjust weights - - // Maximum weight across all enabled servers - maximum := b.maxWeight() - if maximum == 0 { - return nil, errors.New("all servers have 0 weight") - } - - // GCD across all enabled servers - gcd := b.weightGcd() - - for { - b.index = (b.index + 1) % len(b.servers) - if b.index == 0 { - b.currentWeight -= gcd - if b.currentWeight <= 0 { - b.currentWeight = maximum - } - } - srv := b.servers[b.index] - if srv.weight >= b.currentWeight { - return srv, nil - } - } -} diff --git a/pkg/tcp/wrr_load_balancer_test.go b/pkg/tcp/wrr_load_balancer_test.go index fc3438ac6..f51ec4736 100644 --- a/pkg/tcp/wrr_load_balancer_test.go +++ b/pkg/tcp/wrr_load_balancer_test.go @@ -9,50 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -type fakeConn struct { - writeCall map[string]int - closeCall int -} - -func (f *fakeConn) Read(b []byte) (n int, err error) { - panic("implement me") -} - -func (f *fakeConn) Write(b []byte) (n int, err error) { - f.writeCall[string(b)]++ - return len(b), nil -} - -func (f *fakeConn) Close() error { - f.closeCall++ - return nil -} - -func (f *fakeConn) LocalAddr() net.Addr { - panic("implement me") -} - -func (f *fakeConn) RemoteAddr() net.Addr { - panic("implement me") -} - -func (f *fakeConn) SetDeadline(t time.Time) error { - panic("implement me") -} - -func (f *fakeConn) SetReadDeadline(t time.Time) error { - panic("implement me") -} - -func (f *fakeConn) SetWriteDeadline(t time.Time) error { - panic("implement me") -} - -func (f *fakeConn) CloseWrite() error { - panic("implement me") -} - -func TestLoadBalancing(t *testing.T) { +func TestWRRLoadBalancer_LoadBalancing(t *testing.T) { testCases := []struct { desc string serversWeight map[string]int @@ -124,9 +81,9 @@ func TestLoadBalancing(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - balancer := NewWRRLoadBalancer() + balancer := NewWRRLoadBalancer(false) for server, weight := range test.serversWeight { - balancer.AddWeightServer(HandlerFunc(func(conn WriteCloser) { + balancer.Add(server, HandlerFunc(func(conn WriteCloser) { _, err := conn.Write([]byte(server)) require.NoError(t, err) }), &weight) @@ -142,3 +99,196 @@ func TestLoadBalancing(t *testing.T) { }) } } + +func TestWRRLoadBalancer_NoServiceUp(t *testing.T) { + balancer := NewWRRLoadBalancer(false) + + balancer.Add("first", HandlerFunc(func(conn WriteCloser) { + _, err := conn.Write([]byte("first")) + require.NoError(t, err) + }), pointer(1)) + + balancer.Add("second", HandlerFunc(func(conn WriteCloser) { + _, err := conn.Write([]byte("second")) + require.NoError(t, err) + }), pointer(1)) + + balancer.SetStatus(t.Context(), "first", false) + balancer.SetStatus(t.Context(), "second", false) + + conn := &fakeConn{writeCall: make(map[string]int)} + balancer.ServeTCP(conn) + + assert.Empty(t, conn.writeCall) + assert.Equal(t, 1, conn.closeCall) +} + +func TestWRRLoadBalancer_OneServerDown(t *testing.T) { + balancer := NewWRRLoadBalancer(false) + + balancer.Add("first", HandlerFunc(func(conn WriteCloser) { + _, err := conn.Write([]byte("first")) + require.NoError(t, err) + }), pointer(1)) + + balancer.Add("second", HandlerFunc(func(conn WriteCloser) { + _, err := conn.Write([]byte("second")) + require.NoError(t, err) + }), pointer(1)) + + balancer.SetStatus(t.Context(), "second", false) + + conn := &fakeConn{writeCall: make(map[string]int)} + for range 3 { + balancer.ServeTCP(conn) + } + assert.Equal(t, 3, conn.writeCall["first"]) +} + +func TestWRRLoadBalancer_DownThenUp(t *testing.T) { + balancer := NewWRRLoadBalancer(false) + + balancer.Add("first", HandlerFunc(func(conn WriteCloser) { + _, err := conn.Write([]byte("first")) + require.NoError(t, err) + }), pointer(1)) + + balancer.Add("second", HandlerFunc(func(conn WriteCloser) { + _, err := conn.Write([]byte("second")) + require.NoError(t, err) + }), pointer(1)) + + balancer.SetStatus(t.Context(), "second", false) + + conn := &fakeConn{writeCall: make(map[string]int)} + for range 3 { + balancer.ServeTCP(conn) + } + assert.Equal(t, 3, conn.writeCall["first"]) + + balancer.SetStatus(t.Context(), "second", true) + + conn = &fakeConn{writeCall: make(map[string]int)} + for range 2 { + balancer.ServeTCP(conn) + } + assert.Equal(t, 1, conn.writeCall["first"]) + assert.Equal(t, 1, conn.writeCall["second"]) +} + +func TestWRRLoadBalancer_Propagate(t *testing.T) { + balancer1 := NewWRRLoadBalancer(true) + + balancer1.Add("first", HandlerFunc(func(conn WriteCloser) { + _, err := conn.Write([]byte("first")) + require.NoError(t, err) + }), pointer(1)) + + balancer1.Add("second", HandlerFunc(func(conn WriteCloser) { + _, err := conn.Write([]byte("second")) + require.NoError(t, err) + }), pointer(1)) + + balancer2 := NewWRRLoadBalancer(true) + + balancer2.Add("third", HandlerFunc(func(conn WriteCloser) { + _, err := conn.Write([]byte("third")) + require.NoError(t, err) + }), pointer(1)) + + balancer2.Add("fourth", HandlerFunc(func(conn WriteCloser) { + _, err := conn.Write([]byte("fourth")) + require.NoError(t, err) + }), pointer(1)) + + topBalancer := NewWRRLoadBalancer(true) + + topBalancer.Add("balancer1", balancer1, pointer(1)) + _ = balancer1.RegisterStatusUpdater(func(up bool) { + topBalancer.SetStatus(t.Context(), "balancer1", up) + }) + + topBalancer.Add("balancer2", balancer2, pointer(1)) + _ = balancer2.RegisterStatusUpdater(func(up bool) { + topBalancer.SetStatus(t.Context(), "balancer2", up) + }) + + conn := &fakeConn{writeCall: make(map[string]int)} + for range 8 { + topBalancer.ServeTCP(conn) + } + assert.Equal(t, 2, conn.writeCall["first"]) + assert.Equal(t, 2, conn.writeCall["second"]) + assert.Equal(t, 2, conn.writeCall["third"]) + assert.Equal(t, 2, conn.writeCall["fourth"]) + + // fourth gets downed, but balancer2 still up since third is still up. + balancer2.SetStatus(t.Context(), "fourth", false) + + conn = &fakeConn{writeCall: make(map[string]int)} + for range 8 { + topBalancer.ServeTCP(conn) + } + assert.Equal(t, 2, conn.writeCall["first"]) + assert.Equal(t, 2, conn.writeCall["second"]) + assert.Equal(t, 4, conn.writeCall["third"]) + assert.Equal(t, 0, conn.writeCall["fourth"]) + + // third gets downed, and the propagation triggers balancer2 to be marked as + // down as well for topBalancer. + balancer2.SetStatus(t.Context(), "third", false) + + conn = &fakeConn{writeCall: make(map[string]int)} + for range 8 { + topBalancer.ServeTCP(conn) + } + assert.Equal(t, 4, conn.writeCall["first"]) + assert.Equal(t, 4, conn.writeCall["second"]) + assert.Equal(t, 0, conn.writeCall["third"]) + assert.Equal(t, 0, conn.writeCall["fourth"]) +} + +func pointer[T any](v T) *T { return &v } + +type fakeConn struct { + writeCall map[string]int + closeCall int +} + +func (f *fakeConn) Read(b []byte) (n int, err error) { + panic("implement me") +} + +func (f *fakeConn) Write(b []byte) (n int, err error) { + f.writeCall[string(b)]++ + return len(b), nil +} + +func (f *fakeConn) Close() error { + f.closeCall++ + return nil +} + +func (f *fakeConn) LocalAddr() net.Addr { + panic("implement me") +} + +func (f *fakeConn) RemoteAddr() net.Addr { + panic("implement me") +} + +func (f *fakeConn) SetDeadline(t time.Time) error { + panic("implement me") +} + +func (f *fakeConn) SetReadDeadline(t time.Time) error { + panic("implement me") +} + +func (f *fakeConn) SetWriteDeadline(t time.Time) error { + panic("implement me") +} + +func (f *fakeConn) CloseWrite() error { + panic("implement me") +} diff --git a/webui/src/pages/tcp/TcpService.spec.tsx b/webui/src/pages/tcp/TcpService.spec.tsx index 4860bd9e8..e6b835832 100644 --- a/webui/src/pages/tcp/TcpService.spec.tsx +++ b/webui/src/pages/tcp/TcpService.spec.tsx @@ -33,8 +33,18 @@ describe('', () => { address: 'http://10.0.1.12:80', }, ], - passHostHeader: true, terminationDelay: 10, + healthCheck: { + interval: '30s', + timeout: '10s', + port: 8080, + unhealthyInterval: '1m', + send: 'PING', + expect: 'PONG', + }, + }, + serverStatus: { + 'http://10.0.1.12:80': 'UP', }, status: 'enabled', usedBy: ['router-test1@docker'], @@ -65,19 +75,31 @@ describe('', () => { const titleTags = headings.filter((h1) => h1.innerHTML === 'service-test1') expect(titleTags.length).toBe(1) - const serviceDetails = getByTestId('service-details') + const serviceDetails = getByTestId('tcp-service-details') expect(serviceDetails.innerHTML).toContain('Type') expect(serviceDetails.innerHTML).toContain('loadbalancer') expect(serviceDetails.innerHTML).toContain('Provider') expect(serviceDetails.querySelector('svg[data-testid="docker"]')).toBeTruthy() expect(serviceDetails.innerHTML).toContain('Status') expect(serviceDetails.innerHTML).toContain('Success') - expect(serviceDetails.innerHTML).toContain('Pass Host Header') - expect(serviceDetails.innerHTML).toContain('True') expect(serviceDetails.innerHTML).toContain('Termination Delay') expect(serviceDetails.innerHTML).toContain('10 ms') - const serversList = getByTestId('servers-list') + const healthCheck = getByTestId('tcp-health-check') + expect(healthCheck.innerHTML).toContain('Interval') + expect(healthCheck.innerHTML).toContain('30s') + expect(healthCheck.innerHTML).toContain('Timeout') + expect(healthCheck.innerHTML).toContain('10s') + expect(healthCheck.innerHTML).toContain('Port') + expect(healthCheck.innerHTML).toContain('8080') + expect(healthCheck.innerHTML).toContain('Unhealthy Interval') + expect(healthCheck.innerHTML).toContain('1m') + expect(healthCheck.innerHTML).toContain('Send') + expect(healthCheck.innerHTML).toContain('PING') + expect(healthCheck.innerHTML).toContain('Expect') + expect(healthCheck.innerHTML).toContain('PONG') + + const serversList = getByTestId('tcp-servers-list') expect(serversList.childNodes.length).toBe(1) expect(serversList.innerHTML).toContain('http://10.0.1.12:80') @@ -130,7 +152,7 @@ describe('', () => { , ) - const serversList = getByTestId('servers-list') + const serversList = getByTestId('tcp-servers-list') expect(serversList.childNodes.length).toBe(1) expect(serversList.innerHTML).toContain('http://10.0.1.12:81') @@ -160,4 +182,62 @@ describe('', () => { getByTestId('routers-table') }).toThrow('Unable to find an element by: [data-testid="routers-table"]') }) + + it('should render weighted services', async () => { + const mockData = { + weighted: { + services: [ + { + name: 'service1@docker', + weight: 80, + }, + { + name: 'service2@kubernetes', + weight: 20, + }, + ], + }, + status: 'enabled', + usedBy: ['router-test1@docker'], + name: 'weighted-service-test', + provider: 'docker', + type: 'weighted', + routers: [ + { + entryPoints: ['tcp'], + service: 'weighted-service-test', + rule: 'HostSNI(`*`)', + status: 'enabled', + using: ['tcp'], + name: 'router-test1@docker', + provider: 'docker', + }, + ], + } + + const { container, getByTestId } = renderWithProviders( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + , + ) + + const headings = Array.from(container.getElementsByTagName('h1')) + const titleTags = headings.filter((h1) => h1.innerHTML === 'weighted-service-test') + expect(titleTags.length).toBe(1) + + const serviceDetails = getByTestId('tcp-service-details') + expect(serviceDetails.innerHTML).toContain('Type') + expect(serviceDetails.innerHTML).toContain('weighted') + expect(serviceDetails.innerHTML).toContain('Provider') + expect(serviceDetails.querySelector('svg[data-testid="docker"]')).toBeTruthy() + expect(serviceDetails.innerHTML).toContain('Status') + expect(serviceDetails.innerHTML).toContain('Success') + + const weightedServices = getByTestId('tcp-weighted-services') + expect(weightedServices.childNodes.length).toBe(2) + expect(weightedServices.innerHTML).toContain('service1@docker') + expect(weightedServices.innerHTML).toContain('80') + expect(weightedServices.innerHTML).toContain('service2@kubernetes') + expect(weightedServices.innerHTML).toContain('20') + expect(weightedServices.querySelector('svg[data-testid="docker"]')).toBeTruthy() + }) }) diff --git a/webui/src/pages/tcp/TcpService.tsx b/webui/src/pages/tcp/TcpService.tsx index 6c1d262b5..f2f3ef1fe 100644 --- a/webui/src/pages/tcp/TcpService.tsx +++ b/webui/src/pages/tcp/TcpService.tsx @@ -1,19 +1,234 @@ -import { Flex, H1, Skeleton, styled, Text } from '@traefiklabs/faency' +import { Box, Flex, H1, Skeleton, styled, Text } from '@traefiklabs/faency' +import { useMemo } from 'react' +import { FiGlobe, FiInfo, FiShield } from 'react-icons/fi' import { useParams } from 'react-router-dom' -import { DetailSectionSkeleton } from 'components/resources/DetailSections' +import ProviderIcon from 'components/icons/providers' +import { + DetailSection, + DetailSectionSkeleton, + ItemBlock, + ItemTitle, + LayoutTwoCols, + ProviderName, +} from 'components/resources/DetailSections' +import { ResourceStatus } from 'components/resources/ResourceStatus' import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection' -import { ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail' +import Tooltip from 'components/Tooltip' +import { ResourceDetailDataType, ServiceDetailType, useResourceDetail } from 'hooks/use-resource-detail' import Page from 'layout/Page' -import { ServicePanels } from 'pages/http/HttpService' import { NotFound } from 'pages/NotFound' +type TcpDetailProps = { + data: ServiceDetailType +} + const SpacedColumns = styled(Flex, { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(360px, 1fr))', gridGap: '16px', }) +const ServicesGrid = styled(Box, { + display: 'grid', + gridTemplateColumns: '2fr 1fr 1fr', + alignItems: 'center', + padding: '$3 $5', + borderBottom: '1px solid $tableRowBorder', +}) + +const ServersGrid = styled(Box, { + display: 'grid', + alignItems: 'center', + padding: '$3 $5', + borderBottom: '1px solid $tableRowBorder', +}) + +const GridTitle = styled(Text, { + fontSize: '14px', + fontWeight: 700, + color: 'hsl(0, 0%, 56%)', +}) + +type TcpServer = { + address: string +} + +type ServerStatus = { + [server: string]: string +} + +type TcpHealthCheck = { + port?: number + send?: string + expect?: string + interval?: string + unhealthyInterval?: string + timeout?: string +} + +function getTcpServerStatusList(data: ServiceDetailType): ServerStatus { + const serversList: ServerStatus = {} + + data.loadBalancer?.servers?.forEach((server: any) => { + // TCP servers should have address, but handle both url and address for compatibility + const serverKey = (server as TcpServer).address || (server as any).url + if (serverKey) { + serversList[serverKey] = 'DOWN' + } + }) + + if (data.serverStatus) { + Object.entries(data.serverStatus).forEach(([server, status]) => { + serversList[server] = status + }) + } + + return serversList +} + +export const TcpServicePanels = ({ data }: TcpDetailProps) => { + const serversList = getTcpServerStatusList(data) + const getProviderFromName = (serviceName: string): string => { + const [, provider] = serviceName.split('@') + return provider || data.provider + } + const providerName = useMemo(() => { + return data.provider + }, [data.provider]) + + return ( + + } title="Service Details"> + + {data.type && ( + + {data.type} + + )} + {data.provider && ( + + + {providerName} + + )} + + {data.status && ( + + + + )} + {data.loadBalancer && ( + <> + {data.loadBalancer.terminationDelay && ( + + {`${data.loadBalancer.terminationDelay} ms`} + + )} + + )} + + {data.loadBalancer?.healthCheck && ( + } title="Health Check"> + + {(() => { + const tcpHealthCheck = data.loadBalancer.healthCheck as unknown as TcpHealthCheck + return ( + <> + + {tcpHealthCheck.interval && ( + + {tcpHealthCheck.interval} + + )} + {tcpHealthCheck.timeout && ( + + {tcpHealthCheck.timeout} + + )} + + + {tcpHealthCheck.port && ( + + {tcpHealthCheck.port} + + )} + {tcpHealthCheck.unhealthyInterval && ( + + {tcpHealthCheck.unhealthyInterval} + + )} + + + {tcpHealthCheck.send && ( + + + {tcpHealthCheck.send} + + + )} + {tcpHealthCheck.expect && ( + + + {tcpHealthCheck.expect} + + + )} + + + ) + })()} + + + )} + {!!data?.weighted?.services?.length && ( + } title="Services" noPadding> + <> + + Name + Weight + Provider + + + {data.weighted.services.map((service) => ( + + {service.name} + {service.weight} + + + + + ))} + + + + )} + {Object.keys(serversList).length > 0 && ( + } title="Servers" noPadding> + <> + + Status + Address + + + {Object.entries(serversList).map(([server, status]) => ( + + + + + {server} + + + + ))} + + + + )} + + ) +} + type TcpServiceRenderProps = { data?: ResourceDetailDataType error?: Error @@ -38,6 +253,7 @@ export const TcpServiceRender = ({ data, error, name }: TcpServiceRenderProps) = + @@ -51,7 +267,7 @@ export const TcpServiceRender = ({ data, error, name }: TcpServiceRenderProps) = return (

{data.name}

- +
) From d6598f370c219402e790a8be1908a33eefbbd0d6 Mon Sep 17 00:00:00 2001 From: Simon Delicata Date: Wed, 22 Oct 2025 11:58:05 +0200 Subject: [PATCH 015/134] Multi-layer routing Co-authored-by: Romain --- .golangci.yml | 4 + .../kubernetes-crd-definition-v1.yml | 20 + .../traefik.io_ingressroutes.yaml | 20 + .../http/routing/multi-layer-routing.md | 188 ++++ .../http/routing/router.md | 11 +- .../kubernetes/crd/http/ingressroute.md | 168 +++- .../other-providers/file.toml | 2 + .../other-providers/file.yaml | 6 + docs/mkdocs.yml | 3 + integration/fixtures/k8s/01-traefik-crd.yml | 20 + .../fixtures/routing/multi_layer_auth.toml | 51 + integration/resources/compose/routing.yml | 8 + integration/routing_test.go | 154 +++ pkg/config/dynamic/http_config.go | 1 + pkg/config/dynamic/zz_generated.deepcopy.go | 5 + pkg/config/runtime/runtime_http.go | 8 +- pkg/middlewares/accesslog/field_middleware.go | 35 + .../accesslog/field_middleware_test.go | 96 ++ pkg/middlewares/accesslog/logger_test.go | 77 ++ .../parent_refs_cross_namespace_allowed.yml | 28 + .../parent_refs_cross_namespace_denied.yml | 28 + .../parent_refs_default_namespace.yml | 27 + .../fixtures/parent_refs_missing_parent.yml | 16 + .../fixtures/parent_refs_multiple_parents.yml | 42 + .../crd/fixtures/parent_refs_services.yml | 139 +++ ...ent_refs_single_parent_multiple_routes.yml | 30 + ...parent_refs_single_parent_single_route.yml | 28 + pkg/provider/kubernetes/crd/kubernetes.go | 9 +- .../kubernetes/crd/kubernetes_http.go | 65 +- pkg/provider/kubernetes/crd/kubernetes_tcp.go | 6 +- .../kubernetes/crd/kubernetes_test.go | 316 ++++++ .../crd/traefikio/v1alpha1/ingressroute.go | 12 + .../v1alpha1/zz_generated.deepcopy.go | 21 + pkg/server/aggregator.go | 14 +- pkg/server/router/router.go | 275 ++++- pkg/server/router/router_test.go | 936 ++++++++++++++++++ pkg/server/routerfactory.go | 2 + 37 files changed, 2834 insertions(+), 37 deletions(-) create mode 100644 docs/content/reference/routing-configuration/http/routing/multi-layer-routing.md create mode 100644 integration/fixtures/routing/multi_layer_auth.toml create mode 100644 integration/resources/compose/routing.yml create mode 100644 integration/routing_test.go create mode 100644 pkg/middlewares/accesslog/field_middleware_test.go create mode 100644 pkg/provider/kubernetes/crd/fixtures/parent_refs_cross_namespace_allowed.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/parent_refs_cross_namespace_denied.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/parent_refs_default_namespace.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/parent_refs_missing_parent.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/parent_refs_multiple_parents.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/parent_refs_services.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/parent_refs_single_parent_multiple_routes.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/parent_refs_single_parent_single_route.yml diff --git a/.golangci.yml b/.golangci.yml index 30673cca6..949f689dc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -322,6 +322,10 @@ linters: text: 'SA6002: argument should be pointer-like to avoid allocations' - path: integration/integration_test.go text: 'var (gatewayAPIConformanceRunTest|traefikVersion) is unused' + - path: pkg/server/router/router.go + text: 'appendAssign: append result not assigned to the same slice' + linters: + - gocritic paths: - pkg/provider/kubernetes/crd/generated/ diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index 354dcfbab..bc667c19a 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -48,6 +48,26 @@ spec: items: type: string type: array + parentRefs: + description: |- + ParentRefs defines references to parent IngressRoute resources for multi-layer routing. + When set, this IngressRoute's routers will be children of the referenced parent IngressRoute's routers. + More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#parentrefs + items: + description: IngressRouteRef is a reference to an IngressRoute resource. + properties: + name: + description: Name defines the name of the referenced IngressRoute + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + IngressRoute resource. + type: string + required: + - name + type: object + type: array routes: description: Routes defines the list of routes. items: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml index c963b6d47..a99934f69 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml @@ -48,6 +48,26 @@ spec: items: type: string type: array + parentRefs: + description: |- + ParentRefs defines references to parent IngressRoute resources for multi-layer routing. + When set, this IngressRoute's routers will be children of the referenced parent IngressRoute's routers. + More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#parentrefs + items: + description: IngressRouteRef is a reference to an IngressRoute resource. + properties: + name: + description: Name defines the name of the referenced IngressRoute + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + IngressRoute resource. + type: string + required: + - name + type: object + type: array routes: description: Routes defines the list of routes. items: diff --git a/docs/content/reference/routing-configuration/http/routing/multi-layer-routing.md b/docs/content/reference/routing-configuration/http/routing/multi-layer-routing.md new file mode 100644 index 000000000..3e878fa63 --- /dev/null +++ b/docs/content/reference/routing-configuration/http/routing/multi-layer-routing.md @@ -0,0 +1,188 @@ +--- +title: "Multi-Layer Routing" +description: "Learn how to use Traefik's multi-layer routing to create hierarchical router relationships where parent routers can apply middleware before child routers make routing decisions." +--- + +# Multi-Layer Routing + +Hierarchical Router Relationships for Advanced Routing Scenarios. + +## Overview + +Multi-layer routing enables you to create hierarchical relationships between routers, +where parent routers can process requests through middleware before child routers make final routing decisions. + +This feature allows middleware at the parent level to modify requests (adding headers, performing authentication, etc.) that influence how child routers evaluate their rules and route traffic to services. + +Multi-layer routing is particularly useful for progressive request enrichment, where each layer adds context to the request, enabling increasingly specific routing decisions: + +- **Authentication-Based Routing**: Parent router authenticates requests and adds user context (roles, permissions) as headers, child routers route based on these headers +- **Staged Middleware Application**: Apply common middleware (rate limiting, CORS) at parent level (for a given domain/path), but specific middleware at child level + +!!! info "Provider Support" + + Multi-layer routing is supported by the following providers: + + - **File provider** (YAML, TOML, JSON) + - **KV stores** (Consul, etcd, Redis, ZooKeeper) + - **Kubernetes CRD** (IngressRoute) + + Multi-layer routing is not available for other providers (Docker, Kubernetes Ingress, Gateway API, etc.). + + +## How It Works + +``` +Request → EntryPoint → Parent Router → Middleware → Child Router A → Service A + ↓ → Child Router B → Service B + Modify Request + (e.g., add headers) +``` + +1. **Request arrives** at an entrypoint +2. **Parent router matches** based on its rule (e.g., ```Host(`example.com`)```) +3. **Parent middleware executes**, potentially modifying the request +4. **One child router matches** based on its rule (which may use modified request attributes) +5. **Request is forwarded** to the matching child router's service + +## Building a Router Hierarchy + +### Root Routers + +- Have no `parentRefs` (top of the hierarchy) +- **Can** have `tls`, `observability`, and `entryPoints` configuration +- Can be either parent routers (with children) or standalone routers (with service) +- **Can** have models applied (non-root routers cannot have models) + +### Intermediate Routers + +- Reference their parent router(s) via `parentRefs` +- Have one or more child routers +- **Must not** have a `service` defined +- **Must not** have `entryPoints`, `tls`, or `observability` configuration + +### Leaf Routers + +- Reference their parent router(s) via `parentRefs` +- **Must** have a `service` defined +- **Must not** have `entryPoints`, `tls`, or `observability` configuration + +## Configuration Example + +??? example "Authentication-Based Routing" + + ```yaml tab="File (YAML)" + ## Dynamic configuration + http: + routers: + # Parent router with authentication + api-parent: + rule: "PathPrefix(`/api`)" + middlewares: + - auth-middleware + entryPoints: + - websecure + tls: {} + # Note: No service defined - this is a parent router + + # Child router for admin users + api-admin: + rule: "HeadersRegexp(`X-User-Role`, `admin`)" + service: admin-service + parentRefs: + - api-parent + + # Child router for regular users + api-user: + rule: "HeadersRegexp(`X-User-Role`, `user`)" + service: user-service + parentRefs: + - api-parent + + middlewares: + auth-middleware: + forwardAuth: + address: "http://auth-service:8080/auth" + authResponseHeaders: + - X-User-Role + - X-User-Name + + services: + admin-service: + loadBalancer: + servers: + - url: "http://admin-backend:8080" + + user-service: + loadBalancer: + servers: + - url: "http://user-backend:8080" + ``` + + ```toml tab="File (TOML)" + ## Dynamic configuration + [http.routers] + # Parent router with authentication + [http.routers.api-parent] + rule = "PathPrefix(`/api`)" + middlewares = ["auth-middleware"] + entryPoints = ["websecure"] + [http.routers.api-parent.tls] + # Note: No service defined - this is a parent router + + # Child router for admin users + [http.routers.api-admin] + rule = "HeadersRegexp(`X-User-Role`, `admin`)" + service = "admin-service" + parentRefs = ["api-parent"] + + # Child router for regular users + [http.routers.api-user] + rule = "HeadersRegexp(`X-User-Role`, `user`)" + service = "user-service" + parentRefs = ["api-parent"] + + [http.middlewares] + [http.middlewares.auth-middleware.forwardAuth] + address = "http://auth-service:8080/auth" + authResponseHeaders = ["X-User-Role", "X-User-Name"] + + [http.services] + [http.services.admin-service.loadBalancer] + [[http.services.admin-service.loadBalancer.servers]] + url = "http://admin-backend:8080" + + [http.services.user-service.loadBalancer] + [[http.services.user-service.loadBalancer.servers]] + url = "http://user-backend:8080" + ``` + + ```txt tab="KV (Consul/etcd/Redis/ZK)" + | Key | Value | + |------------------------------------------------------------------------|---------------------------------| + | `traefik/http/routers/api-parent/rule` | `PathPrefix(\`/api\`)` | + | `traefik/http/routers/api-parent/middlewares/0` | `auth-middleware` | + | `traefik/http/routers/api-parent/entrypoints/0` | `websecure` | + | `traefik/http/routers/api-parent/tls` | `true` | + | `traefik/http/routers/api-admin/rule` | `HeadersRegexp(\`X-User-Role\`, \`admin\`)` | + | `traefik/http/routers/api-admin/service` | `admin-service` | + | `traefik/http/routers/api-admin/parentrefs/0` | `api-parent` | + | `traefik/http/routers/api-user/rule` | `HeadersRegexp(\`X-User-Role\`, \`user\`)` | + | `traefik/http/routers/api-user/service` | `user-service` | + | `traefik/http/routers/api-user/parentrefs/0` | `api-parent` | + | `traefik/http/middlewares/auth-middleware/forwardauth/address` | `http://auth-service:8080/auth` | + | `traefik/http/middlewares/auth-middleware/forwardauth/authresponseheaders/0` | `X-User-Role` | + | `traefik/http/middlewares/auth-middleware/forwardauth/authresponseheaders/1` | `X-User-Name` | + | `traefik/http/services/admin-service/loadbalancer/servers/0/url` | `http://admin-backend:8080` | + | `traefik/http/services/user-service/loadbalancer/servers/0/url` | `http://user-backend:8080` | + ``` + + **How it works:** + + 1. Request to `/api/endpoint` matches `api-parent` router + 2. `auth-middleware` (ForwardAuth) validates the request and adds `X-User-Role` header + 3. Modified request is evaluated by child routers + 4. If `X-User-Role: admin`, `api-admin` router matches and forwards to `admin-service` + 5. If `X-User-Role: user`, `api-user` router matches and forwards to `user-service` + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/routing/router.md b/docs/content/reference/routing-configuration/http/routing/router.md index 2e9a7eb56..fa8ccb701 100644 --- a/docs/content/reference/routing-configuration/http/routing/router.md +++ b/docs/content/reference/routing-configuration/http/routing/router.md @@ -32,6 +32,9 @@ http: metrics: true accessLogs: true tracing: true + parentRefs: + - "parent-router-1" + - "parent-router-2" service: my-service ``` @@ -43,6 +46,7 @@ http: priority = 10 middlewares = ["auth", "ratelimit"] service = "my-service" + parentRefs = ["parent-router-1", "parent-router-2"] [http.routers.my-router.tls] certResolver = "letsencrypt" @@ -88,15 +92,15 @@ labels: "traefik.http.routers.my-router.tls.domains[0].sans=www.example.com", "traefik.http.routers.my-router.observability.metrics=true", "traefik.http.routers.my-router.observability.accessLogs=true", - "traefik.http.routers.my-router.observability.tracing=true" + "traefik.http.routers.my-router.observability.tracing=true", ] } ``` ## Configuration Options -| Field | Description | Default | Required | -|----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|----------| +| Field | Description | Default | Required | +|----------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|----------| | `entryPoints` | The list of entry points to which the router is attached. If not specified, HTTP routers are attached to all entry points. | All entry points | No | | `rule` | Rules are a set of matchers configured with values, that determine if a particular request matches specific criteria. If the rule is verified, the router becomes active, calls middlewares, and then forwards the request to the service. See [Rules & Priority](./rules-and-priority.md) for details. | | Yes | | `priority` | To avoid path overlap, routes are sorted, by default, in descending order using rules length. The priority is directly equal to the length of the rule, and so the longest length has the highest priority. A value of `0` for the priority is ignored. See [Rules & Priority](./rules-and-priority.md) for details. | Rule length | No | @@ -106,6 +110,7 @@ labels: | `tls.options` | The name of the TLS options to use for configuring TLS parameters (cipher suites, min/max TLS version, client authentication, etc.). See [TLS Options](../tls/tls-options.md) for detailed configuration. | `default` | No | | `tls.domains` | List of domains and Subject Alternative Names (SANs) for explicit certificate domain specification. When using ACME certificate resolvers, domains are automatically extracted from router rules, making this option optional. | | No | | `observability` | Observability configuration for the router. Allows fine-grained control over access logs, metrics, and tracing per router. See [Observability](./observability.md) for details. | Inherited from entry points | No | +| `parentRefs` | References to parent router names for multi-layer routing. When specified, this router becomes a child router that processes requests after parent routers have applied their middlewares. See [Multi-Layer Routing](../../../../routing/multi-layer-routing.md) for details. | | No | | `service` | The name of the service that will handle the matched requests. Services can be load balancer services, weighted round robin, mirroring, or failover services. See [Service](../load-balancing/service.md) for details. | | Yes | ## Router Naming diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md b/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md index 882c6e076..a6c437090 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md @@ -23,6 +23,9 @@ metadata: spec: entryPoints: - web + parentRefs: + - name: parent-gateway + namespace: default # Optional - defaults to same namespace routes: - kind: Rule # Rule on the Host @@ -74,9 +77,12 @@ spec: ## Configuration Options -| Field | Description | Default | Required | -|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| +| Field | Description | Default | Required | +|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| | `entryPoints` | List of [entry points](../../../../install-configuration/entrypoints.md) names.
If not specified, HTTP routers will accept requests from all EntryPoints in the list of default EntryPoints. | | No | +| `parentRefs` | List of references to parent IngressRoute resources for multi-layer routing. When specified, this IngressRoute's routers become children of the referenced parent IngressRoute's routers. See [Multi-Layer Routing](#multi-layer-routing-with-ingressroutes) section for details. | | No | +| `parentRefs[n].name` | Name of the referenced parent IngressRoute resource. | | Yes | +| `parentRefs[n].namespace` | Namespace of the referenced parent IngressRoute resource.
If not specified, defaults to the same namespace as the child IngressRoute.
Cross-namespace references require `allowCrossNamespace` provider option to be enabled. | | No | | `routes` | List of routes. | | Yes | | `routes[n].kind` | Kind of router matching, only `Rule` is allowed yet. | "Rule" | No | | `routes[n].match` | Defines the [rule](../../../http/routing/rules-and-priority.md#rules) corresponding to an underlying router. | | Yes | @@ -213,6 +219,162 @@ TLS options references, a conflict occurs, such as in the example below. ... ``` -If that happens, both mappings are discarded, and the host name +If that happens, both mappings are discarded, and the host name (`example.net` in the example) for these routers gets associated with the default TLS options instead. + +### Multi-Layer Routing with IngressRoutes + +Multi-layer routing allows creating hierarchical relationships between IngressRoutes, +where parent IngressRoutes can apply middleware before child IngressRoutes make routing decisions. + +This is particularly useful for authentication-based routing, +where a parent IngressRoute authenticates requests and adds context (e.g., user roles as headers), +and child IngressRoutes route based on that context. + +When a child IngressRoute references a parent IngressRoute with multiple routes, +**all** parent routers then become parents of **all** child routers. + +!!! info "Comprehensive Multi-Layer Routing Documentation" + + For detailed information about multi-layer routing concepts, validation rules, and use cases, see the dedicated [Multi-Layer Routing](../../../../routing-configuration/http/routing/multi-layer-routing.md) page. + +#### Configuration Requirements + +### Root IngressRoutes + +- Have no `parentRefs` (top of the hierarchy) +- **Can** have `entryPoints`, `tls`, and `observability` configuration +- Can be either parent IngressRoutes (with children) or standalone IngressRoutes (with service) + +### Intermediate IngressRoutes + +- Reference their parent IngressRoute(s) via `parentRefs` +- Have one or more child IngressRoutes +- **Must not** have a `service` defined +- **Must not** have `entryPoints`, `tls`, or `observability` configuration + +### Leaf IngressRoutes + +- Reference their parent IngressRoute(s) via `parentRefs` +- **Must** have a `service` defined +- **Must not** have `entryPoints`, `tls`, or `observability` configuration + +!!! warning "Cross-Namespace References" + + Cross-namespace parent references require the `allowCrossNamespace` provider option to be enabled. + If disabled, child IngressRoute creation will be skipped with an error logged. + +#### Example: Authentication-Based Routing + +??? example "Parent IngressRoute with ForwardAuth and Child IngressRoutes" + + ```yaml tab="Parent IngressRoute" + apiVersion: traefik.io/v1alpha1 + kind: IngressRoute + metadata: + name: api-parent + namespace: default + spec: + entryPoints: + - websecure + tls: + certResolver: letsencrypt + routes: + # Parent route with authentication - no services + - match: Host(`api.example.com`) && PathPrefix(`/api`) + kind: Rule + middlewares: + - name: auth-middleware + namespace: default + --- + apiVersion: traefik.io/v1alpha1 + kind: Middleware + metadata: + name: auth-middleware + namespace: default + spec: + forwardAuth: + address: "http://auth-service.default.svc.cluster.local:8080/auth" + authResponseHeaders: + - X-User-Role + - X-User-Name + ``` + + ```yaml tab="Child IngressRoutes" + # Child IngressRoute for admin users + apiVersion: traefik.io/v1alpha1 + kind: IngressRoute + metadata: + name: api-admin + namespace: default + spec: + parentRefs: + - name: api-parent + namespace: default # Optional - defaults to same namespace + routes: + - match: HeadersRegexp(`X-User-Role`, `admin`) + kind: Rule + services: + - name: admin-service + port: 80 + --- + # Child IngressRoute for regular users + apiVersion: traefik.io/v1alpha1 + kind: IngressRoute + metadata: + name: api-user + namespace: default + spec: + parentRefs: + - name: api-parent + routes: + - match: HeadersRegexp(`X-User-Role`, `user`) + kind: Rule + services: + - name: user-service + port: 80 + ``` + + ```yaml tab="Services" + apiVersion: v1 + kind: Service + metadata: + name: auth-service + namespace: default + spec: + ports: + - port: 8080 + selector: + app: auth-service + --- + apiVersion: v1 + kind: Service + metadata: + name: admin-service + namespace: default + spec: + ports: + - port: 80 + selector: + app: admin-backend + --- + apiVersion: v1 + kind: Service + metadata: + name: user-service + namespace: default + spec: + ports: + - port: 80 + selector: + app: user-backend + ``` + + **How it works:** + + 1. Request to `https://api.example.com/api/endpoint` matches the parent router + 2. `auth-middleware` (ForwardAuth) validates the request with `auth-service` + 3. `auth-service` returns 200 OK with `X-User-Role` header (e.g., `admin` or `user`) + 4. Child routers evaluate rules against the modified request (with `X-User-Role` header) + 5. Request is routed to `admin-service` or `user-service` based on the role diff --git a/docs/content/reference/routing-configuration/other-providers/file.toml b/docs/content/reference/routing-configuration/other-providers/file.toml index 907cf02e1..9e00b358f 100644 --- a/docs/content/reference/routing-configuration/other-providers/file.toml +++ b/docs/content/reference/routing-configuration/other-providers/file.toml @@ -7,6 +7,7 @@ middlewares = ["foobar", "foobar"] service = "foobar" rule = "foobar" + parentRefs = ["foobar", "foobar"] ruleSyntax = "foobar" priority = 42 [http.routers.Router0.tls] @@ -30,6 +31,7 @@ middlewares = ["foobar", "foobar"] service = "foobar" rule = "foobar" + parentRefs = ["foobar", "foobar"] ruleSyntax = "foobar" priority = 42 [http.routers.Router1.tls] diff --git a/docs/content/reference/routing-configuration/other-providers/file.yaml b/docs/content/reference/routing-configuration/other-providers/file.yaml index 438a74a65..fdb8f2c1e 100644 --- a/docs/content/reference/routing-configuration/other-providers/file.yaml +++ b/docs/content/reference/routing-configuration/other-providers/file.yaml @@ -11,6 +11,9 @@ http: - foobar service: foobar rule: foobar + parentRefs: + - foobar + - foobar ruleSyntax: foobar priority: 42 tls: @@ -39,6 +42,9 @@ http: - foobar service: foobar rule: foobar + parentRefs: + - foobar + - foobar ruleSyntax: foobar priority: 42 tls: diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 13b8c52d7..5ad7f5a3d 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -267,6 +267,7 @@ nav: - 'Router' : 'reference/routing-configuration/http/routing/router.md' - 'Rules & Priority' : 'reference/routing-configuration/http/routing/rules-and-priority.md' - 'Observability': 'reference/routing-configuration/http/routing/observability.md' + - 'Multi-Layer Routing': 'reference/routing-configuration/http/routing/multi-layer-routing.md' - 'Load Balancing' : - 'Service' : 'reference/routing-configuration/http/load-balancing/service.md' - 'ServersTransport' : 'reference/routing-configuration/http/load-balancing/serverstransport.md' @@ -363,6 +364,8 @@ nav: - 'Features': 'deprecation/features.md' - 'User Guides': - 'FastProxy': 'user-guides/fastproxy.md' + - 'Kubernetes and Let''s Encrypt': 'user-guides/crd-acme/index.md' + - 'Kubernetes and cert-manager': 'user-guides/cert-manager.md' - 'gRPC Examples': 'user-guides/grpc.md' - 'WebSocket Examples': 'user-guides/websocket.md' - 'Contributing': diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 354dcfbab..bc667c19a 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -48,6 +48,26 @@ spec: items: type: string type: array + parentRefs: + description: |- + ParentRefs defines references to parent IngressRoute resources for multi-layer routing. + When set, this IngressRoute's routers will be children of the referenced parent IngressRoute's routers. + More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#parentrefs + items: + description: IngressRouteRef is a reference to an IngressRoute resource. + properties: + name: + description: Name defines the name of the referenced IngressRoute + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + IngressRoute resource. + type: string + required: + - name + type: object + type: array routes: description: Routes defines the list of routes. items: diff --git a/integration/fixtures/routing/multi_layer_auth.toml b/integration/fixtures/routing/multi_layer_auth.toml new file mode 100644 index 000000000..4de17abe6 --- /dev/null +++ b/integration/fixtures/routing/multi_layer_auth.toml @@ -0,0 +1,51 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + noColor = true + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[api] + insecure = true + +[providers.file] + filename = "{{ .SelfFilename }}" + +## Dynamic Configuration ## + +[http.middlewares] + [http.middlewares.auth-middleware.forwardAuth] + address = "http://127.0.0.1:{{ .AuthPort }}/auth" + authResponseHeaders = ["X-User-Role", "X-User-Name"] + +[http.services] + [http.services.admin-service.loadBalancer] + [[http.services.admin-service.loadBalancer.servers]] + url = "http://{{ .AdminIP }}:80" + + [http.services.developer-service.loadBalancer] + [[http.services.developer-service.loadBalancer.servers]] + url = "http://{{ .DeveloperIP }}:80" + +[http.routers] + # Parent router: matches path, applies auth middleware + [http.routers.parent-router] + rule = "PathPrefix(`/whoami`)" + middlewares = ["auth-middleware"] + + # Child router for admin role + [http.routers.admin-router] + rule = "Header(`X-User-Role`, `admin`)" + service = "admin-service" + parentRefs = ["parent-router@file"] + + # Child router for developer role + [http.routers.developer-router] + rule = "Header(`X-User-Role`, `developer`)" + service = "developer-service" + parentRefs = ["parent-router@file"] \ No newline at end of file diff --git a/integration/resources/compose/routing.yml b/integration/resources/compose/routing.yml new file mode 100644 index 000000000..88bbc6d93 --- /dev/null +++ b/integration/resources/compose/routing.yml @@ -0,0 +1,8 @@ +services: + whoami-admin: + image: traefik/whoami + hostname: whoami-admin + + whoami-developer: + image: traefik/whoami + hostname: whoami-developer \ No newline at end of file diff --git a/integration/routing_test.go b/integration/routing_test.go new file mode 100644 index 000000000..8f643802b --- /dev/null +++ b/integration/routing_test.go @@ -0,0 +1,154 @@ +package integration + +import ( + "net" + "net/http" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/traefik/traefik/v3/integration/try" +) + +// RoutingSuite tests multi-layer routing with authentication middleware. +type RoutingSuite struct{ BaseSuite } + +func TestRoutingSuite(t *testing.T) { + suite.Run(t, new(RoutingSuite)) +} + +func (s *RoutingSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("routing") + s.composeUp() +} + +func (s *RoutingSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +// authHandler implements the ForwardAuth protocol. +// It validates Bearer tokens and adds X-User-Role and X-User-Name headers. +func authHandler(w http.ResponseWriter, r *http.Request) { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + w.WriteHeader(http.StatusUnauthorized) + return + } + + if !strings.HasPrefix(authHeader, "Bearer ") { + w.WriteHeader(http.StatusUnauthorized) + return + } + + token := strings.TrimPrefix(authHeader, "Bearer ") + role, username, ok := getUserByToken(token) + if !ok { + w.WriteHeader(http.StatusUnauthorized) + return + } + + // Set headers that will be forwarded by Traefik + w.Header().Set("X-User-Role", role) + w.Header().Set("X-User-Name", username) + w.WriteHeader(http.StatusOK) +} + +// getUserByToken returns the role and username for a given token. +func getUserByToken(token string) (role, username string, ok bool) { + users := map[string]struct { + role string + username string + }{ + "bob-token": {role: "admin", username: "bob"}, + "jack-token": {role: "developer", username: "jack"}, + "alice-token": {role: "guest", username: "alice"}, + } + + u, exists := users[token] + return u.role, u.username, exists +} + +// TestMultiLayerRoutingWithAuth tests the complete multi layer routing scenario: +// - Parent router matches path and applies authentication middleware +// - Auth middleware validates token and adds role header +// - Child routers route based on the role header added by the middleware +func (s *RoutingSuite) TestMultiLayerRoutingWithAuth() { + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(s.T(), err) + defer listener.Close() + + _, authPort, err := net.SplitHostPort(listener.Addr().String()) + require.NoError(s.T(), err) + + go func() { + _ = http.Serve(listener, http.HandlerFunc(authHandler)) + }() + + adminIP := s.getComposeServiceIP("whoami-admin") + require.NotEmpty(s.T(), adminIP) + + developerIP := s.getComposeServiceIP("whoami-developer") + require.NotEmpty(s.T(), developerIP) + + file := s.adaptFile("fixtures/routing/multi_layer_auth.toml", struct { + AuthPort string + AdminIP string + DeveloperIP string + }{ + AuthPort: authPort, + AdminIP: adminIP, + DeveloperIP: developerIP, + }) + + s.traefikCmd(withConfigFile(file)) + + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("parent-router")) + require.NoError(s.T(), err) + + // Test 1: bob (admin role) routes to admin-service + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) + require.NoError(s.T(), err) + req.Header.Set("Authorization", "Bearer bob-token") + + err = try.Request(req, 2*time.Second, + try.StatusCodeIs(http.StatusOK), + try.BodyContains("whoami-admin")) + require.NoError(s.T(), err) + + // Test 2: jack (developer role) routes to developer-service + req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) + require.NoError(s.T(), err) + req.Header.Set("Authorization", "Bearer jack-token") + + err = try.Request(req, 2*time.Second, + try.StatusCodeIs(http.StatusOK), + try.BodyContains("whoami-developer")) + require.NoError(s.T(), err) + + // Test 3: Invalid token returns 401 + req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) + require.NoError(s.T(), err) + req.Header.Set("Authorization", "Bearer invalid-token") + + err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusUnauthorized)) + require.NoError(s.T(), err) + + // Test 4: Missing token returns 401 + req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) + require.NoError(s.T(), err) + + err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusUnauthorized)) + require.NoError(s.T(), err) + + // Test 5: Valid auth but role has no matching child router returns 404 + req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) + require.NoError(s.T(), err) + req.Header.Set("Authorization", "Bearer alice-token") + + err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) +} diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index 5d07f96aa..7ea0ac3c7 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -69,6 +69,7 @@ type Router struct { Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"` Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"` Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"` + ParentRefs []string `json:"parentRefs,omitempty" toml:"parentRefs,omitempty" yaml:"parentRefs,omitempty" label:"-" export:"true"` // Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. RuleSyntax string `json:"ruleSyntax,omitempty" toml:"ruleSyntax,omitempty" yaml:"ruleSyntax,omitempty" export:"true"` Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"` diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index 9ffb5d34c..1e7b9e36d 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -1369,6 +1369,11 @@ func (in *Router) DeepCopyInto(out *Router) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.ParentRefs != nil { + in, out := &in.ParentRefs, &out.ParentRefs + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(RouterTLSConfig) diff --git a/pkg/config/runtime/runtime_http.go b/pkg/config/runtime/runtime_http.go index 69d7e540f..f8f33fcec 100644 --- a/pkg/config/runtime/runtime_http.go +++ b/pkg/config/runtime/runtime_http.go @@ -43,7 +43,8 @@ func (c *Configuration) GetRoutersByEntryPoints(ctx context.Context, entryPoints entryPointsRouters[entryPointName][rtName] = rt } - if entryPointsCount == 0 { + // Root routers must have at least one entry point. + if entryPointsCount == 0 && rt.ParentRefs == nil { rt.AddError(errors.New("no valid entryPoint for this router"), true) logger.Error().Msg("No valid entryPoint for this router") } @@ -80,6 +81,11 @@ type RouterInfo struct { // It is the caller's responsibility to set the initial status. Status string `json:"status,omitempty"` Using []string `json:"using,omitempty"` // Effective entry points used by that router. + + // ChildRefs contains the names of child routers. + // This field is only filled during multi-layer routing computation of parentRefs, + // and used when building the runtime configuration. + ChildRefs []string `json:"-"` } // AddError adds err to r.Err, if it does not already exist. diff --git a/pkg/middlewares/accesslog/field_middleware.go b/pkg/middlewares/accesslog/field_middleware.go index 4d439bc25..d0e56e920 100644 --- a/pkg/middlewares/accesslog/field_middleware.go +++ b/pkg/middlewares/accesslog/field_middleware.go @@ -2,6 +2,7 @@ package accesslog import ( "net/http" + "strings" "time" "github.com/rs/zerolog/log" @@ -76,3 +77,37 @@ func InitServiceFields(rw http.ResponseWriter, req *http.Request, next http.Hand next.ServeHTTP(rw, req) } + +const separator = " -> " + +// ConcatFieldHandler concatenates field values instead of overriding them. +type ConcatFieldHandler struct { + next http.Handler + name string + value string +} + +// NewConcatFieldHandler creates a ConcatField handler that concatenates values. +func NewConcatFieldHandler(next http.Handler, name, value string) http.Handler { + return &ConcatFieldHandler{next: next, name: name, value: value} +} + +func (c *ConcatFieldHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + table := GetLogData(req) + if table == nil { + c.next.ServeHTTP(rw, req) + return + } + + // Check if field already exists and concatenate if so + if existingValue, exists := table.Core[c.name]; exists && existingValue != nil { + if existingStr, ok := existingValue.(string); ok && strings.TrimSpace(existingStr) != "" { + table.Core[c.name] = existingStr + separator + c.value + c.next.ServeHTTP(rw, req) + return + } + } + + table.Core[c.name] = c.value + c.next.ServeHTTP(rw, req) +} diff --git a/pkg/middlewares/accesslog/field_middleware_test.go b/pkg/middlewares/accesslog/field_middleware_test.go new file mode 100644 index 000000000..50a8a3406 --- /dev/null +++ b/pkg/middlewares/accesslog/field_middleware_test.go @@ -0,0 +1,96 @@ +package accesslog + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConcatFieldHandler_ServeHTTP(t *testing.T) { + testCases := []struct { + desc string + existingValue interface{} + newValue string + expectedResult string + }{ + { + desc: "first router - no existing value", + existingValue: nil, + newValue: "router1", + expectedResult: "router1", + }, + { + desc: "second router - concatenate with existing string", + existingValue: "router1", + newValue: "router2", + expectedResult: "router1 -> router2", + }, + { + desc: "third router - concatenate with existing chain", + existingValue: "router1 -> router2", + newValue: "router3", + expectedResult: "router1 -> router2 -> router3", + }, + { + desc: "empty existing value - treat as first", + existingValue: " ", + newValue: "router1", + expectedResult: "router1", + }, + { + desc: "non-string existing value - replace with new value", + existingValue: 123, + newValue: "router1", + expectedResult: "router1", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + logData := &LogData{ + Core: CoreLogData{}, + } + if test.existingValue != nil { + logData.Core[RouterName] = test.existingValue + } + + req := httptest.NewRequest(http.MethodGet, "/test", nil) + req = req.WithContext(context.WithValue(req.Context(), DataTableKey, logData)) + + handler := NewConcatFieldHandler(nextHandler, RouterName, test.newValue) + + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + assert.Equal(t, test.expectedResult, logData.Core[RouterName]) + assert.Equal(t, http.StatusOK, rec.Code) + }) + } +} + +func TestConcatFieldHandler_ServeHTTP_NoLogData(t *testing.T) { + nextHandlerCalled := false + nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + nextHandlerCalled = true + w.WriteHeader(http.StatusOK) + }) + + handler := NewConcatFieldHandler(nextHandler, RouterName, "router1") + + // Create request without LogData in context. + req := httptest.NewRequest(http.MethodGet, "/test", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + + // Verify next handler was called and no panic occurred. + assert.True(t, nextHandlerCalled) + assert.Equal(t, http.StatusOK, rec.Code) +} diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index 2da72991e..8062f59eb 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -1180,6 +1180,83 @@ func logWriterTestHandlerFunc(rw http.ResponseWriter, r *http.Request) { rw.WriteHeader(testStatus) } +func TestConcatFieldHandler_LoggerIntegration(t *testing.T) { + logFilePath := filepath.Join(t.TempDir(), "access.log") + config := &otypes.AccessLog{FilePath: logFilePath, Format: CommonFormat} + + logger, err := NewHandler(t.Context(), config) + require.NoError(t, err) + t.Cleanup(func() { + err := logger.Close() + require.NoError(t, err) + }) + + req := &http.Request{ + Header: map[string][]string{ + "User-Agent": {testUserAgent}, + "Referer": {testReferer}, + }, + Proto: testProto, + Host: testHostname, + Method: testMethod, + RemoteAddr: fmt.Sprintf("%s:%d", testHostname, testPort), + URL: &url.URL{ + User: url.UserPassword(testUsername, ""), + Path: testPath, + }, + Body: io.NopCloser(bytes.NewReader([]byte("testdata"))), + } + + chain := alice.New() + chain = chain.Append(capture.Wrap) + + // Injection of the observability variables in the request context. + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + return observability.WithObservabilityHandler(next, observability.Observability{ + AccessLogsEnabled: true, + }), nil + }) + + chain = chain.Append(logger.AliceConstructor()) + + // Simulate multi-layer routing with concatenated router names + var handler http.Handler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + logData := GetLogData(r) + if logData != nil { + logData.Core[ServiceURL] = testServiceName + logData.Core[OriginStatus] = testStatus + logData.Core[OriginContentSize] = testContentSize + logData.Core[RetryAttempts] = testRetryAttempts + logData.Core[StartUTC] = testStart.UTC() + logData.Core[StartLocal] = testStart.Local() + } + rw.WriteHeader(testStatus) + if _, err := rw.Write([]byte(testContent)); err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + } + }) + + // Create chain of ConcatFieldHandlers to simulate multi-layer routing + handler = NewConcatFieldHandler(handler, RouterName, "child-router") + handler = NewConcatFieldHandler(handler, RouterName, "parent-router") + handler = NewConcatFieldHandler(handler, RouterName, "root-router") + + finalHandler, err := chain.Then(handler) + require.NoError(t, err) + + recorder := httptest.NewRecorder() + finalHandler.ServeHTTP(recorder, req) + + logData, err := os.ReadFile(logFilePath) + require.NoError(t, err) + + result, err := ParseAccessLog(string(logData)) + require.NoError(t, err) + + expectedRouterName := "\"root-router -> parent-router -> child-router\"" + assert.Equal(t, expectedRouterName, result[RouterName]) +} + func doLoggingWithAbortedStream(t *testing.T, config *otypes.AccessLog) { t.Helper() diff --git a/pkg/provider/kubernetes/crd/fixtures/parent_refs_cross_namespace_allowed.yml b/pkg/provider/kubernetes/crd/fixtures/parent_refs_cross_namespace_allowed.yml new file mode 100644 index 000000000..9d25cf43c --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/parent_refs_cross_namespace_allowed.yml @@ -0,0 +1,28 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: parent-cross + namespace: ns-a +spec: + entryPoints: + - web + routes: + - match: Host(`cross.example.com`) + kind: Rule +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: child-cross-allowed + namespace: ns-b +spec: + parentRefs: + - name: parent-cross + namespace: ns-a + routes: + - match: Path(`/cross`) + kind: Rule + services: + - name: cross-service + port: 9000 \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/fixtures/parent_refs_cross_namespace_denied.yml b/pkg/provider/kubernetes/crd/fixtures/parent_refs_cross_namespace_denied.yml new file mode 100644 index 000000000..0ca584e74 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/parent_refs_cross_namespace_denied.yml @@ -0,0 +1,28 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: parent-cross + namespace: ns-a +spec: + entryPoints: + - web + routes: + - match: Host(`cross.example.com`) + kind: Rule +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: child-cross-denied + namespace: ns-b +spec: + parentRefs: + - name: parent-cross + namespace: ns-a + routes: + - match: Path(`/denied`) + kind: Rule + services: + - name: cross-service + port: 9000 \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/fixtures/parent_refs_default_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/parent_refs_default_namespace.yml new file mode 100644 index 000000000..aa82b47ca --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/parent_refs_default_namespace.yml @@ -0,0 +1,27 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: parent-default + namespace: default +spec: + entryPoints: + - web + routes: + - match: Host(`default.example.com`) + kind: Rule +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: child-same + namespace: default +spec: + parentRefs: + - name: parent-default + routes: + - match: Path(`/same`) + kind: Rule + services: + - name: same-service + port: 9000 \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/fixtures/parent_refs_missing_parent.yml b/pkg/provider/kubernetes/crd/fixtures/parent_refs_missing_parent.yml new file mode 100644 index 000000000..c35ac69e8 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/parent_refs_missing_parent.yml @@ -0,0 +1,16 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: child-missing-parent + namespace: default +spec: + parentRefs: + - name: non-existent-parent + namespace: default + routes: + - match: Path(`/missing`) + kind: Rule + services: + - name: child-service + port: 9000 \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/fixtures/parent_refs_multiple_parents.yml b/pkg/provider/kubernetes/crd/fixtures/parent_refs_multiple_parents.yml new file mode 100644 index 000000000..e4f099b0d --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/parent_refs_multiple_parents.yml @@ -0,0 +1,42 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: parent-a + namespace: default +spec: + entryPoints: + - web + routes: + - match: Host(`a.example.com`) + kind: Rule +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: parent-b + namespace: default +spec: + entryPoints: + - web + routes: + - match: Host(`b.example.com`) + kind: Rule +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: child-multi-parents + namespace: default +spec: + parentRefs: + - name: parent-a + namespace: default + - name: parent-b + namespace: default + routes: + - match: Path(`/shared`) + kind: Rule + services: + - name: shared-service + port: 9000 \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/fixtures/parent_refs_services.yml b/pkg/provider/kubernetes/crd/fixtures/parent_refs_services.yml new file mode 100644 index 000000000..0523ea401 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/parent_refs_services.yml @@ -0,0 +1,139 @@ +apiVersion: v1 +kind: Service +metadata: + name: child-service + namespace: default +spec: + ports: + - name: web + port: 9000 +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: child-service-abc + namespace: default + labels: + kubernetes.io/service-name: child-service +addressType: IPv4 +ports: + - name: web + port: 9000 +endpoints: + - addresses: + - 10.10.2.1 + - 10.10.2.2 + conditions: + ready: true +--- +apiVersion: v1 +kind: Service +metadata: + name: users-service + namespace: default +spec: + ports: + - name: web + port: 9000 +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: users-service-abc + namespace: default + labels: + kubernetes.io/service-name: users-service +addressType: IPv4 +ports: + - name: web + port: 9000 +endpoints: + - addresses: + - 10.10.5.1 + - 10.10.5.2 + conditions: + ready: true +--- +apiVersion: v1 +kind: Service +metadata: + name: shared-service + namespace: default +spec: + ports: + - name: web + port: 9000 +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: shared-service-abc + namespace: default + labels: + kubernetes.io/service-name: shared-service +addressType: IPv4 +ports: + - name: web + port: 9000 +endpoints: + - addresses: + - 10.10.8.1 + - 10.10.8.2 + conditions: + ready: true +--- +apiVersion: v1 +kind: Service +metadata: + name: cross-service + namespace: ns-b +spec: + ports: + - name: web + port: 9000 +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: cross-service-abc + namespace: ns-b + labels: + kubernetes.io/service-name: cross-service +addressType: IPv4 +ports: + - name: web + port: 9000 +endpoints: + - addresses: + - 10.10.11.1 + - 10.10.11.2 + conditions: + ready: true +--- +apiVersion: v1 +kind: Service +metadata: + name: same-service + namespace: default +spec: + ports: + - name: web + port: 9000 +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: same-service-abc + namespace: default + labels: + kubernetes.io/service-name: same-service +addressType: IPv4 +ports: + - name: web + port: 9000 +endpoints: + - addresses: + - 10.10.14.1 + - 10.10.14.2 + conditions: + ready: true \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/fixtures/parent_refs_single_parent_multiple_routes.yml b/pkg/provider/kubernetes/crd/fixtures/parent_refs_single_parent_multiple_routes.yml new file mode 100644 index 000000000..4d91e32a6 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/parent_refs_single_parent_multiple_routes.yml @@ -0,0 +1,30 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: parent-multi + namespace: default +spec: + entryPoints: + - web + routes: + - match: Host(`api.example.com`) && PathPrefix(`/v1`) + kind: Rule + - match: Host(`api.example.com`) && PathPrefix(`/v2`) + kind: Rule +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: child-multi-routes + namespace: default +spec: + parentRefs: + - name: parent-multi + namespace: default + routes: + - match: Path(`/users`) + kind: Rule + services: + - name: users-service + port: 9000 \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/fixtures/parent_refs_single_parent_single_route.yml b/pkg/provider/kubernetes/crd/fixtures/parent_refs_single_parent_single_route.yml new file mode 100644 index 000000000..073776252 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/parent_refs_single_parent_single_route.yml @@ -0,0 +1,28 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: parent-single + namespace: default +spec: + entryPoints: + - web + routes: + - match: Host(`parent.example.com`) + kind: Rule +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: child-single + namespace: default +spec: + parentRefs: + - name: parent-single + namespace: default + routes: + - match: Path(`/api`) + kind: Rule + services: + - name: child-service + port: 9000 \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index b9f5c0aa5..92b45a124 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -1379,15 +1379,18 @@ func buildCertificates(client Client, tlsStore, namespace string, certificates [ return nil } -func makeServiceKey(rule, ingressName string) (string, error) { +func makeServiceKey(rule, ingressName string) string { h := sha256.New() + + // As explained in https://pkg.go.dev/hash#Hash, + // Write never returns an error. if _, err := h.Write([]byte(rule)); err != nil { - return "", err + return "" } key := fmt.Sprintf("%s-%.10x", ingressName, h.Sum(nil)) - return key, nil + return key } func makeID(namespace, name string) string { diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index bf785af63..346752c42 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -61,6 +61,12 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli disableClusterScopeResources: p.DisableClusterScopeResources, } + parentRouterNames, err := resolveParentRouterNames(client, ingressRoute, p.AllowCrossNamespace) + if err != nil { + logger.Error().Err(err).Msg("Error resolving parent routers") + continue + } + for _, route := range ingressRoute.Spec.Routes { if len(route.Kind) > 0 && route.Kind != "Rule" { logger.Error().Msgf("Unsupported match kind: %s. Only \"Rule\" is supported for now.", route.Kind) @@ -72,11 +78,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli continue } - serviceKey, err := makeServiceKey(route.Match, ingressName) - if err != nil { - logger.Error().Err(err).Send() - continue - } + serviceKey := makeServiceKey(route.Match, ingressName) mds, err := p.makeMiddlewareKeys(ctx, ingressRoute.Namespace, route.Middlewares) if err != nil { @@ -87,7 +89,8 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli normalized := provider.Normalize(makeID(ingressRoute.Namespace, serviceKey)) serviceName := normalized - if len(route.Services) > 1 { + switch { + case len(route.Services) > 1: spec := traefikv1alpha1.TraefikServiceSpec{ Weighted: &traefikv1alpha1.WeightedRoundRobin{ Services: route.Services, @@ -99,7 +102,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli logger.Error().Err(errBuild).Send() continue } - } else if len(route.Services) == 1 { + case len(route.Services) == 1: fullName, serversLB, err := cb.nameAndService(ctx, ingressRoute.Namespace, route.Services[0].LoadBalancerSpec) if err != nil { logger.Error().Err(err).Send() @@ -111,6 +114,9 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli } else { serviceName = fullName } + default: + // Routes without services leave serviceName empty. + serviceName = "" } r := &dynamic.Router{ @@ -121,6 +127,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli Rule: route.Match, Service: serviceName, Observability: route.Observability, + ParentRefs: parentRouterNames, } if ingressRoute.Spec.TLS != nil { @@ -202,6 +209,50 @@ func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace str return mds, nil } +// resolveParentRouterNames resolves parent IngressRoute references to router names. +// It returns the list of parent router names and an error if one occurred during processing. +func resolveParentRouterNames(client Client, ingressRoute *traefikv1alpha1.IngressRoute, allowCrossNamespace bool) ([]string, error) { + // If no parent refs, return empty list (not an error). + if len(ingressRoute.Spec.ParentRefs) == 0 { + return nil, nil + } + + var parentRouterNames []string + for _, parentRef := range ingressRoute.Spec.ParentRefs { + // Determine parent namespace (default to child namespace if not specified). + parentNamespace := parentRef.Namespace + if parentNamespace == "" { + parentNamespace = ingressRoute.Namespace + } + + // Validate cross-namespace access. + if !isNamespaceAllowed(allowCrossNamespace, ingressRoute.Namespace, parentNamespace) { + return nil, fmt.Errorf("cross-namespace reference to parent IngressRoute %s/%s not allowed", parentNamespace, parentRef.Name) + } + + var parentIngressRoute *traefikv1alpha1.IngressRoute + for _, ir := range client.GetIngressRoutes() { + if ir.Name == parentRef.Name && ir.Namespace == parentNamespace { + parentIngressRoute = ir + break + } + } + + if parentIngressRoute == nil { + return nil, fmt.Errorf("parent IngressRoute %s/%s does not exist", parentNamespace, parentRef.Name) + } + + // Compute router names for all routes in parent IngressRoute. + for _, route := range parentIngressRoute.Spec.Routes { + serviceKey := makeServiceKey(route.Match, parentIngressRoute.Name) + routerName := provider.Normalize(makeID(parentIngressRoute.Namespace, serviceKey)) + parentRouterNames = append(parentRouterNames, routerName) + } + } + + return parentRouterNames, nil +} + type configBuilder struct { client Client allowCrossNamespace bool diff --git a/pkg/provider/kubernetes/crd/kubernetes_tcp.go b/pkg/provider/kubernetes/crd/kubernetes_tcp.go index c4c3fa6d4..bbe48f638 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_tcp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_tcp.go @@ -50,11 +50,7 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client continue } - key, err := makeServiceKey(route.Match, ingressName) - if err != nil { - logger.Error().Err(err).Send() - continue - } + key := makeServiceKey(route.Match, ingressName) mds, err := p.makeMiddlewareTCPKeys(ctx, ingressRouteTCP.Namespace, route.Middlewares) if err != nil { diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index ac6b2cbac..124fd87c9 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -5351,6 +5351,322 @@ func TestLoadIngressRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "IngressRoute with single parent (single route)", + paths: []string{"parent_refs_services.yml", "parent_refs_single_parent_single_route.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-parent-single-3c07cfffe8e5f876a01e": { + EntryPoints: []string{"web"}, + Rule: "Host(`parent.example.com`)", + }, + "default-child-single-2bba0a3de1b50b70a519": { + Service: "default-child-single-2bba0a3de1b50b70a519", + Rule: "Path(`/api`)", + ParentRefs: []string{"default-parent-single-3c07cfffe8e5f876a01e"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-child-single-2bba0a3de1b50b70a519": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "http://10.10.2.1:9000", + }, + { + URL: "http://10.10.2.2:9000", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "IngressRoute with single parent (multiple routes) - all parent routers in ParentRefs", + paths: []string{"parent_refs_services.yml", "parent_refs_single_parent_multiple_routes.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-parent-multi-4aac0d541c2b669a2d5d": { + EntryPoints: []string{"web"}, + Rule: "Host(`api.example.com`) && PathPrefix(`/v1`)", + }, + "default-parent-multi-0af1ca0a94f5b87a125e": { + EntryPoints: []string{"web"}, + Rule: "Host(`api.example.com`) && PathPrefix(`/v2`)", + }, + "default-child-multi-routes-b0479051e6a353d66211": { + Service: "default-child-multi-routes-b0479051e6a353d66211", + Rule: "Path(`/users`)", + ParentRefs: []string{"default-parent-multi-4aac0d541c2b669a2d5d", "default-parent-multi-0af1ca0a94f5b87a125e"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-child-multi-routes-b0479051e6a353d66211": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "http://10.10.5.1:9000", + }, + { + URL: "http://10.10.5.2:9000", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "IngressRoute with multiple parents", + paths: []string{"parent_refs_services.yml", "parent_refs_multiple_parents.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-parent-a-629990b524bf9a1a8d27": { + EntryPoints: []string{"web"}, + Rule: "Host(`a.example.com`)", + }, + "default-parent-b-add617f9b95cff009054": { + EntryPoints: []string{"web"}, + Rule: "Host(`b.example.com`)", + }, + "default-child-multi-parents-8013b5025acddd1761d1": { + Service: "default-child-multi-parents-8013b5025acddd1761d1", + Rule: "Path(`/shared`)", + ParentRefs: []string{"default-parent-a-629990b524bf9a1a8d27", "default-parent-b-add617f9b95cff009054"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-child-multi-parents-8013b5025acddd1761d1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "http://10.10.8.1:9000", + }, + { + URL: "http://10.10.8.2:9000", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "IngressRoute with missing parent - routers skipped", + paths: []string{"parent_refs_services.yml", "parent_refs_missing_parent.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "IngressRoute with cross-namespace parent allowed", + allowCrossNamespace: true, + paths: []string{"parent_refs_services.yml", "parent_refs_cross_namespace_allowed.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "ns-a-parent-cross-74575ab54671a3ede28c": { + EntryPoints: []string{"web"}, + Rule: "Host(`cross.example.com`)", + }, + "ns-b-child-cross-allowed-0bad04de665623bf2362": { + Service: "ns-b-child-cross-allowed-0bad04de665623bf2362", + Rule: "Path(`/cross`)", + ParentRefs: []string{"ns-a-parent-cross-74575ab54671a3ede28c"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "ns-b-child-cross-allowed-0bad04de665623bf2362": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "http://10.10.11.1:9000", + }, + { + URL: "http://10.10.11.2:9000", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "IngressRoute with cross-namespace parent denied", + allowCrossNamespace: false, + paths: []string{"parent_refs_services.yml", "parent_refs_cross_namespace_denied.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "ns-a-parent-cross-74575ab54671a3ede28c": { + EntryPoints: []string{"web"}, + Rule: "Host(`cross.example.com`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "IngressRoute with parent namespace defaulting to child namespace", + paths: []string{"parent_refs_services.yml", "parent_refs_default_namespace.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-parent-default-9b8ab283eeed3eb66561": { + EntryPoints: []string{"web"}, + Rule: "Host(`default.example.com`)", + }, + "default-child-same-9234eba1edcfbd8a7723": { + Service: "default-child-same-9234eba1edcfbd8a7723", + Rule: "Path(`/same`)", + ParentRefs: []string{"default-parent-default-9b8ab283eeed3eb66561"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-child-same-9234eba1edcfbd8a7723": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "http://10.10.14.1:9000", + }, + { + URL: "http://10.10.14.2:9000", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, } for _, test := range testCases { diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go index f723afcda..d7b7af331 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go @@ -19,6 +19,10 @@ type IngressRouteSpec struct { // TLS defines the TLS configuration. // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/router/#tls TLS *TLS `json:"tls,omitempty"` + // ParentRefs defines references to parent IngressRoute resources for multi-layer routing. + // When set, this IngressRoute's routers will be children of the referenced parent IngressRoute's routers. + // More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#parentrefs + ParentRefs []IngressRouteRef `json:"parentRefs,omitempty"` } // Route holds the HTTP route configuration. @@ -211,6 +215,14 @@ type MiddlewareRef struct { Namespace string `json:"namespace,omitempty"` } +// IngressRouteRef is a reference to an IngressRoute resource. +type IngressRouteRef struct { + // Name defines the name of the referenced IngressRoute resource. + Name string `json:"name"` + // Namespace defines the namespace of the referenced IngressRoute resource. + Namespace string `json:"namespace,omitempty"` +} + // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:storageversion diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go index be6159b65..a7e852863 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go @@ -432,6 +432,22 @@ func (in *IngressRouteList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IngressRouteRef) DeepCopyInto(out *IngressRouteRef) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressRouteRef. +func (in *IngressRouteRef) DeepCopy() *IngressRouteRef { + if in == nil { + return nil + } + out := new(IngressRouteRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressRouteSpec) DeepCopyInto(out *IngressRouteSpec) { *out = *in @@ -452,6 +468,11 @@ func (in *IngressRouteSpec) DeepCopyInto(out *IngressRouteSpec) { *out = new(TLS) (*in).DeepCopyInto(*out) } + if in.ParentRefs != nil { + in, out := &in.ParentRefs, &out.ParentRefs + *out = make([]IngressRouteRef, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/server/aggregator.go b/pkg/server/aggregator.go index 8f22474fe..94006244b 100644 --- a/pkg/server/aggregator.go +++ b/pkg/server/aggregator.go @@ -46,7 +46,9 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint for pvd, configuration := range configurations { if configuration.HTTP != nil { for routerName, router := range configuration.HTTP.Routers { - if len(router.EntryPoints) == 0 { + // If no entrypoint is defined, and the router has no parentRefs (i.e. is not a child router), + // we set the default entrypoints. + if len(router.EntryPoints) == 0 && router.ParentRefs == nil { log.Debug(). Str(logs.RouterName, routerName). Strs(logs.EntryPointName, defaultEntryPoints). @@ -164,6 +166,11 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration { rts := make(map[string]*dynamic.Router) for name, rt := range cfg.HTTP.Routers { + // Only root routers can have models applied. + if rt.ParentRefs != nil { + continue + } + router := rt.DeepCopy() if !router.DefaultRule && router.RuleSyntax == "" { @@ -265,6 +272,11 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration { func applyDefaultObservabilityModel(cfg dynamic.Configuration) { if cfg.HTTP != nil { for _, router := range cfg.HTTP.Routers { + // Only root routers can have models applied. + if router.ParentRefs != nil { + continue + } + if router.Observability == nil { router.Observability = &dynamic.RouterObservabilityConfig{ AccessLogs: pointer(true), diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index bac2d5c48..590dec468 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -6,6 +6,8 @@ import ( "fmt" "math" "net/http" + "slices" + "sort" "strings" "github.com/containous/alice" @@ -149,6 +151,12 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str continue } + // Only build handlers for root routers (routers without ParentRefs). + // Routers with ParentRefs will be built as part of their parent router's muxer. + if len(routerConfig.ParentRefs) > 0 { + continue + } + handler, err := m.buildRouterHandler(ctxRouter, routerName, routerConfig) if err != nil { routerConfig.AddError(err, true) @@ -215,33 +223,272 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn } router.Middlewares = qualifiedNames - if router.Service == "" { - return nil, errors.New("the service is missing on the router") - } - - qualifiedService := provider.GetQualifiedName(ctx, router.Service) - chain := alice.New() if router.DefaultRule { chain = chain.Append(denyrouterrecursion.WrapHandler(routerName)) } - // Access logs, metrics, and tracing middlewares are idempotent if the associated signal is disabled. - chain = chain.Append(observability.WrapRouterHandler(ctx, routerName, router.Rule, qualifiedService)) - metricsHandler := metricsMiddle.RouterMetricsHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, qualifiedService) + var ( + nextHandler http.Handler + serviceName string + err error + ) + // Check if this router has child routers or a service. + switch { + case len(router.ChildRefs) > 0: + // This router routes to child routers - create a muxer for them + nextHandler, err = m.buildChildRoutersMuxer(ctx, router.ChildRefs) + if err != nil { + return nil, fmt.Errorf("building child routers muxer: %w", err) + } + serviceName = fmt.Sprintf("%s-muxer", routerName) + case router.Service != "": + // This router routes to a service + qualifiedService := provider.GetQualifiedName(ctx, router.Service) + nextHandler, err = m.serviceManager.BuildHTTP(ctx, qualifiedService) + if err != nil { + return nil, err + } + serviceName = qualifiedService + default: + return nil, errors.New("router must have either a service or child routers") + } + + // Access logs, metrics, and tracing middlewares are idempotent if the associated signal is disabled. + chain = chain.Append(observability.WrapRouterHandler(ctx, routerName, router.Rule, serviceName)) + + metricsHandler := metricsMiddle.RouterMetricsHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, serviceName) chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler)) + chain = chain.Append(func(next http.Handler) (http.Handler, error) { - return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil + return accesslog.NewConcatFieldHandler(next, accesslog.RouterName, routerName), nil }) mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares) - sHandler, err := m.serviceManager.BuildHTTP(ctx, qualifiedService) - if err != nil { - return nil, err + return chain.Extend(*mHandler).Then(nextHandler) +} + +// ParseRouterTree sets up router tree and validates router configuration. +// This function performs the following operations in order: +// +// 1. Populate ChildRefs: Uses ParentRefs to build the parent-child relationship graph +// 2. Root-first traversal: Starting from root routers (no ParentRefs), traverses the tree +// 3. Cycle detection: Detects circular dependencies and removes cyclic links +// 4. Reachability check: Marks routers unreachable from any root as disabled +// 5. Dead-end detection: Marks routers with no service and no children as disabled +// 6. Validation: Checks for configuration errors +// +// Router status is set during this process: +// - Enabled: Reachable routers with valid configuration +// - Disabled: Unreachable, dead-end, or routers with critical errors +// - Warning: Routers with non-critical errors (like cycles) +// +// The function modifies router.Status, router.ChildRefs, and adds errors to router.Err. +func (m *Manager) ParseRouterTree() { + if m.conf == nil || m.conf.Routers == nil { + return } - return chain.Extend(*mHandler).Then(sHandler) + // Populate ChildRefs based on ParentRefs and find root routers. + var rootRouters []string + for routerName, router := range m.conf.Routers { + if len(router.ParentRefs) == 0 { + rootRouters = append(rootRouters, routerName) + continue + } + + for _, parentName := range router.ParentRefs { + if parentRouter, exists := m.conf.Routers[parentName]; exists { + // Add this router as a child of its parent + if !slices.Contains(parentRouter.ChildRefs, routerName) { + parentRouter.ChildRefs = append(parentRouter.ChildRefs, routerName) + } + } else { + router.AddError(fmt.Errorf("parent router %q does not exist", parentName), true) + } + } + + // Check for non-root router with TLS config. + if router.TLS != nil { + router.AddError(errors.New("non-root router cannot have TLS configuration"), true) + continue + } + + // Check for non-root router with Observability config. + if router.Observability != nil { + router.AddError(errors.New("non-root router cannot have Observability configuration"), true) + continue + } + + // Check for non-root router with Entrypoint config. + if len(router.EntryPoints) > 0 { + router.AddError(errors.New("non-root router cannot have Entrypoints configuration"), true) + continue + } + } + sort.Strings(rootRouters) + + // Root-first traversal with cycle detection. + visited := make(map[string]bool) + currentPath := make(map[string]bool) + var path []string + + for _, rootName := range rootRouters { + if !visited[rootName] { + m.traverse(rootName, visited, currentPath, path) + } + } + + for routerName, router := range m.conf.Routers { + // Set status for all routers based on reachability. + if !visited[routerName] { + router.AddError(errors.New("router is not reachable"), true) + continue + } + + // Detect dead-end routers (no service + no children) - AFTER cycle handling. + if router.Service == "" && len(router.ChildRefs) == 0 { + router.AddError(errors.New("router has no service and no child routers"), true) + continue + } + + // Check for router with service that is referenced as a parent. + if router.Service != "" && len(router.ChildRefs) > 0 { + router.AddError(errors.New("router has both a service and is referenced as a parent by other routers"), true) + continue + } + } +} + +// traverse performs a depth-first traversal starting from root routers, +// detecting cycles and marking visited routers for reachability detection. +func (m *Manager) traverse(routerName string, visited, currentPath map[string]bool, path []string) { + if currentPath[routerName] { + // Found a cycle - handle it properly. + m.handleCycle(routerName, path) + return + } + + if visited[routerName] { + return + } + + router, exists := m.conf.Routers[routerName] + // Since the ChildRefs population already guarantees router existence, this check is purely defensive. + if !exists { + visited[routerName] = true + return + } + + visited[routerName] = true + currentPath[routerName] = true + newPath := append(path, routerName) + + // Sort ChildRefs for deterministic behavior. + sortedChildRefs := make([]string, len(router.ChildRefs)) + copy(sortedChildRefs, router.ChildRefs) + sort.Strings(sortedChildRefs) + + // Traverse children. + for _, childName := range sortedChildRefs { + m.traverse(childName, visited, currentPath, newPath) + } + + currentPath[routerName] = false +} + +// handleCycle handles cycle detection and removes the victim from guilty router's ChildRefs. +func (m *Manager) handleCycle(victimRouter string, path []string) { + // Find where the cycle starts in the path + cycleStart := -1 + for i, name := range path { + if name == victimRouter { + cycleStart = i + break + } + } + + if cycleStart < 0 { + return + } + + // Build the cycle path: from cycle start to current + victim. + cyclePath := append(path[cycleStart:], victimRouter) + cycleRouters := strings.Join(cyclePath, " -> ") + + // The guilty router is the last one in the path (the one creating the cycle). + if len(path) > 0 { + guiltyRouterName := path[len(path)-1] + guiltyRouter, exists := m.conf.Routers[guiltyRouterName] + if !exists { + return + } + + // Add cycle error to guilty router. + guiltyRouter.AddError(fmt.Errorf("cyclic reference detected in router tree: %s", cycleRouters), false) + + // Remove victim from guilty router's ChildRefs. + for i, childRef := range guiltyRouter.ChildRefs { + if childRef == victimRouter { + guiltyRouter.ChildRefs = append(guiltyRouter.ChildRefs[:i], guiltyRouter.ChildRefs[i+1:]...) + break + } + } + } +} + +// buildChildRoutersMuxer creates a muxer for child routers. +func (m *Manager) buildChildRoutersMuxer(ctx context.Context, childRefs []string) (http.Handler, error) { + childMuxer := httpmuxer.NewMuxer(m.parser) + + // Set a default handler for the child muxer (404 Not Found). + childMuxer.SetDefaultHandler(http.NotFoundHandler()) + + childCount := 0 + for _, childName := range childRefs { + childRouter, exists := m.conf.Routers[childName] + if !exists { + return nil, fmt.Errorf("child router %q does not exist", childName) + } + + // Skip child routers with errors. + if len(childRouter.Err) > 0 { + continue + } + + logger := log.Ctx(ctx).With().Str(logs.RouterName, childName).Logger() + ctxChild := logger.WithContext(provider.AddInContext(ctx, childName)) + + // Set priority if not set. + if childRouter.Priority == 0 { + childRouter.Priority = httpmuxer.GetRulePriority(childRouter.Rule) + } + + // Build the child router handler. + childHandler, err := m.buildRouterHandler(ctxChild, childName, childRouter) + if err != nil { + childRouter.AddError(err, true) + logger.Error().Err(err).Send() + continue + } + + // Add the child router to the muxer. + if err = childMuxer.AddRoute(childRouter.Rule, childRouter.RuleSyntax, childRouter.Priority, childHandler); err != nil { + childRouter.AddError(err, true) + logger.Error().Err(err).Send() + continue + } + + childCount++ + } + + // Prevent empty muxer. + if childCount == 0 { + return nil, fmt.Errorf("no child routers could be added to muxer (%d skipped)", len(childRefs)) + } + + return childMuxer, nil } diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index 1458b2e99..839494f0b 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -1,6 +1,7 @@ package router import ( + "context" "crypto/tls" "io" "math" @@ -11,6 +12,7 @@ import ( "testing" "time" + "github.com/containous/alice" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" @@ -927,6 +929,940 @@ func BenchmarkService(b *testing.B) { } } +func TestManager_ComputeMultiLayerRouting(t *testing.T) { + testCases := []struct { + desc string + routers map[string]*dynamic.Router + expectedStatuses map[string]string + expectedChildRefs map[string][]string + expectedErrors map[string][]string + expectedErrorCounts map[string]int + }{ + { + desc: "Simple router", + routers: map[string]*dynamic.Router{ + "A": { + Service: "A-service", + }, + }, + expectedStatuses: map[string]string{ + "A": runtime.StatusEnabled, + }, + expectedChildRefs: map[string][]string{ + "A": {}, + }, + }, + { + // A->B1 + // ->B2 + desc: "Router with two children", + routers: map[string]*dynamic.Router{ + "A": {}, + "B1": { + ParentRefs: []string{"A"}, + Service: "B1-service", + }, + "B2": { + ParentRefs: []string{"A"}, + Service: "B2-service", + }, + }, + expectedStatuses: map[string]string{ + "A": runtime.StatusEnabled, + "B1": runtime.StatusEnabled, + "B2": runtime.StatusEnabled, + }, + expectedChildRefs: map[string][]string{ + "A": {"B1", "B2"}, + "B1": nil, + "B2": nil, + }, + }, + { + desc: "Non-root router with TLS config", + routers: map[string]*dynamic.Router{ + "A": {}, + "B": { + ParentRefs: []string{"A"}, + Service: "B-service", + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + expectedStatuses: map[string]string{ + "A": runtime.StatusEnabled, + "B": runtime.StatusDisabled, + }, + expectedChildRefs: map[string][]string{ + "A": {"B"}, + "B": nil, + }, + expectedErrors: map[string][]string{ + "B": {"non-root router cannot have TLS configuration"}, + }, + }, + { + desc: "Non-root router with observability config", + routers: map[string]*dynamic.Router{ + "A": {}, + "B": { + ParentRefs: []string{"A"}, + Service: "B-service", + Observability: &dynamic.RouterObservabilityConfig{}, + }, + }, + expectedStatuses: map[string]string{ + "A": runtime.StatusEnabled, + "B": runtime.StatusDisabled, + }, + expectedChildRefs: map[string][]string{ + "A": {"B"}, + "B": nil, + }, + expectedErrors: map[string][]string{ + "B": {"non-root router cannot have Observability configuration"}, + }, + }, + { + desc: "Non-root router with EntryPoints config", + routers: map[string]*dynamic.Router{ + "A": {}, + "B": { + ParentRefs: []string{"A"}, + Service: "B-service", + EntryPoints: []string{"web"}, + }, + }, + expectedStatuses: map[string]string{ + "A": runtime.StatusEnabled, + "B": runtime.StatusDisabled, + }, + expectedChildRefs: map[string][]string{ + "A": {"B"}, + "B": nil, + }, + expectedErrors: map[string][]string{ + "B": {"non-root router cannot have Entrypoints configuration"}, + }, + }, + + { + desc: "Router with non-existing parent", + routers: map[string]*dynamic.Router{ + "B": { + ParentRefs: []string{"A"}, + Service: "B-service", + }, + }, + expectedStatuses: map[string]string{ + "B": runtime.StatusDisabled, + }, + expectedChildRefs: map[string][]string{ + "B": nil, + }, + expectedErrors: map[string][]string{ + "B": {"parent router \"A\" does not exist", "router is not reachable"}, + }, + }, + { + desc: "Dead-end router with no child and no service", + routers: map[string]*dynamic.Router{ + "A": {}, + }, + expectedStatuses: map[string]string{ + "A": runtime.StatusDisabled, + }, + expectedChildRefs: map[string][]string{ + "A": {}, + }, + expectedErrors: map[string][]string{ + "A": {"router has no service and no child routers"}, + }, + }, + { + // A->B->A + desc: "Router is not reachable", + routers: map[string]*dynamic.Router{ + "A": { + ParentRefs: []string{"B"}, + }, + "B": { + ParentRefs: []string{"A"}, + }, + }, + expectedStatuses: map[string]string{ + "A": runtime.StatusDisabled, + "B": runtime.StatusDisabled, + }, + expectedChildRefs: map[string][]string{ + "A": {"B"}, + "B": {"A"}, + }, + // Cycle detection does not visit unreachable routers (it avoids computing the cycle dependency graph for unreachable routers). + expectedErrors: map[string][]string{ + "A": {"router is not reachable"}, + "B": {"router is not reachable"}, + }, + }, + { + // A->B->C->D->B + desc: "Router creating a cycle is a dead-end and should be disabled", + routers: map[string]*dynamic.Router{ + "A": {}, + "B": { + ParentRefs: []string{"A", "D"}, + }, + "C": { + ParentRefs: []string{"B"}, + }, + "D": { + ParentRefs: []string{"C"}, + }, + }, + expectedStatuses: map[string]string{ + "A": runtime.StatusEnabled, + "B": runtime.StatusEnabled, + "C": runtime.StatusEnabled, + "D": runtime.StatusDisabled, // Dead-end router is disabled, because the cycle error broke the link with B. + }, + expectedChildRefs: map[string][]string{ + "A": {"B"}, + "B": {"C"}, + "C": {"D"}, + "D": {}, + }, + expectedErrors: map[string][]string{ + "D": { + "cyclic reference detected in router tree: B -> C -> D -> B", + "router has no service and no child routers", + }, + }, + }, + { + // A->B->C->D->B + // ->E + desc: "Router creating a cycle A->B->C->D->B but which is referenced elsewhere, must be set to warning status", + routers: map[string]*dynamic.Router{ + "A": {}, + "B": { + ParentRefs: []string{"A", "D"}, + }, + "C": { + ParentRefs: []string{"B"}, + }, + "D": { + ParentRefs: []string{"C"}, + }, + "E": { + ParentRefs: []string{"D"}, + Service: "E-service", + }, + }, + expectedStatuses: map[string]string{ + "A": runtime.StatusEnabled, + "B": runtime.StatusEnabled, + "C": runtime.StatusEnabled, + "D": runtime.StatusWarning, + "E": runtime.StatusEnabled, + }, + expectedChildRefs: map[string][]string{ + "A": {"B"}, + "B": {"C"}, + "C": {"D"}, + "D": {"E"}, + }, + expectedErrors: map[string][]string{ + "D": {"cyclic reference detected in router tree: B -> C -> D -> B"}, + }, + }, + { + desc: "Parent router with all children having errors", + routers: map[string]*dynamic.Router{ + "parent": {}, + "child-a": { + ParentRefs: []string{"parent"}, + Service: "child-a-service", + TLS: &dynamic.RouterTLSConfig{}, // Invalid: non-root cannot have TLS + }, + "child-b": { + ParentRefs: []string{"parent"}, + Service: "child-b-service", + TLS: &dynamic.RouterTLSConfig{}, // Invalid: non-root cannot have TLS + }, + }, + expectedStatuses: map[string]string{ + "parent": runtime.StatusEnabled, // Enabled during ParseRouterTree (no config errors). Would be disabled during handler building when empty muxer is detected. + "child-a": runtime.StatusDisabled, + "child-b": runtime.StatusDisabled, + }, + expectedChildRefs: map[string][]string{ + "parent": {"child-a", "child-b"}, + "child-a": nil, + "child-b": nil, + }, + expectedErrors: map[string][]string{ + "child-a": {"non-root router cannot have TLS configuration"}, + "child-b": {"non-root router cannot have TLS configuration"}, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + // Create runtime routers + runtimeRouters := make(map[string]*runtime.RouterInfo) + for name, router := range test.routers { + runtimeRouters[name] = &runtime.RouterInfo{ + Router: router, + Status: runtime.StatusEnabled, + } + } + + conf := &runtime.Configuration{ + Routers: runtimeRouters, + } + + manager := &Manager{ + conf: conf, + } + + // Execute the function we're testing + manager.ParseRouterTree() + + // Verify ChildRefs are populated correctly + for routerName, expectedChildren := range test.expectedChildRefs { + router := runtimeRouters[routerName] + assert.ElementsMatch(t, expectedChildren, router.ChildRefs) + } + + // Verify statuses are set correctly + var gotStatuses map[string]string + for routerName, router := range runtimeRouters { + if gotStatuses == nil { + gotStatuses = make(map[string]string) + } + gotStatuses[routerName] = router.Status + } + assert.Equal(t, test.expectedStatuses, gotStatuses) + + // Verify errors are added correctly + var gotErrors map[string][]string + for routerName, router := range runtimeRouters { + for _, err := range router.Err { + if gotErrors == nil { + gotErrors = make(map[string][]string) + } + gotErrors[routerName] = append(gotErrors[routerName], err) + } + } + assert.Equal(t, test.expectedErrors, gotErrors) + }) + } +} + +func TestManager_buildChildRoutersMuxer(t *testing.T) { + testCases := []struct { + desc string + childRefs []string + routers map[string]*dynamic.Router + services map[string]*dynamic.Service + middlewares map[string]*dynamic.Middleware + expectedError string + expectedRequests []struct { + path string + statusCode int + } + }{ + { + desc: "simple child router with service", + childRefs: []string{"child1"}, + routers: map[string]*dynamic.Router{ + "child1": { + Rule: "Path(`/api`)", + Service: "child1-service", + }, + }, + services: map[string]*dynamic.Service{ + "child1-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8080"}}, + }, + }, + }, + expectedRequests: []struct { + path string + statusCode int + }{ + {path: "/api", statusCode: http.StatusOK}, + {path: "/unknown", statusCode: http.StatusNotFound}, + }, + }, + { + desc: "multiple child routers with different rules", + childRefs: []string{"child1", "child2"}, + routers: map[string]*dynamic.Router{ + "child1": { + Rule: "Path(`/api`)", + Service: "child1-service", + }, + "child2": { + Rule: "Path(`/web`)", + Service: "child2-service", + }, + }, + services: map[string]*dynamic.Service{ + "child1-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8080"}}, + }, + }, + "child2-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8081"}}, + }, + }, + }, + expectedRequests: []struct { + path string + statusCode int + }{ + {path: "/api", statusCode: http.StatusOK}, + {path: "/web", statusCode: http.StatusOK}, + {path: "/unknown", statusCode: http.StatusNotFound}, + }, + }, + { + desc: "child router with middleware", + childRefs: []string{"child1"}, + routers: map[string]*dynamic.Router{ + "child1": { + Rule: "Path(`/api`)", + Service: "child1-service", + Middlewares: []string{"test-middleware"}, + }, + }, + services: map[string]*dynamic.Service{ + "child1-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8080"}}, + }, + }, + }, + middlewares: map[string]*dynamic.Middleware{ + "test-middleware": { + Headers: &dynamic.Headers{ + CustomRequestHeaders: map[string]string{"X-Test": "value"}, + }, + }, + }, + expectedRequests: []struct { + path string + statusCode int + }{ + {path: "/api", statusCode: http.StatusOK}, + {path: "/unknown", statusCode: http.StatusNotFound}, + }, + }, + { + desc: "nested child routers (child with its own children)", + childRefs: []string{"intermediate"}, + routers: map[string]*dynamic.Router{ + "intermediate": { + Rule: "PathPrefix(`/api`)", + // No service - this will have its own children + }, + "leaf1": { + Rule: "Path(`/api/v1`)", + Service: "leaf1-service", + ParentRefs: []string{"intermediate"}, + }, + "leaf2": { + Rule: "Path(`/api/v2`)", + Service: "leaf2-service", + ParentRefs: []string{"intermediate"}, + }, + }, + services: map[string]*dynamic.Service{ + "leaf1-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8080"}}, + }, + }, + "leaf2-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8081"}}, + }, + }, + }, + expectedRequests: []struct { + path string + statusCode int + }{ + {path: "/api/v1", statusCode: http.StatusOK}, + {path: "/api/v2", statusCode: http.StatusOK}, + {path: "/unknown", statusCode: http.StatusNotFound}, + }, + }, + { + desc: "all child routers have errors - should return error", + childRefs: []string{"child1", "child2"}, + routers: map[string]*dynamic.Router{ + "child1": { + Rule: "Path(`/api`)", + Service: "child1-service", + ParentRefs: []string{"parent"}, + TLS: &dynamic.RouterTLSConfig{}, // Invalid: non-root router cannot have TLS + }, + "child2": { + Rule: "Path(`/web`)", + Service: "child2-service", + ParentRefs: []string{"parent"}, + TLS: &dynamic.RouterTLSConfig{}, // Invalid: non-root router cannot have TLS + }, + }, + services: map[string]*dynamic.Service{ + "child1-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8080"}}, + }, + }, + "child2-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8081"}}, + }, + }, + }, + expectedError: "no child routers could be added to muxer (2 skipped)", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + // Create runtime routers + runtimeRouters := make(map[string]*runtime.RouterInfo) + for name, router := range test.routers { + runtimeRouters[name] = &runtime.RouterInfo{ + Router: router, + } + } + + // Create runtime services + runtimeServices := make(map[string]*runtime.ServiceInfo) + for name, service := range test.services { + runtimeServices[name] = &runtime.ServiceInfo{ + Service: service, + } + } + + // Create runtime middlewares + runtimeMiddlewares := make(map[string]*runtime.MiddlewareInfo) + for name, middleware := range test.middlewares { + runtimeMiddlewares[name] = &runtime.MiddlewareInfo{ + Middleware: middleware, + } + } + + conf := &runtime.Configuration{ + Routers: runtimeRouters, + Services: runtimeServices, + Middlewares: runtimeMiddlewares, + } + + // Set up the manager with mocks + serviceManager := &mockServiceManager{} + middlewareBuilder := &mockMiddlewareBuilder{} + parser, err := httpmuxer.NewSyntaxParser() + require.NoError(t, err) + + manager := NewManager(conf, serviceManager, middlewareBuilder, nil, nil, parser) + + // Compute multi-layer routing to populate ChildRefs + manager.ParseRouterTree() + + // Build the child routers muxer + ctx := t.Context() + muxer, err := manager.buildChildRoutersMuxer(ctx, test.childRefs) + + if test.expectedError != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), test.expectedError) + return + } + + if len(test.childRefs) == 0 { + assert.Error(t, err) + return + } + + require.NoError(t, err) + require.NotNil(t, muxer) + + // Test that the muxer routes requests correctly + for _, req := range test.expectedRequests { + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, req.path, nil) + muxer.ServeHTTP(recorder, request) + + assert.Equal(t, req.statusCode, recorder.Code, "unexpected status code for path %s", req.path) + } + }) + } +} + +func TestManager_buildHTTPHandler_WithChildRouters(t *testing.T) { + testCases := []struct { + desc string + router *runtime.RouterInfo + childRouters map[string]*dynamic.Router + services map[string]*dynamic.Service + expectedError string + expectedRequests []struct { + path string + statusCode int + } + }{ + { + desc: "router with child routers", + router: &runtime.RouterInfo{ + Router: &dynamic.Router{ + Rule: "PathPrefix(`/api`)", + }, + ChildRefs: []string{"child1", "child2"}, + }, + childRouters: map[string]*dynamic.Router{ + "child1": { + Rule: "Path(`/api/v1`)", + Service: "child1-service", + }, + "child2": { + Rule: "Path(`/api/v2`)", + Service: "child2-service", + }, + }, + services: map[string]*dynamic.Service{ + "child1-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8080"}}, + }, + }, + "child2-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8081"}}, + }, + }, + }, + expectedRequests: []struct { + path string + statusCode int + }{ + {path: "/unknown", statusCode: http.StatusNotFound}, + }, + }, + { + desc: "router with service (normal case)", + router: &runtime.RouterInfo{ + Router: &dynamic.Router{ + Rule: "PathPrefix(`/api`)", + Service: "main-service", + }, + }, + services: map[string]*dynamic.Service{ + "main-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8080"}}, + }, + }, + }, + expectedRequests: []struct { + path string + statusCode int + }{}, + }, + { + desc: "router with neither service nor child routers - error", + router: &runtime.RouterInfo{ + Router: &dynamic.Router{ + Rule: "PathPrefix(`/api`)", + }, + }, + expectedError: "router must have either a service or child routers", + }, + { + desc: "router with child routers but missing child - error", + router: &runtime.RouterInfo{ + Router: &dynamic.Router{ + Rule: "PathPrefix(`/api`)", + }, + ChildRefs: []string{"nonexistent"}, + }, + expectedError: "child router \"nonexistent\" does not exist", + }, + { + desc: "router with all children having errors - returns empty muxer error", + router: &runtime.RouterInfo{ + Router: &dynamic.Router{ + Rule: "PathPrefix(`/api`)", + }, + ChildRefs: []string{"child1", "child2"}, + }, + childRouters: map[string]*dynamic.Router{ + "child1": { + Rule: "Path(`/api/v1`)", + Service: "child1-service", + ParentRefs: []string{"parent"}, + TLS: &dynamic.RouterTLSConfig{}, // Invalid for non-root + }, + "child2": { + Rule: "Path(`/api/v2`)", + Service: "child2-service", + ParentRefs: []string{"parent"}, + TLS: &dynamic.RouterTLSConfig{}, // Invalid for non-root + }, + }, + services: map[string]*dynamic.Service{ + "child1-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8080"}}, + }, + }, + "child2-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8081"}}, + }, + }, + }, + expectedError: "no child routers could be added to muxer (2 skipped)", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + // Create runtime routers + runtimeRouters := make(map[string]*runtime.RouterInfo) + runtimeRouters["test-router"] = test.router + for name, router := range test.childRouters { + runtimeRouters[name] = &runtime.RouterInfo{ + Router: router, + } + } + + // Create runtime services + runtimeServices := make(map[string]*runtime.ServiceInfo) + for name, service := range test.services { + runtimeServices[name] = &runtime.ServiceInfo{ + Service: service, + } + } + + conf := &runtime.Configuration{ + Routers: runtimeRouters, + Services: runtimeServices, + } + + // Set up the manager with mocks + serviceManager := &mockServiceManager{} + middlewareBuilder := &mockMiddlewareBuilder{} + parser, err := httpmuxer.NewSyntaxParser() + require.NoError(t, err) + + manager := NewManager(conf, serviceManager, middlewareBuilder, nil, nil, parser) + + // Run ParseRouterTree to validate configuration and populate ChildRefs/errors + manager.ParseRouterTree() + + // Build the HTTP handler + ctx := t.Context() + handler, err := manager.buildHTTPHandler(ctx, test.router, "test-router") + + if test.expectedError != "" { + assert.Error(t, err) + assert.Contains(t, err.Error(), test.expectedError) + return + } + + require.NoError(t, err) + require.NotNil(t, handler) + + // Test that the handler routes requests correctly + for _, req := range test.expectedRequests { + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, req.path, nil) + handler.ServeHTTP(recorder, request) + + assert.Equal(t, req.statusCode, recorder.Code, "unexpected status code for path %s", req.path) + } + }) + } +} + +func TestManager_BuildHandlers_WithChildRouters(t *testing.T) { + testCases := []struct { + desc string + routers map[string]*dynamic.Router + services map[string]*dynamic.Service + entryPoints []string + expectedEntryPoint string + expectedRequests []struct { + path string + statusCode int + } + }{ + { + desc: "parent router with child routers", + routers: map[string]*dynamic.Router{ + "parent": { + EntryPoints: []string{"web"}, + Rule: "PathPrefix(`/api`)", + }, + "child1": { + Rule: "Path(`/api/v1`)", + Service: "child1-service", + ParentRefs: []string{"parent"}, + }, + "child2": { + Rule: "Path(`/api/v2`)", + Service: "child2-service", + ParentRefs: []string{"parent"}, + }, + }, + services: map[string]*dynamic.Service{ + "child1-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8080"}}, + }, + }, + "child2-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8081"}}, + }, + }, + }, + entryPoints: []string{"web"}, + expectedEntryPoint: "web", + expectedRequests: []struct { + path string + statusCode int + }{ + {path: "/unknown", statusCode: http.StatusNotFound}, + }, + }, + { + desc: "multiple parent routers with children", + routers: map[string]*dynamic.Router{ + "api-parent": { + EntryPoints: []string{"web"}, + Rule: "PathPrefix(`/api`)", + }, + "web-parent": { + EntryPoints: []string{"web"}, + Rule: "PathPrefix(`/web`)", + }, + "api-child": { + Rule: "Path(`/api/v1`)", + Service: "api-service", + ParentRefs: []string{"api-parent"}, + }, + "web-child": { + Rule: "Path(`/web/index`)", + Service: "web-service", + ParentRefs: []string{"web-parent"}, + }, + }, + services: map[string]*dynamic.Service{ + "api-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8080"}}, + }, + }, + "web-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{{URL: "http://localhost:8081"}}, + }, + }, + }, + entryPoints: []string{"web"}, + expectedEntryPoint: "web", + expectedRequests: []struct { + path string + statusCode int + }{}, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + // Create runtime routers + runtimeRouters := make(map[string]*runtime.RouterInfo) + for name, router := range test.routers { + runtimeRouters[name] = &runtime.RouterInfo{ + Router: router, + } + } + + // Create runtime services + runtimeServices := make(map[string]*runtime.ServiceInfo) + for name, service := range test.services { + runtimeServices[name] = &runtime.ServiceInfo{ + Service: service, + } + } + + conf := &runtime.Configuration{ + Routers: runtimeRouters, + Services: runtimeServices, + } + + // Set up the manager with mocks + serviceManager := &mockServiceManager{} + middlewareBuilder := &mockMiddlewareBuilder{} + parser, err := httpmuxer.NewSyntaxParser() + require.NoError(t, err) + + manager := NewManager(conf, serviceManager, middlewareBuilder, nil, nil, parser) + + // Compute multi-layer routing to set up parent-child relationships + manager.ParseRouterTree() + + // Build handlers + ctx := t.Context() + handlers := manager.BuildHandlers(ctx, test.entryPoints, false) + + require.Contains(t, handlers, test.expectedEntryPoint) + handler := handlers[test.expectedEntryPoint] + require.NotNil(t, handler) + + // Test that the handler routes requests correctly + for _, req := range test.expectedRequests { + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, req.path, nil) + request.Host = "test.com" + handler.ServeHTTP(recorder, request) + + assert.Equal(t, req.statusCode, recorder.Code, "unexpected status code for path %s", req.path) + } + }) + } +} + +// Mock implementations for testing + +type mockServiceManager struct{} + +func (m *mockServiceManager) BuildHTTP(_ context.Context, _ string) (http.Handler, error) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("mock service response")) + }), nil +} + +func (m *mockServiceManager) LaunchHealthCheck(_ context.Context) {} + +type mockMiddlewareBuilder struct{} + +func (m *mockMiddlewareBuilder) BuildChain(_ context.Context, _ []string) *alice.Chain { + chain := alice.New() + return &chain +} + type proxyBuilderMock struct{} func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _ bool, _ time.Duration) (http.Handler, error) { diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go index c14e0536d..a2eaf296b 100644 --- a/pkg/server/routerfactory.go +++ b/pkg/server/routerfactory.go @@ -105,6 +105,8 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.observabilityMgr, f.tlsManager, f.parser) + routerManager.ParseRouterTree() + handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false) handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true) From c50216919a2193fd6c2333f1ffcfbcb5c1082bc3 Mon Sep 17 00:00:00 2001 From: Marcus Speight Date: Thu, 23 Oct 2025 09:00:05 +0100 Subject: [PATCH 016/134] AWS ECS IPv6 Support --- pkg/provider/ecs/ecs.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/provider/ecs/ecs.go b/pkg/provider/ecs/ecs.go index 084b9b9ca..1204387d7 100644 --- a/pkg/provider/ecs/ecs.go +++ b/pkg/provider/ecs/ecs.go @@ -317,8 +317,14 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI protocol: mapping.Protocol, }) } + + privateIP := aws.ToString(container.NetworkInterfaces[0].PrivateIpv4Address) + if privateIP == "" { + privateIP = aws.ToString(container.NetworkInterfaces[0].Ipv6Address) + } + mach = &machine{ - privateIP: aws.ToString(container.NetworkInterfaces[0].PrivateIpv4Address), + privateIP: privateIP, ports: ports, state: ec2types.InstanceStateName(strings.ToLower(aws.ToString(task.LastStatus))), healthStatus: task.HealthStatus, From a754236ce501b63560cf241830bbe22cd1695f0e Mon Sep 17 00:00:00 2001 From: Simon Delicata Date: Thu, 23 Oct 2025 16:16:05 +0200 Subject: [PATCH 017/134] Add least time load balancing strategy --- docs/content/migrate/v3.md | 10 + .../kubernetes-crd-definition-v1.yml | 18 +- .../traefik.io_ingressroutes.yaml | 3 +- .../traefik.io_middlewares.yaml | 3 +- .../traefik.io_traefikservices.yaml | 12 +- .../http/load-balancing/service.md | 42 + .../kubernetes/crd/http/ingressroute.md | 2 +- .../kubernetes/crd/http/service.md | 64 +- integration/docker_test.go | 51 - integration/fixtures/k8s/01-traefik-crd.yml | 18 +- integration/fixtures/leasttime_server.toml | 36 + integration/resources/compose/docker.yml | 11 - integration/simple_test.go | 103 ++ pkg/config/dynamic/http_config.go | 14 +- .../crd/fixtures/with_leasttime_strategy.yml | 16 + .../kubernetes/crd/kubernetes_http.go | 2 +- .../kubernetes/crd/kubernetes_test.go | 48 + .../crd/traefikio/v1alpha1/ingressroute.go | 6 +- pkg/server/service/loadbalancer/hrw/hrw.go | 6 +- .../loadbalancer/leasttime/leasttime.go | 373 ++++++ .../loadbalancer/leasttime/leasttime_test.go | 1093 +++++++++++++++++ pkg/server/service/loadbalancer/p2c/p2c.go | 8 +- pkg/server/service/loadbalancer/wrr/wrr.go | 8 +- pkg/server/service/service.go | 3 + pkg/server/service/service_test.go | 14 + 25 files changed, 1830 insertions(+), 134 deletions(-) create mode 100644 integration/fixtures/leasttime_server.toml create mode 100644 pkg/provider/kubernetes/crd/fixtures/with_leasttime_strategy.yml create mode 100644 pkg/server/service/loadbalancer/leasttime/leasttime.go create mode 100644 pkg/server/service/loadbalancer/leasttime/leasttime_test.go diff --git a/docs/content/migrate/v3.md b/docs/content/migrate/v3.md index 58fd04352..304287398 100644 --- a/docs/content/migrate/v3.md +++ b/docs/content/migrate/v3.md @@ -515,3 +515,13 @@ For the experimental channel: ```shell kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/experimental-install.yaml ``` + +### Kubernetes CRD Provider + +To use the new `leastime` load-balancer algorithm with the Kubernetes CRD provider, you need to update your CRDs. + +**Apply Updated CRDs:** + +```shell +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +``` diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index bc667c19a..8847677d7 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -351,12 +351,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -1313,12 +1314,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -3031,12 +3033,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -3359,12 +3362,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -3506,12 +3510,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -3740,12 +3745,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml index a99934f69..0a8247d64 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml @@ -351,12 +351,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index 627bc5250..9abd70ea4 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -484,12 +484,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml index e3ff0ce56..f0f44c1e0 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml @@ -262,12 +262,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -590,12 +591,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -737,12 +739,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -971,12 +974,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: diff --git a/docs/content/reference/routing-configuration/http/load-balancing/service.md b/docs/content/reference/routing-configuration/http/load-balancing/service.md index c74d5184c..a79326d48 100644 --- a/docs/content/reference/routing-configuration/http/load-balancing/service.md +++ b/docs/content/reference/routing-configuration/http/load-balancing/service.md @@ -485,6 +485,48 @@ Power of two choices algorithm is a load balancing strategy that selects two ser url = "http://private-ip-server-3/" ``` +## Least-Time + +The Least-Time load balancing algorithm selects the server with the lowest average response time (Time To First Byte - TTFB), +combined with the fewest active connections, weighted by server capacity. +This strategy is ideal for heterogeneous backend environments where servers have varying performance characteristics, +different hardware capabilities, or varying network latency. + +The algorithm continuously measures each backend's response time and tracks active connection counts. +When routing a request, +it calculates a score for each healthy server using the formula: `(avg_response_time × (1 + active_connections)) / weight`. +The server with the lowest score receives the request. +When multiple servers have identical scores, +Weighted Round Robin (WRR) with Earliest Deadline First (EDF) scheduling is used as a tie-breaker to ensure fair distribution. + +??? example "Basic Least-Time Load Balancing -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" + + ```yaml tab="YAML" + ## Dynamic configuration + http: + services: + my-service: + loadBalancer: + strategy: "leasttime" + servers: + - url: "http://private-ip-server-1/" + - url: "http://private-ip-server-2/" + - url: "http://private-ip-server-3/" + ``` + + ```toml tab="TOML" + ## Dynamic configuration + [http.services] + [http.services.my-service.loadBalancer] + strategy = "leasttime" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-1/" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-2/" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-3/" + ``` + ## Mirroring The mirroring is able to mirror requests sent to a service to other services. Please note that by default the whole request is buffered in memory while it is being mirrored. See the `maxBodySize` option in the example below for how to modify this behaviour. You can also omit the request body by setting the `mirrorBody` option to false. diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md b/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md index a6c437090..cc658fb2a 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md @@ -57,7 +57,7 @@ spec: httpOnly: true name: cookie secure: true - strategy: RoundRobin + strategy: wrr weight: 10 tls: # Generate a TLS certificate using a certificate resolver diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/http/service.md b/docs/content/reference/routing-configuration/kubernetes/crd/http/service.md index b69515b25..429b654c1 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/http/service.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/http/service.md @@ -47,7 +47,7 @@ spec: httpOnly: true name: cookie secure: true - strategy: RoundRobin + strategy: wrr ``` ```yaml tab="TraefikService" @@ -75,41 +75,41 @@ spec: httpOnly: true name: cookie secure: true - strategy: RoundRobin + strategy: wrr ``` ## Configuration Options -| Field | Description | Default | Required | -|:---------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------|:---------| -| `kind` | Kind of the service targeted.
Two values allowed:
- **Service**: Kubernetes Service
**TraefikService**: Traefik Service.
More information [here](#externalname-service). | "Service" | No | -| `name` | Service name.
The character `@` is not authorized.
More information [here](#middleware). | | Yes | -| `namespace` | Service namespace.
Can be empty if the service belongs to the same namespace as the IngressRoute.
More information [here](#externalname-service). | | No | -| `port` | Service port (number or port name).
Evaluated only if the kind is **Service**. | | No | -| `responseForwarding.`
`flushInterval`
| Interval, in milliseconds, in between flushes to the client while copying the response body.
A negative value means to flush immediately after each write to the client.
This configuration is ignored when a response is a streaming response; for such responses, writes are flushed to the client immediately.
Evaluated only if the kind is **Service**. | 100ms | No | -| `scheme` | Scheme to use for the request to the upstream Kubernetes Service.
Evaluated only if the kind is **Service**. | "http"
"https" if `port` is 443 or contains the string *https*. | No | -| `serversTransport` | Name of ServersTransport resource to use to configure the transport between Traefik and your servers.
Evaluated only if the kind is **Service**. | "" | No | -| `passHostHeader` | Forward client Host header to server.
Evaluated only if the kind is **Service**. | true | No | -| `healthCheck.scheme` | Server URL scheme for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No | -| `healthCheck.mode` | Health check mode.
If defined to grpc, will use the gRPC health check protocol to probe the server.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "http" | No | -| `healthCheck.path` | Server URL path for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No | -| `healthCheck.interval` | Frequency of the health check calls for healthy targets.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "100ms" | No | -| `healthCheck.unhealthyInterval` | Frequency of the health check calls for unhealthy targets.
When not defined, it defaults to the `interval` value.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "100ms" | No | -| `healthCheck.method` | HTTP method for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "GET" | No | -| `healthCheck.status` | Expected HTTP status code of the response to the health check request.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName.
If not set, expect a status between 200 and 399.
Evaluated only if the kind is **Service**. | | No | -| `healthCheck.port` | URL port for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | | No | -| `healthCheck.timeout` | Maximum duration to wait before considering the server unhealthy.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "5s" | No | -| `healthCheck.hostname` | Value in the Host header of the health check request.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No | -| `healthCheck.`
`followRedirect`
| Follow the redirections during the healtchcheck.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | true | No | -| `healthCheck.headers` | Map of header to send to the health check endpoint
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service)). | | No | -| `sticky.`
`cookie.name`
| Name of the cookie used for the stickiness.
When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response.
On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set.
If the server pecified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server).
Evaluated only if the kind is **Service**. | "" | No | -| `sticky.`
`cookie.httpOnly`
| Allow the cookie can be accessed by client-side APIs, such as JavaScript.
Evaluated only if the kind is **Service**. | false | No | -| `sticky.`
`cookie.secure`
| Allow the cookie can only be transmitted over an encrypted connection (i.e. HTTPS).
Evaluated only if the kind is **Service**. | false | No | -| `sticky.`
`cookie.sameSite`
| [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy
Allowed values:
-`none`
-`lax`
`strict`
Evaluated only if the kind is **Service**. | "" | No | -| `sticky.`
`cookie.maxAge`
| Number of seconds until the cookie expires.
Negative number, the cookie expires immediately.
0, the cookie never expires.
Evaluated only if the kind is **Service**. | 0 | No | -| `strategy` | Load balancing strategy between the servers.
RoundRobin is the only supported value yet.
Evaluated only if the kind is **Service**. | "RoundRobin" | No | -| `nativeLB` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik.
Evaluated only if the kind is **Service**. | false | No | -| `nodePortLB` | Use the nodePort IP address when the service type is NodePort.
It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
Evaluated only if the kind is **Service**. | false | No | +| Field | Description | Default | Required | +|:---------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------|:---------| +| `kind` | Kind of the service targeted.
Two values allowed:
- **Service**: Kubernetes Service
**TraefikService**: Traefik Service.
More information [here](#externalname-service). | "Service" | No | +| `name` | Service name.
The character `@` is not authorized.
More information [here](#middleware). | | Yes | +| `namespace` | Service namespace.
Can be empty if the service belongs to the same namespace as the IngressRoute.
More information [here](#externalname-service). | | No | +| `port` | Service port (number or port name).
Evaluated only if the kind is **Service**. | | No | +| `responseForwarding.`
`flushInterval`
| Interval, in milliseconds, in between flushes to the client while copying the response body.
A negative value means to flush immediately after each write to the client.
This configuration is ignored when a response is a streaming response; for such responses, writes are flushed to the client immediately.
Evaluated only if the kind is **Service**. | 100ms | No | +| `scheme` | Scheme to use for the request to the upstream Kubernetes Service.
Evaluated only if the kind is **Service**. | "http"
"https" if `port` is 443 or contains the string *https*. | No | +| `serversTransport` | Name of ServersTransport resource to use to configure the transport between Traefik and your servers.
Evaluated only if the kind is **Service**. | "" | No | +| `passHostHeader` | Forward client Host header to server.
Evaluated only if the kind is **Service**. | true | No | +| `healthCheck.scheme` | Server URL scheme for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No | +| `healthCheck.mode` | Health check mode.
If defined to grpc, will use the gRPC health check protocol to probe the server.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "http" | No | +| `healthCheck.path` | Server URL path for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No | +| `healthCheck.interval` | Frequency of the health check calls for healthy targets.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "100ms" | No | +| `healthCheck.unhealthyInterval` | Frequency of the health check calls for unhealthy targets.
When not defined, it defaults to the `interval` value.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "100ms" | No | +| `healthCheck.method` | HTTP method for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "GET" | No | +| `healthCheck.status` | Expected HTTP status code of the response to the health check request.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName.
If not set, expect a status between 200 and 399.
Evaluated only if the kind is **Service**. | | No | +| `healthCheck.port` | URL port for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | | No | +| `healthCheck.timeout` | Maximum duration to wait before considering the server unhealthy.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "5s" | No | +| `healthCheck.hostname` | Value in the Host header of the health check request.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No | +| `healthCheck.`
`followRedirect`
| Follow the redirections during the healtchcheck.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | true | No | +| `healthCheck.headers` | Map of header to send to the health check endpoint
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service)). | | No | +| `sticky.`
`cookie.name`
| Name of the cookie used for the stickiness.
When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response.
On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set.
If the server pecified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server).
Evaluated only if the kind is **Service**. | "" | No | +| `sticky.`
`cookie.httpOnly`
| Allow the cookie can be accessed by client-side APIs, such as JavaScript.
Evaluated only if the kind is **Service**. | false | No | +| `sticky.`
`cookie.secure`
| Allow the cookie can only be transmitted over an encrypted connection (i.e. HTTPS).
Evaluated only if the kind is **Service**. | false | No | +| `sticky.`
`cookie.sameSite`
| [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy
Allowed values:
-`none`
-`lax`
`strict`
Evaluated only if the kind is **Service**. | "" | No | +| `sticky.`
`cookie.maxAge`
| Number of seconds until the cookie expires.
Negative number, the cookie expires immediately.
0, the cookie never expires.
Evaluated only if the kind is **Service**. | 0 | No | +| `strategy` | Strategy defines the load balancing strategy between the servers.
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time).
Evaluated only if the kind is **Service**. | "RoundRobin" | No | +| `nativeLB` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik.
Evaluated only if the kind is **Service**. | false | No | +| `nodePortLB` | Use the nodePort IP address when the service type is NodePort.
It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
Evaluated only if the kind is **Service**. | false | No | ### ExternalName Service diff --git a/integration/docker_test.go b/integration/docker_test.go index d6a6819ff..1a767ff6f 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -4,7 +4,6 @@ import ( "encoding/json" "io" "net/http" - "strings" "testing" "time" @@ -56,56 +55,6 @@ func (s *DockerSuite) TestSimpleConfiguration() { require.NoError(s.T(), err) } -func (s *DockerSuite) TestWRRServer() { - tempObjects := struct { - DockerHost string - DefaultRule string - }{ - DockerHost: s.getDockerHost(), - DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", - } - - file := s.adaptFile("fixtures/docker/simple.toml", tempObjects) - - s.composeUp() - - s.traefikCmd(withConfigFile(file)) - - whoami1IP := s.getComposeServiceIP("wrr-server") - whoami2IP := s.getComposeServiceIP("wrr-server2") - - // Expected a 404 as we did not configure anything - err := try.GetRequest("http://127.0.0.1:8000/", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - require.NoError(s.T(), err) - - err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("wrr-server")) - require.NoError(s.T(), err) - - repartition := map[string]int{} - for range 4 { - req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) - req.Host = "my.wrr.host" - require.NoError(s.T(), err) - - response, err := http.DefaultClient.Do(req) - require.NoError(s.T(), err) - assert.Equal(s.T(), http.StatusOK, response.StatusCode) - - body, err := io.ReadAll(response.Body) - require.NoError(s.T(), err) - - if strings.Contains(string(body), whoami1IP) { - repartition[whoami1IP]++ - } - if strings.Contains(string(body), whoami2IP) { - repartition[whoami2IP]++ - } - } - - assert.Equal(s.T(), 3, repartition[whoami1IP]) - assert.Equal(s.T(), 1, repartition[whoami2IP]) -} - func (s *DockerSuite) TestDefaultDockerContainers() { tempObjects := struct { DockerHost string diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index bc667c19a..8847677d7 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -351,12 +351,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -1313,12 +1314,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -3031,12 +3033,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -3359,12 +3362,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -3506,12 +3510,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: @@ -3740,12 +3745,13 @@ spec: strategy: description: |- Strategy defines the load balancing strategy between the servers. - Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). RoundRobin value is deprecated and supported for backward compatibility. enum: - wrr - p2c - hrw + - leasttime - RoundRobin type: string weight: diff --git a/integration/fixtures/leasttime_server.toml b/integration/fixtures/leasttime_server.toml new file mode 100644 index 000000000..263fe906a --- /dev/null +++ b/integration/fixtures/leasttime_server.toml @@ -0,0 +1,36 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[api] + insecure = true + +[log] + level = "DEBUG" + noColor = true + +[entryPoints] + + [entryPoints.web] + address = ":8000" + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + [http.routers.router] + service = "service1" + rule = "Path(`/whoami`)" + +[http.services] + + [http.services.service1.loadBalancer] + strategy = "leasttime" + [[http.services.service1.loadBalancer.servers]] + url = "{{ .Server1 }}" + weight = 1 + [[http.services.service1.loadBalancer.servers]] + url = "{{ .Server2 }}" + weight = 1 diff --git a/integration/resources/compose/docker.yml b/integration/resources/compose/docker.yml index 7ee4492cf..788866e34 100644 --- a/integration/resources/compose/docker.yml +++ b/integration/resources/compose/docker.yml @@ -35,14 +35,3 @@ services: labels: traefik.http.Routers.Super.Rule: Host(`my.super.host`) traefik.http.Services.powpow.LoadBalancer.server.Port: 2375 - - wrr-server: - image: traefik/whoami - labels: - traefik.http.Routers.wrr-server.Rule: Host(`my.wrr.host`) - traefik.http.Services.wrr-server.LoadBalancer.server.Weight: 4 - wrr-server2: - image: traefik/whoami - labels: - traefik.http.Routers.wrr-server.Rule: Host(`my.wrr.host`) - traefik.http.Services.wrr-server.LoadBalancer.server.Weight: 1 diff --git a/integration/simple_test.go b/integration/simple_test.go index 2be9f7f06..ed30d7428 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -885,6 +885,109 @@ func (s *SimpleSuite) TestWRRServer() { assert.Equal(s.T(), 1, repartition[whoami2IP]) } +func (s *SimpleSuite) TestLeastTimeServer() { + s.createComposeProject("base") + + s.composeUp() + defer s.composeDown() + + whoami1IP := s.getComposeServiceIP("whoami1") + whoami2IP := s.getComposeServiceIP("whoami2") + + file := s.adaptFile("fixtures/leasttime_server.toml", struct { + Server1 string + Server2 string + }{Server1: "http://" + whoami1IP, Server2: "http://" + whoami2IP}) + + s.traefikCmd(withConfigFile(file)) + + err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("service1")) + require.NoError(s.T(), err) + + // Verify leasttime strategy is configured + err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("leasttime")) + require.NoError(s.T(), err) + + // Make requests and verify both servers respond + repartition := map[string]int{} + for range 10 { + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) + require.NoError(s.T(), err) + + response, err := http.DefaultClient.Do(req) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) + + body, err := io.ReadAll(response.Body) + require.NoError(s.T(), err) + + if strings.Contains(string(body), whoami1IP) { + repartition[whoami1IP]++ + } + if strings.Contains(string(body), whoami2IP) { + repartition[whoami2IP]++ + } + } + + // Both servers should have received requests + assert.Positive(s.T(), repartition[whoami1IP]) + assert.Positive(s.T(), repartition[whoami2IP]) +} + +func (s *SimpleSuite) TestLeastTimeHeterogeneousPerformance() { + // Create test servers with different response times + var fastServerCalls, slowServerCalls atomic.Int32 + + fastServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + fastServerCalls.Add(1) + time.Sleep(10 * time.Millisecond) // Fast server + rw.WriteHeader(http.StatusOK) + _, _ = rw.Write([]byte("fast-server")) + })) + defer fastServer.Close() + + slowServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + slowServerCalls.Add(1) + time.Sleep(100 * time.Millisecond) // Slow server + rw.WriteHeader(http.StatusOK) + _, _ = rw.Write([]byte("slow-server")) + })) + defer slowServer.Close() + + file := s.adaptFile("fixtures/leasttime_server.toml", struct { + Server1 string + Server2 string + }{Server1: fastServer.URL, Server2: slowServer.URL}) + + s.traefikCmd(withConfigFile(file)) + + err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("service1")) + require.NoError(s.T(), err) + + // Make 20 requests to build up response time statistics + for range 20 { + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) + require.NoError(s.T(), err) + + response, err := http.DefaultClient.Do(req) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) + _, _ = io.ReadAll(response.Body) + response.Body.Close() + } + + // Verify that the fast server received significantly more requests (>70%) + fastCalls := fastServerCalls.Load() + slowCalls := slowServerCalls.Load() + totalCalls := fastCalls + slowCalls + + assert.Equal(s.T(), int32(20), totalCalls) + + // Fast server should get >70% of traffic due to lower response time + fastPercentage := float64(fastCalls) / float64(totalCalls) * 100 + assert.Greater(s.T(), fastPercentage, 70.0) +} + func (s *SimpleSuite) TestWRR() { s.createComposeProject("base") diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index 7ea0ac3c7..f9022c19d 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -190,6 +190,12 @@ type WRRService struct { GRPCStatus *GRPCStatus `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` } +// SetDefaults Default values for a WRRService. +func (w *WRRService) SetDefaults() { + defaultWeight := 1 + w.Weight = &defaultWeight +} + // +k8s:deepcopy-gen=true // HRWService is a reference to a service load-balanced with highest random weight. @@ -204,12 +210,6 @@ func (w *HRWService) SetDefaults() { w.Weight = &defaultWeight } -// SetDefaults Default values for a WRRService. -func (w *WRRService) SetDefaults() { - defaultWeight := 1 - w.Weight = &defaultWeight -} - // +k8s:deepcopy-gen=true type GRPCStatus struct { @@ -267,6 +267,8 @@ const ( BalancerStrategyP2C BalancerStrategy = "p2c" // BalancerStrategyHRW is the highest random weight strategy. BalancerStrategyHRW BalancerStrategy = "hrw" + // BalancerStrategyLeastTime is the least-time strategy. + BalancerStrategyLeastTime BalancerStrategy = "leasttime" ) // +k8s:deepcopy-gen=true diff --git a/pkg/provider/kubernetes/crd/fixtures/with_leasttime_strategy.yml b/pkg/provider/kubernetes/crd/fixtures/with_leasttime_strategy.yml new file mode 100644 index 000000000..4f47fd4bb --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_leasttime_strategy.yml @@ -0,0 +1,16 @@ +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default +spec: + entryPoints: + - web + routes: + - match: Host(`foo.com`) && PathPrefix(`/leasttime`) + kind: Rule + priority: 12 + services: + - name: whoami2 + port: 8080 + strategy: leasttime diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 346752c42..26f8b1bfd 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -384,7 +384,7 @@ func (c configBuilder) buildServersLB(namespace string, svc traefikv1alpha1.Load // TODO: remove this when the fake client apply default values. if svc.Strategy != "" { switch svc.Strategy { - case dynamic.BalancerStrategyWRR, dynamic.BalancerStrategyP2C, dynamic.BalancerStrategyHRW: + case dynamic.BalancerStrategyWRR, dynamic.BalancerStrategyP2C, dynamic.BalancerStrategyHRW, dynamic.BalancerStrategyLeastTime: lb.Strategy = svc.Strategy // Here we are just logging a warning as the default value is already applied. diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 124fd87c9..eac9b5fc1 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -5667,6 +5667,54 @@ func TestLoadIngressRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Simple Ingress Route with leasttime strategy", + paths: []string{"services.yml", "with_leasttime_strategy.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-55869f6407935ccfa805": { + EntryPoints: []string{"web"}, + Service: "default-test-route-55869f6407935ccfa805", + Rule: "Host(`foo.com`) && PathPrefix(`/leasttime`)", + Priority: 12, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-route-55869f6407935ccfa805": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyLeastTime, + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.3:8080", + }, + { + URL: "http://10.10.0.4:8080", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, } for _, test := range testCases { diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go index d7b7af331..c3978e94a 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go @@ -118,10 +118,10 @@ type LoadBalancerSpec struct { // It defaults to https when Kubernetes Service port is 443, http otherwise. Scheme string `json:"scheme,omitempty"` // Strategy defines the load balancing strategy between the servers. - // Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight). + // Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time). // RoundRobin value is deprecated and supported for backward compatibility. - // TODO: when the deprecated RoundRobin value will be removed, set the default value to wrr. - // +kubebuilder:validation:Enum=wrr;p2c;hrw;RoundRobin + // TODO: when the deprecated RoundRobin value will be removed, set the default kubebuilder value to wrr. + // +kubebuilder:validation:Enum=wrr;p2c;hrw;leasttime;RoundRobin Strategy dynamic.BalancerStrategy `json:"strategy,omitempty"` // PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. // By default, passHostHeader is true. diff --git a/pkg/server/service/loadbalancer/hrw/hrw.go b/pkg/server/service/loadbalancer/hrw/hrw.go index f213b326a..51ca397fd 100644 --- a/pkg/server/service/loadbalancer/hrw/hrw.go +++ b/pkg/server/service/loadbalancer/hrw/hrw.go @@ -13,6 +13,8 @@ import ( "github.com/traefik/traefik/v3/pkg/ip" ) +var errNoAvailableServer = errors.New("no available server") + type namedHandler struct { http.Handler name string @@ -114,15 +116,13 @@ func (b *Balancer) SetStatus(ctx context.Context, childName string, up bool) { // Not thread safe. func (b *Balancer) RegisterStatusUpdater(fn func(up bool)) error { if !b.wantsHealthCheck { - return errors.New("healthCheck not enabled in config for this weighted service") + return errors.New("healthCheck not enabled in config for this HRW service") } b.updaters = append(b.updaters, fn) return nil } -var errNoAvailableServer = errors.New("no available server") - func (b *Balancer) nextServer(ip string) (*namedHandler, error) { b.handlersMu.RLock() var healthy []*namedHandler diff --git a/pkg/server/service/loadbalancer/leasttime/leasttime.go b/pkg/server/service/loadbalancer/leasttime/leasttime.go new file mode 100644 index 000000000..41f4f5218 --- /dev/null +++ b/pkg/server/service/loadbalancer/leasttime/leasttime.go @@ -0,0 +1,373 @@ +package leasttime + +import ( + "context" + "errors" + "math" + "net/http" + "net/http/httptrace" + "sync" + "sync/atomic" + "time" + + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer" +) + +const sampleSize = 100 // Number of response time samples to track. + +var errNoAvailableServer = errors.New("no available server") + +// namedHandler wraps an HTTP handler with metrics and server information. +// Tracks response time (TTFB) and inflight request count for load balancing decisions. +type namedHandler struct { + http.Handler + name string + weight float64 + + deadlineMu sync.RWMutex + deadline float64 // WRR tie-breaking (EDF scheduling). + + inflightCount atomic.Int64 // Number of inflight requests. + + responseTimeMu sync.RWMutex + responseTimes [sampleSize]float64 // Fixed-size ring buffer (TTFB measurements in ms). + responseTimeIdx int // Current position in ring buffer. + responseTimeSum float64 // Sum of all values in buffer. + sampleCount int // Number of samples collected so far. +} + +// updateResponseTime updates the average response time for this server using a ring buffer. +func (s *namedHandler) updateResponseTime(elapsed time.Duration) { + s.responseTimeMu.Lock() + defer s.responseTimeMu.Unlock() + + ms := float64(elapsed.Milliseconds()) + + if s.sampleCount < sampleSize { + // Still filling the buffer. + s.responseTimes[s.responseTimeIdx] = ms + s.responseTimeSum += ms + s.sampleCount++ + } else { + // Buffer is full, replace oldest value. + oldValue := s.responseTimes[s.responseTimeIdx] + s.responseTimes[s.responseTimeIdx] = ms + s.responseTimeSum = s.responseTimeSum - oldValue + ms + } + + s.responseTimeIdx = (s.responseTimeIdx + 1) % sampleSize +} + +// getAvgResponseTime returns the average response time in milliseconds. +// Returns 0 if no samples have been collected yet (cold start). +func (s *namedHandler) getAvgResponseTime() float64 { + s.responseTimeMu.RLock() + defer s.responseTimeMu.RUnlock() + + if s.sampleCount == 0 { + return 0 + } + return s.responseTimeSum / float64(s.sampleCount) +} + +func (s *namedHandler) getDeadline() float64 { + s.deadlineMu.RLock() + defer s.deadlineMu.RUnlock() + return s.deadline +} + +func (s *namedHandler) setDeadline(deadline float64) { + s.deadlineMu.Lock() + defer s.deadlineMu.Unlock() + s.deadline = deadline +} + +// Balancer implements the least-time load balancing algorithm. +// It selects the server with the lowest average response time (TTFB) and fewest active connections. +type Balancer struct { + wantsHealthCheck bool + + // handlersMu protects the handlers slice, the status and the fenced maps. + handlersMu sync.RWMutex + handlers []*namedHandler + // status is a record of which child services of the Balancer are healthy, keyed + // by name of child service. A service is initially added to the map when it is + // created via Add, and it is later removed or added to the map as needed, + // through the SetStatus method. + status map[string]struct{} + // fenced is the list of terminating yet still serving child services. + fenced map[string]struct{} + + // updaters is the list of hooks that are run (to update the Balancer + // parent(s)), whenever the Balancer status changes. + // No mutex is needed, as it is modified only during the configuration build. + updaters []func(bool) + + sticky *loadbalancer.Sticky + + // deadlineMu protects EDF scheduling state (curDeadline and all handler deadline fields). + // Separate from handlersMu to reduce lock contention during tie-breaking. + curDeadlineMu sync.RWMutex + // curDeadline is used for WRR tie-breaking (EDF scheduling). + curDeadline float64 +} + +// New creates a new least-time load balancer. +func New(stickyConfig *dynamic.Sticky, wantsHealthCheck bool) *Balancer { + balancer := &Balancer{ + status: make(map[string]struct{}), + fenced: make(map[string]struct{}), + wantsHealthCheck: wantsHealthCheck, + } + if stickyConfig != nil && stickyConfig.Cookie != nil { + balancer.sticky = loadbalancer.NewSticky(*stickyConfig.Cookie) + } + + return balancer +} + +// SetStatus sets on the balancer that its given child is now of the given +// status. childName is only needed for logging purposes. +func (b *Balancer) SetStatus(ctx context.Context, childName string, up bool) { + b.handlersMu.Lock() + defer b.handlersMu.Unlock() + + upBefore := len(b.status) > 0 + + status := "DOWN" + if up { + status = "UP" + } + + log.Ctx(ctx).Debug().Msgf("Setting status of %s to %v", childName, status) + + if up { + b.status[childName] = struct{}{} + } else { + delete(b.status, childName) + } + + upAfter := len(b.status) > 0 + status = "DOWN" + if upAfter { + status = "UP" + } + + // No Status Change. + if upBefore == upAfter { + // We're still with the same status, no need to propagate. + log.Ctx(ctx).Debug().Msgf("Still %s, no need to propagate", status) + return + } + + // Status Change. + log.Ctx(ctx).Debug().Msgf("Propagating new %s status", status) + for _, fn := range b.updaters { + fn(upAfter) + } +} + +// RegisterStatusUpdater adds fn to the list of hooks that are run when the +// status of the Balancer changes. +// Not thread safe. +func (b *Balancer) RegisterStatusUpdater(fn func(up bool)) error { + if !b.wantsHealthCheck { + return errors.New("healthCheck not enabled in config for this LeastTime service") + } + b.updaters = append(b.updaters, fn) + return nil +} + +// getHealthyServers returns the list of healthy, non-fenced servers. +func (b *Balancer) getHealthyServers() []*namedHandler { + b.handlersMu.RLock() + defer b.handlersMu.RUnlock() + + var healthy []*namedHandler + for _, h := range b.handlers { + if _, ok := b.status[h.name]; ok { + if _, fenced := b.fenced[h.name]; !fenced { + healthy = append(healthy, h) + } + } + } + return healthy +} + +// selectWRR selects a server from candidates using Weighted Round Robin (EDF scheduling). +// This is used for tie-breaking when multiple servers have identical scores. +func (b *Balancer) selectWRR(candidates []*namedHandler) *namedHandler { + if len(candidates) == 0 { + return nil + } + + selected := candidates[0] + minDeadline := math.MaxFloat64 + + // Find handler with earliest deadline. + for _, h := range candidates { + handlerDeadline := h.getDeadline() + if handlerDeadline < minDeadline { + minDeadline = handlerDeadline + selected = h + } + } + + // Update deadline based on when this server was selected (minDeadline), + // not the global curDeadline. This ensures proper weighted distribution. + newDeadline := minDeadline + 1/selected.weight + selected.setDeadline(newDeadline) + + // Track the maximum deadline assigned for initializing new servers. + b.curDeadlineMu.Lock() + if newDeadline > b.curDeadline { + b.curDeadline = newDeadline + } + b.curDeadlineMu.Unlock() + + return selected +} + +// Score = (avgResponseTime × (1 + inflightCount)) / weight. +func (b *Balancer) nextServer() (*namedHandler, error) { + healthy := b.getHealthyServers() + + if len(healthy) == 0 { + return nil, errNoAvailableServer + } + + if len(healthy) == 1 { + return healthy[0], nil + } + + // Calculate scores and find minimum. + minScore := math.MaxFloat64 + var candidates []*namedHandler + + for _, h := range healthy { + avgRT := h.getAvgResponseTime() + inflight := float64(h.inflightCount.Load()) + score := (avgRT * (1 + inflight)) / h.weight + + if score < minScore { + minScore = score + candidates = []*namedHandler{h} + } else if score == minScore { + candidates = append(candidates, h) + } + } + + if len(candidates) == 1 { + return candidates[0], nil + } + + // Multiple servers with same score: use WRR (EDF) tie-breaking. + selected := b.selectWRR(candidates) + if selected == nil { + return nil, errNoAvailableServer + } + + return selected, nil +} + +func (b *Balancer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + // Handle sticky sessions first. + if b.sticky != nil { + h, rewrite, err := b.sticky.StickyHandler(req) + if err != nil { + log.Error().Err(err).Msg("Error while getting sticky handler") + } else if h != nil { + b.handlersMu.RLock() + _, ok := b.status[h.Name] + b.handlersMu.RUnlock() + if ok { + if rewrite { + if err := b.sticky.WriteStickyCookie(rw, h.Name); err != nil { + log.Error().Err(err).Msg("Writing sticky cookie") + } + } + + h.ServeHTTP(rw, req) + return + } + } + } + + server, err := b.nextServer() + if err != nil { + if errors.Is(err, errNoAvailableServer) { + http.Error(rw, errNoAvailableServer.Error(), http.StatusServiceUnavailable) + } else { + http.Error(rw, err.Error(), http.StatusInternalServerError) + } + return + } + + if b.sticky != nil { + if err := b.sticky.WriteStickyCookie(rw, server.name); err != nil { + log.Error().Err(err).Msg("Error while writing sticky cookie") + } + } + + // Track inflight requests. + server.inflightCount.Add(1) + defer server.inflightCount.Add(-1) + + startTime := time.Now() + trace := &httptrace.ClientTrace{ + GotFirstResponseByte: func() { + // Update average response time (TTFB). + server.updateResponseTime(time.Since(startTime)) + }, + } + traceCtx := httptrace.WithClientTrace(req.Context(), trace) + server.ServeHTTP(rw, req.WithContext(traceCtx)) +} + +// AddServer adds a handler with a server. +func (b *Balancer) AddServer(name string, handler http.Handler, server dynamic.Server) { + b.Add(name, handler, server.Weight, server.Fenced) +} + +// Add adds a handler. +// A handler with a non-positive weight is ignored. +func (b *Balancer) Add(name string, handler http.Handler, weight *int, fenced bool) { + w := 1 + if weight != nil { + w = *weight + } + + if w <= 0 { // non-positive weight is meaningless. + return + } + + h := &namedHandler{Handler: handler, name: name, weight: float64(w)} + + // Initialize deadline by adding 1/weight to current deadline. + // This staggers servers to prevent all starting at the same time. + var deadline float64 + b.curDeadlineMu.RLock() + deadline = b.curDeadline + 1/h.weight + b.curDeadlineMu.RUnlock() + + h.setDeadline(deadline) + + // Update balancer's current deadline with the new server's deadline. + b.curDeadlineMu.Lock() + b.curDeadline = deadline + b.curDeadlineMu.Unlock() + + b.handlersMu.Lock() + b.handlers = append(b.handlers, h) + b.status[name] = struct{}{} + if fenced { + b.fenced[name] = struct{}{} + } + b.handlersMu.Unlock() + + if b.sticky != nil { + b.sticky.AddHandler(name, handler) + } +} diff --git a/pkg/server/service/loadbalancer/leasttime/leasttime_test.go b/pkg/server/service/loadbalancer/leasttime/leasttime_test.go new file mode 100644 index 000000000..aae0d8240 --- /dev/null +++ b/pkg/server/service/loadbalancer/leasttime/leasttime_test.go @@ -0,0 +1,1093 @@ +package leasttime + +import ( + "context" + "net/http" + "net/http/httptest" + "net/http/httptrace" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/traefik/traefik/v3/pkg/config/dynamic" +) + +type key string + +const serviceName key = "serviceName" + +func pointer[T any](v T) *T { return &v } + +// responseRecorder tracks which servers handled requests. +type responseRecorder struct { + *httptest.ResponseRecorder + save map[string]int +} + +func (r *responseRecorder) WriteHeader(statusCode int) { + server := r.Header().Get("server") + if server != "" { + r.save[server]++ + } + r.ResponseRecorder.WriteHeader(statusCode) +} + +// TestBalancer tests basic server addition and least-time selection. +func TestBalancer(t *testing.T) { + balancer := New(nil, false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(5 * time.Millisecond) + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(5 * time.Millisecond) + rw.Header().Set("server", "second") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 10 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + // With least-time and equal response times, both servers should get some traffic. + assert.Positive(t, recorder.save["first"]) + assert.Positive(t, recorder.save["second"]) + assert.Equal(t, 10, recorder.save["first"]+recorder.save["second"]) +} + +// TestBalancerNoService tests behavior when no servers are configured. +func TestBalancerNoService(t *testing.T) { + balancer := New(nil, false) + + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + + assert.Equal(t, http.StatusServiceUnavailable, recorder.Result().StatusCode) +} + +// TestBalancerNoServiceUp tests behavior when all servers are marked down. +func TestBalancerNoServiceUp(t *testing.T) { + balancer := New(nil, false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusInternalServerError) + }), pointer(1), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusInternalServerError) + }), pointer(1), false) + + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "first", false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", false) + + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + + assert.Equal(t, http.StatusServiceUnavailable, recorder.Result().StatusCode) +} + +// TestBalancerOneServerDown tests that down servers are excluded from selection. +func TestBalancerOneServerDown(t *testing.T) { + balancer := New(nil, false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusInternalServerError) + }), pointer(1), false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", false) + + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 3 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + assert.Equal(t, 3, recorder.save["first"]) + assert.Equal(t, 0, recorder.save["second"]) +} + +// TestBalancerOneServerDownThenUp tests server status transitions. +func TestBalancerOneServerDownThenUp(t *testing.T) { + balancer := New(nil, false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(5 * time.Millisecond) + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(5 * time.Millisecond) + rw.Header().Set("server", "second") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", false) + + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 3 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + assert.Equal(t, 3, recorder.save["first"]) + assert.Equal(t, 0, recorder.save["second"]) + + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", true) + recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 20 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + // Both servers should get some traffic. + assert.Positive(t, recorder.save["first"]) + assert.Positive(t, recorder.save["second"]) + assert.Equal(t, 20, recorder.save["first"]+recorder.save["second"]) +} + +// TestBalancerAllServersZeroWeight tests that all zero-weight servers result in no available server. +func TestBalancerAllServersZeroWeight(t *testing.T) { + balancer := New(nil, false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), pointer(0), false) + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), pointer(0), false) + + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + + assert.Equal(t, http.StatusServiceUnavailable, recorder.Result().StatusCode) +} + +// TestBalancerOneServerZeroWeight tests that zero-weight servers are ignored. +func TestBalancerOneServerZeroWeight(t *testing.T) { + balancer := New(nil, false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), pointer(0), false) + + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 3 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + // Only first server should receive traffic. + assert.Equal(t, 3, recorder.save["first"]) + assert.Equal(t, 0, recorder.save["second"]) +} + +// TestBalancerPropagate tests status propagation to parent balancers. +func TestBalancerPropagate(t *testing.T) { + balancer1 := New(nil, true) + + balancer1.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + balancer1.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "second") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + balancer2 := New(nil, true) + balancer2.Add("third", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "third") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + balancer2.Add("fourth", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "fourth") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + topBalancer := New(nil, true) + topBalancer.Add("balancer1", balancer1, pointer(1), false) + topBalancer.Add("balancer2", balancer2, pointer(1), false) + err := balancer1.RegisterStatusUpdater(func(up bool) { + topBalancer.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "balancer1", up) + }) + assert.NoError(t, err) + err = balancer2.RegisterStatusUpdater(func(up bool) { + topBalancer.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "balancer2", up) + }) + assert.NoError(t, err) + + // Set all children of balancer1 to down, should propagate to top. + balancer1.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "first", false) + balancer1.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "second", false) + + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 4 { + topBalancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + // Only balancer2 should receive traffic. + assert.Equal(t, 0, recorder.save["first"]) + assert.Equal(t, 0, recorder.save["second"]) + assert.Equal(t, 4, recorder.save["third"]+recorder.save["fourth"]) +} + +// TestBalancerOneServerFenced tests that fenced servers are excluded from selection. +func TestBalancerOneServerFenced(t *testing.T) { + balancer := New(nil, false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "second") + rw.WriteHeader(http.StatusOK) + }), pointer(1), true) // fenced + + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 3 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + // Only first server should receive traffic. + assert.Equal(t, 3, recorder.save["first"]) + assert.Equal(t, 0, recorder.save["second"]) +} + +// TestBalancerAllFencedServers tests that all fenced servers result in no available server. +func TestBalancerAllFencedServers(t *testing.T) { + balancer := New(nil, false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), pointer(1), true) + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), pointer(1), true) + + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + + assert.Equal(t, http.StatusServiceUnavailable, recorder.Result().StatusCode) +} + +// TestBalancerRegisterStatusUpdaterWithoutHealthCheck tests error when registering updater without health check. +func TestBalancerRegisterStatusUpdaterWithoutHealthCheck(t *testing.T) { + balancer := New(nil, false) + + err := balancer.RegisterStatusUpdater(func(up bool) {}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "healthCheck not enabled") +} + +// TestBalancerSticky tests sticky session support. +func TestBalancerSticky(t *testing.T) { + balancer := New(&dynamic.Sticky{ + Cookie: &dynamic.Cookie{ + Name: "test", + }, + }, false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "second") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + // First request should set cookie. + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + firstServer := recorder.Header().Get("server") + assert.NotEmpty(t, firstServer) + + // Extract cookie from first response. + cookies := recorder.Result().Cookies() + assert.NotEmpty(t, cookies) + + // Second request with cookie should hit same server. + req := httptest.NewRequest(http.MethodGet, "/", nil) + for _, cookie := range cookies { + req.AddCookie(cookie) + } + + recorder2 := httptest.NewRecorder() + balancer.ServeHTTP(recorder2, req) + secondServer := recorder2.Header().Get("server") + + assert.Equal(t, firstServer, secondServer) +} + +// TestBalancerStickyFallback tests that sticky sessions fallback to least-time when sticky server is down. +func TestBalancerStickyFallback(t *testing.T) { + balancer := New(&dynamic.Sticky{ + Cookie: &dynamic.Cookie{ + Name: "test", + }, + }, false) + + balancer.Add("server1", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(50 * time.Millisecond) + rw.Header().Set("server", "server1") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + balancer.Add("server2", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(50 * time.Millisecond) + rw.Header().Set("server", "server2") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + // Make initial request to establish sticky session with server1. + recorder1 := httptest.NewRecorder() + balancer.ServeHTTP(recorder1, httptest.NewRequest(http.MethodGet, "/", nil)) + firstServer := recorder1.Header().Get("server") + assert.NotEmpty(t, firstServer) + + // Extract cookie from first response. + cookies := recorder1.Result().Cookies() + assert.NotEmpty(t, cookies) + + // Mark the sticky server as DOWN + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "test"), firstServer, false) + + // Request with sticky cookie should fallback to the other server + req2 := httptest.NewRequest(http.MethodGet, "/", nil) + for _, cookie := range cookies { + req2.AddCookie(cookie) + } + recorder2 := httptest.NewRecorder() + balancer.ServeHTTP(recorder2, req2) + fallbackServer := recorder2.Header().Get("server") + assert.NotEqual(t, firstServer, fallbackServer) + assert.NotEmpty(t, fallbackServer) + + // New sticky cookie should be written for the fallback server + newCookies := recorder2.Result().Cookies() + assert.NotEmpty(t, newCookies) + + // Verify sticky session persists with the fallback server + req3 := httptest.NewRequest(http.MethodGet, "/", nil) + for _, cookie := range newCookies { + req3.AddCookie(cookie) + } + recorder3 := httptest.NewRecorder() + balancer.ServeHTTP(recorder3, req3) + assert.Equal(t, fallbackServer, recorder3.Header().Get("server")) + + // Bring original server back UP + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "test"), firstServer, true) + + // Request with fallback server cookie should still stick to fallback server + req4 := httptest.NewRequest(http.MethodGet, "/", nil) + for _, cookie := range newCookies { + req4.AddCookie(cookie) + } + recorder4 := httptest.NewRecorder() + balancer.ServeHTTP(recorder4, req4) + assert.Equal(t, fallbackServer, recorder4.Header().Get("server")) +} + +// TestBalancerStickyFenced tests that sticky sessions persist to fenced servers (graceful shutdown) +// Fencing enables zero-downtime deployments: fenced servers reject NEW connections +// but continue serving EXISTING sticky sessions until they complete. +func TestBalancerStickyFenced(t *testing.T) { + balancer := New(&dynamic.Sticky{ + Cookie: &dynamic.Cookie{ + Name: "test", + }, + }, false) + + balancer.Add("server1", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "server1") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + balancer.Add("server2", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "server2") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + // Establish sticky session with any server. + recorder1 := httptest.NewRecorder() + balancer.ServeHTTP(recorder1, httptest.NewRequest(http.MethodGet, "/", nil)) + stickyServer := recorder1.Header().Get("server") + assert.NotEmpty(t, stickyServer) + + cookies := recorder1.Result().Cookies() + assert.NotEmpty(t, cookies) + + // Fence the sticky server (simulate graceful shutdown). + balancer.handlersMu.Lock() + balancer.fenced[stickyServer] = struct{}{} + balancer.handlersMu.Unlock() + + // Existing sticky session should STILL work (graceful draining). + req := httptest.NewRequest(http.MethodGet, "/", nil) + for _, cookie := range cookies { + req.AddCookie(cookie) + } + recorder2 := httptest.NewRecorder() + balancer.ServeHTTP(recorder2, req) + assert.Equal(t, stickyServer, recorder2.Header().Get("server")) + + // But NEW requests should NOT go to the fenced server. + recorder3 := httptest.NewRecorder() + balancer.ServeHTTP(recorder3, httptest.NewRequest(http.MethodGet, "/", nil)) + newServer := recorder3.Header().Get("server") + assert.NotEqual(t, stickyServer, newServer) + assert.NotEmpty(t, newServer) +} + +// TestRingBufferBasic tests basic ring buffer functionality with few samples. +func TestRingBufferBasic(t *testing.T) { + handler := &namedHandler{ + Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), + name: "test", + weight: 1, + } + + // Test cold start - no samples. + avg := handler.getAvgResponseTime() + assert.InDelta(t, 0.0, avg, 0) + + // Add one sample. + handler.updateResponseTime(10 * time.Millisecond) + avg = handler.getAvgResponseTime() + assert.InDelta(t, 10.0, avg, 0) + + // Add more samples. + handler.updateResponseTime(20 * time.Millisecond) + handler.updateResponseTime(30 * time.Millisecond) + avg = handler.getAvgResponseTime() + assert.InDelta(t, 20.0, avg, 0) // (10 + 20 + 30) / 3 = 20 +} + +// TestRingBufferWraparound tests ring buffer behavior when it wraps around +func TestRingBufferWraparound(t *testing.T) { + handler := &namedHandler{ + Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), + name: "test", + weight: 1, + } + + // Fill the buffer with 100 samples of 10ms each. + for range sampleSize { + handler.updateResponseTime(10 * time.Millisecond) + } + avg := handler.getAvgResponseTime() + assert.InDelta(t, 10.0, avg, 0) + + // Add one more sample (should replace oldest). + handler.updateResponseTime(20 * time.Millisecond) + avg = handler.getAvgResponseTime() + // Sum: 99*10 + 1*20 = 1010, avg = 1010/100 = 10.1 + assert.InDelta(t, 10.1, avg, 0) + + // Add 10 more samples of 30ms. + for range 10 { + handler.updateResponseTime(30 * time.Millisecond) + } + avg = handler.getAvgResponseTime() + // Sum: 89*10 + 1*20 + 10*30 = 890 + 20 + 300 = 1210, avg = 1210/100 = 12.1 + assert.InDelta(t, 12.1, avg, 0) +} + +// TestRingBufferLarge tests ring buffer with many samples (> 100). +func TestRingBufferLarge(t *testing.T) { + handler := &namedHandler{ + Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), + name: "test", + weight: 1, + } + + // Add 150 samples. + for i := range 150 { + handler.updateResponseTime(time.Duration(i+1) * time.Millisecond) + } + + // Should only track last 100 samples: 51, 52, ..., 150 + // Sum = (51 + 150) * 100 / 2 = 10050 + // Avg = 10050 / 100 = 100.5 + avg := handler.getAvgResponseTime() + assert.InDelta(t, 100.5, avg, 0) +} + +// TestInflightCounter tests inflight request tracking. +func TestInflightCounter(t *testing.T) { + balancer := New(nil, false) + + var inflightAtRequest atomic.Int64 + + balancer.Add("test", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + inflightAtRequest.Store(balancer.handlers[0].inflightCount.Load()) + rw.Header().Set("server", "test") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + // Check that inflight count is 0 initially. + balancer.handlersMu.RLock() + handler := balancer.handlers[0] + balancer.handlersMu.RUnlock() + assert.Equal(t, int64(0), handler.inflightCount.Load()) + + // Make a request. + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + + // During request, inflight should have been 1. + assert.Equal(t, int64(1), inflightAtRequest.Load()) + + // After request completes, inflight should be back to 0. + assert.Equal(t, int64(0), handler.inflightCount.Load()) +} + +// TestConcurrentResponseTimeUpdates tests thread safety of response time updates. +func TestConcurrentResponseTimeUpdates(t *testing.T) { + handler := &namedHandler{ + Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), + name: "test", + weight: 1, + } + + // Concurrently update response times. + var wg sync.WaitGroup + numGoroutines := 10 + updatesPerGoroutine := 20 + + for i := range numGoroutines { + wg.Add(1) + go func(id int) { + defer wg.Done() + for range updatesPerGoroutine { + handler.updateResponseTime(time.Duration(id+1) * time.Millisecond) + } + }(i) + } + + wg.Wait() + + // Should have exactly 100 samples (buffer size). + assert.Equal(t, sampleSize, handler.sampleCount) +} + +// TestConcurrentInflightTracking tests thread safety of inflight counter. +func TestConcurrentInflightTracking(t *testing.T) { + handler := &namedHandler{ + Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(10 * time.Millisecond) + rw.WriteHeader(http.StatusOK) + }), + name: "test", + weight: 1, + } + + var maxInflight atomic.Int64 + + var wg sync.WaitGroup + numRequests := 50 + + for range numRequests { + wg.Add(1) + go func() { + defer wg.Done() + handler.inflightCount.Add(1) + defer handler.inflightCount.Add(-1) + + // Track maximum inflight count. + current := handler.inflightCount.Load() + for { + maxLoad := maxInflight.Load() + if current <= maxLoad || maxInflight.CompareAndSwap(maxLoad, current) { + break + } + } + + time.Sleep(1 * time.Millisecond) + }() + } + + wg.Wait() + + // All requests completed, inflight should be 0. + assert.Equal(t, int64(0), handler.inflightCount.Load()) + // Max inflight should be > 1 (concurrent requests). + assert.Greater(t, maxInflight.Load(), int64(1)) +} + +// TestConcurrentRequestsRespectInflight tests that the load balancer dynamically +// adapts to inflight request counts during concurrent request processing. +func TestConcurrentRequestsRespectInflight(t *testing.T) { + balancer := New(nil, false) + + // Use a channel to control when handlers start sleeping. + // This ensures we can fill one server with inflight requests before routing new ones. + blockChan := make(chan struct{}) + + // Add two servers with equal response times and weights. + balancer.Add("server1", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + <-blockChan // Wait for signal to proceed. + time.Sleep(10 * time.Millisecond) + rw.Header().Set("server", "server1") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + balancer.Add("server2", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + <-blockChan // Wait for signal to proceed. + time.Sleep(10 * time.Millisecond) + rw.Header().Set("server", "server2") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + // Pre-warm both servers to establish equal average response times. + for i := range sampleSize { + balancer.handlers[0].responseTimes[i] = 10.0 + } + balancer.handlers[0].responseTimeSum = 10.0 * sampleSize + balancer.handlers[0].sampleCount = sampleSize + + for i := range sampleSize { + balancer.handlers[1].responseTimes[i] = 10.0 + } + balancer.handlers[1].responseTimeSum = 10.0 * sampleSize + balancer.handlers[1].sampleCount = sampleSize + + // Phase 1: Launch concurrent requests to server1 that will block. + var wg sync.WaitGroup + inflightRequests := 5 + + for range inflightRequests { + wg.Add(1) + go func() { + defer wg.Done() + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + }() + } + + // Wait for goroutines to start and increment inflight counters. + // They will block on the channel, keeping inflight count high. + time.Sleep(50 * time.Millisecond) + + // Verify inflight counts before making new requests. + server1Inflight := balancer.handlers[0].inflightCount.Load() + server2Inflight := balancer.handlers[1].inflightCount.Load() + assert.Equal(t, int64(5), server1Inflight+server2Inflight) + + // Phase 2: Make new requests while the initial requests are blocked. + // These should see the high inflight counts and route to the less-loaded server. + var saveMu sync.Mutex + save := map[string]int{} + newRequests := 50 + + // Launch new requests in background so they don't block. + var newWg sync.WaitGroup + for range newRequests { + newWg.Add(1) + go func() { + defer newWg.Done() + rec := httptest.NewRecorder() + balancer.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil)) + server := rec.Header().Get("server") + if server != "" { + saveMu.Lock() + save[server]++ + saveMu.Unlock() + } + }() + } + + // Wait for new requests to start and see the inflight counts. + time.Sleep(50 * time.Millisecond) + + close(blockChan) + + wg.Wait() + newWg.Wait() + + saveMu.Lock() + total := save["server1"] + save["server2"] + server1Count := save["server1"] + server2Count := save["server2"] + saveMu.Unlock() + + assert.Equal(t, newRequests, total) + + // With inflight tracking, load should naturally balance toward equal distribution. + // We allow variance due to concurrent execution and race windows in server selection. + assert.InDelta(t, 25.0, float64(server1Count), 5.0) // 20-30 requests + assert.InDelta(t, 25.0, float64(server2Count), 5.0) // 20-30 requests +} + +// TestTTFBMeasurement tests TTFB measurement accuracy. +func TestTTFBMeasurement(t *testing.T) { + balancer := New(nil, false) + + // Add server with known delay. + delay := 50 * time.Millisecond + balancer.Add("slow", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(delay) + rw.Header().Set("server", "slow") + rw.WriteHeader(http.StatusOK) + httptrace.ContextClientTrace(req.Context()).GotFirstResponseByte() + }), pointer(1), false) + + // Make multiple requests to build average. + for range 5 { + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + // Check that average response time is approximately the delay. + avg := balancer.handlers[0].getAvgResponseTime() + + // Allow 5ms tolerance for Go timing jitter and test environment variations. + assert.InDelta(t, float64(delay.Milliseconds()), avg, 5.0) +} + +// TestZeroSamplesReturnsZero tests that getAvgResponseTime returns 0 when no samples. +func TestZeroSamplesReturnsZero(t *testing.T) { + handler := &namedHandler{ + Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), + name: "test", + weight: 1, + } + + avg := handler.getAvgResponseTime() + assert.InDelta(t, 0.0, avg, 0) +} + +// TestScoreCalculationWithWeights tests that weights are properly considered in score calculation. +func TestScoreCalculationWithWeights(t *testing.T) { + balancer := New(nil, false) + + // Add two servers with same response time but different weights. + // Server with higher weight should be preferred. + balancer.Add("weighted", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(50 * time.Millisecond) + rw.Header().Set("server", "weighted") + rw.WriteHeader(http.StatusOK) + httptrace.ContextClientTrace(req.Context()).GotFirstResponseByte() + }), pointer(3), false) // Weight 3 + + balancer.Add("normal", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(50 * time.Millisecond) + rw.Header().Set("server", "normal") + rw.WriteHeader(http.StatusOK) + httptrace.ContextClientTrace(req.Context()).GotFirstResponseByte() + }), pointer(1), false) // Weight 1 + + // Make requests to build up response time averages. + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 2 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + // Score for weighted: (50 × (1 + 0)) / 3 = 16.67 + // Score for normal: (50 × (1 + 0)) / 1 = 50 + // After warmup, weighted server has 3x better score (16.67 vs 50) and should receive nearly all requests. + recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 10 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + assert.Equal(t, 10, recorder.save["weighted"]) + assert.Zero(t, recorder.save["normal"]) +} + +// TestScoreCalculationWithInflight tests that inflight requests are considered in score calculation. +func TestScoreCalculationWithInflight(t *testing.T) { + balancer := New(nil, false) + + // We'll manually control the inflight counters to test the score calculation. + // Add two servers with same response time. + balancer.Add("server1", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(10 * time.Millisecond) + rw.Header().Set("server", "server1") + rw.WriteHeader(http.StatusOK) + httptrace.ContextClientTrace(req.Context()).GotFirstResponseByte() + }), pointer(1), false) + + balancer.Add("server2", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(10 * time.Millisecond) + rw.Header().Set("server", "server2") + rw.WriteHeader(http.StatusOK) + httptrace.ContextClientTrace(req.Context()).GotFirstResponseByte() + }), pointer(1), false) + + // Build up response time averages for both servers. + for range 2 { + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + // Now manually set server1 to have high inflight count. + balancer.handlers[0].inflightCount.Store(5) + + // Make requests - they should prefer server2 because: + // Score for server1: (10 × (1 + 5)) / 1 = 60 + // Score for server2: (10 × (1 + 0)) / 1 = 10 + recorder := &responseRecorder{save: map[string]int{}} + for range 5 { + // Manually increment to simulate the ServeHTTP behavior. + server, _ := balancer.nextServer() + server.inflightCount.Add(1) + + if server.name == "server1" { + recorder.save["server1"]++ + } else { + recorder.save["server2"]++ + } + } + + // Server2 should get all requests + assert.Equal(t, 5, recorder.save["server2"]) + assert.Zero(t, recorder.save["server1"]) +} + +// TestScoreCalculationColdStart tests that new servers (0ms avg) get fair selection +func TestScoreCalculationColdStart(t *testing.T) { + balancer := New(nil, false) + + // Add a warm server with established response time + balancer.Add("warm", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(50 * time.Millisecond) + rw.Header().Set("server", "warm") + rw.WriteHeader(http.StatusOK) + httptrace.ContextClientTrace(req.Context()).GotFirstResponseByte() + }), pointer(1), false) + + // Warm up the first server + for range 10 { + recorder := httptest.NewRecorder() + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + // Now add a cold server (new, no response time data) + balancer.Add("cold", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(10 * time.Millisecond) // Actually faster + rw.Header().Set("server", "cold") + rw.WriteHeader(http.StatusOK) + httptrace.ContextClientTrace(req.Context()).GotFirstResponseByte() + }), pointer(1), false) + + // Cold server should get selected because: + // Score for warm: (50 × (1 + 0)) / 1 = 50 + // Score for cold: (0 × (1 + 0)) / 1 = 0 + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 20 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + // Cold server should get all or most requests initially due to 0ms average + assert.Greater(t, recorder.save["cold"], 10) + + // After cold server builds up its average, it should continue to get more traffic + // because it's actually faster (10ms vs 50ms) + recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 20 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + assert.Greater(t, recorder.save["cold"], recorder.save["warm"]) +} + +// TestFastServerGetsMoreTraffic verifies that servers with lower response times +// receive proportionally more traffic in steady state (after cold start). +// This tests the core selection bias of the least-time algorithm. +func TestFastServerGetsMoreTraffic(t *testing.T) { + balancer := New(nil, false) + + // Add two servers with different static response times. + balancer.Add("fast", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(20 * time.Millisecond) + rw.Header().Set("server", "fast") + rw.WriteHeader(http.StatusOK) + httptrace.ContextClientTrace(req.Context()).GotFirstResponseByte() + }), pointer(1), false) + + balancer.Add("slow", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(100 * time.Millisecond) + rw.Header().Set("server", "slow") + rw.WriteHeader(http.StatusOK) + httptrace.ContextClientTrace(req.Context()).GotFirstResponseByte() + }), pointer(1), false) + + // After just 1 request to each server, the algorithm identifies the fastest server + // and routes nearly all subsequent traffic there (converges in ~2 requests). + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 50 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + assert.Greater(t, recorder.save["fast"], recorder.save["slow"]) + assert.Greater(t, recorder.save["fast"], 48) // Expect ~96-98% to fast server (48-49/50). +} + +// TestTrafficShiftsWhenPerformanceDegrades verifies that the load balancer +// adapts to changing server performance by shifting traffic away from degraded servers. +// This tests the adaptive behavior - the core value proposition of least-time load balancing. +func TestTrafficShiftsWhenPerformanceDegrades(t *testing.T) { + balancer := New(nil, false) + + // Use atomic to dynamically control server1's response time. + server1Delay := atomic.Int64{} + server1Delay.Store(5) // Start with 5ms + + balancer.Add("server1", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(time.Duration(server1Delay.Load()) * time.Millisecond) + rw.Header().Set("server", "server1") + rw.WriteHeader(http.StatusOK) + httptrace.ContextClientTrace(req.Context()).GotFirstResponseByte() + }), pointer(1), false) + + balancer.Add("server2", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(5 * time.Millisecond) // Static 5ms + rw.Header().Set("server", "server2") + rw.WriteHeader(http.StatusOK) + httptrace.ContextClientTrace(req.Context()).GotFirstResponseByte() + }), pointer(1), false) + + // Pre-fill ring buffers to eliminate cold start effects and ensure deterministic equal performance state. + for _, h := range balancer.handlers { + for i := range sampleSize { + h.responseTimes[i] = 5.0 + } + h.responseTimeSum = 5.0 * sampleSize + h.sampleCount = sampleSize + } + + // Phase 1: Both servers perform equally (5ms each). + recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 50 { + balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + // With equal performance and pre-filled buffers, distribution should be balanced via WRR tie-breaking. + total := recorder.save["server1"] + recorder.save["server2"] + assert.Equal(t, 50, total) + assert.InDelta(t, 25, recorder.save["server1"], 10) // 25 ± 10 requests + assert.InDelta(t, 25, recorder.save["server2"], 10) // 25 ± 10 requests + + // Phase 2: server1 degrades (simulating GC pause, CPU spike, or network latency). + server1Delay.Store(15) // Now 15ms (3x slower) + + // Make more requests to shift the moving average. + // Ring buffer has 100 samples, need significant new samples to shift average. + // server1's average will climb from ~5ms toward 15ms. + recorder2 := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} + for range 60 { + balancer.ServeHTTP(recorder2, httptest.NewRequest(http.MethodGet, "/", nil)) + } + + // server2 should get significantly more traffic (>75%) + // Score for server1: (~10-15ms × 1) / 1 = 10-15 (as average climbs) + // Score for server2: (5ms × 1) / 1 = 5 + total2 := recorder2.save["server1"] + recorder2.save["server2"] + assert.Equal(t, 60, total2) + assert.Greater(t, recorder2.save["server2"], 45) // At least 75% (45/60) + assert.Less(t, recorder2.save["server1"], 15) // At most 25% (15/60) +} + +// TestMultipleServersWithSameScore tests WRR tie-breaking when multiple servers have identical scores. +// Uses nextServer() directly to avoid timing variations in the test. +func TestMultipleServersWithSameScore(t *testing.T) { + balancer := New(nil, false) + + // Add three servers with identical response times and weights. + balancer.Add("server1", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(5 * time.Millisecond) + rw.Header().Set("server", "server1") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + balancer.Add("server2", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(5 * time.Millisecond) + rw.Header().Set("server", "server2") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + balancer.Add("server3", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(5 * time.Millisecond) + rw.Header().Set("server", "server3") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) + + // Set all servers to identical response times to trigger tie-breaking. + for _, h := range balancer.handlers { + for i := range sampleSize { + h.responseTimes[i] = 5.0 + } + h.responseTimeSum = 5.0 * sampleSize + h.sampleCount = sampleSize + } + + // With all servers having identical scores, WRR tie-breaking should distribute fairly. + // Test the selection logic directly without actual HTTP requests to avoid timing variations. + counts := map[string]int{"server1": 0, "server2": 0, "server3": 0} + for range 90 { + server, err := balancer.nextServer() + assert.NoError(t, err) + counts[server.name]++ + } + + total := counts["server1"] + counts["server2"] + counts["server3"] + assert.Equal(t, 90, total) + + // With WRR and 90 requests, each server should get ~30 requests (±1 due to initialization). + assert.InDelta(t, 30, counts["server1"], 1) + assert.InDelta(t, 30, counts["server2"], 1) + assert.InDelta(t, 30, counts["server3"], 1) +} + +// TestWRRTieBreakingWeightedDistribution tests weighted distribution among tied servers. +// Uses nextServer() directly to avoid timing variations in the test. +func TestWRRTieBreakingWeightedDistribution(t *testing.T) { + balancer := New(nil, false) + + // Add two servers with different weights. + // To create equal scores, response times must be proportional to weights. + balancer.Add("weighted", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(15 * time.Millisecond) // 3x longer due to 3x weight + rw.Header().Set("server", "weighted") + rw.WriteHeader(http.StatusOK) + }), pointer(3), false) // Weight 3 + + balancer.Add("normal", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(5 * time.Millisecond) + rw.Header().Set("server", "normal") + rw.WriteHeader(http.StatusOK) + }), pointer(1), false) // Weight 1 + + // Since response times is proportional to weights, both scores are equal, so WRR tie-breaking will apply. + // weighted: score = (15 * 1) / 3 = 5 + // normal: score = (5 * 1) / 1 = 5 + for i := range sampleSize { + balancer.handlers[0].responseTimes[i] = 15.0 + } + balancer.handlers[0].responseTimeSum = 15.0 * sampleSize + balancer.handlers[0].sampleCount = sampleSize + + for i := range sampleSize { + balancer.handlers[1].responseTimes[i] = 5.0 + } + balancer.handlers[1].responseTimeSum = 5.0 * sampleSize + balancer.handlers[1].sampleCount = sampleSize + + // Test the selection logic directly without actual HTTP requests to avoid timing variations. + counts := map[string]int{"weighted": 0, "normal": 0} + for range 80 { + server, err := balancer.nextServer() + assert.NoError(t, err) + counts[server.name]++ + } + + total := counts["weighted"] + counts["normal"] + assert.Equal(t, 80, total) + + // With 3:1 weight ratio, distribution should be ~75%/25% (60/80 and 20/80), ±1 due to initialization. + assert.InDelta(t, 60, counts["weighted"], 1) + assert.InDelta(t, 20, counts["normal"], 1) +} diff --git a/pkg/server/service/loadbalancer/p2c/p2c.go b/pkg/server/service/loadbalancer/p2c/p2c.go index d41de0a2e..948a7d5a1 100644 --- a/pkg/server/service/loadbalancer/p2c/p2c.go +++ b/pkg/server/service/loadbalancer/p2c/p2c.go @@ -14,6 +14,8 @@ import ( "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer" ) +var errNoAvailableServer = errors.New("no available server") + type namedHandler struct { http.Handler @@ -81,7 +83,7 @@ func New(stickyConfig *dynamic.Sticky, wantsHealthCheck bool) *Balancer { } // SetStatus sets on the balancer that its given child is now of the given -// status. balancerName is only needed for logging purposes. +// status. childName is only needed for logging purposes. func (b *Balancer) SetStatus(ctx context.Context, childName string, up bool) { b.handlersMu.Lock() defer b.handlersMu.Unlock() @@ -126,14 +128,12 @@ func (b *Balancer) SetStatus(ctx context.Context, childName string, up bool) { // Not thread safe. func (b *Balancer) RegisterStatusUpdater(fn func(up bool)) error { if !b.wantsHealthCheck { - return errors.New("healthCheck not enabled in config for this weighted service") + return errors.New("healthCheck not enabled in config for this P2C service") } b.updaters = append(b.updaters, fn) return nil } -var errNoAvailableServer = errors.New("no available server") - func (b *Balancer) nextServer() (*namedHandler, error) { // We kept the same representation (map) as in the WRR strategy to improve maintainability. // However, with the P2C strategy, we only need a slice of healthy servers. diff --git a/pkg/server/service/loadbalancer/wrr/wrr.go b/pkg/server/service/loadbalancer/wrr/wrr.go index b2665b56d..69bc2c498 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr.go +++ b/pkg/server/service/loadbalancer/wrr/wrr.go @@ -12,6 +12,8 @@ import ( "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer" ) +var errNoAvailableServer = errors.New("no available server") + type namedHandler struct { http.Handler name string @@ -94,7 +96,7 @@ func (b *Balancer) Pop() interface{} { } // SetStatus sets on the balancer that its given child is now of the given -// status. balancerName is only needed for logging purposes. +// status. childName is only needed for logging purposes. func (b *Balancer) SetStatus(ctx context.Context, childName string, up bool) { b.handlersMu.Lock() defer b.handlersMu.Unlock() @@ -139,14 +141,12 @@ func (b *Balancer) SetStatus(ctx context.Context, childName string, up bool) { // Not thread safe. func (b *Balancer) RegisterStatusUpdater(fn func(up bool)) error { if !b.wantsHealthCheck { - return errors.New("healthCheck not enabled in config for this weighted service") + return errors.New("healthCheck not enabled in config for this WRR service") } b.updaters = append(b.updaters, fn) return nil } -var errNoAvailableServer = errors.New("no available server") - func (b *Balancer) nextServer() (*namedHandler, error) { b.handlersMu.Lock() defer b.handlersMu.Unlock() diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index c49bd8809..e61820f3d 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -29,6 +29,7 @@ import ( "github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/failover" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/hrw" + "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/leasttime" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/mirror" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/p2c" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/wrr" @@ -402,6 +403,8 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName lb = p2c.New(service.Sticky, service.HealthCheck != nil) case dynamic.BalancerStrategyHRW: lb = hrw.New(service.HealthCheck != nil) + case dynamic.BalancerStrategyLeastTime: + lb = leasttime.New(service.Sticky, service.HealthCheck != nil) default: return nil, fmt.Errorf("unsupported load-balancer strategy %q", service.Strategy) } diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go index 05d674e16..574dd1a5b 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -82,6 +82,20 @@ func TestGetLoadBalancer(t *testing.T) { fwd: &forwarderMock{}, expectError: false, }, + { + desc: "Fails when unsupported strategy is set", + serviceName: "test", + service: &dynamic.ServersLoadBalancer{ + Strategy: "invalid", + Servers: []dynamic.Server{ + { + URL: "http://localhost:8080", + }, + }, + }, + fwd: &forwarderMock{}, + expectError: true, + }, } for _, test := range testCases { From 5c489c05fc48ac66b61f379046535ed07469660e Mon Sep 17 00:00:00 2001 From: leccelecce <24962424+leccelecce@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:40:05 +0100 Subject: [PATCH 018/134] Reduce vertical padding in dashboard table rows for more compact layout --- webui/src/App.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/webui/src/App.tsx b/webui/src/App.tsx index a3dd0e387..b3f536ef2 100644 --- a/webui/src/App.tsx +++ b/webui/src/App.tsx @@ -1,4 +1,4 @@ -import { Box, darkTheme, FaencyProvider, lightTheme } from '@traefiklabs/faency' +import { globalCss, Box, darkTheme, FaencyProvider, lightTheme } from '@traefiklabs/faency' import { Suspense, useEffect } from 'react' import { HelmetProvider } from 'react-helmet-async' import { HashRouter, Navigate, Route, Routes as RouterRoutes, useLocation } from 'react-router-dom' @@ -71,6 +71,12 @@ export const Routes = () => { const isDev = import.meta.env.NODE_ENV === 'development' +const customGlobalStyle = globalCss({ + 'span[role=cell]': { // target the AriaTd component + p: '$2 $3' + }, +}) + const App = () => { const isDarkMode = useIsDarkMode() @@ -95,6 +101,7 @@ const App = () => { > + {customGlobalStyle()} From 10be359327116c78fba4e404fb64f91a2044258b Mon Sep 17 00:00:00 2001 From: Alexis Couvreur Date: Fri, 24 Oct 2025 08:08:04 -0400 Subject: [PATCH 019/134] Allow discovering non-running Docker containers --- .../other-providers/docker.md | 22 + integration/docker_test.go | 58 ++- integration/resources/compose/docker.yml | 6 + pkg/provider/docker/builder_test.go | 1 + pkg/provider/docker/config.go | 28 ++ pkg/provider/docker/config_test.go | 459 ++++++++++++++++++ pkg/provider/docker/data.go | 1 + pkg/provider/docker/pdocker.go | 4 +- pkg/provider/docker/shared.go | 7 +- pkg/provider/docker/shared_labels.go | 25 +- 10 files changed, 600 insertions(+), 11 deletions(-) diff --git a/docs/content/reference/routing-configuration/other-providers/docker.md b/docs/content/reference/routing-configuration/other-providers/docker.md index a6773b190..a17ae2a09 100644 --- a/docs/content/reference/routing-configuration/other-providers/docker.md +++ b/docs/content/reference/routing-configuration/other-providers/docker.md @@ -688,6 +688,27 @@ You can tell Traefik to consider (or not) the container by setting `traefik.enab This option overrides the value of `exposedByDefault`. +#### `traefik.docker.allownonrunning` + +```yaml +- "traefik.docker.allownonrunning=true" +``` + +By default, Traefik only considers containers in "running" state. +This option controls whether containers that are not in "running" state (e.g., stopped, paused, exited) should still be visible to Traefik for service discovery. + +When this label is set to true, Traefik will: + +- Keep the router and service configuration even when the container is not running +- Create services with empty backend server lists +- Return 503 Service Unavailable for requests to stopped containers (instead of 404 Not Found) +- Execute the full middleware chain, allowing middlewares to intercept requests + +!!! warning "Configuration Collision" + + As the `traefik.docker.allownonrunning` enables the discovery of all containers exposing this option disregarding their state, + if multiple stopped containers expose the same router but their configurations diverge, then the routers will be dropped. + #### `traefik.docker.network` ```yaml @@ -700,4 +721,5 @@ If a container is linked to several networks, be sure to set the proper network otherwise it will randomly pick one (depending on how docker is returning them). !!! warning + When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`. diff --git a/integration/docker_test.go b/integration/docker_test.go index 1a767ff6f..08606b60b 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -32,7 +32,7 @@ func (s *DockerSuite) TearDownSuite() { } func (s *DockerSuite) TearDownTest() { - s.composeStop("simple", "withtcplabels", "withlabels1", "withlabels2", "withonelabelmissing", "powpow") + s.composeStop("simple", "withtcplabels", "withlabels1", "withlabels2", "withonelabelmissing", "powpow", "nonRunning") } func (s *DockerSuite) TestSimpleConfiguration() { @@ -222,3 +222,59 @@ func (s *DockerSuite) TestRestartDockerContainers() { err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("powpow")) require.NoError(s.T(), err) } + +func (s *DockerSuite) TestDockerAllowNonRunning() { + tempObjects := struct { + DockerHost string + DefaultRule string + }{ + DockerHost: s.getDockerHost(), + DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", + } + + file := s.adaptFile("fixtures/docker/simple.toml", tempObjects) + + s.composeUp("nonRunning") + + // Start traefik + s.traefikCmd(withConfigFile(file)) + + // Verify the container is working when running + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) + require.NoError(s.T(), err) + req.Host = "non.running.host" + + resp, err := try.ResponseUntilStatusCode(req, 3*time.Second, http.StatusOK) + require.NoError(s.T(), err) + + body, err := io.ReadAll(resp.Body) + require.NoError(s.T(), err) + assert.Contains(s.T(), string(body), "Hostname:") + + // Verify the router exists in Traefik configuration + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers", 1*time.Second, try.BodyContains("NonRunning")) + require.NoError(s.T(), err) + + // Stop the container + s.composeStop("nonRunning") + + // Wait a bit for container stop to be detected + time.Sleep(2 * time.Second) + + // Verify the router still exists in configuration even though container is stopped + // This is the key test - the router should persist due to allowNonRunning=true + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers", 10*time.Second, try.BodyContains("NonRunning")) + require.NoError(s.T(), err) + + // Verify the service still exists in configuration + err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1*time.Second, try.BodyContains("nonRunning")) + require.NoError(s.T(), err) + + // HTTP requests should fail (502 Bad Gateway) since container is stopped but router exists + req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) + require.NoError(s.T(), err) + req.Host = "non.running.host" + + err = try.Request(req, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) + require.NoError(s.T(), err) +} diff --git a/integration/resources/compose/docker.yml b/integration/resources/compose/docker.yml index 788866e34..783d5be38 100644 --- a/integration/resources/compose/docker.yml +++ b/integration/resources/compose/docker.yml @@ -35,3 +35,9 @@ services: labels: traefik.http.Routers.Super.Rule: Host(`my.super.host`) traefik.http.Services.powpow.LoadBalancer.server.Port: 2375 + + nonRunning: + image: traefik/whoami + labels: + traefik.http.Routers.NonRunning.Rule: Host(`non.running.host`) + traefik.docker.allownonrunning: "true" diff --git a/pkg/provider/docker/builder_test.go b/pkg/provider/docker/builder_test.go index c0b4f1d7f..4d001c8ce 100644 --- a/pkg/provider/docker/builder_test.go +++ b/pkg/provider/docker/builder_test.go @@ -12,6 +12,7 @@ func containerJSON(ops ...func(*containertypes.InspectResponse)) containertypes. ContainerJSONBase: &containertypes.ContainerJSONBase{ Name: "fake", HostConfig: &containertypes.HostConfig{}, + State: &containertypes.State{}, }, Config: &containertypes.Config{}, NetworkSettings: &containertypes.NetworkSettings{ diff --git a/pkg/provider/docker/config.go b/pkg/provider/docker/config.go index 713586295..c0140655e 100644 --- a/pkg/provider/docker/config.go +++ b/pkg/provider/docker/config.go @@ -114,6 +114,11 @@ func (p *DynConfBuilder) buildTCPServiceConfiguration(ctx context.Context, conta } } + // Keep an empty server load-balancer for non-running containers. + if container.Status != "" && container.Status != containertypes.StateRunning { + return nil + } + // Keep an empty server load-balancer for unhealthy containers. if container.Health != "" && container.Health != containertypes.Healthy { return nil } @@ -138,6 +143,11 @@ func (p *DynConfBuilder) buildUDPServiceConfiguration(ctx context.Context, conta } } + // Keep an empty server load-balancer for non-running containers. + if container.Status != "" && container.Status != containertypes.StateRunning { + return nil + } + // Keep an empty server load-balancer for unhealthy containers. if container.Health != "" && container.Health != containertypes.Healthy { return nil } @@ -164,6 +174,11 @@ func (p *DynConfBuilder) buildServiceConfiguration(ctx context.Context, containe } } + // Keep an empty server load-balancer for non-running containers. + if container.Status != "" && container.Status != containertypes.StateRunning { + return nil + } + // Keep an empty server load-balancer for unhealthy containers. if container.Health != "" && container.Health != containertypes.Healthy { return nil } @@ -196,6 +211,19 @@ func (p *DynConfBuilder) keepContainer(ctx context.Context, container dockerData return false } + // AllowNonRunning has precedence over AllowEmptyServices. + // If AllowNonRunning is true, we don't care about the container health/status, + // and we need to quit before checking it. + // Only configurable with the Docker provider. + if container.ExtraConf.AllowNonRunning { + return true + } + + if container.Status != "" && container.Status != containertypes.StateRunning { + logger.Debug().Msg("Filtering non running container") + return false + } + if !p.AllowEmptyServices && container.Health != "" && container.Health != containertypes.Healthy { logger.Debug().Msg("Filtering unhealthy or starting container") return false diff --git a/pkg/provider/docker/config_test.go b/pkg/provider/docker/config_test.go index e54b980c3..33b8e390a 100644 --- a/pkg/provider/docker/config_test.go +++ b/pkg/provider/docker/config_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/types" ) @@ -3935,6 +3936,464 @@ func TestDynConfBuilder_build(t *testing.T) { } } +func TestDynConfBuilder_build_allowNonRunning(t *testing.T) { + testCases := []struct { + desc string + containers []dockerData + expected *dynamic.Configuration + }{ + { + desc: "exited container with allowNonRunning=true should create router and service without servers", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Status: "exited", + Health: "", + ExtraConf: configuration{ + Enable: true, + AllowNonRunning: true, + }, + NetworkSettings: networkSettings{ + NetworkMode: "bridge", + Ports: nat.PortMap{ + "80/tcp": []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: pointer(true), + Strategy: "wrr", + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "exited container with allowNonRunning=false should not create anything", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Status: "exited", + Health: "", + ExtraConf: configuration{ + Enable: true, + AllowNonRunning: false, + }, + NetworkSettings: networkSettings{ + NetworkMode: "bridge", + Ports: nat.PortMap{ + "80/tcp": []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "running container with allowNonRunning=true should work normally with servers", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Status: "running", + Health: "", + ExtraConf: configuration{ + Enable: true, + AllowNonRunning: true, + }, + NetworkSettings: networkSettings{ + NetworkMode: "bridge", + Ports: nat.PortMap{ + "80/tcp": []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:80", + }, + }, + PassHostHeader: pointer(true), + Strategy: "wrr", + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "created container with allowNonRunning=true should create router and service without servers)", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Status: "created", + Health: "", + ExtraConf: configuration{ + Enable: true, + AllowNonRunning: true, + }, + NetworkSettings: networkSettings{ + NetworkMode: "bridge", + Ports: nat.PortMap{ + "80/tcp": []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: pointer(true), + Strategy: "wrr", + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "dead container with allowNonRunning=true should create router and service without servers)", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Status: "dead", + Health: "", + ExtraConf: configuration{ + Enable: true, + AllowNonRunning: true, + }, + NetworkSettings: networkSettings{ + NetworkMode: "bridge", + Ports: nat.PortMap{ + "80/tcp": []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: pointer(true), + Strategy: "wrr", + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "exited container with TCP configuration and allowNonRunning=true should create TCP service without servers", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Status: "exited", + Health: "", + Labels: map[string]string{ + "traefik.tcp.routers.Test.rule": "HostSNI(`test.localhost`)", + }, + ExtraConf: configuration{ + Enable: true, + AllowNonRunning: true, + }, + NetworkSettings: networkSettings{ + NetworkMode: "bridge", + Ports: nat.PortMap{ + "80/tcp": []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "Test": { + Service: "Test", + Rule: "HostSNI(`test.localhost`)", + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "Test": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{}, + }, + }, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "exited container with UDP configuration and allowNonRunning=true should create UDP service without servers", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Status: "exited", + Health: "", + Labels: map[string]string{ + "traefik.udp.routers.Test.entrypoints": "udp", + }, + ExtraConf: configuration{ + Enable: true, + AllowNonRunning: true, + }, + NetworkSettings: networkSettings{ + NetworkMode: "bridge", + Ports: nat.PortMap{ + "80/udp": []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "Test": { + Service: "Test", + EntryPoints: []string{"udp"}, + }, + }, + Services: map[string]*dynamic.UDPService{ + "Test": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{}, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(DefaultTemplateRule, nil) + require.NoError(t, err) + + p := Shared{ + ExposedByDefault: true, + DefaultRule: DefaultTemplateRule, + defaultRuleTpl: defaultRuleTpl, + } + + builder := NewDynConfBuilder(p, nil, false) + configuration := builder.build(t.Context(), test.containers) + + assert.Equal(t, test.expected, configuration) + }) + } +} + func TestDynConfBuilder_getIPPort_docker(t *testing.T) { type expected struct { ip string diff --git a/pkg/provider/docker/data.go b/pkg/provider/docker/data.go index 4c42d396e..22dde04e7 100644 --- a/pkg/provider/docker/data.go +++ b/pkg/provider/docker/data.go @@ -10,6 +10,7 @@ type dockerData struct { ID string ServiceName string Name string + Status string Labels map[string]string // List of labels set to container or service NetworkSettings networkSettings Health string diff --git a/pkg/provider/docker/pdocker.go b/pkg/provider/docker/pdocker.go index 0720746a3..0ed6e53aa 100644 --- a/pkg/provider/docker/pdocker.go +++ b/pkg/provider/docker/pdocker.go @@ -165,7 +165,9 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } func (p *Provider) listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) { - containerList, err := dockerClient.ContainerList(ctx, container.ListOptions{}) + containerList, err := dockerClient.ContainerList(ctx, container.ListOptions{ + All: true, + }) if err != nil { return nil, err } diff --git a/pkg/provider/docker/shared.go b/pkg/provider/docker/shared.go index 8d2ef4001..01eb92e19 100644 --- a/pkg/provider/docker/shared.go +++ b/pkg/provider/docker/shared.go @@ -43,9 +43,9 @@ func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClie return dockerData{} } - // This condition is here to avoid to have empty IP https://github.com/traefik/traefik/issues/2459 - // We register only container which are running - if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running { + // Always parse all containers (running and stopped) + // The allowNonRunning filtering will be applied later in service configuration + if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil { return parseContainer(containerInspected) } @@ -61,6 +61,7 @@ func parseContainer(container containertypes.InspectResponse) dockerData { dData.ID = container.ContainerJSONBase.ID dData.Name = container.ContainerJSONBase.Name dData.ServiceName = dData.Name // Default ServiceName to be the container's Name. + dData.Status = container.ContainerJSONBase.State.Status if container.ContainerJSONBase.HostConfig != nil { dData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode diff --git a/pkg/provider/docker/shared_labels.go b/pkg/provider/docker/shared_labels.go index 6eab7143d..7d3bed8c1 100644 --- a/pkg/provider/docker/shared_labels.go +++ b/pkg/provider/docker/shared_labels.go @@ -16,17 +16,24 @@ const ( // configuration contains information from the labels that are globals (not related to the dynamic configuration) // or specific to the provider. type configuration struct { - Enable bool - Network string - LBSwarm bool + Enable bool + Network string + LBSwarm bool + AllowNonRunning bool } type labelConfiguration struct { Enable bool - Docker *specificConfiguration + Docker *dockerSpecificConfiguration Swarm *specificConfiguration } +type dockerSpecificConfiguration struct { + Network *string + LBSwarm bool + AllowNonRunning bool +} + type specificConfiguration struct { Network *string LBSwarm bool @@ -43,9 +50,15 @@ func (p *Shared) extractDockerLabels(container dockerData) (configuration, error network = *conf.Docker.Network } + var allowNonRunning bool + if conf.Docker != nil { + allowNonRunning = conf.Docker.AllowNonRunning + } + return configuration{ - Enable: conf.Enable, - Network: network, + Enable: conf.Enable, + Network: network, + AllowNonRunning: allowNonRunning, }, nil } From db4f2629160af57f62870102dd05cf012d27185f Mon Sep 17 00:00:00 2001 From: "Gina A." <70909035+gndz07@users.noreply.github.com> Date: Mon, 27 Oct 2025 17:40:06 +0100 Subject: [PATCH 020/134] Add Traefik Hub demo in dashboard --- webui/package.json | 5 +- .../img/gopher-something-went-wrong.png | Bin 0 -> 46066 bytes webui/src/App.tsx | 88 +-- webui/src/components/SpinnerLoader.tsx | 6 +- .../components/icons/providers/Knative.tsx | 10 +- .../src/components/icons/providers/index.tsx | 2 +- webui/src/layout/Navigation.tsx | 14 +- webui/src/layout/Page.spec.tsx | 4 +- webui/src/layout/Page.tsx | 29 +- webui/src/pages/NotFound.tsx | 30 +- webui/src/pages/dashboard/Dashboard.tsx | 279 ++++---- webui/src/pages/http/HttpMiddleware.spec.tsx | 7 + webui/src/pages/http/HttpMiddleware.tsx | 23 +- webui/src/pages/http/HttpMiddlewares.spec.tsx | 8 +- webui/src/pages/http/HttpMiddlewares.tsx | 9 +- webui/src/pages/http/HttpRouter.spec.tsx | 4 + webui/src/pages/http/HttpRouter.tsx | 23 +- webui/src/pages/http/HttpRouters.spec.tsx | 8 +- webui/src/pages/http/HttpRouters.tsx | 9 +- webui/src/pages/http/HttpService.spec.tsx | 6 + webui/src/pages/http/HttpService.tsx | 23 +- webui/src/pages/http/HttpServices.spec.tsx | 8 +- webui/src/pages/http/HttpServices.tsx | 9 +- .../src/pages/hub-demo/HubDashboard.spec.tsx | 204 ++++++ webui/src/pages/hub-demo/HubDashboard.tsx | 147 ++++ webui/src/pages/hub-demo/HubDemoNav.tsx | 84 +++ webui/src/pages/hub-demo/demoNavContext.tsx | 15 + webui/src/pages/hub-demo/hub-demo.d.ts | 21 + webui/src/pages/hub-demo/icons/api.tsx | 68 ++ webui/src/pages/hub-demo/icons/dashboard.tsx | 28 + webui/src/pages/hub-demo/icons/gateway.tsx | 69 ++ webui/src/pages/hub-demo/icons/hub.tsx | 18 + webui/src/pages/hub-demo/icons/index.ts | 5 + webui/src/pages/hub-demo/icons/portal.tsx | 48 ++ .../src/pages/hub-demo/use-hub-demo.spec.tsx | 301 +++++++++ webui/src/pages/hub-demo/use-hub-demo.tsx | 89 +++ .../scriptVerification.integration.spec.ts | 156 +++++ .../workers/scriptVerification.spec.ts | 125 ++++ .../hub-demo/workers/scriptVerification.ts | 57 ++ .../workers/scriptVerificationWorker.ts | 189 ++++++ webui/src/pages/tcp/TcpMiddleware.spec.tsx | 5 + webui/src/pages/tcp/TcpMiddleware.tsx | 23 +- webui/src/pages/tcp/TcpMiddlewares.spec.tsx | 8 +- webui/src/pages/tcp/TcpMiddlewares.tsx | 9 +- webui/src/pages/tcp/TcpRouter.spec.tsx | 4 + webui/src/pages/tcp/TcpRouter.tsx | 23 +- webui/src/pages/tcp/TcpRouters.spec.tsx | 8 +- webui/src/pages/tcp/TcpRouters.tsx | 9 +- webui/src/pages/tcp/TcpService.spec.tsx | 6 + webui/src/pages/tcp/TcpService.tsx | 23 +- webui/src/pages/tcp/TcpServices.spec.tsx | 8 +- webui/src/pages/tcp/TcpServices.tsx | 9 +- webui/src/pages/udp/UdpRouter.spec.tsx | 4 + webui/src/pages/udp/UdpRouter.tsx | 23 +- webui/src/pages/udp/UdpRouters.spec.tsx | 8 +- webui/src/pages/udp/UdpRouters.tsx | 9 +- webui/src/pages/udp/UdpService.spec.tsx | 6 + webui/src/pages/udp/UdpService.tsx | 23 +- webui/src/pages/udp/UdpServices.spec.tsx | 8 +- webui/src/pages/udp/UdpServices.tsx | 9 +- webui/src/types/global.d.ts | 1 + webui/src/utils/test.tsx | 7 +- webui/test/setup.ts | 6 +- webui/yarn.lock | 636 +++++++++--------- 64 files changed, 2481 insertions(+), 622 deletions(-) create mode 100644 webui/public/img/gopher-something-went-wrong.png create mode 100644 webui/src/pages/hub-demo/HubDashboard.spec.tsx create mode 100644 webui/src/pages/hub-demo/HubDashboard.tsx create mode 100644 webui/src/pages/hub-demo/HubDemoNav.tsx create mode 100644 webui/src/pages/hub-demo/demoNavContext.tsx create mode 100644 webui/src/pages/hub-demo/hub-demo.d.ts create mode 100644 webui/src/pages/hub-demo/icons/api.tsx create mode 100644 webui/src/pages/hub-demo/icons/dashboard.tsx create mode 100644 webui/src/pages/hub-demo/icons/gateway.tsx create mode 100644 webui/src/pages/hub-demo/icons/hub.tsx create mode 100644 webui/src/pages/hub-demo/icons/index.ts create mode 100644 webui/src/pages/hub-demo/icons/portal.tsx create mode 100644 webui/src/pages/hub-demo/use-hub-demo.spec.tsx create mode 100644 webui/src/pages/hub-demo/use-hub-demo.tsx create mode 100644 webui/src/pages/hub-demo/workers/scriptVerification.integration.spec.ts create mode 100644 webui/src/pages/hub-demo/workers/scriptVerification.spec.ts create mode 100644 webui/src/pages/hub-demo/workers/scriptVerification.ts create mode 100644 webui/src/pages/hub-demo/workers/scriptVerificationWorker.ts diff --git a/webui/package.json b/webui/package.json index 3721a8ae1..d2972a97c 100644 --- a/webui/package.json +++ b/webui/package.json @@ -43,6 +43,8 @@ "type": "module", "dependencies": { "@eslint/js": "^9.32.0", + "@noble/ed25519": "^3.0.0", + "@noble/hashes": "^2.0.1", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.1", @@ -56,6 +58,7 @@ "@typescript-eslint/parser": "^8.38.0", "@vitejs/plugin-react": "^4.7.0", "@vitest/coverage-v8": "^3.2.4", + "@vitest/web-worker": "^4.0.2", "chart.js": "^4.4.1", "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", @@ -85,7 +88,7 @@ "usehooks-ts": "^2.14.0", "vite": "^5.4.19", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^3.2.4", + "vitest": "^4.0.3", "vitest-canvas-mock": "^0.3.3" }, "devDependencies": { diff --git a/webui/public/img/gopher-something-went-wrong.png b/webui/public/img/gopher-something-went-wrong.png new file mode 100644 index 0000000000000000000000000000000000000000..fb0d6d4c31521832673ca3775184dae5104967ad GIT binary patch literal 46066 zcmZsi_dnI|8^_;gIOo{MmU-+MM@F_|lbK{Cj?Bo2>~V%YvZC~{4k}7Uk|g8UDne3H zw(PPwIQaGleDBBO{^7nK_Yc?oxUcJayzb|H-Ko}=#*B14bN~P_nwl8c000>OuS+8! z|7POz=pX+ZoLZaP8vYx)-V1RF4s{EQz=lS-hex^Hi)5h$#p?iGgP6u)TV>ihU*h*uci)b;l5XaX)L$}ffx5J$Wf z9LtYZN_Z6GayQ!jUR-c|#@I9>ATEhtUh!UXn59QxAU-i7J=`xUfmK|=JB-LCu5dR# zEC84692jZk8O)hl=^a7vjf!^-icT#G2_~kpN}#!9m5pwA7uN@8m*4I9J}jTRA z!Y%~I>< zVoOd|pD#=|#!^r|86Ymo9 zJTF|n&ZBXYOVLU}|C+3p#pNqE1a#e4WQ}>90+}RrH~02rNaXKp#*!BPVvm}Hl?{d5 z!aYL?W`4=?*Mhk(>a6_zYa4Q3)Y#KIv1oZW$0hdR(aDK$cCEe#9_2^WaEVrS47*e{ zpk?EM3eUFDlCt*+6CoB?HMPF)?u&m=9EeMgO3l6E;?E?aLdPd_|FQggf9S(BRd3N?9{=d@~Cn-Z6?dxabEob+O=SJcKEq=aSHeDY-r7o9K3z{;6hgUZi zI?B)18gll=CP%VQuAiND2k*7l{;HdrUTrvg6WhzKV~PcUI;yFGu5HBR#oyRt9EAf9KivCp%KM*&7y1cLV`G<`^zEI1K(*r@ ztqTJ8b-!U7Xq{|*d_J=8kFChCvM1mlf^+Mp10jybo=3~hGAnS9WBiBr1$>2q8r4ye z$Hkic*8~XWn4RmOG8eT|n-grYF^8rx%m!n#Tgw?o`=)s2c}ffsoV#f^Eql7t_jYCG zCM+clO(R+&!T4e{`L=$0PkVaCdoXP&!=iZh*Gw1Tw9(3^ht{haax#aXx^>=phzuY^ z#WHN-(hDO)*SLOG9(QV+V??&d2+`5gOq)I3HqDc~E5|p%W52ZtCwV*r@!azYGMqI_OqW@XN8hJ4OyISx)pZn>oi_ZQj z+8lJT>rVWK^qqB+l2>&t@Go0zr@xd_qj!}w#7XZlbS8XHV_l6rWjSRN;Akmqe{@3$$M_$3|Pj!-ONgS4Y$t~#MqM`}(t4gyJDD*kw#TGeiQQU>( znpnZ6IGU3rfdgF~5R+wyG>Wc=O8Lg1f&h!&{UfJWxdM^RH5(9p9cC=_Q}B(&Qig2031^x<=_6xs1DM)(Uu98Hb-bK8|9f&N>UDv{BLFS} z+P5^(0N39A4jKbV-gfe{Di_q3aX&fwqDkG^2(d^O72N2K;SIF%ftJ&1H+_#MYa@K1 zCe44LH|}IVnDMh7s}mPY*?@e)x9k7DL6wD|Ko!X>|E=g+`%clUXN2A>{(~)h+4|1L zc_ViHU{%qsW%HVC8@BXgr7DfL&%0sc~&vmlQ39 zJPqIS3@5~i?4|4SGJy2yznFuMFJ>9ICWBh>9kGAJR2G;mj7i^i)*|V-K@6?G6L|QV z@L2-Q`TE=oh}G~mr|3T(VU3lj`Qd9DT3_2IYXevG_k^f;BNtO0e_|URLH46RlHjiQ znz_DxqtmsWf?F&e3Le8C6LS8jB2j7a6<3aux#h}CB&BPeB?CSaxMs;GI<$&y8oo7l z^f2R|h#P5l^jYYZE26zb>x4raJFXu0-R!5I;-wTX7!<0PDD&s+=l>B`l>~)62mF`P zwqiLpnRBoXFW% zYxwh1Qvfb?mLdpJI(WH|xcvdN)yr@EeB(n7ofrBsquW*qTSwWLjJq}Elkek$1W<1K z*MZ)#{(uTvz8b3on+&TkVILSb6X-RlUebC!lKQM$Dhy&xtGO3Wy6+a!$R&r!V{auFwuHmnZlD6Mb1r-#bem3u3#Ufx~7k zT?g}YBB2Du;hFUo(1)B3@5ClSx>=cpEW{FwbmR&q*X9ULG4Gg~Nd-@AsfIAArXSCc z&x0K~`|gu!&c)&aeSv&(#$@&+D0N0P(e=?$V*a!_{@V6q>dxSNL?4UN?j!HHon|C# z!JB?|b!4>izyD%nV#u4ii+HJXzd5emW$AVs&tF&92w0?RO$a&tzm7q>L|xqaX&~ZQ zvb3|Ftcj5I?WH6Z!+%tqD1%tt-dM{Y_o z-7rR&cF{Jdb4T6>U)gc}oeslFpD#C1`aA z{%v^F^5{M?>Ox%3iIzm%!pneMTHtAaK@?z*A6BfApCtrgMs+(lMjf;3fjdVt;rlu! ztA&q1xWmJi@6bu5k+*`fXU&|PNAc&L3WTH$>6c{-!(-tuf`=VxS{Srf)*A)^HqbAD z(+fDF;8(KM_`S|&pi8kos0~y$&+bwZ6u3yRm*JjWU4{R!v_GD9KrsL5rcGuf>1P99 zfOv}k{N=i_!xyYK=^}S%9@9IWJ#6e?`_lhcMB|JP-%j(KH=7yQa%M6@$&T0);HRsD zO^_rI--+Ot@Ohxs=2%WEmc0-=gEMHWdm0$_yRjxQ9pn_cJA&;RZV1(R(Uk(A;N)%@s?(bJ%P zDat1)T=58G3@ARm*mK5t429Ny=D9vgfUNXzihd9T_9!3>SI4IB6fZG|oEkz-Tw63a zVxyL8T=O0%l>vcfO-vy8^4_U=CI%vAPl#bohkAE1{USTHtP<&mF$iKnoy_d$QS>2t z=ha6yTHsM6D{O!1ugBuGXD?3%(si8a5WqNKFA4C+(f{Za)|-$;0)K;XH}44DYtlNs z*H4wdN3MscoI~PBS=8~pi~A+L)w=_oK+rl;-pV<3gssUnXZ~=9w5Y~J^ zy{b$U4kfedbDPd`$>A5~%)F>SB9WzJI}ov5#XH9#j&U1xD?&tC(n8>~U^^e^ zN0K-&^0Z9gJ&So6msG0I*WTbjK#vxXsf<#!H83Y5wdJHl}xIRE{4Kx^M zRhnJ`>LKFwAzSQ#+}HDf%>XUJ5&;-V+`2pWwFwjbIH;Wr@FpK$ZsAzcEmh;wm^JBv zdI1`cdrT3rH0KWxE>YlOY&;`9l(54O#Tad&HBU}-rV5@$D8cUlk`+2qXMo_n>U4j_hJ(C9Czuy)DCMD|m?mEh2ky}`1FtO(m)_Q_AeHt`{{rA+(x z9+oh1Ln!66*i>q=m>1aDE^>xom0ELabnPQCaz8qKVmPaQ!p&Y z3B}72`zc5-;kNFvPsq)i{VENCWS#U=2df_`j^7aihk15xjNA|tcpfG6DEcBrH)AFn z^BM#AK)-+vX~+g@f~%vTMjmUBW-{b>uf~U%Za9^a@scC+bglU}9`duS25BDSYG|oM zPLeohB3Gyq=kT1J>+7ocnfClTbW%PhkHCePQ^H#%VkzNV9URZ6j&8|9MuaJryY>e^ zue56t9Ms{SSl?h$-O9bCB&okirc?^tzk9MXQzu7TT{IPoTPAi(G2F<`Vx!0M%*JoM z1;sV!GwGVm$>b5lF4iw-qfUauzf_fMdNY9jxe#|(^96G;%5$%`8EzdBDt!bP(xJ2L z(+8V8#_LghD+;&cpKk)lxn`}eqI2AReA(A9i}HX0JoxnYC#Sb>cHUl{hU%n=uUsn- z%8(A)1k9WH1qCkOKSf2!0=2%tRnWAZj&uPE$*@M=&Rd8s!!XC|j39**Tv!OY@jKGZ z;R!WGXH`12eY||1L9`?q^c4)HbWxtdMpgOr+i1%#U0m`{Lp7v^O?$H2a{%Wbs|E6L z=Pv9T#JfH%{d4Se{VfW;g?Yr6)+v}HaZ;yjvVPqR(t&l(0y1ey@3;F8jiBjyI_&sc zM4ryVp_8GdKSrdJXNyxv-3Zb+fwN0~5KKGE<`VBpeJbF_UUOkkjpV0?B)fK&4n)s( zeJ5NYApl}R!9&ZlCY$Bbp2@`4@B})6DuqD;mYM4bD@L6(2S0;qd*hjTO(G!Cmo1fa zQ3&vq=6|s%tdzZ26W=)Az?*FYe}POjxB-+tu4|_4QWerkaYM_xUqN`5{!Eo)sg6u6!?2lubrpGUEV?;&(Di{zHcnC&8Pp<6+w1U~7cts$B^*Z6k z_rsI9Sm|$=EHEk!qe|i-jlgds>}%MU*h|x-AjvDr^~RLQH5f(;yo| zAS6R`S2|2kFMz}6IK#0Cj9O;ZKvDdz%(2rTNvL$_%W#>K(b+u zyf7ViSLh_F9*nc?<^Gn6vR8VXTOzgSeD_6v-rRju?5v^<_h{uzz%OU*;>&2YrYzI_ zWqbzq_b@9rbsPu~AW~1w&r(pDBTjA%kRM47EI#kPR!``NdhXgsKS>eXH<1y^h`SUX zXJ&EpD!05cL6mCBvSLkm_GiGM@m6A{`>SpTZ)^Eh8}HU#uq&BffOHre-)=fx>lYhc zw_E(?9AiTK6K-kA>clZP9C-TUWMgXo`aqydjpVNak|g*E`7z}MOz`Ef>CJ40rk7(u z(<}E>B%@GVFW>#(3&H@QeB3Om0OF1CRz=E8nLu)K5=jL8RY+35#RS}e!jDIMif_EW zsMz_~+ryF7XN=_*2ZPqdC=^18>z6eV!1hzFbp@$!1wE&CPQw<5bAiYgsqI_smUmAO z0Agy(0S)?!G>5Rm@ASKNT7fjRwKe|<4_yvghTGWxXylB54s?_&O9$~}T>0&#gm5om zm@%9kFtlK$h*)GpeT}naezKt2Nu!VdTwh&Rhg~Chp(V}@Q2IzH4}KS&xnqI=H3~aW zc#jy62%-*>`zRhGMEit>iFWoTID(CxwM}Y?&MIZje8zDFdTeCgNUjWvVqWai_CV@3 z#M58ztu3r#D)V^EPmupoS*_IkwAvmqJEV$ACep)v_Sz!1*IE_G?m0nkJ8%&Zw;0l( z7!kAw;XQ;2F(Zkmx94hY{uWw|Fa6Fy?HyNBFMh_tMhsHI$n6_2H=@nKENQ7M*PqHe zAAS5C{|pfnq$4{$d8&Jk*(8{SfU_yDSS)~<*9gl?u9S%_Z7I?hMGn~@R~j&xeHX&4bcH|(@LUYaCymKxGvh;Te65^! zyhTlUNit150lNa~z0w1)5A}mUD1G#MG%x}q7K7+;%LsBQFK}omaU!7$(uK_x(A0W` z)~}jnz0Mcfd;M^$%8I|_0y&uB8SS%tL^qn{2^(x%jVnC%q1h+8*ew+*7H93O(dPH* z@SiotMZru3Z$Y>50_2caIAqbgi!No=XNE>j zgH2IfQSkT!BsNIN)ovbNBoZhLz47;L(!QcQUl^y!a)IFmTn1SU|AHGo44U=c#LaG| z3M>%@!6Nu-AiJ3C2a1S%7wq`vZfK{%QO3I;7lR0ZFXx1L>MJi)@0&jEgm)87AFv8{ zsxF0|3MZPju^#z}QjdP0_!~s1P(I2m9v(~24dmkqB-iPF&p}$g8tzy*>N`@PpDW`Kmz&&(pEqFs;2o*`1`7Ad!c%H2_~%yV?D9+ z__#h1bO2BbkRLDdyy}(1rg!9Q^_!xXg()-3oVfZ9BMu;$5ihzGCCL9PxQlqX{Z>Wf z+~nR>8s?%%B@fTBnbNraD{fhW_L`hed0oDaLkJER@_P0y_Vm7rx-4!s@dc+@v%dd* z;+-HrRhhqlvLm!!28_M%+z=x$Wo@^*>72i+hpLH3jf8^7ObM+sNB<$`*KEVdw|?tJ zhjaz8=dr2|Uy10HwLU{%R(5^T{N^Yf@df-CZg;_XOyBBvV2u@Rie+0BRA+|^3Vp+R z72xuQAf4^HKAS4A%wk8CNP z-TAW@^yrd3Q%MO1ymFeAUeRt02)0O2A0#mDrmU8`(paJ z;%Jdu)GPyVi8iCA04;0`zWXTX_NJ#e0g=W;{&nmjNe_iV;pN&)2>I8TUm<{WY;XY; zChqp?$#X7X2)s4b`#!)E146^F%Q*QFf;QK212mhZ0QeS6t_3wif!{B0gwl3@qMvq} zNTeeiN_#?c52Qf%AX{J3%HUvKlX`vhH~S2-IXIpVmqePUy%oR=>nd}WxrJ1Ly4%A$ zQn!qm8&MmFLH|vIFS2}=nRW4#-aG99Tv4YB2oo{zgANsZD#Qt~WV{k=TPpvf@IQww zIop19@$}X|Wmmo89d*3qrhHywB2WlX;#Mb6zFY^YzmWf$eiee-!6UF2bcad1I%H!_8_g9u&PV~I% zID-nq&0wYOuO1%!{%c=Abj&p#VN=br8?^CbwV^I@poI$pH3j)bAPA_CL+L!RyRlUk zA;-M9I65FkoK#C-*V#IBD8mhM-*Fcj(?Fdv`_3&Nz3=#^)sJUn{K$OoDL3SFcjN|r zo8?Uzkvb62psg4>aOe4&+uL7(@2)2f(?49%Lf0jDXLglfa!@89?FD(xPHi|eSqU?d zUnb!#4OA^RNYvN;g8(O$$b*qN8&jX2i6ZiIR!pQN4<3wtyNIj!dj8}L3wY6Fa68Xs z0ev*;_of_wEis&BkiR8Mxw?1SGPZ1c-fV1Bk7oO&*lE3iDEj`gz=UL(al6;;e&mzv zQ7#*=262XI#ZblNZufVxlh_%9bGAQeC%Vq6{7gOv3i*&E32@cH^0EBjxzWd&(W{@% zk@#3@BBTU?3+9f4FUsF1e+syw#Dxm${hP}tT>I^|JcFhW_~7H;W!D<#qGDfk7x;h-wK_Mg1#3(Ki5CFd!mBe@?I_7 zcg2^ccWmi!&sHdbZ!o|Jm-yiMUIstc5I2)%xe5B0ksE4n<{pHbj$eJyUHdmSbWzpp zfv-XV&#bv4DG~+Ob4)Zs(B@(A&#rV#69eK{WM7Z&NLT&LWgE`a$-D>T1PMk%3t!P}}hv&&s{}S4R_(;iX*1rQU@0E|z7WvvK`3}51*E{3J#hzgJgP$5)dfbMgvgb_@&hhRJiYX}3WKho@^44!UXDIhhphriHE6 zD8k?+KH`sin(i!MRKfhMUCaAjjS(c8fK_)BfQ^og-hjU%s+AtX@Fh+T&F)yX_r9Qp`6`j zi=X^_)Ggu7Rr!d%PmZPNJKkAa;J={{6?j*l@gI92%lIaq7Sr>pxi#xXG4E$81Z)rq zrixGPI^RnX_W^FLvwoH+3676jwS+D9qxCvYpIzDAZnCm6gm`iK1f$p zj-&=DGcbO$#_&dVYwVDkpq*v^Q1%P7&&x=0mP{>^fSH6hmnxxXW^#Hs@45St(6Lrt zM>LDhPCq-^Y4*n|rJJBxIB z+g?EP*N!#WfpweK{PLP#kmaxcGI8*L5w*A7@yobPYxls+!urMfRg^TIn4LVLCR9;> zM5^j7WtDuC4GTULx9uDQufXshW;l<=JV|2IDC*$34?<;Tv(f}*jRSP$frn?P^rNwDO z1;L$T+n@9w#H5=a}&G5<~x#ZzCNnfHr=cUS{yH-V+}}7RDDSS*N~F zXZy;hCEseLvr$BT2dr>6mV&BlkLa{0{4O>QXUGu1 z=w?!)ci{_9#`|_i`P8Qv2=(q6=+x?RIpKt3OM|Ju8$;rQFKqvKRSu`5l&<`ybX(o6 zPO1;x_g@vbS15-Cf@qLDQ8~3+F6VHLlI@G@G_F09jWQ3+*FSNIFL;~o&Hs@3Amm^L z@ovHhI|T2TwJh?h1kR)B_|d+npO29Y(usIxFfo#Q+7rMclc~^;A9B<(gdSkc!1_!k zKt~A(v#Uc--qP9VFCA~M*HI)#PI@WNw+_`4VXQA7XGU) z^aM+F)A-As0e<4Gg)x}D@gUfn@@iv(Vckb&K>Hv}oX=yOBey*+A=BoJ!PFQV>eThp zi7b}m*HyW$jMh}7_h2wOlh{jTM0OSD7`c@NY3*aWnRj3-VN8DVinunMo7ma$qSepg z=;+K2caB;~T)c2}-PgT?{>qcMLi3-`f}p_DHRHIl5Bhc0M>0yZv2z~|s%ED_*o!XR zZ1yH{G}En1y;oCOEw!Rm@`0jFo{&aeoz*63D-`J7Tb0K~6`^i{36h$*Q~ilud6==k z!JLHa0+`V}Aedpo2%`rwKM$#BhY7uuZ;$|o0$Y__O;+fm2W$2K)ovruY$Ff}Bj6^G zoh>E~x`iwnn#KSxW#e}F(cqNx$E!l?jbdz(!(ve0hnc28o<(a2wTJB?cb*xbK$q;;c`j|!PLAq`?z(UXm>76g&>jaEQ zYa%PJV>Wx=muAwC+ejkGGspa(pjeqa!Fim6g8}+*)vYllDgvWnoyT4{|Rr~v}yw*jLaj%DE+VJUZEHAlmCgy!R0;+|6C% z$}Kt+3EbW5wBEa)V~E8qBT zb_BuKzY=HrDpBBS${SUsA`-U~Fi-j^!`!f+P9dny!}ouS67sK5=s^STid+(Z{u}~b z1s9$tW|?FT()2jovOGcf$i`lAPYjz&`;;%nf&tl>E*XOl24xf9M#-2r5%^odh z<(*jSw8Mk4n-3Y zto9*fENinbU<1Qms-puvfrLduv%=N5kwo1nR3558D4RKpI}uvnk9%In$t~iuOAZQ< zybnyeRkg5qF@l`G|GdGA;cLvCHADBas!F)b33#K*+#neT?&isT0@ zS4hjult@7Sy*a`bU^__DD@_BM3ZZZH=!}mNW?j>0cIv|%*nj!Npf0>cC;`Sf>a)o* z-7#5@oanZ%!Pr)7Y*Jo*lN~`ghr;*hfID4xO!&~l&_0Qs8-N4*v39!<*pGAL$kIPilY2uc8ge0_b# z=2x$WXjjn;trql-Iq3n3x)sdQ1|yE${lf2LST$oX8U^uOvuxme?aPfRC%tJyS!=1Y zesr5Al1JC*GeC_uN2^Ky4h@9jx8?!~#o50AC+MLJ>0&umV5TSmS3T^22DSN><;CpV zWR`*Otimr;L*TCXPy`nKS5t8Q??INJfhB-vS1aw!@K((-WrdU9 zS;^Ttg?k*-M2jF9c79@>3rHYQ^oGX+(t2e7uHa&*>?LeQcvE&UzD>M|!^1X-vp_DM zmkd&CV-_b&gWkYc&NW7tU4>UZ>>>mVVT`(px_2?Lnb&Bf`SBov@k^@SD7KRi2*zy- zIRYmf-Oq$wbC$*2D~fqG3s}V2|1wb){cKjd!c#P>Tsfne)xv zOk~@D!HS}N4OVFxfSp=wTKJ+CY4yYwlt zp`_7Bg6gfOQ(mWi=IeB=&bWAs9(SYcW&kJrXX|i|R>sC!WZ2W)$57Qae>aV0!`7@{ zb57xJ>K;$oY^F7|-eL0LtfJ~NF5q(~*fAH0H&4wz1Xv}{tf=RAk8p_?uN$BZvLUa7 z;2xeSpCMVQU?7*g@w4s6C1B_IU;W%rGNgu1;3-XCHCkAGmVXT|10L{@b{q#}=ilXw zS-SH1$&lpK_`Pp4zFEJIhdcjqRe{;h4|hDJ`}&wE&+;s&dP~84RH01m!UQTj>y=0Y znHy833otCrr0`j&}g3C?6N=lKLk$_l%=NIiy)v2Nd8HgW$@20 zr09kfT|#^{C240r`Gmdzyg`WLr$M-1HB8O=%lPX6_n3qb@Q}{>@OHYN4CQ0(ZuYOo z>G8i`_1=HlG{ye9_G18qfw(hi~M=?jK|dV`f)ct}?9}Fc_+E6MamZKQwgS zr8sVpyj6vk<6p4^n`izI!T$*sjYeSWEWnK9YoQ$0He2EE!#60SyAl8q13^k&{qrFk zRu2Hxi46>0#qh0iXaAXQ{8jxKAXTQ&^Cq;VzwWu1Y0GGE$hTu8hI#AVHC~po|5lUw zT&-y?f6-Io9|4r$J(CJ-L64TRcsvo28+TGo1Y2+p2@JyKP5z}&Uv1UDs;#%iSqAR$ zX_S3rSz!#)p&79ky4k|Y&l>UMW!D5>(BbuFb%lZ z$16JDV9@&F?d}to?d9BaW!iYX!imLFO|PJHnDuThk$1bg`6XnM59z620+b-mY?*Wt z(nMj1OKgRB?Agj!#h&9`+BkuJ-4h?VF!r?sG^aokL+KR>d=tBt0br_Tr@Fq5U7-`iR7klaX_-?I3Ni}R=ZjB&c zOwc6in~^O(48QLLeu=L`?nM-zL+lt-nu1RKzs07is19Hl;11QJk3h& zFHjU1`s?nG?#6LA^ZZXDyY`$s45>Ep5otv-r6>Z$@Uw!u7`^`Xa!8XqHwhB<43HIS z?A$VOLH9w+mCMY&_-oYDz72kH$K0j9!D@O@iPDDY#xG9{@gl8l(VwBMS-+8<6zFLaalA z5VHzW;O}}&WcqBP5SZdVkN1XPy14xQoP+BZY1wDGCv1EP@Q;Qb-&HRB7XkmHkp)qDPNAVHbK`R$0+Q=^CusB;ETLl`7&^uQA_*^!1x$a5eC3 z*WU493^PirUL0@8JevZfu(z558=Qa{Q12`ngV1G!1Vk)rzx^RYhqPGnsC&-%S%U9Y z(AMg8|2kOQJBG-}@0a=|WQeT3c-ta^&_dvwhi>zK&_ei39WtB+ zcr;VxF|B19J+aGFcpwfxAhc4MkX^_`R1d@f)-%@YSHiw^)nYmWx*mN+LB32^cbCx{CS+rdClXj?wt#bz- zTXk9(kXL%V2iSf_i6PkUq`3V4;~)b;`bcr5}Q>NWaa27fYYSmZX{bd8iMQ12i}E(fJ_ zLJCw-+kQEtjG|Wuq-pQvIHm9(xgV|x#rik+{HoYeetbgfXu38;A3o(nh`95%>hBF_ zc5>1r(gM6*IV%^6c@Y>}$`YY=4C}OlDe6fkS?ikT_hEtx;9pTwPxvy~HwPWuf9NdvcN`5KD`xgBhj>pO2t)Fjndwrn&TymkTg}r29~G<@q(l zXjJYu=NtS*)W;{i8f`?K$itzbryI?~_7`e|ky)kn{$G9T`7Iy|A_A?z&Eh$EnpU@@lh)_W$F`pw(5 z@3M3pr}tNPDofkXj@K6!c6WE{elY>e5_p9;ABUuOK2I2m)9MKHzsETHjp}LKo+#nr zYIhYkKzDNR8LIE(UkO#EbP23#_1$E48W4a>$Y^}siSQCp&(m&s$jM!l1E#KRCI#T7 zNaLL>_!hbc^P93;#x-2jZx!kI*{I-xOI@KTv~32dh82b=e_#3#Q((~p7xR``c#tpT zKQvV1lw8m5C3VRIjDHkInLF9+=wM@)+l)H-^Ym%rpIt6Gd^Z$LWWHK8?|)O6&)r2T z|C^lLe85W^=~Ai7yfRX7rRfR3^g-IF=il)^Z^j<3qt7In1>j1dm{r?)I{)`&3v@kU zT^r+Ariqt;FYGTb`lRBd6Ph{;Q_5H)era2Y=g!Hse-^|zG=j_%SEd!bUoZdpfurIt zW==_H69l7e(EA3%x}sUnWy~DeZ^RV|g#V>F!~}5xd~p2l-On<-6`ml79oE=k%#)XLw86nOQbIu9T^kwqQf13mL;7Cv z$@M~$LJx(uFjm+~q$I))4%HP4x;FF7;sVgmkAJF9ODaq53xYCz>iB72lV_0c)+Q79 zf~W~LIg`F*XL6IKm@88|3Q1=2eQ3Qn-&noZd&br5`s7Tr_sWD@HLS`t%BXPrBBZVf z1Y`CxVj7Ub3Mv~&5nf}_pUOOLaU8wQ)AZ&3h*Y0@w}L^N^i>9ZM+s|ieN}wusRIUj zWRdIqnxFgFCd8t-5_OAop1-gQv;`i=KPEDgg}2=gH#i7_a<-;UqTmVIJ&eGA&N5;L zbgvdjk&pJaGl2%Ct-qlcLD7cE$6Am z=Unl83FFHkYR|u#N~rtw*8Aecgxf^R#mwSI)*Q?Dds<6wmyDTMzC{J^X)*ZZdmRdE zgOHGxH%!oaT{HGC-oTEVVU^4l#-htE-kc{+W;+>$ucj$eBY{$+Cje30XCShN>Zmun z;rJT4qHipE31PfWLL4LSuw9VxxO>OQ=O;`k_6_#~G!1-lVcI5{ge-$!xyZ0BvoYsV zIky(H|I(ciW7rh= zkZ@|oOI3{EsT`a#;;A3|`9NtIsuhQ(44%jQ85LU`Js;6v>=cFb;)k}y3nFNZB=FUn zcsf*vOARe)+Z!sJrxlnuSpn{4w`rEc2Hhf}j+O4H^}JvS#J4kmh-YHydMKQ$XwZ{S zLMhr>Z(Cbi$Cm!d`nQ{tZ(3WG&OcHmG{SGCE_E~O;1jM<7R@`v+`5@R8y_QhK#Mi6 zB+B^RnFD}=dqFE-?-WCqidK)3tl@igiGwnPFx!fXEO&bo(q9b;!W>oJi5QTeyuxFZ zNb&Du!SCrr7C-Ts?3FdDzkEO7lG&oN^uvFA_i6U)E=GB62LFct9Jm>RQ!Y(pW`|T# z_WbooPuwKc#)CW}JWdBM?QdIdEr)D<=>k72=K^Mb?$)(lYHwwuf-tND25m9>cM`Yw zGmVB9@IZo3a8BY{TKBLAB3D}pDkU0X3)c%Dr@#Hq&P6jk1=`_r)*oM<*8$ZNcl*@Y z=jx0%*sS{Dw-374@S4RJSYbs|#_X9vZMUINrb?DO1eN5^Db|T`1#%WH)FqkG?(4RF zP8|KvoeUJX%8?toR_NvV)}*~ietpv~jEPK8BB``DChR~~xenktWUE=iF3K829RVR8 zwLUf5sf!YvW4k^e2m?!&uF3T4{&&qVd-vCG$@%54Wg=)%eCK?~G&L)C7i=Tj!!d}BavKZ6%SLPI9Vs;PR-bF_3ZY&Lb(aIS70GsQb z0Xn!Yx2D|Hd*rFUVRozVL*u3~z|kpe2o^fJ9s)Iq%8>Mp1b@fzJ@d9^}dfSgOIfkQk!8% z#manO|JKE{cLenV3>_b9_bm;OE%*b*&+o$Hh_+_lLLawj$Jy4(k(_~yL0+o6S$U$E zvth`;Pu>xHt8oYukZ9(G(8A5DB?ER&rK#-Yo*u)mmaeOw_I;5@*s|#DV;oQ+^)+1{ zpDus#hIc8ESjV}JN=jQIQO3r^HMrpiIBFS#Us}kSAC3bk_ z`VP9bUHqGG0s5ViO|}*0D4F)ZXq(YsVAzm;#XqK0Vq{I_zV&_&q+@@J+v` zg?V!pTCLcOCr!!Lz-rtT*GdR7Z9Be2n4ZBKzYT7*1b9g>&I4r{Kc>tV$Pmm%;Bs}c z2P#=_Du;lrKB_}AvHj0Bve4D0g@u!jPdUM7xuZk&w0*s&i96L7y_EO9W;== zMztPy#T8Fcpxs(sxh@4fj#soaBDrqB1f!rBhk_EA06jJK#A<9ND>IFbtVJI*GzB zWzlG?BYQ3@C>~=nT0S81VYP2OpYuFX;0~V!lB;e!LaxdGVW8rH`8RH?0(u1%kTXO9 zr<2TDTBlr}P^GHM4QOP?<#KpMLV>W$iZexuQ;)u?E%zw$fM~qmd4B)ka)R|ffaLL| zjB|Yea7KU$IaPt^+K1B(!CU`CuwrN=LqrJXCCv*HsL%N#+tq@t!Ps`FbbvF&w`iWDI6=$~ z)qsu(VeIzy)4{)IEq-77AcxtCj7ALF;FI;^d1-K?=o2((ll1%XstUqB70q`{?H|h( zM#B-+F@W&L*T;xjJZb^)Ndq!ATLzGjGv?QomS7$2Kz>j;e#H&w_(;n^J zVh>jbM&)ZcoNIw8I5j!kwvvFEX3VaNoVhqFi9Q8Rp!md??TSi}{^ zjTGRh$!^}FqA?LD3w}1ps_hehQ}ABIG1b%Qt=Z23e`lw^KpXdJxACAv{7e3+uc`$h zyYZAesO}oAh!t!xQVZJZz47aKPSN4?rG>jBqZa?j$~4YbVnUO7mm5uh|nVHOJ7LW8)n#gSH}t`6q{n#7#Mp7o(W? zl=V%p)3f~nL;d`4>7`(sOhE>19r&&#<+EyC=-7h$tt^PNAw-Z5d67)@&l>>U&;{=c z$*R3je}-0i%aB>{P9h4Bz%}Pwk(&dXQG`xdqeg!@(Gvfo@@pD0A{cb}Qz+D?L1d!| zYya&iOBBOLlzy9l|M3_cXc^R@aQ}w%5s*>VTPR1Rjs8-CZx67`;h}lHLh-gj>-?H@ za(k>0cZGTr?i{4K*X!tXv3XJAPr7j?R)@(cSav4oJj*EipfmW>EaERA*(rv!ZA$7ZG!H5tF4GYJvy!Prw&5)+rD`A)G*tk*9U^V|5Sw)=$|0s*+82XUGv^j9tzh9DN z7jXeei{+;{4O0Nf$k!Wb*O>tZPFCA4IC8YoJ90u)VXo5qCP@FN5Ci z@*B|%6g`~rlW7h4mOZLWd4zopg&ldh7Y!RJ_cJ5ec1oAw!ca%Lxh;L+#jGMt+_W#) zd2HEP-LWcL!%lo8z#ok1bucBKG&g*NBM_BHd^z=8h#4s4KyV23S9}{`4anIl?`ee4 z{P!YDTF;XNV?eW{$$n9r0$gbsGf+1#{~HkSH>soetfNw`>(3kH88JU7qY=1T)tL8j zNeI3~SE}w5^pu7ypK82{o#Nr?;2pALmp-X|O)J71lq}>t(S?J}B`%FhQj}V|uD*K+ zl^yCDm_7OHSd%oVKMK52t4YwJAf|Y~ib6QgLa$AJ_i~Jkv?+~E7I?_5(9gzj9e{xu z4CqmQKITzB2sL$?mzD#(7Ov1E*gTxi>pMn`FgOBeMXXM>usno4(uj0_ph!e^@lDhG zN=s?sx_58ue51nKhhcURWDS++IIU{jdX+5;R^RD8fApj4cDw)~OBg9`o9ER{3z|u) zmArP;ZcqHz&lh%jIO%kGva~IFdAeFR*(-Q9PN%Yrv?cfHZS|;}1vQ|1G6C+qxAj?~AI}_zb~-R5HW^;K_NfPT zv-0^wG`p0isr`Gsx)Z;UAae{=}$c%18|N8t9{m-qGA`#<9S^ zLlmg(sO}p^7uV5#{W`sb+^ek=N$TO_Dh*MObkaEgh`3|LGXf@UV=77%J{){Ms-a%X{VdsxM0^1=wO20O>IgrQhg8BwBQEWzRAD(6iT6` zx|?8TP}tS`NlK#4B%2SB&X%t8oS4k@AE0w33g$;{bQda}Yn;5t2%9TwO)D4>vH|JW!r`e+U#NN?YJ_a;6+aCioJzl~%M^u@$ z#2PjQ@tHj*EHIW;Rk7GfdCJng{-*d^!=xZGj$aGmORW6C@yvMj!sYoszJa?HQwGPlaZ9Vg`>(?hyk;*S$YNy`wTr@T1XAG$-ZTAQ=>MEL z2Jt`RR|Bpc`X3ji(CU;w0;LEL1M3)N@Y5@Nt6^S@wvJ9kQexMt)oG zC|iaQqyJp7XC6l8v1PE0m1LpXJ0%JoF9c>OvS>J?c=1f+x{W=`!uAO_ru505P84&b|VwCtPIGP zUgF>TN^tK8n&*jAq>0Ne)R3aAfH`C5Yxh}+lVO=f)}oYY3T0XHhVo*$2HBJXeH*>= z6iR)C0t2VHd32Z&iJlN-j44H6%{Ab6)w9KG@jOv@0R8ho|L^384UqMOpO_;Fg^~AK zuJBkRXSnm-E|5(c`B5#o2Qr7q8g{3k?i9>|80$s&nnsf~$6bm>Hj*!v$lBSU0376e zhf~%*MwWT?avY$E+~H59_&pcQ^2K{%m>6G1Gmc0$C3}AIR5q$$2n!>c1LRyxh`l*C z8T%}owNDSJ)OUlF_oh$MXOidm)jug&nyx#c-`$67Tf;8S^bkcZyUar1D~NP^U|s4|TrejiU6wH7T6Xas4~l|LV<%_S``Z^)4bKzJoM`z&=LT zF|l-IfSqt9FC6Me&YkAoSP19)d|!g2C_$UCPe%{nwuv_RbA3-VS1me`+2o2S*4ivT zQ}7$)HoSl_n>Dfyq)F}KM?OFD)!5QV*+KW;opB1nen8E2edJ-p3-#Xlo}Nx_^7#%d zMah@KMb(+*PAKJ}VeBZ$`xtFo;n zc*oo+-LL`*m%T~EKspa12iA__3idGEf@1a0Jx2Z_>N`cgX*ruqWvw9*K?9__)7 zKz(I4rvQTpu0xbDtU*xsJ3B+YnyoyGK&36;D!l;KS`cxKng2g}_*3#9ds?Q+f%!W4 z&T3Rl$-U*R;k0rqv$HFtQTc9V&b;17)vN1PB*nP`3Zli$C<0pG(0}iy>?zk-nr!;6 zA^kN`pO~U>`qj^g_~S)yW2Zu)2nNd+&KQCUTNIlS^grUCBBaUk?QCdD2D}7hLG8tH zhqaGgBY;d=OHgYeY<{@HO0o8|4sOg?#x(Z-$Av%K`c|g}Tk#d;MpSoh%A1)tZ=j(A zJcjM6Cwjw}J~0@JTz*u9=a*i9uVNvGB(_p!HIdGL;{ zyu3UTiFaqx6iCESccNnhbtwNE2W6mJ*b+(x^kuM^1tu>VjQn$cTz-xXk4#R=YF?c$k8Bi8q2RKjclxEZ}<36he#0+Ve%_p_QFm%GZ7+w_Wtl7(6 zM@j9djrX!Lm4b4GC?u~5*3tlTjNmdRxjn*mddXIM-qv9Oz36hyYbYazw$s3Qh1+Kd z^JV_~dWDM)3v)pvhGKM!f~k{R(f>b3sz|{&J_hd84_S)-j4+o!3#KV71!uBhg!q74NbM&v?4h@d89i9x z5TQ^6cvi6>U9g<2PpLqPUoqd&t@P1qtuoY?1_d2@`Ba~T@4@=*3M+>}cU{;4s)%>@5aE4L{s3*+G9)Fl!GCLAwG7#3i-vG! zR*@y|=65Bf`ATtm&L8lZ0ZbEB;3zk!Y+PpxfG(>kttstj5f#LGC{Y$Lu#bS8UM-zQ zMu@Hq$o~l+_q_|Sialr!Q`{1w>V|6KG#ENA#+WQ@nwGT1pY`&p)zJ`sKK(gKVRzfk z&hF-u?S@B5IK0!DLp2iG;f7_sz3(i{+8|Eo(4qTw;$evU<7 z(OGFvc`2Vw6n~ne3#rA@m4Q@5{W0A#Hq9U48k{zm>y!y+;9Q2hHo3J&shTXAJcfK~ zyVZ)5w{JE23ez=rVHi3;nT!7%Lr|J6J)5nh?SzhsK$SU^m?-)l*}nl0*nt97eqowt zeAe`?ynUz;Qg2+xTUv2Bd>RC3Y4bWPHK%Lg)Z>u6{S|IMZOJKw^TAWqbu76Gl)SC) zsJCZ_VkOWFQ)WPxo4}~f&WwA$V0b2}xndL~xIHM0lLLk!Y2W}g3{9)5kLG`OrB&xc zJUDWb3C-Uk1#gqDVitD4Z~aD?wge7H-};BeAs!_I8v74y5&+Ea0{2lA%}X66Z9#K@ z_^>-hW)15nO#_kjOyOmas@-9pKn?Ke`ta(=-l+xkAtf6%S-0_7y4$iXZhOLJto8@4 zX34v3(TAMV*vTJaP)~-J&p<4F_BRJvgD-wMP{ia@6)dkt1?m))GoEO_d*}7rv7~p9 z9v;wo+@3Lavhg_Rr#M-F1QgFf)XVRG*q#J8~PQ%>eEI`U%&Cn>%GM zc&(sHJhHmHCl*k`Of&l8$4?{TB5O8O7G@Y0rX2=%E+VRg6nTf|pDv4qat6`1C7giN z7u{zpddMZE4Y7Y~Ye-_wZMEBW_so9X_i~DI{RG+4x(cqXFo0^Gp*R%bw7LRDOdrKI zH&XWJFME0k=^ga$#A4Q}<7nND^a4r-K-B&}7YC_^o(=M&2KMRX<;9EJrpR)0-m=Jp z*5{46tBDqhW#l9v2&8gz)N%^7(kL)sUdWs)0n6HKjs8{oU=qNYAD4&sJDD)CW& zs~NEGW;lu9TXeQ`#i}S*;w#vb+1+39O1AfYk|x}H_|iK;)b{TPVaBj@1Bg&?6g{or zdRkgWYnl4c`H8>V=JnT@x2x#WchV_FAm4KGvdK%0< ztSmmZIhV1h(clY=MJGU~;@S^rE-Y_>Y9V^ti-9;DGk=HwWK!#;&J7bP55O^K`m=_G zXu8ckG7ZbmtpNvY30}E##QTWz>3^&xH?7mJU^y7<9i5hBJTtF^#yGBFDc*_UErF z)7P5=IXiNmZ2ypo?Yn1tX>h)O|wX)pi$v6}}L~ z2UEaxeEtdXuiWBg!#N$00;k=wOtBZw&EaO&0KNKSIhvHgIS*W~iwo(;g0U$KXEqTG zDBNGKXZiW_ESaV=3SvhkMwFwB1tL@*-VM^@a}NKLbl`&R@tbf9dDd=NTixchIA)mV zC=tC~aBz!~9IpwNj4bqr*?-zL1=gdFJ`%#3kCGA zQuH=n4IpU+6I{RwAsza^U%6oY^H?Yp6b5g4 z!O2l{acfMy>7`Pfksrp0JeZ|i<;{SoduII-q!`*>ZWQe((we89Ll|zr{|e5D3wCi> zo`N8w=r1S*!qLwFDcUzJbH<=_H(cond`6E16uhR|Y#pl^V1w8vxOv+})zZ+dLBa*> z9gFC)%c8UHI)vUfy{p-CR=le`kqx0F!A{CXLEe{tuiaV)`#)>E+=|kd4*|U10z5mqKblk(kg?kzkv)d z_2r7qdQ3TvFG++V4Hcn91Hx_KW-KVJ$P1+iRxjBEJe5W1+&pd*^t}i{Fxyce)4}eh zfRh$@A1Y#ucoWst;T>Ihss+yq(1kTY?ze7R)x0m~$>(`8F$~JU7@fA9zEK4D4i4O~ z$lT$zZqx~ji`$3v)*|{#r$L*EPnT_%O2<9k(BuCyOaRB%H^G5wB# zZ~Aul1ILI8q(4Q87EZpo{=zY)Ai zbvRIjrp4GoUPoy~p_|Y~jiC2;F0Z`pj4OT}B}yY%2T!2FacTq!@EUycO9%5Ug63VYFVS?5PHu^DOwRD}*DZ+&X*t`m>?MUe=!M%yb?Uw_D zW&5NJOS~pCrj3PB1@#$RrVp3awu%v*Mni6D0$i;x^$CavE;%Yca8C?3gy>Fl0AlxF??-by|@K z=y7W&Y@1WRX*;dm9QD86CFI(vV%^lMH7_5b!+y(|0W^a-8l}KIup+~b)~gnCo?&An zOG(UxNM?-qT3LXb`#nsI6z4g^Tc-UiY+UC$E&*FvtI6+Dt!E4lN;H^68t0Pu+%0Ksu` z6tWIe#V<*#K;^KPiy$^Pi#q%z7>w8aNU6>6(jOyH88NGua8u}Cc2u1}(Wg&ZTj6rB zgcEhcop&p3XB!m`&=l#Q%gDKX+1ldnRo@PQd*L32x@xet%Fk!Q?9N@63GF}IyaB}m zk&DEP`&P0xFO5kbUY6#(fmr@YhUx%5UB4xvWIrmQ!?q%041pkIW>T2&`0gunfP^Cb z(tz;`pKEWfk&9>qm!(6knd%K}H!VJO1Dgqnq3olh`72EM8r1Ng2yF@-`*BBa6dF=% z0$4M>QI0#*ro()8I%vpmX@v-Y(ZhoYTQWB@{1+HT^t*E#U z^<=lO26=oa>)ffkrf^rp_{0QnL2NZ$wl}{@&%6@6v%?5}uNMRE6$1I6uoN~g>aGc5 zqmoon-JX^KRi-s;3WHZNZLe0{Z#d10xpSOL`ZDOX6~58%y9rcJ^9tisv^DE%d(-3i zl|1c5mgSWwFt`fI3&sBxZ&khyY$kz>&g8*CkUX-QWD^9oYn|4x=TPBV(3Am*8Qu0W zL$*whv-ZP%-RV7+D5-al6&Pz!;$C1P3%^P^EK`F$c4W*ZYV9`j4L*HRfV&!P8&S9) zNIOtc*wgVQ?)CDa?aRHz9@(w$=z2|TJBHs5)4BUt=BlzFlF0v>6*lz#es&vz5FRoT z*CN{b{l4;yPq#3&D#BDes{HZWwBF%mMQ3H#_-t*IyGs+ZO9oL|`0`zeOFm`}hM?Ig zSdgdnvkzhtf{vEBOs(PSktLr@-)^gKSK@8t=>NC?mit4OYr(u2PL$?O-Rt4w7d{?) zqNG3}&hDDk?X~^C>6k$vbn~H?$Fnv)!EN_#axgY)|stN z(sovI5E;a!2lpSeRG*lboOgUKz)qw9Hgb{CvUCOGv16VKEZas>2?+KjnRL+Fs?%21 zFE)znppcKAj(;M)PA0&D@FOjJ{aj8_o$u)vMb*l!UqPWtq~E_pL#rp9!uA6w z{{I@75BhuhGH{ljOscZt#NEfeG3_h4k57f}%p2dWMK*0G2KGBP)SPD6ruUkGTq(um zN$>)s%Bw(}CK+Z6+({c(on=vj8PVDR8SO<7N|&ie@sb%lW?nn|>6u-e5&Q3eYXg#S zYV73D(@zp__vD~1{K){-Gg#aYA~t`|(DeO&Ue-4!rkBka2d5X+A*4@@lkC_(W}wYy zkA}IRI}d%mkkwBIKylJ?WEjbuw{0f8qR^f0auRq?@yKt&;U8f{9hJcw>Euu65g6fs zyb0KJgCaj+pIVD@BhUY|`wqGU>mdl*sAMx?hK^32(Joo_KO-@GT6>Cd5G~sXK*M{k z)eE1eiZV$x9A?_?h-nW5+USWa%~OMp{s*Z=$;2`6_8ou@w0R>W{VgAyz=sYwHQe0? z9+{?9X?sZDyS3MyH8Lu^%On%~Y(NzECSk>O;^PAZ{*o4Bhx&N>oN0Pm6I&B{dHY%o z=}1=r6yolQ}iXkhz7^?dkk5OeB&h{BQBY+qxa6| zf*yuKoxRR|TZObaKUv4D{>BuPEvSabYYOU~djjyEKxFm~kUD8v%A@RQxYLWQyzX#^is9=yJ8zN1KUv&zvcqFn7=QhS-fU<<;!m&3hD1=^LmN8$4s%$0H z(g4}xMh&f=CsNv>vOl=%R8sy2Xkux#qicAvnK%pZ_N*>ROs?xkP)hu917z6gDjlhL zU+dp6u`l0f{oTaOdk$NrZ?CyZiWZd+MDyMEm(NRJo%4m!`D^=qUwJ-z^*dn*^iLjn zW}e+my&-$JNlzl@WI&kELP|swqXYDs)5$nm4|@I~R}VR`^cvZ&J|n{^f}>+koo-N- zIaz)cE5iyf99eTSB)~@R-9g>0&4XCh1AixboCr})X92;%=QZ0x*?|IF^8u4|3R=tb z48|Srm*?Msa$=&ixZfYVaU*E=;~S{d`Z|x8GKl=VC^Db|9r|dH;^gH-%ql2LvTZqO zwq?6%Dbr1fXNP*IoiG?&I0(NO*?Ff+$0kwlqc!MVZjga7jNRfO|IF{J_}u=TYqc=J zzN$Dg>+06>1L0wl?%vqppQCwgJ6%H~gpeM-U;DFWT^~bC;dnug0zuP^SAq^pUGs(M zPvJ3EliFLC6S=FHys3K&UW%!M6%^s8;>$-!)lo8HKD>%_;oj}5wdQ9zEj z|6jyCGp&3jU|+LIlQQMB(m-c8`FEpC$IVZNv;|2YWVgMBKvhtenx8&W0K;1@5K7+1 z6qxn~u)!7My<>dKOO3>V{3y1rrgJWg(Jd3v-kz5`Xgv`aX=+B`M_M2V6Xfjqpc3js zi)Ofr{D7BwE!}71ujDIp^7gjR!?Q7-I_VQ))%g{Fz-2No9nAG-_eHK^Mrf}qAGy6b z{eZOuL{|7el4yEB9!l_7Ec9Rd`TMjJFNpa)2jq)^ypr+d(`xjT%dIPuT+Rh(wF3etC0%6pPV)==PiNt)c0MwC(KDR63gpyb{xY5}nx9lQ0*d>&*O36K%dNkO}dn$bW z{rSion|-q!s*QdkmXfG=Do=wp`R0{G9nPOvG|o>N6^U^r$%LOV)9f&Nw*~mH9Mr+M z^E2Rda{*GB0%iWlDaDcQUkdzLSTx5$C-K@ zV5S(FrRxvs7=qAqh2O)OQlj!-+NKP?qP6X;3-Zy;Qz>ck7|w& zWKO%&(-!zx;B(AY6zGnCn%&c&gF47F6=n=0dq8JHm%qdzMncKKLpLuox4?b7BRsPk zy>TFOsd*J}Yuj3oqE|`P+UeCLd}w(*T+e(%G(!%~JZtflI~?j)xrAN#l>v_sv2;f? zxxi*b={(MTr}lmLfyDNc5PL2Rd73s?>X#KdQDc=ojuNfg^A787CmI0sdl<~-zsL=h zAAubnW+~y9K9?Pr7u)C29Ry>QVD>1e2~ta`^ByFn<-@J&L(%fTpzO7{2QOINM(tWD zMD$L}KIcOA40awah(^2va;R_K3u`w12fh@##^Lg<59U)dC;gA7X`RXyc4*K;n(-jZdjGYo%O^RDk=aR@vGCD0Ip*&o-Nmc(RfyXi=f~BcdyGleNv3%AC-q@kHC5*FNh`wx zN?Rs;65;wBz^U$ZnuNqY(B;Dzu|hIUm)f4|O8OV?jP8QnD3OZEObM_JNF^bE87Wp$a4gI$(l1Q;- zI0j0@e9+0*i!}g@^J}`4Ho`xW8A~y@3VJH_zOkkH^a&_nUee^;&V(qymo+fW_95&zIVmIu)yw-6QUFy7h| zx$Yx37pHjBQTMPh`MawNY$^r(9(yNNur~qZXl|8G=YJPhEBYYJZ98%s(Ki~_f9Q1j^c3SWe3OFO!GN6R+p@02MX*^42tv*KHuig z_O5n}vj37ey>YYi!4DEAU0<~$&&)6;Ei$q0h*nbD`ezT87yCS<@j^R;LjbXvB(8(f zU@e8_B%pWt7a_4FPM@@@G)4`9BDlRN@j8le-`Shn#Pr&=2kL?I7a#T&F<|yj7_eAu z66m#;h@Hq=w||{HH%dW&=Mi=KqK|3n7s^(#l)T+2`<66exZ4riHukyRGGCRVuC8oy zKl`_yO*X(=k?4tR5+%^H#0|Hxhw%ov01z;OE2plP$ra#T?-SLY{{jVydKhOxSkLej z&Vr`}j8m@1i;uKPZLZ3nWpRUl4EhHDsyYWPJWg-Veud9z)Jf1SAq2Z%y=~4bZpPRz zgVq3>l0>xPq9}AlHB=~9t$^X=XPXXTPF)v1_k5QRhW)uO(k)poZ`a-!+W z9Hd~@Yj2T9+)EBAG2FFTKup=u2@{loMR(#tQ(c49{_GtR&QyuFo{Ek4ZIUDx=fhr{lrWr4EfRx)TN$~uISc6@m3Wl5wNA9dY`@e*+ zwsIVBq39tyAZJ@?6U%%WpZeIH7TDlU;9sjTe7L>KGb$YVl|FIcY8kcNE)*yYY!Z?b zi0JD=ci_zUXPaR!OPAvE6E25fQXH{8X`E2_JS6tQR1kSi;~KMU!D2N6CxNexE)#0rK0lhn zupR99t+VB-IctPfae}Z&xr$_hwr!B{nr>uI@EUfGnMTb|d$HH92?4UV+%<}8%3JTX zDU8f`%I)sUL@c|MMF}x!WHqWN}_~hcMMiQOu1!oYq%b=Yy<7 z2EmjK$K=kl>&qi3>+@-Ko*QU`-StlEcEJa@r`*j4gfAXie4rGOHAv8`P2lDf*Pn$p zb)i#A%Aob)EE90MWJJCtj3Z*Lef$+$4|oEeJJxH)i#^{ddAKZ-(Hl;Re>{+yhed6NkL8GKc1jY=}N;f=NJc(;NIR22cc zI(Ja99;E9jXm9KoHJs$$`^A?7Y(0AV``NdZPlq4F{++%59B@(9IAqa8W*q9u_-7aJ4t*18RG@-dxq8c zL%huE+24wQFXuBk;B#jlWwh1O-n^8p6=lH~jMQf$i4A?_F98BRY3QY?JgIF#r}6R) zGvP^`P=nK!kj{$~&a1>uOZf~t%;@etOQKI@YX)!!6-4~=oPArjyHE|6%kU;CpU}Sa zp#8GEl17z=1jX}dl@=R+X{%#}I732|8fbOy(wE3kCxbgGNj9T%u-bPTBEiFNLPjTz z)Am01Vw4);NX}-P=gi;d^JQ=Mpr6;xk{^FOVC(UJR zD|+||5%nU)@_mXZ_Chr)rm7jQvh%qQ?h`PupqxHPTP#xhTu6xCQY6+lnz23BfGNSl z?eDmKW!f)TIHN*rTuo@O~ZXj9XLefjTvGZYIrzt^@D`hA4_^`{s?QU1@FPxj$tk;wg{x?4ltK*p1Frrn%-M;muI_R-y;lGZoA>qe7sTeyY1 zN8oHLXe4@3OfRxs2r<RR_D8hADmwRX)}itRwM)*>J_KBbs%Q}g$Y%^JPY z!}k`|{hWCM!TlI&=C^Q4ECgltnvuP%W9nz{fqKSa&61gvcSvm)NMWhdT(%tA5TR`; zcEQO%3JR)BWVPMYxI=9I@QY6uhD&#fEm&|~&B+jaR}J*@dc6gO-MpOz|$F8^B_>a(o@E%4D%KLjbAs`M%T#G zn&EkB>VNQgU!!r_ZrQhiFRAosYX2=8~ikQaZZ6=aw_&8UyB& zZu`f7$C1K>rG4?PwD*@d7LRbQ4adYI`!s4h*iJ0|vNHO-BnrdiW(KWk&MB88>EQJJs+!iQ z*#i3E5>PoG@G=bns-5};iGsG_G#*mJYOh4L)O+bS$WPnWsfgrjHU~WF%~c(zFpHSJ zUiPmO_S%!CDF}E+FI>LJUnxhqnKah!so%F^)e(^XM$4|65@ir>l_t>S)a)T)ChfZT zN4Aqof$NV9Zvb(Dr-mA6ptg$Ucai2nW*bk_3>Cb7`6^{U>UHA%QV1oo8WcfAr~|EH zbAx!RVSjM19&nff(3m_8E^CzbD!X0k$MOFt4A_^R3rd6B&9qK-E?Ka4lz*-Lhlv%1 z-2b6o#~E0I;i>rdyxCW9==U{iUiIlT8a`#{qBm!Jmny(VDk0U3$W9Ji>?j4m8NMg} zRp^a1%-=XjIH0CAu}-S56YRopQ1{@5cw|moqK@P5tXzi(15iD8NX0b>wK3pa;(V+(g7EG$-~!*w1J55lMisvD2=QQCv&@zGbS%S zT&o$+XBZU?o{FK+soKC&75pJc`uQ|etQE$3(XzZ!Kr)EXe;44;%PfHiKrWpUK^Kh- zKYzWIzvgJ@0DOXA=dK;x!y6ilTPAQiyVTQz5;I;kp|s7<>cn?!1h5P&X62W!AdW8{ zNKuyc6^jPZp@^$XUy_LY9ynAj^r+97fc5$TN@h`C$Nsjyhuut&&eusJiIv|qbTj*p z3%jaLKjo6Erj?6@_kPN)NjkR3%V!!*I(N0Mv&Fnbo>)O|Kj#BNYyl1MrdagI4z+4QzF$Rs z>A|mgv4JzgtDRS+pPlMowUhS9mBB*IDpEevvFqk7u&rWi7+{K>pkc^;oNCRo$T^DB zS?_K{KE!9rPcZn@%|q_5eI5^&Npk z9>A65Eu9#^iEI0I$=+H3p^5}#-;2BBBz1-^Mn9_n)^2IkIg&+k>k)}|?eynwY)fZ9s*^j1dGa!)TGLVfbnuxs%xS= z*k+f`M?tnNq||qWk#t#rx>y&<{fL_tCLW!P-fapY9QW)tP&hJ+d{v0^|d?%pugBJ6N zYQLSI$)0$YqdJ=I{Cw}{&M)jUeUb@d!DK}*GuuF{Rzm71J;phO0G~G`LkKf`lhf(|6e;Pir3^ZsY$2tub2bN(9A5U zi;+2te-mB}P3h!RIe1~Rcw8fz7qs}`U^yeZUL1j~n%)V&aN;KOlq_Z88WXxCBER#5 z9J@s;%u^5e)BS2v3ji`YI_wE_O-;h5BO!D9^SfUIXxl4gJ^hZ7Cd%kgqwgqo6Gz$> z^}X#yEWz`!sC7um}HUOIMa=X-a!>uqkw0Its^2e@Yz+$TGH)@e$pKJelryeiDor7cqg8kHz&+3i8oSjoK zVtNZy37{CzTKumkjL_@yqxN{BUS^nn>(Hbxo{H9!iZwUUwA1>0=syDNeA@Tlr<+mM zuVZhXZSa?6Z`A0MgLK(2p@`|Q!+7c@k`fRo^MtdceUZ<$zI=VulY-YHfj|wI!*Ct} zJfrY0zW5aGaNTYTH(lVm6c8szKt&|x4r!-#^qv|+hGEEKZ66u$oD+u%ads*$6lcrX z!LGTZpadg^_^#o>FZs4(_x$#q-S4w?q+sBQV!_EV#lHD37`V(ho6ZOl)n}pvoLig< zEM$g&7s9zAD>3g(tU*2|oTV<5Y{)2|u)W87rRH`c5=|e$P)jfJq39gaJ}|E8oJzb= zrL$p&+Wo%s(~YiAiZRS*xA;?-OUu-YWR&xYKFE-=823fmS1`jA!a22F2i`dgRpQF zhiBFmkims0Q5NK`Bzq@IPowX)K*+{}z{Bx%$GeWfQ4vzF>41p=iN2aAK!;Zy7)V9h zlc{F?)BI7+p>{$I+fU&HAb6k*XGMh2o5mgrS8yUBi=oa(y^!5_;)Er{!{~YHCavodvy5*EORwi%W)UIC#c}<) z^GaR9-d{1#q zF^Zt~Pv2O(ph`#&ylWAduMUK5ce%a!<5A=l>}Wl|g>juHbrT+{{-UHcz^ zz7Dz!c%&O&IQE4#cf3!Zo*O!ZhkWu|{?J8bR>MnV35Pke1XdZdgAfdaO5*1Q+@cML z0)Wx(hyWnUi0b4{T-6qm6d0(P)%N@K*GeBYkD{#`6E^jAwdaZ zy#-I$B*uLRko$#AZj+_P?HOX$`{N)b9SO|ud!mw<9o<@9-XWJ;?HV-wDSJmZ_Ujh` z$z-3rTAI|Zhq|%-gjB^I-#_Idu`u@XIyzK#L%;e*B6M#WvxUBLoJw!F_bAQnUs-X{ zV%pz8_;ORADc$9Nbb_%3hh9qIq2ieDN|CPaQC@wyWYBjVffX6gma~_dZu76*Gb0`X zmzBNjx-tN_XnR1vHOKjbi6*|37VxGAlL~oC30*W}1$cwtqr{u&;_vazoTb0%CuZ9T z$C+NI0UK-i*O87FQ;FF~PUovRT5fzuJk^!2No#Gc*>4A&kC;6g%!%XWBLF)7pZ~80 z|49J%JoPnb*ayD-IZO>R9=Tf$zutQ%k4zT0f)YZx`V47=iSh`HGn*(iJ0{o$<{bz@ z0G_Dd9gkI69NZW;d#GLUHSZ;Gv9`5!*3<4$#twy8H&YP^h5g)e_vK*E&!3qMG9$d# zp%UJ;6QXmmXP<}ySU-I_57J1JMV1dl(iFMkh6F;_(gC?36a(HBCylZb3{U@5q?!hW zx@SQwFa2hDjHg@mt_m%Z`E!4-4Ndrmh7e8cV-Y)utfpWwE(9|~2oC2_rKV)ObITtE z-eB*nUgT=L5?S9!!Oi&Y=6a^s#|)dqVHQ#GWO!H-SpvK@ z^Pjw>+*b|cAtK(c3qZ$9hEf7iJ|^u0WLscqQ13;d?o-8-*>vOL#K=+EVUcH7R^<<& zdmnltBxh$03yb|Iv2F8L6+67}pdKGdYQ!%N=P^fUJnyO?;R_Q?*)!<@4Xa|@aX_tv zO4B$f#DWKmQlo+Lz}(fLNUkh%z|ezZ+R6x3&FGiA%SgOYiqvD&*WlM2@h`7-`X`+L zuXv9XgZdjRq$IDvEAi|)E)A{(fBalPT?Skrv?~L2;+K>VQ32&_*5D%vAcqbGFp(Nk z%2z6SNBzzB>YJ_JjTQLr3H<)1{gaf^pDOnOToLBXh^}=;gx*;p{^P+uj-xlH{?L~c zWHv+cjrxo%O_U4SxL^w+296D7O@IcyN_8cUE2!$lhGI=M{I`({Kwd(Z7Lx{P%YJri za1}-lNDW3+oQ|DupMHT!?)%s-kL#{p5T@XcI?)78&PlR!%8$^ru#DVt|;rd^cfy|BOuW`0jRB0%?u2@OnMb_{Vm|k zCZv3AgupNd=Y_xjr<8Yhlsnw+@_>z?qwz099CjgE8{E79A@PA}_~RPm>e#3jtvec^ za3KZ4rx^F(()j3TyUc-ZF%37Nef~QazcT2!wn39ke!OX}DB!r(phuep$` z8u-;drOFn9pc71ZNKM&6ixU+Q-fkfvz}!H#(!))1Ws|lp)Rc8)Lxe>Ypf87K-~=rU ztp1-Ae{K5h-Hgd0PtR&pCpaSQDfeK#>qg*pJ2g=NpwpzTt&y*!!Zj;EjXpFA^SamyN0Pwwf@idf4qLWh36Xks#b0a( zzUA*YeARKdcnE?Q+z0VJ&-wIe4;Y+&kh@sJnL@IFQIhylC!qLL@1BJDVcnJRB3HXC zroa{)xw-Z8q}*mk@MTrwfm?Z(e$dfmv-iJq5oye8KBsmq92R?T8G$(MB}=mY-r>ya z8r-VFREQ>#>GhSc4E70M;DLlS`Sg_2Ni-T z0jl4R-wdn(LlhicKKkg+@%lV@8V~a%1%0_WLx~!euxsDitLt2eZ~8aCg@^h#kAvKp z$US0=@_dlmJ;T8|>G*5yp?h&AIyBCpPJ>V~9;CDmpI0)cid{~Wxv3{Lp z6F$|(B7bvYMy%9R(+g!d#b@DCV()x<{CVp$;gsxnH|yfKT0%3^4)4rYEeRBMn?O(8 zQ9i>#a3D1NjY)w#3KVtxA~_QsPI%L5aQ~yc+5%mAa$q)J zWF!?E!_mX6=SNDX1Mn53#Pf|4Ik6B>I@t`dV`!IY2V>2Eg!`jIf{41!mvp_K9smdc z%P`W$pK|~;us4!4q}SgLvpVDbw|=KI&)beY`i+g=Di5${ZN^555V^t!sF0^-wQ0S+ z!vhjf?2lUXcl7Mie5ghHOjf|Ar-_{HGIKb_CN?fQSY;$2UF1dW>4cHhp|m)l!1)nE z#J2OuA7G}9%evZs!rX?C8ngHt&pDZAR(7M~PA@P;e_#HVD5%>h{nW4=c{(Ixr^;%2 z#tn?&>8ZdOwX>1+N#%ikl=@`tcv)t9`QHK%g+^^sbp|`;qByYA|%-J zvzio!VqiI!_`wX=1iKyCSpa;!`6nKLx+eSG3Px>nnboOELpVloBF?#x!{0_c+@$|i znZ+v}md+F9@4$-NVD#_3a@IE>6cV$d=Vwi=&n*4XF6Y zApia3O!o#MtBRajLjHr)kqsZlgXb9e_m}{~@yTBr!=cq?A&Xr)(gqUlirx|cWC#&; zKs0_J^jrdWq%+1qWWTIpNblTGvsHYZF^_J$neo@{#V3W}s!Jh~e-3pbCBqMpgjgHs zECW1RC{EX&6op3I=b`(U+cL&APH(Up z)+cec*8?dc(f|coXepXZQB07bp3`oW*{corJfexSE^=|e{E1E-AKh4*sEL}^wF zT5cp_x-S1Zl|D9w0`8ODsOt&-J)Qk)2eAnseU@c)d}l^Q2$%wH4CfZ&0X~=ET?qLr zB`p3y5eA8Y3CHe8A(}hXSj!MZo6!F(65l4`4l^O%)t2a03lt6MyZQkU!b4wXX^BJ{ z8PEPLoQ=D5kd!A2${KB&`1O2+PWLZ4m1kKzbWilrCi1(8c&R~8$AhB8o-LI0U`_z0Fo3#D)5HYoNxnF6IU$h&D6H%0tpG;E!*jv3*|EPgzGKUm3o~flo zk1bQ8XYoLoVi|nz*e+qA8;%`t%J%>c7Q-WM_@uk9)O!|#VO^lnyGFT3h7^*(!F)Uw zP>rOxq1oGLugV!guuXuF+AwrD;%pgJFdYx7I zuW2FqY7DD%QYuu{xLN^d4{pUwp$>l>URAZko$~+2MYOioF$WFZ@1&ouCf2JN)3V_@ z3-yR-?BKq7#v1VV%v1u%;20%>z)^yDbIB`wcu!;fBnVlMmm&di%&3TlIV_nhhub09 zaC~ojbe>1ZVk3|9g3tj6)6{v50Qer zWlo-*V{}WDh(N z%;}Eu7M!n?cc)q>RkT}<@X?{J_=VR71SrXXp#$E#h3PJ%oj>rWya4!^scPr9CM7vJ zRBE7(X~PZxjoAxiK`ft!2SD>iRn4RZa%XZA2H8))`VW$dt@=fLlyv>z1$g z^=7R7!xRzt_TMZn=B*{o;9R`~qV0D!xrl43@jkXNPhPo9HR>?LXcz7e@Cxx3j-U*k zUMF|$gWG4C+7u45kNx<@62Z=Qi;h%TZeHrCUG~6h&uYEuS(g1rxbu%z?bqg3o&TqG z?^mQx)ej_(2O^kRJWa=5)v6l7hZ085dOnnznvsNavK-C%0hmI5`1D9^BBbvD|(4 z{=IMTrkJ0pABzR}$G6LG=N}NtX|H)+gN{>6%W%{sxhQFRih8Z~YR^&J?k1IP(M;c% z(h8sCKcekUmiqfJd3Q)kmM)rq=Zg2F7L~jmJUmo5-Sb%<27jGlKML`24tlP}$=>d> zlqci!rWJ9gHL|A37=Y9Fd8^|rCT!B@lG|MuDu))C!gHjEdN0)7N^4}|Bfft6k1Z%C zY=Owx{u(^-HkGoGPoDEKe(3{ai-7mZAg9XJ582ESDc`;E8C$D|NY2|w+&a&mzD=uD zQ(^1T;cES1sq(E(qpz*c|?-+U}&>ZB0PemCTa5eCB;Y4^mB0BwOFy zPOzBpR5>fAY%C(}`U9pMH5RpoFq4t$L1&;_fcxGVuet9TD5xDu^Km)P3m6x-s8}~) zeOSS|J?FBa)Im*ujJtUQZi@0#Luj>TAm~rPoy&1>#co3BC9L+4Lb9!5IiG^U-4Yli zhNF(6-68)deJ?J=&={7fwe5mD?i-o#QmRmg#pkJll|j-YMD!T~gq=P;k^zLd3FYi` ze?Xqh3tZ6lIPTr$(NQl-PR8R$#fbhis;E+qum{f}m_b{|s#?Lk6(%8Ol3az#u(=(9 z5xbwg>xATEkE5&4!qVqsSktM6Z(}0{Orbi^5PH43q#iy;z=^_mfy8$er=T6df)5cC zBET4n*x(k8Cvgjz>}{GM9#;XN!fTm8p!`X{XXWwF;~_W(_{6$B@-}e`ZE-An#th7l zlBHD(mZf6vq=upk3dw}=9FHR=F1zpF79h%s#apkRDhS`Bm*uvQ$31feXW@afFNZIB zgAEMYBQDA>!Z-usfQ1S=QTt10M<7XD;U`7-CK)yk`z`uss&zrxRV?+gUGid04(N~( z6J_dAU%ih&kF;96+KYx)^FN33q_Zt!mQJm4Q2lGFo8MmT(Cw zx#39t6_W#(#%@WqXN9=H6WHI_LymohS&k1G8iRE!B9Y0s{2n*5``AdUNnh?!Qq${V z!D-tB8$9PU{Rw3Tn@V9-2#?FfT7LA<<>qmP^-`0b9?6pgcVk7t_E`VnnCG}c++Xnz zx-dh~>{Ly)%9|3>m~V^nHDni`z3IrG-v3NQ=t8N|ea^r%hP|M*{=Z4x1H)?3!3n-D z_~WgHk~nGFwIgQ|_f^h!e!a`(d@K6~BG=#ll|<@eh?n5nQvmlbthq;NwfwA5&Rt@Mtb#DTv6OcE5e;%3F#aY!ES+{{>F#ZYH~GL zqK#=$?M4x&mC%XAlZZhBhgn9ADi!rtO>quFb7HJZ^c2_RB<9$q z3od^3IMX&89;UUIa)!H_|5xMIkQ4nifgdIea(ea|pSosp^ z4+8d5_a`Mf!4$^}J|-HVQz90V{(6rlj4!vAe)_tv-x22O@e9FT$^+xLlf2(92!PDq zOkMdav_K~haUz6p*A5)K58$6wZMYb4n8ztHX zS(#$UW4tL8Jz?8v@(0t)XBryNLEhOuZJd|%MDI0T%K2IjIQWF`zz8VIz%FO$K zh3V?OioFa_9-q?>NV2&>Dr6&gkwdMo4DPqt4}ZqOT_Z!r((driir|tpT0D$sIphxg zUrM6FWBOdg_I<_t#F=>Fx$0L+Fu$efe7i#NVnJh4>w5}byXNmMp6fES)i1})heh$; z&%+9)A@&^D3H?O=GJNk;9#NjUmAz^oO&;&Wvr9g18FXzqHM0`V?p($DAK9@}OE*b{ z>}W>ov_Bfe{Ozfq2kVZX`?y&wx1_MALe`ffrT29g&bSpUl0uEhpdN9!X`)3>07Ins z9Ps2}JX74gz<|9~)@t#;vQTfwkvB_%t^P@S2_^jV*L*oKbrHO`LhX-Kf=Bl&fHH=w!mIQd=w zHc6h{`AB;E)2{J!;6XFWV&sK(0$TgJK7&~Y_EI|qnh(g0nrOwlsQxWH9KghH*fHVV zZ>!+{ByFH}Y#H@?7P_g!#laMDd3!w6UKy2&G6LL(8f7MmGDglJY<*AStJ~iM!d@=T zw;`i=IV8LT9fL)2Jj7_~OKS~4q`b)L0_k=OkJCze_}tB+W4l~}Y}V7n{F@wvf?csz z&r1F7NmhPhA~UEs+BLzylKqtnFy4T`si*go3H}SU z_H!#CJ{x)*?!WVqU!r32qqsTbh|fn6y;N!99HB2`g z9FSV_UoX4A(=iQXJ5?aQu}4fsmKU*O6{gv5RZlAy$uV1-%WX_>!%=Lev}KH-Ebfb^ zq)G}BYlR3-%MW)k~sEke_|uu^o}%^ZO(U2 zUAiEIuUjFPyP)mhH{HuxRB)DvUUb2FfyB|*<1a37W5Zi%OKedTg{ZIc6DU5O8*-O# z)Pq`7-p0k@r7T&1JlFjBpfKPA;+uIRN%N!r105GPt21WH$)PvykmxOuNbZmj0_`d z+#Rm_TyWL5lyDUc*1y-6*D|}D8hBQZQp5A?xj0B) zws}OXbcMa1Nr~LQ&F@+wI9oePk!trobY%Akol@4$UPeDcK7&~~@Ut;u&Sx|eN;t^N zl*Jz>Eq};>GeL^G$f1O?+`l-2w1!D1{o8@Y#>SuZd|y^JxLl}9%)7V(!KtWrI=(x> zjx5G2XyZmbwSnTnfKYXJeK^yVgN`srmlu-}c5(#@Z8Sb1_xk5`P|lWgK3zqJ(@q#p z;bDDSf+Q-NV1~Rt8?J0&@Km~%A4@+D7Q|-ed>OwtybmG|f=nvwL8A#LzrJN8O1i3U zjK324W#S=>gKKay{x+)HP+aL9DxGn68~ED241_*wBh6A`yDetg?kVtLH^hM_pE)i| zbP|}853`duGj>NPZJHnZ{6`I6`*@`L>^&#r;Z4#dp2fe0dA~m84jtRbYp)~V@Qo<( zr|rXp$X6CDwxWOyr3~9CpJtb|h=|JRdKXPpZ)7<1Ruac6)N4^MOX(gh)fqFy$9;!p ze_wWIU_0~BoXh6=$iu|aCs z+$lMFVsI`bEd1@4;??iFpK*f?&gW%SVha(rkPqkFHv$h-qOgBI?gd<|L;B)f@% z)zt^%i=$K%{Z_-*7oJAo1}>@2cBfbW{+gW}fr&Lo>Knph9+Woo{UUT}kvBKjSyhOi z?ouFYj)k%PKD0zSYl#bd-sSE&mU2+3nzpyp5cTmDFNa%7q7roTjC8y4kLlF`(U_7o z`Mnv6e0PsZ>7XL7>M}a^DAotLW_R8s^AvO2@Gqz8Tim6}8R0!$IsA1d8M`4WL|t9| z%cp?xa!c&$LGxi7&FVS%`4WW{3E2(7bm9Hp4Z$w~yJSSfKQ^HWh-SZF)qrgI(PpVGO4`uWC{I8W!P8+}r!pdtO?J&8AjbAI_ga zsEIcPZP;GGQ_-tULR?V4HF{wqQN#q3qF9S+#yrh?{wEW+B8G92^*^a791tFEm24E;EW|z0$VRV1Gr=Ozxq}^!me7Cwm{# z=nw_mm&M@-J&OL(-ZUBc8)Sldh$-}ZFj2mb zztSGq8-e3uNIv_IWchwUSz&XN?j4PCJNxJ5=#%fSh+T>Q_D>XhsHG1c+3DmdeGN=j zFMM9c1>&J6npujJo_4Zo4Tb5gk=^227P%rnt%>Y1HgkUPuhq|cySyEVUTp46{`MY- zL3dx6(;jhsc5jZp@z@h$@lX1lKr&^kJd}UkSj9@9itD!0TXbL;6I9sruM4un%wnL{ z)4tQlWlo3xq}QyehkPBDv(4UdNiKpQJmo;{Y$?`g*hsJ*~!zH$~oi;U} z1`Qt`?sG!U2S03Iy~Qv#ZrbO5bC)siGlqzxG_KBx42L~CA`^s{C8?~D;I8j#xHlgO#QIH*x(Y})@=2kg zan|A}CoFh*D{Lq4NvA?EL_r z(^9D4q7PrIx%aksa1<;SrJR z-9H(Z;m9d}@`Pw{$%w-qoev@`Ec%Y0XbS9~>JA-A`}t6Rv~K+Elv#AI5)`RL=}Qf;c1Z5_u5YRaLdkm>fNSJ{>yv zYh^{RYYu0`a{mEtqqiit@>bb@$o98TR)NB{T{M21sb#cs^1??(KJ*jIcdD*Rmq~o; z9b0S&gf1!pdh|^8(p&aKbT>!1vD+MS67R3d(xk{8_hZxFWAnB{c(Bh@>;?Tm zAA?pZPpqcmU67lbR{tLf-|44!uL!@GWqq*4F5H?PQMttos^N{p7FarMszstA&#NH; z{vRY47SKlAKb|28u?^!W`Nv-`KffantP93WqZMwHiiG?hvCk|nKyd~c5EO>k`OKJ+ zI^xUbEoEX&x{8A zZppq6rd_v(#*-ZzKXhY&L35)j?#xh-WY6T`+xga>;`|?erCZ#n;wc#QG|p#Y$l~%| zhzWEzpEvMC@Dy8F6%TLT|L)Re9WuCF5^&&{CisBhALMv3=E(rQ`mK~iz$U=*X|lPX z)o*%V{#WMqpPkUU$xsc*$EAT1w9P0uCEDQ0(-h)d%6b*Cr?BPD&@rF6JTeo* zOycf~j!_+o@F{}j9AFFCnsZnQfq6Rv2@~C9#)-l}hx*Hxk#}Yzl=nenA(z)BQ`I?$ z+*n0!P+rW^@lU}g;tf4gw03-vfH!f?ecDPNHk+hZWjg+2P7#D5)zG?>z6Mj(zlvj< z4(yWiZo5}+ZW$CS1vz4$bKc0Sd1^0sz0B?tb$NGgCt!`;)Y2|YG*7-}F z4>OsMs=2klKWmjZ7ynNVoR)jW^~!zB#`)l=u$rhHe?JC(ngQ>bT76hK8?fDS^TG^O zJ6o*UeC7W80Ns?0JU5GIy|9jdy3J0y0K8v1Bu-%KRElOEWp-@UIu;2s?$LFR_CNIc zdA3%)#z2VI%3uvXfWMj(|0BiqF|PZ4cNR&0M78xIk9k~U{6JxBj)hn?@WB=HbJA}N zc~n5#YZdBh%vf6?*Y?&1J2nP!G*a+d8|O=DWXMyOS#QZU%r{$>9@%gtshOm4)TyTfn7hf;-^Db^FiY6i2OZ4`or9Bq_XbzrrH*RHP z6^J#tn5kR>c~6^sZ8fRl^elr%_zwTBj4IY~Z?KBs+}}r22Dig6>#+@%r}GJ+_ugwS zfnmz6IAm$uN`-m0zWQf3OUje3!S7#^>pZb9Hp;(Tc+HFqwzsq>+8k={;_7sgNuFBs za&QnUQ*jX!F{d=|?gsP*FbMq|+`27E_Du=@;bW(KKzif@UaOv)8fg;hW&y?Z*_@nE zkF;L=^+=%?iPU?2Ag6o#^he?=WMR#($m_W3+lKgEyb;ZEI~^mO_SK?gFX_98isJ8@ z=eFZd``cC>rpWJ}-z6bnTPa<5U+HlK>_q`a*1XcI_+K?DHvK+Qb880*0lJ2}sZX7( zzgth?#(9QJwJm(yIkbC258ql{7wNn0J{UB`HB~iQI{dkhbSXHT0UfxyJBJRD~ zrLMgo=Z??9E*~?CjwD5Xn4#I9)rq+9>Z<>az$zj#Arm7~h6#?61Y|R0=2Nc3;qU8@ z3ZxFT#SI13kH3q3az9?4_)EDcRnKHqYevJ-*F@Vh)je*2o$r5PM^0#2@E-9YzHHXs z3oa`D-FAUnC+8sO4JGOhCYYc7<2DUQ2>i`d{l!535@E!=d=UC z5>P%CP~ONh_B=CvQ-@r8lK&nM#^#*)JRtqV2*P;X)h^nVOhEAXnh#jTS8pJFGElWt zAhq#O$da>d^drx2_rFiMD|SD6?8;(zIn~vEv|HeWV;e^0CRq|#KLs~eB!WW}Hm=u; zJ`~d&fFgwdBQiLR&~f>U4E^hNM9CUDcIIy$J~yKetfTieU9gfYApR?W2DhNsDZw+ojd~i;*2bF7jrCHk+h_GvbZ!atY|t?$N0WX`V4>URRhI~lwFHowTKROBNK|uA>T@QCqR)Z*MMot)NEOi!(ugbRh70tf!44n1+tW)s6;%w3jE_7l zNS{=ymjv|0Ig{W=FTrNNt*>;7YlhoyQGb1#X{q!)uBg6GzuP37`1hCvfAEcgQpf)q zGDSq(ADo_X|1%*ve6L5$RWrw?qC6fGr`+)NX3hRLK$izLJKJND?Q=l=*zfnH$39K4 ziE;XoTSB*~4q%oURleFq-h2Mc;k(-e6_QRzn#S6wHfVEoSW~dNk{5Yi`1*!9U|;qAbhUxX4qk z+yB{jDqGf#zR%D*THClGN-{E@T_VFs-&nG7mQC?e;zuSj!3)|6UQzJtYV-CO)g%py z?P?g15O&Y4LYc1g zfv1~~!*?wMHd8a&^|4{~!fO!_rEzS@{MS#!b@K60Ob7&YKN>-(Ou-zreuw#$29e&D znQQILqI*oZ#G36VD?=H>D^5+BT&;>@6AF+AVz1Cuas3VBqgvk)lr=Pz#oHU zNlweyV$w}ePvP;$5^PIFnI45OY=~%86djxXPYVel(GyVz!0LZd`}UhKY2)m5D;Q0Zr;4bn0*k++6TJTcgQTR?6hSyr^EeC{VISJo?zu zD?Dj~K0sbV=7uv$MMo=+|Kh?D7y-n%2fslWN(SmVnV|E4d-7K=v4i_w+fJ#xUn19J z$9gdIZLI&+z8pMlL4s|sD5w$XRWtu=ExsfFen)SFNE)dcWm5LM^OChCj@yq5$cXi0 zYfCJxQ%24{&@(~-n+bJ_U*9!{<#a$qw(<`xZ#e}L9{`76!K(RC|}fy_qF zeK6<~&&YXV+KH<{UkWNl8|L%I+9~2_&Wv~UVsZz6c{b9#9@1bgI+0Kkugg;0 zIZbv_+|h~@zvMrSLt%H-v)#DRJos6TZWZjrX%6P?dahjL*^lA zmG?TSUgAItx!%g3thof0AT3v*r1C6}0IM~POISJTb%+v|wZb2V=U$UDE)z5Y z9RbtdGq0b^pXqEox+d8;ZwsjfUXE83*gEak3M2`r8bwl$D@#vhd;iiGP2=-c%uC-| z9kG9&`>&b9Bytl{y8~)uMg}c7HXnoy`=;qTOjj?qQK(tFbgVKA46bzJ9wu{o37Jer4^%>)afB9kYv)LbT%56-uH8u*(3Yv?Ea3{`;8%-X;C@q_K`|5 yF5v$jE%PzBRR6o6d~eslZHV)K@BM$AXOl$6x!Z2#E?fQYVJ&riwJH@@^#22?P3RQ> literal 0 HcmV?d00001 diff --git a/webui/src/App.tsx b/webui/src/App.tsx index b3f536ef2..8e256437f 100644 --- a/webui/src/App.tsx +++ b/webui/src/App.tsx @@ -1,5 +1,5 @@ import { globalCss, Box, darkTheme, FaencyProvider, lightTheme } from '@traefiklabs/faency' -import { Suspense, useEffect } from 'react' +import { Suspense, useContext, useEffect } from 'react' import { HelmetProvider } from 'react-helmet-async' import { HashRouter, Navigate, Route, Routes as RouterRoutes, useLocation } from 'react-router-dom' import { SWRConfig } from 'swr' @@ -12,6 +12,7 @@ import { useIsDarkMode } from 'hooks/use-theme' import ErrorSuspenseWrapper from 'layout/ErrorSuspenseWrapper' import { Dashboard, HTTPPages, NotFound, TCPPages, UDPPages } from 'pages' import { DashboardSkeleton } from 'pages/dashboard/Dashboard' +import { HubDemoContext, HubDemoProvider } from 'pages/hub-demo/demoNavContext' export const LIGHT_THEME = lightTheme('blue') export const DARK_THEME = darkTheme('blue') @@ -33,47 +34,56 @@ const ScrollToTop = () => { } export const Routes = () => { + const { routes: hubDemoRoutes } = useContext(HubDemoContext) + return ( - }> - - }> - - - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - + + }> + + }> + + + } + /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + {/* Hub Dashboard demo content */} + {hubDemoRoutes?.map((route, idx) => )} + + } /> + + + ) } const isDev = import.meta.env.NODE_ENV === 'development' const customGlobalStyle = globalCss({ - 'span[role=cell]': { // target the AriaTd component - p: '$2 $3' + // target the AriaTd component, but exclude anything inside hub-ui-demo-app + 'body:not(:has(hub-ui-demo-app)) span[role=cell]': { + p: '$2 $3', }, }) @@ -101,9 +111,11 @@ const App = () => { > - {customGlobalStyle()} - - + + {customGlobalStyle()} + + + diff --git a/webui/src/components/SpinnerLoader.tsx b/webui/src/components/SpinnerLoader.tsx index 663130307..e336fc436 100644 --- a/webui/src/components/SpinnerLoader.tsx +++ b/webui/src/components/SpinnerLoader.tsx @@ -2,17 +2,17 @@ import { Flex } from '@traefiklabs/faency' import { motion } from 'framer-motion' import { FiLoader } from 'react-icons/fi' -export const SpinnerLoader = () => ( +export const SpinnerLoader = ({ size = 24 }: { size?: number }) => ( - + ) diff --git a/webui/src/components/icons/providers/Knative.tsx b/webui/src/components/icons/providers/Knative.tsx index e6adf46e2..8eaf21d50 100644 --- a/webui/src/components/icons/providers/Knative.tsx +++ b/webui/src/components/icons/providers/Knative.tsx @@ -2,10 +2,12 @@ import { ProviderIconProps } from 'components/icons/providers' export default function Knative(props: ProviderIconProps) { return ( - - + + ) } diff --git a/webui/src/components/icons/providers/index.tsx b/webui/src/components/icons/providers/index.tsx index cd953d1b0..8745a7644 100644 --- a/webui/src/components/icons/providers/index.tsx +++ b/webui/src/components/icons/providers/index.tsx @@ -8,7 +8,7 @@ import File from 'components/icons/providers/File' import Http from 'components/icons/providers/Http' import Hub from 'components/icons/providers/Hub' import Internal from 'components/icons/providers/Internal' -import Knative from "components/icons/providers/Knative"; +import Knative from 'components/icons/providers/Knative' import Kubernetes from 'components/icons/providers/Kubernetes' import Nomad from 'components/icons/providers/Nomad' import Plugin from 'components/icons/providers/Plugin' diff --git a/webui/src/layout/Navigation.tsx b/webui/src/layout/Navigation.tsx index 62b29eedf..658ef2257 100644 --- a/webui/src/layout/Navigation.tsx +++ b/webui/src/layout/Navigation.tsx @@ -2,6 +2,7 @@ import { Badge, Box, Button, + CSS, DialogTitle, DropdownMenu, DropdownMenuContent, @@ -37,6 +38,7 @@ import TooltipText from 'components/TooltipText' import { VersionContext } from 'contexts/version' import useTotals from 'hooks/use-overview-totals' import { useIsDarkMode } from 'hooks/use-theme' +import ApimDemoNavMenu from 'pages/hub-demo/HubDemoNav' import { Route, ROUTES } from 'routes' export const LAPTOP_BP = 1025 @@ -54,7 +56,7 @@ const NavigationDrawer = styled(Flex, { }, }) -const BasicNavigationItem = ({ +export const BasicNavigationItem = ({ route, count, isSmallScreen, @@ -270,7 +272,7 @@ export const SideNav = ({ ))} ))} - + } css={{ @@ -283,12 +285,14 @@ export const SideNav = ({ {!isSmallScreen || isExpanded ? 'Plugins' : ''} + + ) } -export const TopNav = () => { +export const TopNav = ({ css, noHubButton = false }: { css?: CSS; noHubButton?: boolean }) => { const [hasHubButtonComponent, setHasHubButtonComponent] = useState(false) const { showHubButton, version } = useContext(VersionContext) const isDarkMode = useIsDarkMode() @@ -341,8 +345,8 @@ export const TopNav = () => { }, [showHubButton]) return ( - - {hasHubButtonComponent && ( + + {!noHubButton && hasHubButtonComponent && ( ', () => { it('should render an empty page', () => { - const { getByTestId } = renderWithProviders() - expect(getByTestId('Test page')).toBeInTheDocument() + const { getByTestId } = renderWithProviders(, { route: '/test' }) + expect(getByTestId('/test page')).toBeInTheDocument() }) }) diff --git a/webui/src/layout/Page.tsx b/webui/src/layout/Page.tsx index f11afa091..ec8b6ea81 100644 --- a/webui/src/layout/Page.tsx +++ b/webui/src/layout/Page.tsx @@ -1,6 +1,7 @@ import { Flex, globalCss, styled } from '@traefiklabs/faency' -import { ReactNode, useState } from 'react' +import { ReactNode, useMemo, useState } from 'react' import { Helmet } from 'react-helmet-async' +import { useLocation } from 'react-router-dom' import Container from './Container' import { LAPTOP_BP, SideBarPanel, SideNav, TopNav } from './Navigation' @@ -40,14 +41,31 @@ export interface Props { children?: ReactNode } -const Page = ({ children, title }: Props) => { +const Page = ({ children }: Props) => { + const { pathname } = useLocation() const [isSideBarPanelOpen, setIsSideBarPanelOpen] = useState(false) + const location = useLocation() + + const isDemoPage = useMemo(() => pathname.includes('hub-dashboard'), [pathname]) + + const renderedContent = useMemo(() => { + if (isDemoPage) { + return children + } + + return ( + + + {children} + + ) + }, [children, isDemoPage, location.pathname]) return ( {globalStyles()} - {title ? `${title} - ` : ''}Traefik Proxy + Traefik Proxy @@ -56,10 +74,7 @@ const Page = ({ children, title }: Props) => { justify="center" css={{ flex: 1, margin: 'auto', ml: 264, [`@media (max-width:${LAPTOP_BP}px)`]: { ml: 60 } }} > - - - {children} - + {renderedContent} diff --git a/webui/src/pages/NotFound.tsx b/webui/src/pages/NotFound.tsx index 51a130c4c..486b0b217 100644 --- a/webui/src/pages/NotFound.tsx +++ b/webui/src/pages/NotFound.tsx @@ -1,24 +1,24 @@ import { Box, Button, Flex, H1, Text } from '@traefiklabs/faency' +import { Helmet } from 'react-helmet-async' import { useNavigate } from 'react-router-dom' -import Page from 'layout/Page' - export const NotFound = () => { const navigate = useNavigate() return ( - - - -

404

- - - I'm sorry, nothing around here... - - - - + + + Not found - Traefik Proxy + + +

404

+
+ + I'm sorry, nothing around here... + + +
) } diff --git a/webui/src/pages/dashboard/Dashboard.tsx b/webui/src/pages/dashboard/Dashboard.tsx index 9100823eb..b26cf0a9c 100644 --- a/webui/src/pages/dashboard/Dashboard.tsx +++ b/webui/src/pages/dashboard/Dashboard.tsx @@ -1,12 +1,12 @@ import { Card, CSS, Flex, Grid, H2, Text } from '@traefiklabs/faency' import { ReactNode, useMemo } from 'react' +import { Helmet } from 'react-helmet-async' import useSWR from 'swr' import ProviderIcon from 'components/icons/providers' import FeatureCard, { FeatureCardSkeleton } from 'components/resources/FeatureCard' import ResourceCard from 'components/resources/ResourceCard' import TraefikResourceStatsCard, { StatsCardSkeleton } from 'components/resources/TraefikResourceStatsCard' -import Page from 'layout/Page' import { capitalizeFirstLetter } from 'utils/string' const RESOURCES = ['routers', 'services', 'middlewares'] @@ -76,159 +76,158 @@ export const Dashboard = () => { } return ( - - - - {entrypoints?.map((i, idx) => ( - - {i.address} - - ))} - + + + Dashboard - Traefik Proxy + + + {entrypoints?.map((i, idx) => ( + + {i.address} + + ))} + - - {overview?.http && hasResources.http ? ( - RESOURCES.map((i) => ( - - )) - ) : ( - No related objects to show. - )} - + + {overview?.http && hasResources.http ? ( + RESOURCES.map((i) => ( + + )) + ) : ( + No related objects to show. + )} + - - {overview?.tcp && hasResources.tcp ? ( - RESOURCES.map((i) => ( - - )) - ) : ( - No related objects to show. - )} - + + {overview?.tcp && hasResources.tcp ? ( + RESOURCES.map((i) => ( + + )) + ) : ( + No related objects to show. + )} + - - {overview?.udp && hasResources.udp ? ( - RESOURCES.map((i) => ( - - )) - ) : ( - No related objects to show. - )} - + + {overview?.udp && hasResources.udp ? ( + RESOURCES.map((i) => ( + + )) + ) : ( + No related objects to show. + )} + - - {features.length - ? features.map((i, idx) => { - return - }) - : null} - + + {features.length + ? features.map((i, idx) => { + return + }) + : null} + - - {overview?.providers?.length ? ( - overview.providers.map((p, idx) => ( - - - - {p} - - - )) - ) : ( - No related objects to show. - )} - - - + + {overview?.providers?.length ? ( + overview.providers.map((p, idx) => ( + + + + {p} + + + )) + ) : ( + No related objects to show. + )} + + ) } export const DashboardSkeleton = () => { return ( - - - - {[...Array(5)].map((_, i) => ( - - ))} - + + + {[...Array(5)].map((_, i) => ( + + ))} + - - {[...Array(3)].map((_, i) => ( - - ))} - + + {[...Array(3)].map((_, i) => ( + + ))} + - - {[...Array(3)].map((_, i) => ( - - ))} - + + {[...Array(3)].map((_, i) => ( + + ))} + - - {[...Array(3)].map((_, i) => ( - - ))} - + + {[...Array(3)].map((_, i) => ( + + ))} + - - {[...Array(3)].map((_, i) => ( - - ))} - + + {[...Array(3)].map((_, i) => ( + + ))} + - - {[...Array(3)].map((_, i) => ( - - ))} - - - + + {[...Array(3)].map((_, i) => ( + + ))} + + ) } diff --git a/webui/src/pages/http/HttpMiddleware.spec.tsx b/webui/src/pages/http/HttpMiddleware.spec.tsx index d7a7c39bb..8bf003735 100644 --- a/webui/src/pages/http/HttpMiddleware.spec.tsx +++ b/webui/src/pages/http/HttpMiddleware.spec.tsx @@ -7,6 +7,7 @@ describe('', () => { it('should render the error message', () => { const { getByTestId } = renderWithProviders( , + { route: '/http/middlewares/mock-middleware', withPage: true }, ) expect(getByTestId('error-text')).toBeInTheDocument() }) @@ -14,6 +15,7 @@ describe('', () => { it('should render the skeleton', () => { const { getByTestId } = renderWithProviders( , + { route: '/http/middlewares/mock-middleware', withPage: true }, ) expect(getByTestId('skeleton')).toBeInTheDocument() }) @@ -21,6 +23,7 @@ describe('', () => { it('should render the not found page', () => { const { getByTestId } = renderWithProviders( , + { route: '/http/middlewares/mock-middleware', withPage: true }, ) expect(getByTestId('Not found page')).toBeInTheDocument() }) @@ -53,6 +56,7 @@ describe('', () => { const { container, getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/http/middlewares/middleware-simple', withPage: true }, ) const headings = Array.from(container.getElementsByTagName('h1')) @@ -99,6 +103,7 @@ describe('', () => { const { container, getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/http/middlewares/middleware-plugin', withPage: true }, ) const headings = Array.from(container.getElementsByTagName('h1')) @@ -338,6 +343,7 @@ describe('', () => { const { container, getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/http/middlewares/middleware-complex', withPage: true }, ) const headings = Array.from(container.getElementsByTagName('h1')) @@ -459,6 +465,7 @@ describe('', () => { const { container, getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/http/middlewares/middleware-plugin-no-type', withPage: true }, ) const headings = Array.from(container.getElementsByTagName('h1')) diff --git a/webui/src/pages/http/HttpMiddleware.tsx b/webui/src/pages/http/HttpMiddleware.tsx index 4b2fe7e2a..d762e6975 100644 --- a/webui/src/pages/http/HttpMiddleware.tsx +++ b/webui/src/pages/http/HttpMiddleware.tsx @@ -1,11 +1,11 @@ import { Box, Card, H1, Skeleton, styled, Text } from '@traefiklabs/faency' +import { Helmet } from 'react-helmet-async' import { useParams } from 'react-router-dom' import { DetailSectionSkeleton } from 'components/resources/DetailSections' import { RenderMiddleware } from 'components/resources/MiddlewarePanel' import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection' import { ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail' -import Page from 'layout/Page' import { NotFound } from 'pages/NotFound' import breakpoints from 'utils/breakpoints' @@ -27,23 +27,29 @@ type HttpMiddlewareRenderProps = { export const HttpMiddlewareRender = ({ data, error, name }: HttpMiddlewareRenderProps) => { if (error) { return ( - + <> + + {name} - Traefik Proxy + Sorry, we could not fetch detail information for this Middleware right now. Please, try again later. - + ) } if (!data) { return ( - + <> + + {name} - Traefik Proxy + - + ) } @@ -52,7 +58,10 @@ export const HttpMiddlewareRender = ({ data, error, name }: HttpMiddlewareRender } return ( - + <> + + {data.name} - Traefik Proxy +

{data.name}

@@ -60,7 +69,7 @@ export const HttpMiddlewareRender = ({ data, error, name }: HttpMiddlewareRender -
+ ) } diff --git a/webui/src/pages/http/HttpMiddlewares.spec.tsx b/webui/src/pages/http/HttpMiddlewares.spec.tsx index 1f4ce2607..b49b42062 100644 --- a/webui/src/pages/http/HttpMiddlewares.spec.tsx +++ b/webui/src/pages/http/HttpMiddlewares.spec.tsx @@ -76,10 +76,13 @@ describe('', () => { .spyOn(useFetchWithPagination, 'default') .mockImplementation(() => useFetchWithPaginationMock({ pages })) - const { container, getByTestId } = renderWithProviders() + const { container, getByTestId } = renderWithProviders(, { + route: '/http/middlewares', + withPage: true, + }) expect(mock).toHaveBeenCalled() - expect(getByTestId('HTTP Middlewares page')).toBeInTheDocument() + expect(getByTestId('/http/middlewares page')).toBeInTheDocument() const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1] expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(5) @@ -120,6 +123,7 @@ describe('', () => { pageCount={1} pages={[]} />, + { route: '/http/middlewares', withPage: true }, ) expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]') const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2] diff --git a/webui/src/pages/http/HttpMiddlewares.tsx b/webui/src/pages/http/HttpMiddlewares.tsx index f61a4ed6b..e3274009e 100644 --- a/webui/src/pages/http/HttpMiddlewares.tsx +++ b/webui/src/pages/http/HttpMiddlewares.tsx @@ -1,5 +1,6 @@ import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency' import { useMemo } from 'react' +import { Helmet } from 'react-helmet-async' import useInfiniteScroll from 'react-infinite-scroll-hook' import { useSearchParams } from 'react-router-dom' @@ -14,7 +15,6 @@ import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' -import Page from 'layout/Page' import { parseMiddlewareType } from 'libs/parsers' export const makeRowRender = (): RenderRowType => { @@ -109,7 +109,10 @@ export const HttpMiddlewares = () => { ) return ( - + <> + + HTTP Middlewares - Traefik Proxy + { pageCount={pageCount} pages={pages} /> - + ) } diff --git a/webui/src/pages/http/HttpRouter.spec.tsx b/webui/src/pages/http/HttpRouter.spec.tsx index a7f1e3ad7..ca90455d9 100644 --- a/webui/src/pages/http/HttpRouter.spec.tsx +++ b/webui/src/pages/http/HttpRouter.spec.tsx @@ -10,6 +10,7 @@ describe('', () => { it('should render the error message', () => { const { getByTestId } = renderWithProviders( , + { route: '/http/routers/mock-router', withPage: true }, ) expect(getByTestId('error-text')).toBeInTheDocument() }) @@ -17,6 +18,7 @@ describe('', () => { it('should render the skeleton', () => { const { getByTestId } = renderWithProviders( , + { route: '/http/routers/mock-router', withPage: true }, ) expect(getByTestId('skeleton')).toBeInTheDocument() }) @@ -24,6 +26,7 @@ describe('', () => { it('should render the not found page', () => { const { getByTestId } = renderWithProviders( , + { route: '/http/routers/mock-router', withPage: true }, ) expect(getByTestId('Not found page')).toBeInTheDocument() }) @@ -40,6 +43,7 @@ describe('', () => { const { getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/http/routers/orphan-router@file', withPage: true }, ) const routerStructure = getByTestId('router-structure') diff --git a/webui/src/pages/http/HttpRouter.tsx b/webui/src/pages/http/HttpRouter.tsx index dbb493e4d..62801083a 100644 --- a/webui/src/pages/http/HttpRouter.tsx +++ b/webui/src/pages/http/HttpRouter.tsx @@ -1,5 +1,6 @@ import { Flex, styled, Text } from '@traefiklabs/faency' import { useContext, useEffect, useMemo } from 'react' +import { Helmet } from 'react-helmet-async' import { FiGlobe, FiLayers, FiLogIn, FiZap } from 'react-icons/fi' import { useParams } from 'react-router-dom' @@ -9,7 +10,6 @@ import RouterPanel from 'components/resources/RouterPanel' import TlsPanel from 'components/resources/TlsPanel' import { ToastContext } from 'contexts/toasts' import { EntryPoint, ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail' -import Page from 'layout/Page' import { getErrorData, getValidData } from 'libs/objectHandlers' import { parseMiddlewareType } from 'libs/parsers' import { NotFound } from 'pages/NotFound' @@ -105,17 +105,23 @@ type HttpRouterRenderProps = { export const HttpRouterRender = ({ data, error, name }: HttpRouterRenderProps) => { if (error) { return ( - + <> + + {name} - Traefik Proxy + Sorry, we could not fetch detail information for this Router right now. Please, try again later. - + ) } if (!data) { return ( - + <> + + {name} - Traefik Proxy + @@ -127,7 +133,7 @@ export const HttpRouterRender = ({ data, error, name }: HttpRouterRenderProps) = - + ) } @@ -136,10 +142,13 @@ export const HttpRouterRender = ({ data, error, name }: HttpRouterRenderProps) = } return ( - + <> + + {data.name} - Traefik Proxy + - + ) } diff --git a/webui/src/pages/http/HttpRouters.spec.tsx b/webui/src/pages/http/HttpRouters.spec.tsx index bdddd21b4..a4df44257 100644 --- a/webui/src/pages/http/HttpRouters.spec.tsx +++ b/webui/src/pages/http/HttpRouters.spec.tsx @@ -49,10 +49,13 @@ describe('', () => { .spyOn(useFetchWithPagination, 'default') .mockImplementation(() => useFetchWithPaginationMock({ pages })) - const { container, getByTestId } = renderWithProviders() + const { container, getByTestId } = renderWithProviders(, { + route: '/http/routers', + withPage: true, + }) expect(mock).toHaveBeenCalled() - expect(getByTestId('HTTP Routers page')).toBeInTheDocument() + expect(getByTestId('/http/routers page')).toBeInTheDocument() const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1] expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(4) @@ -100,6 +103,7 @@ describe('', () => { pageCount={1} pages={[]} />, + { route: '/http/routers', withPage: true }, ) expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]') const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2] diff --git a/webui/src/pages/http/HttpRouters.tsx b/webui/src/pages/http/HttpRouters.tsx index d646b8f42..06c2c255f 100644 --- a/webui/src/pages/http/HttpRouters.tsx +++ b/webui/src/pages/http/HttpRouters.tsx @@ -1,5 +1,6 @@ import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency' import { useMemo } from 'react' +import { Helmet } from 'react-helmet-async' import { FiShield } from 'react-icons/fi' import useInfiniteScroll from 'react-infinite-scroll-hook' import { useSearchParams } from 'react-router-dom' @@ -16,7 +17,6 @@ import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' -import Page from 'layout/Page' export const makeRowRender = (protocol = 'http'): RenderRowType => { const HttpRoutersRenderRow = (row) => ( @@ -130,7 +130,10 @@ export const HttpRouters = () => { ) return ( - + <> + + HTTP Routers - Traefik Proxy + { pageCount={pageCount} pages={pages} /> - + ) } diff --git a/webui/src/pages/http/HttpService.spec.tsx b/webui/src/pages/http/HttpService.spec.tsx index 781d43099..76059e21d 100644 --- a/webui/src/pages/http/HttpService.spec.tsx +++ b/webui/src/pages/http/HttpService.spec.tsx @@ -7,6 +7,7 @@ describe('', () => { it('should render the error message', () => { const { getByTestId } = renderWithProviders( , + { route: '/http/services/mock-service', withPage: true }, ) expect(getByTestId('error-text')).toBeInTheDocument() }) @@ -14,6 +15,7 @@ describe('', () => { it('should render the skeleton', () => { const { getByTestId } = renderWithProviders( , + { route: '/http/services/mock-service', withPage: true }, ) expect(getByTestId('skeleton')).toBeInTheDocument() }) @@ -21,6 +23,7 @@ describe('', () => { it('should render the not found page', () => { const { getByTestId } = renderWithProviders( , + { route: '/http/services/mock-service', withPage: true }, ) expect(getByTestId('Not found page')).toBeInTheDocument() }) @@ -71,6 +74,7 @@ describe('', () => { const { container, getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/http/services/mock-service', withPage: true }, ) const headings = Array.from(container.getElementsByTagName('h1')) @@ -142,6 +146,7 @@ describe('', () => { const { getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/http/services/mock-service', withPage: true }, ) const healthCheck = getByTestId('health-check') @@ -196,6 +201,7 @@ describe('', () => { const { getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/http/services/mock-service', withPage: true }, ) const mirrorServices = getByTestId('mirror-services') diff --git a/webui/src/pages/http/HttpService.tsx b/webui/src/pages/http/HttpService.tsx index 4e74c552f..1761cbf05 100644 --- a/webui/src/pages/http/HttpService.tsx +++ b/webui/src/pages/http/HttpService.tsx @@ -1,5 +1,6 @@ import { Badge, Box, Flex, H1, Skeleton, styled, Text } from '@traefiklabs/faency' import { useMemo } from 'react' +import { Helmet } from 'react-helmet-async' import { FiGlobe, FiInfo, FiShield } from 'react-icons/fi' import { useParams } from 'react-router-dom' @@ -18,7 +19,6 @@ import { ResourceStatus } from 'components/resources/ResourceStatus' import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection' import Tooltip from 'components/Tooltip' import { ResourceDetailDataType, ServiceDetailType, useResourceDetail } from 'hooks/use-resource-detail' -import Page from 'layout/Page' import { NotFound } from 'pages/NotFound' type DetailProps = { @@ -270,17 +270,23 @@ type HttpServiceRenderProps = { export const HttpServiceRender = ({ data, error, name }: HttpServiceRenderProps) => { if (error) { return ( - + <> + + {name} - Traefik Proxy + Sorry, we could not fetch detail information for this Service right now. Please, try again later. - + ) } if (!data) { return ( - + <> + + {name} - Traefik Proxy + @@ -288,7 +294,7 @@ export const HttpServiceRender = ({ data, error, name }: HttpServiceRenderProps) - + ) } @@ -297,11 +303,14 @@ export const HttpServiceRender = ({ data, error, name }: HttpServiceRenderProps) } return ( - + <> + + {data.name} - Traefik Proxy +

{data.name}

-
+ ) } diff --git a/webui/src/pages/http/HttpServices.spec.tsx b/webui/src/pages/http/HttpServices.spec.tsx index 720fc2549..31a749c8c 100644 --- a/webui/src/pages/http/HttpServices.spec.tsx +++ b/webui/src/pages/http/HttpServices.spec.tsx @@ -49,10 +49,13 @@ describe('', () => { .spyOn(useFetchWithPagination, 'default') .mockImplementation(() => useFetchWithPaginationMock({ pages })) - const { container, getByTestId } = renderWithProviders() + const { container, getByTestId } = renderWithProviders(, { + route: '/http/services', + withPage: true, + }) expect(mock).toHaveBeenCalled() - expect(getByTestId('HTTP Services page')).toBeInTheDocument() + expect(getByTestId('/http/services page')).toBeInTheDocument() const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1] expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(4) @@ -92,6 +95,7 @@ describe('', () => { pageCount={1} pages={[]} />, + { route: '/http/services', withPage: true }, ) expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]') const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2] diff --git a/webui/src/pages/http/HttpServices.tsx b/webui/src/pages/http/HttpServices.tsx index 6febd6b1f..8c41badcd 100644 --- a/webui/src/pages/http/HttpServices.tsx +++ b/webui/src/pages/http/HttpServices.tsx @@ -1,5 +1,6 @@ import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex, Text } from '@traefiklabs/faency' import { useMemo } from 'react' +import { Helmet } from 'react-helmet-async' import useInfiniteScroll from 'react-infinite-scroll-hook' import { useSearchParams } from 'react-router-dom' @@ -14,7 +15,6 @@ import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' -import Page from 'layout/Page' export const makeRowRender = (): RenderRowType => { const HttpServicesRenderRow = (row) => ( @@ -108,7 +108,10 @@ export const HttpServices = () => { ) return ( - + <> + + HTTP Services - Traefik Proxy + { pageCount={pageCount} pages={pages} /> - + ) } diff --git a/webui/src/pages/hub-demo/HubDashboard.spec.tsx b/webui/src/pages/hub-demo/HubDashboard.spec.tsx new file mode 100644 index 000000000..7f338ab69 --- /dev/null +++ b/webui/src/pages/hub-demo/HubDashboard.spec.tsx @@ -0,0 +1,204 @@ +import { waitFor } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + +import HubDashboard, { resetCache } from './HubDashboard' +import verifySignature from './workers/scriptVerification' + +import { renderWithProviders } from 'utils/test' + +vi.mock('./workers/scriptVerification', () => ({ + default: vi.fn(), +})) + +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom') + return { + ...actual, + useParams: vi.fn(() => ({ id: 'test-id' })), + } +}) + +vi.mock('hooks/use-theme', () => ({ + useIsDarkMode: vi.fn(() => false), + useTheme: vi.fn(() => ({ + selectedTheme: 'light', + appliedTheme: 'light', + setTheme: vi.fn(), + })), +})) + +describe('HubDashboard demo', () => { + const mockVerifyScriptSignature = vi.mocked(verifySignature) + let mockCreateObjectURL: ReturnType + + beforeEach(() => { + vi.clearAllMocks() + + // Mock URL.createObjectURL + mockCreateObjectURL = vi.fn(() => 'blob:mock-url') + globalThis.URL.createObjectURL = mockCreateObjectURL + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('without cache', () => { + beforeEach(() => { + // Reset cache before each test suites + resetCache() + }) + + it('should render loading state during script verification', async () => { + const mockScriptContent = new ArrayBuffer(100) + mockVerifyScriptSignature.mockImplementation( + () => + new Promise((resolve) => + setTimeout(() => resolve({ verified: true, scriptContent: mockScriptContent }), 100), + ), + ) + + const { getByTestId } = renderWithProviders(, { + route: '/hub-dashboard', + }) + + expect(getByTestId('loading')).toBeInTheDocument() + + await waitFor(() => { + expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1) + }) + }) + + it('should render the custom web component when signature is verified', async () => { + const mockScriptContent = new ArrayBuffer(100) + mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent }) + + const { container } = renderWithProviders(, { + route: '/hub-dashboard', + }) + + await waitFor(() => { + expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1) + }) + + const hubComponent = container.querySelector('hub-ui-demo-app') + expect(hubComponent).toBeInTheDocument() + expect(hubComponent?.getAttribute('path')).toBe('dashboard') + expect(hubComponent?.getAttribute('baseurl')).toBe('#/hub-dashboard') + expect(hubComponent?.getAttribute('theme')).toBe('light') + }) + + it('should render error state when signature verification fails', async () => { + mockVerifyScriptSignature.mockResolvedValue({ verified: false }) + + const { container } = renderWithProviders() + + await waitFor(() => { + expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1) + }) + + expect(container.textContent).toContain("Oops! We couldn't load the demo content") + + const errorImage = container.querySelector('img[src="/img/gopher-something-went-wrong.png"]') + expect(errorImage).toBeInTheDocument() + + const links = container.querySelectorAll('a') + const websiteLink = Array.from(links).find((link) => link.href.includes('traefik.io/traefik-hub')) + const docLink = Array.from(links).find((link) => link.href.includes('doc.traefik.io/traefik-hub')) + + expect(websiteLink).toBeInTheDocument() + expect(docLink).toBeInTheDocument() + }) + + it('should render error state when verification throws an error', async () => { + mockVerifyScriptSignature.mockRejectedValue(new Error('Network error')) + + const { container } = renderWithProviders() + + await waitFor(() => { + expect(container.textContent).toContain("Oops! We couldn't load the demo content") + }) + }) + + it('should call verifyScriptSignature with correct parameters', async () => { + const mockScriptContent = new ArrayBuffer(100) + mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent }) + + renderWithProviders() + + await waitFor(() => { + expect(mockVerifyScriptSignature).toHaveBeenCalledWith( + 'https://assets.traefik.io/hub-ui-demo.js', + 'https://assets.traefik.io/hub-ui-demo.js.sig', + ) + }) + }) + + it('should set theme attribute based on dark mode', async () => { + const mockScriptContent = new ArrayBuffer(100) + mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent }) + + const { container } = renderWithProviders() + + await waitFor(() => { + expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1) + }) + + const hubComponent = container.querySelector('hub-ui-demo-app') + + expect(hubComponent?.getAttribute('theme')).toMatch(/light|dark/) + }) + + it('should handle path with :id parameter correctly', async () => { + const mockScriptContent = new ArrayBuffer(100) + mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent }) + + const { container } = renderWithProviders() + + await waitFor(() => { + expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1) + }) + + const hubComponent = container.querySelector('hub-ui-demo-app') + expect(hubComponent).toBeInTheDocument() + expect(hubComponent?.getAttribute('path')).toBe('gateways/test-id') + }) + }) + + describe('with cache', () => { + beforeEach(() => { + resetCache() + }) + + it('should use cached blob URL without calling verifySignature again', async () => { + const mockScriptContent = new ArrayBuffer(100) + mockVerifyScriptSignature.mockResolvedValue({ verified: true, scriptContent: mockScriptContent }) + + // First render + const { container: firstContainer, unmount: firstUnmount } = renderWithProviders( + , + ) + + await waitFor(() => { + expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(1) + }) + + const firstHubComponent = firstContainer.querySelector('hub-ui-demo-app') + expect(firstHubComponent).toBeInTheDocument() + + firstUnmount() + + mockVerifyScriptSignature.mockClear() + + // Second render - should use cache + const { container: secondContainer } = renderWithProviders() + + await waitFor(() => { + const secondHubComponent = secondContainer.querySelector('hub-ui-demo-app') + expect(secondHubComponent).toBeInTheDocument() + }) + + expect(mockVerifyScriptSignature).toHaveBeenCalledTimes(0) + }) + }) +}) diff --git a/webui/src/pages/hub-demo/HubDashboard.tsx b/webui/src/pages/hub-demo/HubDashboard.tsx new file mode 100644 index 000000000..fdbb4d37f --- /dev/null +++ b/webui/src/pages/hub-demo/HubDashboard.tsx @@ -0,0 +1,147 @@ +import { Box, Flex, Image, Link, Text } from '@traefiklabs/faency' +import { useMemo, useEffect, useState } from 'react' +import { Helmet } from 'react-helmet-async' +import { useParams } from 'react-router-dom' + +import verifySignature from './workers/scriptVerification' + +import { SpinnerLoader } from 'components/SpinnerLoader' +import { useIsDarkMode } from 'hooks/use-theme' +import { TopNav } from 'layout/Navigation' + +const SCRIPT_URL = 'https://assets.traefik.io/hub-ui-demo.js' + +// Module-level cache to persist across component mount/unmount +let cachedBlobUrl: string | null = null + +// Export a function to reset the cache (for testing) +export const resetCache = () => { + cachedBlobUrl = null +} + +const HubDashboard = ({ path }: { path: string }) => { + const isDarkMode = useIsDarkMode() + const [scriptError, setScriptError] = useState(undefined) + const [signatureVerified, setSignatureVerified] = useState(false) + const [verificationInProgress, setVerificationInProgress] = useState(false) + const [scriptBlobUrl, setScriptBlobUrl] = useState(null) + + const { id } = useParams() + + const usedPath = useMemo(() => { + if (path?.includes(':id')) { + const splitted = path.split(':') + return `${splitted[0]}/${id}` + } + + return path + }, [id, path]) + + useEffect(() => { + const verifyAndLoadScript = async () => { + setVerificationInProgress(true) + + try { + const { verified, scriptContent: content } = await verifySignature(SCRIPT_URL, `${SCRIPT_URL}.sig`) + + if (!verified || !content) { + setScriptError(true) + setVerificationInProgress(false) + } else { + setScriptError(false) + + const blob = new Blob([content], { type: 'application/javascript' }) + cachedBlobUrl = URL.createObjectURL(blob) + + setScriptBlobUrl(cachedBlobUrl) + setSignatureVerified(true) + setVerificationInProgress(false) + } + } catch { + setScriptError(true) + setVerificationInProgress(false) + } + } + + if (!cachedBlobUrl) { + verifyAndLoadScript() + } else { + setScriptBlobUrl(cachedBlobUrl) + setSignatureVerified(true) + } + }, []) + + if (scriptError && !verificationInProgress) { + return ( + + + Oops! We couldn't load the demo content. + + Don't worry — you can still learn more about{' '} + + Traefik Hub API Management + {' '} + on our{' '} + + website + {' '} + or in our{' '} + + documentation + + . + + + ) + } + + return ( + + + Hub Demo - Traefik Proxy + + {signatureVerified && scriptBlobUrl && } + + + + + {verificationInProgress ? ( + + + + ) : ( + + )} + + ) +} + +export default HubDashboard diff --git a/webui/src/pages/hub-demo/HubDemoNav.tsx b/webui/src/pages/hub-demo/HubDemoNav.tsx new file mode 100644 index 000000000..eba41dc07 --- /dev/null +++ b/webui/src/pages/hub-demo/HubDemoNav.tsx @@ -0,0 +1,84 @@ +import { Badge, Box, Flex, Text } from '@traefiklabs/faency' +import { useContext, useState } from 'react' +import { BsChevronRight } from 'react-icons/bs' + +import { HubDemoContext } from './demoNavContext' +import { HubIcon } from './icons' + +import Tooltip from 'components/Tooltip' +import { BasicNavigationItem, LAPTOP_BP } from 'layout/Navigation' + +const ApimDemoNavMenu = ({ + isResponsive, + isSmallScreen, + isExpanded, +}: { + isResponsive: boolean + isSmallScreen: boolean + isExpanded: boolean +}) => { + const [isCollapsed, setIsCollapsed] = useState(false) + const { navigationItems: hubDemoNavItems } = useContext(HubDemoContext) + + if (!hubDemoNavItems) { + return null + } + + return ( + + setIsCollapsed(!isCollapsed)} + > + + {isSmallScreen ? ( + + + + + + ) : ( + <> + + API management + + + Demo + + + )} + + + + {hubDemoNavItems.map((route, idx) => ( + + ))} + + + ) +} + +export default ApimDemoNavMenu diff --git a/webui/src/pages/hub-demo/demoNavContext.tsx b/webui/src/pages/hub-demo/demoNavContext.tsx new file mode 100644 index 000000000..3f28afc78 --- /dev/null +++ b/webui/src/pages/hub-demo/demoNavContext.tsx @@ -0,0 +1,15 @@ +import { createContext } from 'react' +import { RouteObject } from 'react-router-dom' + +import { useHubDemo } from './use-hub-demo' + +export const HubDemoContext = createContext<{ + routes: RouteObject[] | null + navigationItems: HubDemo.NavItem[] | null +}>({ routes: null, navigationItems: null }) + +export const HubDemoProvider = ({ basePath, children }) => { + const { routes, navigationItems } = useHubDemo(basePath) + + return {children} +} diff --git a/webui/src/pages/hub-demo/hub-demo.d.ts b/webui/src/pages/hub-demo/hub-demo.d.ts new file mode 100644 index 000000000..31c6cfde7 --- /dev/null +++ b/webui/src/pages/hub-demo/hub-demo.d.ts @@ -0,0 +1,21 @@ +namespace HubDemo { + interface Route { + path: string + label: string + icon: string + contentPath: string + dynamicSegments?: string[] + activeMatches?: string[] + } + + interface Manifest { + routes: Route[] + } + + interface NavItem { + path: string + label: string + icon: ReactNode + activeMatches?: string[] + } +} diff --git a/webui/src/pages/hub-demo/icons/api.tsx b/webui/src/pages/hub-demo/icons/api.tsx new file mode 100644 index 000000000..04b51da6f --- /dev/null +++ b/webui/src/pages/hub-demo/icons/api.tsx @@ -0,0 +1,68 @@ +import { Flex } from '@traefiklabs/faency' +import { useId } from 'react' + +import { CustomIconProps } from 'components/icons' + +const ApiIcon = ({ color = 'currentColor', css = {}, ...props }: CustomIconProps) => { + const linearGradient1Id = useId() + const linearGradient2Id = useId() + const linearGradient3Id = useId() + const titleId = useId() + + return ( + + + apis + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export default ApiIcon diff --git a/webui/src/pages/hub-demo/icons/dashboard.tsx b/webui/src/pages/hub-demo/icons/dashboard.tsx new file mode 100644 index 000000000..44ab47aa6 --- /dev/null +++ b/webui/src/pages/hub-demo/icons/dashboard.tsx @@ -0,0 +1,28 @@ +import { Flex } from '@traefiklabs/faency' + +import { CustomIconProps } from 'components/icons' + +const DashboardIcon = ({ color = 'currentColor', css = {}, ...props }: CustomIconProps) => { + return ( + + + dashboard + + + + + + + + ) +} + +export default DashboardIcon diff --git a/webui/src/pages/hub-demo/icons/gateway.tsx b/webui/src/pages/hub-demo/icons/gateway.tsx new file mode 100644 index 000000000..d1f682bf4 --- /dev/null +++ b/webui/src/pages/hub-demo/icons/gateway.tsx @@ -0,0 +1,69 @@ +import { Flex } from '@traefiklabs/faency' +import { useId } from 'react' + +import { CustomIconProps } from 'components/icons' + +const GatewayIcon = ({ color = 'currentColor', css = {}, ...props }: CustomIconProps) => { + const titleId = useId() + + return ( + + + gateways_icon + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export default GatewayIcon diff --git a/webui/src/pages/hub-demo/icons/hub.tsx b/webui/src/pages/hub-demo/icons/hub.tsx new file mode 100644 index 000000000..64050576f --- /dev/null +++ b/webui/src/pages/hub-demo/icons/hub.tsx @@ -0,0 +1,18 @@ +import { CustomIconProps } from 'components/icons' + +const Hub = (props: CustomIconProps) => { + const { color = 'currentColor', ...restProps } = props + + return ( + + + + + + ) +} + +export default Hub diff --git a/webui/src/pages/hub-demo/icons/index.ts b/webui/src/pages/hub-demo/icons/index.ts new file mode 100644 index 000000000..5118c0fe4 --- /dev/null +++ b/webui/src/pages/hub-demo/icons/index.ts @@ -0,0 +1,5 @@ +export { default as ApiIcon } from './api' +export { default as DashboardIcon } from './dashboard' +export { default as GatewayIcon } from './gateway' +export { default as HubIcon } from './hub' +export { default as PortalIcon } from './portal' diff --git a/webui/src/pages/hub-demo/icons/portal.tsx b/webui/src/pages/hub-demo/icons/portal.tsx new file mode 100644 index 000000000..a21413ce8 --- /dev/null +++ b/webui/src/pages/hub-demo/icons/portal.tsx @@ -0,0 +1,48 @@ +import { Flex } from '@traefiklabs/faency' +import { useId } from 'react' + +import { CustomIconProps } from 'components/icons' + +const PortalIcon = ({ color = 'currentColor', css = {}, ...props }: CustomIconProps) => { + const linearGradientId = useId() + const titleId = useId() + + return ( + + + portals + + + + + + + + + + + + + + ) +} + +export default PortalIcon diff --git a/webui/src/pages/hub-demo/use-hub-demo.spec.tsx b/webui/src/pages/hub-demo/use-hub-demo.spec.tsx new file mode 100644 index 000000000..dba13cc97 --- /dev/null +++ b/webui/src/pages/hub-demo/use-hub-demo.spec.tsx @@ -0,0 +1,301 @@ +import { renderHook, waitFor } from '@testing-library/react' +import { ReactNode } from 'react' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + +import { useHubDemo } from './use-hub-demo' +import verifySignature from './workers/scriptVerification' + +vi.mock('./workers/scriptVerification', () => ({ + default: vi.fn(), +})) + +const MOCK_ROUTES_MANIFEST = { + routes: [ + { + path: '/dashboard', + label: 'Dashboard', + icon: 'dashboard', + contentPath: 'dashboard', + }, + { + path: '/gateway', + label: 'Gateway', + icon: 'gateway', + contentPath: 'gateway', + dynamicSegments: [':id'], + activeMatches: ['/gateway/:id'], + }, + ], +} + +describe('useHubDemo', () => { + const mockVerifySignature = vi.mocked(verifySignature) + + beforeEach(() => { + vi.clearAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + const setupMockVerification = (manifest: HubDemo.Manifest) => { + const encoder = new TextEncoder() + const mockScriptContent = encoder.encode(JSON.stringify(manifest)) + + mockVerifySignature.mockResolvedValue({ + verified: true, + scriptContent: mockScriptContent.buffer, + }) + } + + describe('basic functions', () => { + const mockVerifySignature = vi.mocked(verifySignature) + + beforeEach(() => { + vi.clearAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('should return null when signature verification fails', async () => { + mockVerifySignature.mockResolvedValue({ + verified: false, + }) + + const { result } = renderHook(() => useHubDemo('/hub')) + + await waitFor(() => { + expect(mockVerifySignature).toHaveBeenCalled() + }) + + await new Promise((resolve) => setTimeout(resolve, 10)) + + expect(result.current.routes).toBeNull() + expect(result.current.navigationItems).toBeNull() + }) + + it('should return null when scriptContent is missing', async () => { + mockVerifySignature.mockResolvedValue({ + verified: true, + scriptContent: undefined, + }) + + const { result } = renderHook(() => useHubDemo('/hub')) + + await waitFor(() => { + expect(mockVerifySignature).toHaveBeenCalled() + }) + + await new Promise((resolve) => setTimeout(resolve, 10)) + + expect(result.current.routes).toBeNull() + expect(result.current.navigationItems).toBeNull() + }) + + it('should handle errors during manifest fetch', async () => { + const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + + mockVerifySignature.mockRejectedValue(new Error('Network error')) + + const { result } = renderHook(() => useHubDemo('/hub')) + + await waitFor(() => { + expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to load hub demo manifest:', expect.any(Error)) + }) + + expect(result.current.routes).toBeNull() + expect(result.current.navigationItems).toBeNull() + + consoleErrorSpy.mockRestore() + }) + + it('should handle invalid JSON in manifest', async () => { + const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + + const encoder = new TextEncoder() + const invalidJson = encoder.encode('{ invalid json }') + + mockVerifySignature.mockResolvedValue({ + verified: true, + scriptContent: invalidJson.buffer, + }) + + const { result } = renderHook(() => useHubDemo('/hub')) + + await waitFor(() => { + expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to load hub demo manifest:', expect.any(Error)) + }) + + expect(result.current.routes).toBeNull() + expect(result.current.navigationItems).toBeNull() + + consoleErrorSpy.mockRestore() + }) + }) + + describe('routes generation', () => { + it('should generate routes with correct base path', async () => { + setupMockVerification(MOCK_ROUTES_MANIFEST) + + const { result } = renderHook(() => useHubDemo('/hub')) + + await waitFor(() => { + expect(result.current.routes).not.toBeNull() + }) + + expect(result.current.routes).toHaveLength(3) + expect(result.current.routes![0].path).toBe('/hub/dashboard') + expect(result.current.routes![1].path).toBe('/hub/gateway') + expect(result.current.routes![2].path).toBe('/hub/gateway/:id') + }) + + it('should generate routes for dynamic segments', async () => { + setupMockVerification(MOCK_ROUTES_MANIFEST) + + const { result } = renderHook(() => useHubDemo('/hub')) + + await waitFor(() => { + expect(result.current.routes).not.toBeNull() + }) + + expect(result.current.routes).toHaveLength(3) + expect(result.current.routes![0].path).toBe('/hub/dashboard') + expect(result.current.routes![1].path).toBe('/hub/gateway') + expect(result.current.routes![2].path).toBe('/hub/gateway/:id') + }) + + it('should render HubDashboard with correct contentPath for dynamic segments', async () => { + setupMockVerification(MOCK_ROUTES_MANIFEST) + + const { result } = renderHook(() => useHubDemo('/hub')) + + await waitFor(() => { + expect(result.current.routes).not.toBeNull() + }) + + const baseRoute = result.current.routes![1] + const dynamicRoute = result.current.routes![2] + + expect(baseRoute.element).toBeDefined() + expect(dynamicRoute.element).toBeDefined() + + const baseElement = baseRoute.element as ReactNode & { props?: { path: string } } + const dynamicElement = dynamicRoute.element as ReactNode & { props?: { path: string } } + + expect((baseElement as { props: { path: string } }).props.path).toBe('gateway') + expect((dynamicElement as { props: { path: string } }).props.path).toBe('gateway:id') + }) + + it('should update routes when basePath changes', async () => { + setupMockVerification(MOCK_ROUTES_MANIFEST) + + const { result, rerender } = renderHook(({ basePath }) => useHubDemo(basePath), { + initialProps: { basePath: '/hub' }, + }) + + await waitFor(() => { + expect(result.current.routes).not.toBeNull() + }) + + expect(result.current.routes![0].path).toBe('/hub/dashboard') + + rerender({ basePath: '/demo' }) + + expect(result.current.routes![0].path).toBe('/demo/dashboard') + }) + }) + + describe('navigation items generation', () => { + it('should generate navigation items with correct icons', async () => { + setupMockVerification(MOCK_ROUTES_MANIFEST) + + const { result } = renderHook(() => useHubDemo('/hub')) + + await waitFor(() => { + expect(result.current.navigationItems).not.toBeNull() + }) + + expect(result.current.navigationItems).toHaveLength(2) + expect(result.current.navigationItems![0].label).toBe('Dashboard') + expect(result.current.navigationItems![0].path).toBe('/hub/dashboard') + expect(result.current.navigationItems![0].icon).toBeDefined() + expect(result.current.navigationItems![1].label).toBe('Gateway') + }) + + it('should include activeMatches in navigation items', async () => { + setupMockVerification(MOCK_ROUTES_MANIFEST) + + const { result } = renderHook(() => useHubDemo('/hub')) + + await waitFor(() => { + expect(result.current.navigationItems).not.toBeNull() + }) + + expect(result.current.navigationItems![1].activeMatches).toEqual(['/hub/gateway/:id']) + }) + + it('should update navigation items when basePath changes', async () => { + setupMockVerification(MOCK_ROUTES_MANIFEST) + + const { result, rerender } = renderHook(({ basePath }) => useHubDemo(basePath), { + initialProps: { basePath: '/hub' }, + }) + + await waitFor(() => { + expect(result.current.navigationItems).not.toBeNull() + }) + + expect(result.current.navigationItems![0].path).toBe('/hub/dashboard') + + rerender({ basePath: '/demo' }) + + expect(result.current.navigationItems![0].path).toBe('/demo/dashboard') + }) + + it('should handle unknown icon types gracefully', async () => { + const manifestWithUnknownIcon: HubDemo.Manifest = { + routes: [ + { + path: '/unknown', + label: 'Unknown', + icon: 'unknown-icon-type', + contentPath: 'unknown', + }, + ], + } + + setupMockVerification(manifestWithUnknownIcon) + + const { result } = renderHook(() => useHubDemo('/hub')) + + await waitFor(() => { + expect(result.current.navigationItems).not.toBeNull() + }) + + expect(result.current.navigationItems![0].icon).toBeUndefined() + }) + }) + + describe('memoization', () => { + it('should not regenerate routes when manifest and basePath are unchanged', async () => { + setupMockVerification(MOCK_ROUTES_MANIFEST) + + const { result, rerender } = renderHook(() => useHubDemo('/hub')) + + await waitFor(() => { + expect(result.current.routes).not.toBeNull() + }) + + const firstRoutes = result.current.routes + const firstNavItems = result.current.navigationItems + + rerender() + + expect(result.current.routes).toBe(firstRoutes) + expect(result.current.navigationItems).toBe(firstNavItems) + }) + }) +}) diff --git a/webui/src/pages/hub-demo/use-hub-demo.tsx b/webui/src/pages/hub-demo/use-hub-demo.tsx new file mode 100644 index 000000000..4eee4f455 --- /dev/null +++ b/webui/src/pages/hub-demo/use-hub-demo.tsx @@ -0,0 +1,89 @@ +import { ReactNode, useEffect, useMemo, useState } from 'react' +import { RouteObject } from 'react-router-dom' + +import HubDashboard from 'pages/hub-demo/HubDashboard' +import { ApiIcon, DashboardIcon, GatewayIcon, PortalIcon } from 'pages/hub-demo/icons' +import verifySignature from 'pages/hub-demo/workers/scriptVerification' + +const ROUTES_MANIFEST_URL = 'https://traefik.github.io/hub-ui-demo-app/config/routes.json' + +const HUB_DEMO_NAV_ICONS: Record = { + dashboard: , + gateway: , + api: , + portal: , +} + +const useHubDemoRoutesManifest = (): HubDemo.Manifest | null => { + const [manifest, setManifest] = useState(null) + + useEffect(() => { + const fetchManifest = async () => { + try { + const { verified, scriptContent } = await verifySignature(ROUTES_MANIFEST_URL, `${ROUTES_MANIFEST_URL}.sig`) + + if (!verified || !scriptContent) { + setManifest(null) + return + } + + const textDecoder = new TextDecoder() + const jsonString = textDecoder.decode(scriptContent) + const data: HubDemo.Manifest = JSON.parse(jsonString) + setManifest(data) + } catch (error) { + console.error('Failed to load hub demo manifest:', error) + setManifest(null) + } + } + + fetchManifest() + }, []) + + return manifest +} + +export const useHubDemo = (basePath: string) => { + const manifest = useHubDemoRoutesManifest() + + const routes = useMemo(() => { + if (!manifest) { + return null + } + + const routeObjects: RouteObject[] = [] + + manifest.routes.forEach((route: HubDemo.Route) => { + routeObjects.push({ + path: `${basePath}${route.path}`, + element: , + }) + + if (route.dynamicSegments) { + route.dynamicSegments.forEach((segment) => { + routeObjects.push({ + path: `${basePath}${route.path}/${segment}`, + element: , + }) + }) + } + }) + + return routeObjects + }, [basePath, manifest]) + + const navigationItems = useMemo(() => { + if (!manifest) { + return null + } + + return manifest.routes.map((route) => ({ + path: `${basePath}${route.path}`, + label: route.label, + icon: HUB_DEMO_NAV_ICONS[route.icon], + activeMatches: route.activeMatches?.map((r) => `${basePath}${r}`), + })) + }, [basePath, manifest]) + + return { routes, navigationItems } +} diff --git a/webui/src/pages/hub-demo/workers/scriptVerification.integration.spec.ts b/webui/src/pages/hub-demo/workers/scriptVerification.integration.spec.ts new file mode 100644 index 000000000..189632c64 --- /dev/null +++ b/webui/src/pages/hub-demo/workers/scriptVerification.integration.spec.ts @@ -0,0 +1,156 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +import verifySignature from './scriptVerification' + +describe('Script Signature Verification - Integration Tests', () => { + let fetchMock: ReturnType + + const SCRIPT_URL = 'https://example.com/script.js' + const SIGNATURE_URL = 'https://example.com/script.js.sig' + const TEST_PUBLIC_KEY = 'MCowBQYDK2VwAyEAWH71OHphISjNK3mizCR/BawiDxc6IXT1vFHpBcxSIA0=' + const VALID_SCRIPT = "console.log('Hello from verified script!');" + const VALID_SIGNATURE_HEX = + '04c90fcd35caaf3cf4582a2767345f8cd9f6519e1ce79ebaeedbe0d5f671d762d1aa8ec258831557e2de0e47f224883f84eb5a0f22ec18eb7b8c48de3096d000' + const CORRUPTED_SCRIPT = "console.log('Malicious code injected!');" + + beforeEach(() => { + vi.clearAllMocks() + fetchMock = vi.fn() + globalThis.fetch = fetchMock + }) + + it('should verify a valid script with correct signature through real worker', async () => { + fetchMock.mockImplementation((url: string) => { + if (url === SCRIPT_URL) { + return Promise.resolve( + new Response(VALID_SCRIPT, { + status: 200, + headers: { 'Content-Type': 'application/javascript' }, + }), + ) + } + if (url === SIGNATURE_URL) { + return Promise.resolve( + new Response(VALID_SIGNATURE_HEX, { + status: 200, + headers: { 'Content-Type': 'text/plain' }, + }), + ) + } + return Promise.reject(new Error('Unexpected URL')) + }) + + const result = await verifySignature(SCRIPT_URL, SIGNATURE_URL, TEST_PUBLIC_KEY) + + expect(fetchMock).toHaveBeenCalledWith(SCRIPT_URL) + expect(fetchMock).toHaveBeenCalledWith(SIGNATURE_URL) + expect(result.verified).toBe(true) + expect(result.scriptContent).toBeDefined() + }, 15000) + + it('should reject a corrupted script with mismatched signature', async () => { + fetchMock.mockImplementation((url: string) => { + if (url === SCRIPT_URL) { + return Promise.resolve( + new Response(CORRUPTED_SCRIPT, { + status: 200, + headers: { 'Content-Type': 'application/javascript' }, + }), + ) + } + if (url === SIGNATURE_URL) { + return Promise.resolve( + new Response(VALID_SIGNATURE_HEX, { + status: 200, + headers: { 'Content-Type': 'text/plain' }, + }), + ) + } + return Promise.reject(new Error('Unexpected URL')) + }) + + const result = await verifySignature(SCRIPT_URL, SIGNATURE_URL, TEST_PUBLIC_KEY) + + expect(fetchMock).toHaveBeenCalledWith(SCRIPT_URL) + expect(fetchMock).toHaveBeenCalledWith(SIGNATURE_URL) + expect(result.verified).toBe(false) + expect(result.scriptContent).toBeUndefined() + }, 15000) + + it('should reject script with invalid signature format', async () => { + fetchMock.mockImplementation((url: string) => { + if (url === SCRIPT_URL) { + return Promise.resolve( + new Response(VALID_SCRIPT, { + status: 200, + headers: { 'Content-Type': 'application/javascript' }, + }), + ) + } + if (url === SIGNATURE_URL) { + return Promise.resolve( + new Response('not-a-valid-signature', { + status: 200, + headers: { 'Content-Type': 'text/plain' }, + }), + ) + } + return Promise.reject(new Error('Unexpected URL')) + }) + + const result = await verifySignature(SCRIPT_URL, SIGNATURE_URL, TEST_PUBLIC_KEY) + + expect(result.verified).toBe(false) + expect(result.scriptContent).toBeUndefined() + }, 15000) + + it('should reject script with wrong public key', async () => { + const WRONG_PUBLIC_KEY = 'MCowBQYDK2VwAyEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==' + + fetchMock.mockImplementation((url: string) => { + if (url === SCRIPT_URL) { + return Promise.resolve( + new Response(VALID_SCRIPT, { + status: 200, + headers: { 'Content-Type': 'application/javascript' }, + }), + ) + } + if (url === SIGNATURE_URL) { + return Promise.resolve( + new Response(VALID_SIGNATURE_HEX, { + status: 200, + headers: { 'Content-Type': 'text/plain' }, + }), + ) + } + return Promise.reject(new Error('Unexpected URL')) + }) + + const result = await verifySignature(SCRIPT_URL, SIGNATURE_URL, WRONG_PUBLIC_KEY) + + expect(result.verified).toBe(false) + expect(result.scriptContent).toBeUndefined() + }, 15000) + + it('should handle network failures when fetching script', async () => { + fetchMock.mockImplementation(() => + Promise.resolve( + new Response(null, { + status: 404, + statusText: 'Not Found', + }), + ), + ) + + const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + + const result = await verifySignature(SCRIPT_URL, SIGNATURE_URL, TEST_PUBLIC_KEY) + + expect(result.verified).toBe(false) + expect(result.scriptContent).toBeUndefined() + expect(consoleErrorSpy).toHaveBeenCalled() + + consoleErrorSpy.mockRestore() + }, 15000) +}) diff --git a/webui/src/pages/hub-demo/workers/scriptVerification.spec.ts b/webui/src/pages/hub-demo/workers/scriptVerification.spec.ts new file mode 100644 index 000000000..1f438aa71 --- /dev/null +++ b/webui/src/pages/hub-demo/workers/scriptVerification.spec.ts @@ -0,0 +1,125 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + +import verifySignature from './scriptVerification' + +class MockWorker { + onmessage: ((event: MessageEvent) => void) | null = null + onerror: ((error: ErrorEvent) => void) | null = null + postMessage = vi.fn() + terminate = vi.fn() + + simulateMessage(data: unknown) { + if (this.onmessage) { + this.onmessage(new MessageEvent('message', { data })) + } + } + + simulateError(error: Error) { + if (this.onerror) { + this.onerror(new ErrorEvent('error', { error, message: error.message })) + } + } +} + +describe('verifySignature', () => { + let mockWorkerInstance: MockWorker + let originalWorker: typeof Worker + + beforeEach(() => { + vi.clearAllMocks() + + originalWorker = globalThis.Worker + + mockWorkerInstance = new MockWorker() + + globalThis.Worker = class extends EventTarget { + constructor() { + super() + return mockWorkerInstance as any + } + } as any + }) + + afterEach(() => { + globalThis.Worker = originalWorker + vi.restoreAllMocks() + }) + + it('should return true when verification succeeds', async () => { + const scriptPath = 'https://example.com/script.js' + const signaturePath = 'https://example.com/script.js.sig' + + const promise = verifySignature(scriptPath, signaturePath) + + await new Promise((resolve) => setTimeout(resolve, 0)) + + expect(mockWorkerInstance.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ + scriptUrl: scriptPath, + signatureUrl: signaturePath, + requestId: expect.any(String), + }), + ) + + const mockScriptContent = new ArrayBuffer(100) + mockWorkerInstance.simulateMessage({ + success: true, + verified: true, + error: null, + scriptContent: mockScriptContent, + }) + + const result = await promise + + expect(result).toEqual({ verified: true, scriptContent: mockScriptContent }) + expect(mockWorkerInstance.terminate).toHaveBeenCalled() + }) + + it('should return false when verification fails', async () => { + const scriptPath = 'https://example.com/script.js' + const signaturePath = 'https://example.com/script.js.sig' + + const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + + const promise = verifySignature(scriptPath, signaturePath) + + await new Promise((resolve) => setTimeout(resolve, 0)) + + mockWorkerInstance.simulateMessage({ + success: false, + verified: false, + error: 'Signature verification failed', + }) + + const result = await promise + + expect(result).toEqual({ verified: false }) + expect(mockWorkerInstance.terminate).toHaveBeenCalled() + expect(consoleErrorSpy).toHaveBeenCalledWith('Worker verification failed:', 'Signature verification failed') + + consoleErrorSpy.mockRestore() + }) + + it('should return false when worker throws an error', async () => { + const scriptPath = 'https://example.com/script.js' + const signaturePath = 'https://example.com/script.js.sig' + + const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + + const promise = verifySignature(scriptPath, signaturePath) + + await new Promise((resolve) => setTimeout(resolve, 0)) + + // Simulate worker onerror event + const error = new Error('Worker crashed') + mockWorkerInstance.simulateError(error) + + const result = await promise + + expect(result).toEqual({ verified: false }) + expect(mockWorkerInstance.terminate).toHaveBeenCalled() + expect(consoleErrorSpy).toHaveBeenCalledWith('Worker error:', expect.any(ErrorEvent)) + + consoleErrorSpy.mockRestore() + }) +}) diff --git a/webui/src/pages/hub-demo/workers/scriptVerification.ts b/webui/src/pages/hub-demo/workers/scriptVerification.ts new file mode 100644 index 000000000..e03a438da --- /dev/null +++ b/webui/src/pages/hub-demo/workers/scriptVerification.ts @@ -0,0 +1,57 @@ +export interface VerificationResult { + verified: boolean + scriptContent?: ArrayBuffer +} + +const PUBLIC_KEY = 'MCowBQYDK2VwAyEAWMBZ0pMBaL/s8gNXxpAPCIQ8bxjnuz6bQFwGYvjXDfg=' + +async function verifySignature( + contentPath: string, + signaturePath: string, + publicKey: string = PUBLIC_KEY, +): Promise { + return new Promise((resolve) => { + const requestId = Math.random().toString(36).substring(2) + const worker = new Worker(new URL('./scriptVerificationWorker.ts', import.meta.url), { type: 'module' }) + + const timeout = setTimeout(() => { + worker.terminate() + console.error('Script verification timeout') + resolve({ verified: false }) + }, 30000) + + worker.onmessage = (event) => { + clearTimeout(timeout) + worker.terminate() + + const { success, verified, error, scriptContent } = event.data + + if (!success) { + console.error('Worker verification failed:', error) + resolve({ verified: false }) + return + } + + resolve({ + verified: verified === true, + scriptContent: verified ? scriptContent : undefined, + }) + } + + worker.onerror = (error) => { + clearTimeout(timeout) + worker.terminate() + console.error('Worker error:', error) + resolve({ verified: false }) + } + + worker.postMessage({ + requestId, + scriptUrl: contentPath, + signatureUrl: signaturePath, + publicKey, + }) + }) +} + +export default verifySignature diff --git a/webui/src/pages/hub-demo/workers/scriptVerificationWorker.ts b/webui/src/pages/hub-demo/workers/scriptVerificationWorker.ts new file mode 100644 index 000000000..902dce094 --- /dev/null +++ b/webui/src/pages/hub-demo/workers/scriptVerificationWorker.ts @@ -0,0 +1,189 @@ +// Script verification worker +// Runs in isolated context for secure verification + +import { verify } from '@noble/ed25519' +import * as ed25519 from '@noble/ed25519' +import { sha512 } from '@noble/hashes/sha2.js' + +// Set up SHA-512 for @noble/ed25519 v3.x +ed25519.hashes.sha512 = sha512 +ed25519.hashes.sha512Async = (m) => Promise.resolve(sha512(m)) + +function base64ToArrayBuffer(base64: string): ArrayBuffer { + try { + // @ts-expect-error - fromBase64 is not yet in all TypeScript lib definitions + const bytes = Uint8Array.fromBase64(base64) + return bytes.buffer + } catch { + // Fallback for browsers without Uint8Array.fromBase64() + const binaryString = atob(base64) + const bytes = new Uint8Array(binaryString.length) + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i) + } + return bytes.buffer + } +} + +function extractEd25519PublicKey(spkiBytes: Uint8Array): Uint8Array { + if (spkiBytes.length !== 44) { + throw new Error('Invalid SPKI length for Ed25519') + } + return spkiBytes.slice(-32) +} + +async function importPublicKeyWebCrypto(publicKey: string): Promise { + const publicKeyBuffer = base64ToArrayBuffer(publicKey) + + return await crypto.subtle.importKey( + 'spki', + publicKeyBuffer, + { + name: 'Ed25519', + }, + false, + ['verify'], + ) +} + +async function verifyWithWebCrypto( + publicKey: string, + scriptBuffer: ArrayBuffer, + signatureBuffer: ArrayBuffer, +): Promise { + try { + const cryptoPublicKey = await importPublicKeyWebCrypto(publicKey) + + return await crypto.subtle.verify('Ed25519', cryptoPublicKey, signatureBuffer, scriptBuffer) + } catch (error) { + console.log('Web Crypto verification failed:', error instanceof Error ? error.message : 'Unknown error') + return false + } +} + +function parseSignature(signatureBuffer: ArrayBuffer): Uint8Array { + const signatureBytes = new Uint8Array(signatureBuffer) + + // If already 64 bytes, assume it's raw binary + if (signatureBytes.length === 64) { + return signatureBytes + } + + // Try to parse as text (base64 or hex) + const signatureText = new TextDecoder().decode(signatureBytes).trim() + + // base64 decoding + try { + const base64Decoded = new Uint8Array(base64ToArrayBuffer(signatureText)) + if (base64Decoded.length === 64) { + return base64Decoded + } + } catch (e) { + console.error(e) + } + + // hex decoding + if (signatureText.length === 128 && /^[0-9a-fA-F]+$/.test(signatureText)) { + try { + // @ts-expect-error - fromHex is not yet in all TypeScript lib definitions + return Uint8Array.fromHex(signatureText) + } catch { + // Fallback for browsers without Uint8Array.fromHex() + const hexDecoded = new Uint8Array(64) + for (let i = 0; i < 64; i++) { + hexDecoded[i] = parseInt(signatureText.slice(i * 2, i * 2 + 2), 16) + } + return hexDecoded + } + } + + throw new Error(`Unable to parse signature format.`) +} + +async function verifyWithNoble( + publicKey: string, + scriptBuffer: ArrayBuffer, + signatureBuffer: ArrayBuffer, +): Promise { + try { + const publicKeySpki = new Uint8Array(base64ToArrayBuffer(publicKey)) + const publicKeyRaw = extractEd25519PublicKey(publicKeySpki) + + const scriptBytes = new Uint8Array(scriptBuffer) + const signatureBytes = parseSignature(signatureBuffer) + + return verify(signatureBytes, scriptBytes, publicKeyRaw) + } catch (error) { + console.log('Noble verification failed:', error instanceof Error ? error.message : 'Unknown error') + return false + } +} + +self.onmessage = async function (event) { + const { requestId, scriptUrl, signatureUrl, publicKey } = event.data + + try { + const [scriptResponse, signatureResponse] = await Promise.all([fetch(scriptUrl), fetch(signatureUrl)]) + + if (!scriptResponse.ok || !signatureResponse.ok) { + self.postMessage({ + requestId, + success: false, + verified: false, + error: `Failed to fetch files. Script: ${scriptResponse.status} ${scriptResponse.statusText}, Signature: ${signatureResponse.status} ${signatureResponse.statusText}`, + }) + return + } + + const [scriptBuffer, signatureBuffer] = await Promise.all([ + scriptResponse.arrayBuffer(), + signatureResponse.arrayBuffer(), + ]) + + // Try Web Crypto API first, fallback to Noble if it fails + let verified = await verifyWithWebCrypto(publicKey, scriptBuffer, signatureBuffer) + + if (!verified) { + verified = await verifyWithNoble(publicKey, scriptBuffer, signatureBuffer) + } + + // If verified, include script content to avoid re-downloading + let scriptContent: ArrayBuffer | undefined + if (verified) { + scriptContent = scriptBuffer + } + + // Send message with transferable ArrayBuffer for efficiency + const message = { + requestId, + success: true, + verified, + scriptSize: scriptBuffer.byteLength, + signatureSize: signatureBuffer.byteLength, + scriptContent, + } + + if (scriptContent) { + self.postMessage(message, { transfer: [scriptContent] }) + } else { + self.postMessage(message) + } + } catch (error) { + console.error('[Worker] Verification error:', error) + self.postMessage({ + requestId, + success: false, + verified: false, + error: error instanceof Error ? error.message : 'Unknown error', + }) + } +} + +self.onerror = function (error) { + console.error('[Worker] Worker error:', error) + self.postMessage({ + success: false, + verified: false, + error, + }) +} diff --git a/webui/src/pages/tcp/TcpMiddleware.spec.tsx b/webui/src/pages/tcp/TcpMiddleware.spec.tsx index b783e0958..73e69e01c 100644 --- a/webui/src/pages/tcp/TcpMiddleware.spec.tsx +++ b/webui/src/pages/tcp/TcpMiddleware.spec.tsx @@ -7,6 +7,7 @@ describe('', () => { it('should render the error message', () => { const { getByTestId } = renderWithProviders( , + { route: '/tcp/middlewares/mock-middleware', withPage: true }, ) expect(getByTestId('error-text')).toBeInTheDocument() }) @@ -14,6 +15,7 @@ describe('', () => { it('should render the skeleton', () => { const { getByTestId } = renderWithProviders( , + { route: '/tcp/middlewares/mock-middleware', withPage: true }, ) expect(getByTestId('skeleton')).toBeInTheDocument() }) @@ -21,6 +23,7 @@ describe('', () => { it('should render the not found page', () => { const { getByTestId } = renderWithProviders( , + { route: '/tcp/middlewares/mock-middleware', withPage: true }, ) expect(getByTestId('Not found page')).toBeInTheDocument() }) @@ -53,6 +56,7 @@ describe('', () => { const { container, getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/tcp/middlewares/middleware-simple', withPage: true }, ) const headings = Array.from(container.getElementsByTagName('h1')) @@ -103,6 +107,7 @@ describe('', () => { const { container, getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/tcp/middlewares/middleware-complex', withPage: true }, ) const headings = Array.from(container.getElementsByTagName('h1')) diff --git a/webui/src/pages/tcp/TcpMiddleware.tsx b/webui/src/pages/tcp/TcpMiddleware.tsx index f3637a46d..d85cf22ec 100644 --- a/webui/src/pages/tcp/TcpMiddleware.tsx +++ b/webui/src/pages/tcp/TcpMiddleware.tsx @@ -1,11 +1,11 @@ import { Card, Box, H1, Skeleton, styled, Text } from '@traefiklabs/faency' +import { Helmet } from 'react-helmet-async' import { useParams } from 'react-router-dom' import { DetailSectionSkeleton } from 'components/resources/DetailSections' import { RenderMiddleware } from 'components/resources/MiddlewarePanel' import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection' import { ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail' -import Page from 'layout/Page' import { NotFound } from 'pages/NotFound' import breakpoints from 'utils/breakpoints' @@ -27,23 +27,29 @@ type TcpMiddlewareRenderProps = { export const TcpMiddlewareRender = ({ data, error, name }: TcpMiddlewareRenderProps) => { if (error) { return ( - + <> + + {name} - Traefik Proxy + Sorry, we could not fetch detail information for this Middleware right now. Please, try again later. - + ) } if (!data) { return ( - + <> + + {name} - Traefik Proxy + - + ) } @@ -52,7 +58,10 @@ export const TcpMiddlewareRender = ({ data, error, name }: TcpMiddlewareRenderPr } return ( - + <> + + {data.name} - Traefik Proxy +

{data.name}

@@ -60,7 +69,7 @@ export const TcpMiddlewareRender = ({ data, error, name }: TcpMiddlewareRenderPr -
+ ) } diff --git a/webui/src/pages/tcp/TcpMiddlewares.spec.tsx b/webui/src/pages/tcp/TcpMiddlewares.spec.tsx index 3da9cfc5b..12e95f92f 100644 --- a/webui/src/pages/tcp/TcpMiddlewares.spec.tsx +++ b/webui/src/pages/tcp/TcpMiddlewares.spec.tsx @@ -29,10 +29,13 @@ describe('', () => { .spyOn(useFetchWithPagination, 'default') .mockImplementation(() => useFetchWithPaginationMock({ pages })) - const { container, getByTestId } = renderWithProviders() + const { container, getByTestId } = renderWithProviders(, { + route: '/tcp/middlewares', + withPage: true, + }) expect(mock).toHaveBeenCalled() - expect(getByTestId('TCP Middlewares page')).toBeInTheDocument() + expect(getByTestId('/tcp/middlewares page')).toBeInTheDocument() const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1] expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(2) @@ -58,6 +61,7 @@ describe('', () => { pageCount={1} pages={[]} />, + { route: '/tcp/middlewares', withPage: true }, ) expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]') const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2] diff --git a/webui/src/pages/tcp/TcpMiddlewares.tsx b/webui/src/pages/tcp/TcpMiddlewares.tsx index c736d4c47..b0189a2e5 100644 --- a/webui/src/pages/tcp/TcpMiddlewares.tsx +++ b/webui/src/pages/tcp/TcpMiddlewares.tsx @@ -1,5 +1,6 @@ import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency' import { useMemo } from 'react' +import { Helmet } from 'react-helmet-async' import useInfiniteScroll from 'react-infinite-scroll-hook' import { useSearchParams } from 'react-router-dom' @@ -14,7 +15,6 @@ import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' -import Page from 'layout/Page' import { parseMiddlewareType } from 'libs/parsers' export const makeRowRender = (): RenderRowType => { @@ -109,7 +109,10 @@ export const TcpMiddlewares = () => { ) return ( - + <> + + TCP Middlewares - Traefik Proxy + { pageCount={pageCount} pages={pages} /> - + ) } diff --git a/webui/src/pages/tcp/TcpRouter.spec.tsx b/webui/src/pages/tcp/TcpRouter.spec.tsx index bd5fe7059..c1f5bb6a4 100644 --- a/webui/src/pages/tcp/TcpRouter.spec.tsx +++ b/webui/src/pages/tcp/TcpRouter.spec.tsx @@ -7,6 +7,7 @@ describe('', () => { it('should render the error message', () => { const { getByTestId } = renderWithProviders( , + { route: '/tcp/routers/mock-router', withPage: true }, ) expect(getByTestId('error-text')).toBeInTheDocument() }) @@ -14,6 +15,7 @@ describe('', () => { it('should render the skeleton', () => { const { getByTestId } = renderWithProviders( , + { route: '/tcp/routers/mock-router', withPage: true }, ) expect(getByTestId('skeleton')).toBeInTheDocument() }) @@ -21,6 +23,7 @@ describe('', () => { it('should render the not found page', () => { const { getByTestId } = renderWithProviders( , + { route: '/tcp/routers/mock-router', withPage: true }, ) expect(getByTestId('Not found page')).toBeInTheDocument() }) @@ -66,6 +69,7 @@ describe('', () => { const { getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/tcp/routers/tcp-all@docker', withPage: true }, ) const routerStructure = getByTestId('router-structure') diff --git a/webui/src/pages/tcp/TcpRouter.tsx b/webui/src/pages/tcp/TcpRouter.tsx index 1bdac707c..92f9b47b2 100644 --- a/webui/src/pages/tcp/TcpRouter.tsx +++ b/webui/src/pages/tcp/TcpRouter.tsx @@ -1,4 +1,5 @@ import { Flex, styled, Text } from '@traefiklabs/faency' +import { Helmet } from 'react-helmet-async' import { useParams } from 'react-router-dom' import { CardListSection, DetailSectionSkeleton } from 'components/resources/DetailSections' @@ -6,7 +7,6 @@ import MiddlewarePanel from 'components/resources/MiddlewarePanel' import RouterPanel from 'components/resources/RouterPanel' import TlsPanel from 'components/resources/TlsPanel' import { ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail' -import Page from 'layout/Page' import { RouterStructure } from 'pages/http/HttpRouter' import { NotFound } from 'pages/NotFound' @@ -37,17 +37,23 @@ type TcpRouterRenderProps = { export const TcpRouterRender = ({ data, error, name }: TcpRouterRenderProps) => { if (error) { return ( - + <> + + {name} - Traefik Proxy + Sorry, we could not fetch detail information for this Router right now. Please, try again later. - + ) } if (!data) { return ( - + <> + + {name} - Traefik Proxy + @@ -57,7 +63,7 @@ export const TcpRouterRender = ({ data, error, name }: TcpRouterRenderProps) => - + ) } @@ -66,10 +72,13 @@ export const TcpRouterRender = ({ data, error, name }: TcpRouterRenderProps) => } return ( - + <> + + {data.name} - Traefik Proxy + - + ) } diff --git a/webui/src/pages/tcp/TcpRouters.spec.tsx b/webui/src/pages/tcp/TcpRouters.spec.tsx index 60acab68e..fac801fb6 100644 --- a/webui/src/pages/tcp/TcpRouters.spec.tsx +++ b/webui/src/pages/tcp/TcpRouters.spec.tsx @@ -39,10 +39,13 @@ describe('', () => { .spyOn(useFetchWithPagination, 'default') .mockImplementation(() => useFetchWithPaginationMock({ pages })) - const { container, getByTestId } = renderWithProviders() + const { container, getByTestId } = renderWithProviders(, { + route: '/tcp/routers', + withPage: true, + }) expect(mock).toHaveBeenCalled() - expect(getByTestId('TCP Routers page')).toBeInTheDocument() + expect(getByTestId('/tcp/routers page')).toBeInTheDocument() const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1] expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(3) @@ -76,6 +79,7 @@ describe('', () => { pageCount={1} pages={[]} />, + { route: '/tcp/routers', withPage: true }, ) expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]') const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2] diff --git a/webui/src/pages/tcp/TcpRouters.tsx b/webui/src/pages/tcp/TcpRouters.tsx index de8319e79..f3cd3d497 100644 --- a/webui/src/pages/tcp/TcpRouters.tsx +++ b/webui/src/pages/tcp/TcpRouters.tsx @@ -1,5 +1,6 @@ import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency' import { useMemo } from 'react' +import { Helmet } from 'react-helmet-async' import { FiShield } from 'react-icons/fi' import useInfiniteScroll from 'react-infinite-scroll-hook' import { useSearchParams } from 'react-router-dom' @@ -16,7 +17,6 @@ import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' -import Page from 'layout/Page' export const makeRowRender = (): RenderRowType => { const TcpRoutersRenderRow = (row) => ( @@ -126,7 +126,10 @@ export const TcpRouters = () => { ) return ( - + <> + + TCP Routers - Traefik Proxy + { pageCount={pageCount} pages={pages} /> - + ) } diff --git a/webui/src/pages/tcp/TcpService.spec.tsx b/webui/src/pages/tcp/TcpService.spec.tsx index e6b835832..faa8ac0f5 100644 --- a/webui/src/pages/tcp/TcpService.spec.tsx +++ b/webui/src/pages/tcp/TcpService.spec.tsx @@ -7,6 +7,7 @@ describe('', () => { it('should render the error message', () => { const { getByTestId } = renderWithProviders( , + { route: '/tcp/services/mock-service', withPage: true }, ) expect(getByTestId('error-text')).toBeInTheDocument() }) @@ -14,6 +15,7 @@ describe('', () => { it('should render the skeleton', () => { const { getByTestId } = renderWithProviders( , + { route: '/tcp/services/mock-service', withPage: true }, ) expect(getByTestId('skeleton')).toBeInTheDocument() }) @@ -21,6 +23,7 @@ describe('', () => { it('should render the not found page', () => { const { getByTestId } = renderWithProviders( , + { route: '/tcp/services/mock-service', withPage: true }, ) expect(getByTestId('Not found page')).toBeInTheDocument() }) @@ -69,6 +72,7 @@ describe('', () => { const { container, getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/tcp/services/mock-service', withPage: true }, ) const headings = Array.from(container.getElementsByTagName('h1')) @@ -150,6 +154,7 @@ describe('', () => { const { getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/tcp/services/mock-service', withPage: true }, ) const serversList = getByTestId('tcp-servers-list') @@ -176,6 +181,7 @@ describe('', () => { const { getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/tcp/services/mock-service', withPage: true }, ) expect(() => { diff --git a/webui/src/pages/tcp/TcpService.tsx b/webui/src/pages/tcp/TcpService.tsx index f2f3ef1fe..c36106156 100644 --- a/webui/src/pages/tcp/TcpService.tsx +++ b/webui/src/pages/tcp/TcpService.tsx @@ -1,5 +1,6 @@ import { Box, Flex, H1, Skeleton, styled, Text } from '@traefiklabs/faency' import { useMemo } from 'react' +import { Helmet } from 'react-helmet-async' import { FiGlobe, FiInfo, FiShield } from 'react-icons/fi' import { useParams } from 'react-router-dom' @@ -16,7 +17,6 @@ import { ResourceStatus } from 'components/resources/ResourceStatus' import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection' import Tooltip from 'components/Tooltip' import { ResourceDetailDataType, ServiceDetailType, useResourceDetail } from 'hooks/use-resource-detail' -import Page from 'layout/Page' import { NotFound } from 'pages/NotFound' type TcpDetailProps = { @@ -238,17 +238,23 @@ type TcpServiceRenderProps = { export const TcpServiceRender = ({ data, error, name }: TcpServiceRenderProps) => { if (error) { return ( - + <> + + {name} - Traefik Proxy + Sorry, we could not fetch detail information for this Service right now. Please, try again later. - + ) } if (!data) { return ( - + <> + + {name} - Traefik Proxy + @@ -256,7 +262,7 @@ export const TcpServiceRender = ({ data, error, name }: TcpServiceRenderProps) = - + ) } @@ -265,11 +271,14 @@ export const TcpServiceRender = ({ data, error, name }: TcpServiceRenderProps) = } return ( - + <> + + {data.name} - Traefik Proxy +

{data.name}

-
+ ) } diff --git a/webui/src/pages/tcp/TcpServices.spec.tsx b/webui/src/pages/tcp/TcpServices.spec.tsx index cd838bee4..336a26a54 100644 --- a/webui/src/pages/tcp/TcpServices.spec.tsx +++ b/webui/src/pages/tcp/TcpServices.spec.tsx @@ -36,10 +36,13 @@ describe('', () => { .spyOn(useFetchWithPagination, 'default') .mockImplementation(() => useFetchWithPaginationMock({ pages })) - const { container, getByTestId } = renderWithProviders() + const { container, getByTestId } = renderWithProviders(, { + route: '/tcp/services', + withPage: true, + }) expect(mock).toHaveBeenCalled() - expect(getByTestId('TCP Services page')).toBeInTheDocument() + expect(getByTestId('/tcp/services page')).toBeInTheDocument() const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1] expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(3) @@ -73,6 +76,7 @@ describe('', () => { pageCount={1} pages={[]} />, + { route: '/tcp/services', withPage: true }, ) expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]') const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2] diff --git a/webui/src/pages/tcp/TcpServices.tsx b/webui/src/pages/tcp/TcpServices.tsx index a56027b61..77480fd4e 100644 --- a/webui/src/pages/tcp/TcpServices.tsx +++ b/webui/src/pages/tcp/TcpServices.tsx @@ -1,5 +1,6 @@ import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex, Text } from '@traefiklabs/faency' import { useMemo } from 'react' +import { Helmet } from 'react-helmet-async' import useInfiniteScroll from 'react-infinite-scroll-hook' import { useSearchParams } from 'react-router-dom' @@ -14,7 +15,6 @@ import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' -import Page from 'layout/Page' export const makeRowRender = (): RenderRowType => { const TcpServicesRenderRow = (row) => ( @@ -108,7 +108,10 @@ export const TcpServices = () => { ) return ( - + <> + + TCP Services - Traefik Proxy + { pageCount={pageCount} pages={pages} /> - + ) } diff --git a/webui/src/pages/udp/UdpRouter.spec.tsx b/webui/src/pages/udp/UdpRouter.spec.tsx index 65aa5567d..2404b70a3 100644 --- a/webui/src/pages/udp/UdpRouter.spec.tsx +++ b/webui/src/pages/udp/UdpRouter.spec.tsx @@ -7,6 +7,7 @@ describe('', () => { it('should render the error message', () => { const { getByTestId } = renderWithProviders( , + { route: '/udp/routers/mock-router', withPage: true }, ) expect(getByTestId('error-text')).toBeInTheDocument() }) @@ -14,6 +15,7 @@ describe('', () => { it('should render the skeleton', () => { const { getByTestId } = renderWithProviders( , + { route: '/udp/routers/mock-router', withPage: true }, ) expect(getByTestId('skeleton')).toBeInTheDocument() }) @@ -21,6 +23,7 @@ describe('', () => { it('should render the not found page', () => { const { getByTestId } = renderWithProviders( , + { route: '/udp/routers/mock-router', withPage: true }, ) expect(getByTestId('Not found page')).toBeInTheDocument() }) @@ -51,6 +54,7 @@ describe('', () => { const { getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/udp/routers/udp-all@docker', withPage: true }, ) const routerStructure = getByTestId('router-structure') diff --git a/webui/src/pages/udp/UdpRouter.tsx b/webui/src/pages/udp/UdpRouter.tsx index d1aebf1e8..a41bdb0df 100644 --- a/webui/src/pages/udp/UdpRouter.tsx +++ b/webui/src/pages/udp/UdpRouter.tsx @@ -1,10 +1,10 @@ import { Flex, styled, Text } from '@traefiklabs/faency' +import { Helmet } from 'react-helmet-async' import { useParams } from 'react-router-dom' import { CardListSection, DetailSectionSkeleton } from 'components/resources/DetailSections' import RouterPanel from 'components/resources/RouterPanel' import { ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail' -import Page from 'layout/Page' import { RouterStructure } from 'pages/http/HttpRouter' import { NotFound } from 'pages/NotFound' @@ -33,17 +33,23 @@ type UdpRouterRenderProps = { export const UdpRouterRender = ({ data, error, name }: UdpRouterRenderProps) => { if (error) { return ( - + <> + + {name} - Traefik Proxy + Sorry, we could not fetch detail information for this Router right now. Please, try again later. - + ) } if (!data) { return ( - + <> + + {name} - Traefik Proxy + @@ -53,7 +59,7 @@ export const UdpRouterRender = ({ data, error, name }: UdpRouterRenderProps) => - + ) } @@ -62,10 +68,13 @@ export const UdpRouterRender = ({ data, error, name }: UdpRouterRenderProps) => } return ( - + <> + + {data.name} - Traefik Proxy + - + ) } diff --git a/webui/src/pages/udp/UdpRouters.spec.tsx b/webui/src/pages/udp/UdpRouters.spec.tsx index 8045cf8b7..658b8843b 100644 --- a/webui/src/pages/udp/UdpRouters.spec.tsx +++ b/webui/src/pages/udp/UdpRouters.spec.tsx @@ -39,10 +39,13 @@ describe('', () => { .spyOn(useFetchWithPagination, 'default') .mockImplementation(() => useFetchWithPaginationMock({ pages })) - const { container, getByTestId } = renderWithProviders() + const { container, getByTestId } = renderWithProviders(, { + route: '/udp/routers', + withPage: true, + }) expect(mock).toHaveBeenCalled() - expect(getByTestId('UDP Routers page')).toBeInTheDocument() + expect(getByTestId('/udp/routers page')).toBeInTheDocument() const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1] expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(3) @@ -76,6 +79,7 @@ describe('', () => { pageCount={1} pages={[]} />, + { route: '/udp/routers', withPage: true }, ) expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]') const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2] diff --git a/webui/src/pages/udp/UdpRouters.tsx b/webui/src/pages/udp/UdpRouters.tsx index 56c6d8414..ce6348b75 100644 --- a/webui/src/pages/udp/UdpRouters.tsx +++ b/webui/src/pages/udp/UdpRouters.tsx @@ -1,5 +1,6 @@ import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency' import { useMemo } from 'react' +import { Helmet } from 'react-helmet-async' import useInfiniteScroll from 'react-infinite-scroll-hook' import { useSearchParams } from 'react-router-dom' @@ -15,7 +16,6 @@ import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' -import Page from 'layout/Page' export const makeRowRender = (): RenderRowType => { const UdpRoutersRenderRow = (row) => ( @@ -111,7 +111,10 @@ export const UdpRouters = () => { ) return ( - + <> + + UDP Routers - Traefik Proxy + { pageCount={pageCount} pages={pages} /> - + ) } diff --git a/webui/src/pages/udp/UdpService.spec.tsx b/webui/src/pages/udp/UdpService.spec.tsx index 71114f560..b6150c8ee 100644 --- a/webui/src/pages/udp/UdpService.spec.tsx +++ b/webui/src/pages/udp/UdpService.spec.tsx @@ -7,6 +7,7 @@ describe('', () => { it('should render the error message', () => { const { getByTestId } = renderWithProviders( , + { route: '/udp/services/mock-service', withPage: true }, ) expect(getByTestId('error-text')).toBeInTheDocument() }) @@ -14,6 +15,7 @@ describe('', () => { it('should render the skeleton', () => { const { getByTestId } = renderWithProviders( , + { route: '/udp/services/mock-service', withPage: true }, ) expect(getByTestId('skeleton')).toBeInTheDocument() }) @@ -21,6 +23,7 @@ describe('', () => { it('should render the not found page', () => { const { getByTestId } = renderWithProviders( , + { route: '/udp/services/mock-service', withPage: true }, ) expect(getByTestId('Not found page')).toBeInTheDocument() }) @@ -59,6 +62,7 @@ describe('', () => { const { container, getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/udp/services/mock-service', withPage: true }, ) const headings = Array.from(container.getElementsByTagName('h1')) @@ -128,6 +132,7 @@ describe('', () => { const { getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/udp/services/mock-service', withPage: true }, ) const serversList = getByTestId('servers-list') @@ -154,6 +159,7 @@ describe('', () => { const { getByTestId } = renderWithProviders( // eslint-disable-next-line @typescript-eslint/no-explicit-any , + { route: '/udp/services/mock-service', withPage: true }, ) expect(() => { diff --git a/webui/src/pages/udp/UdpService.tsx b/webui/src/pages/udp/UdpService.tsx index 9727a1663..132e63197 100644 --- a/webui/src/pages/udp/UdpService.tsx +++ b/webui/src/pages/udp/UdpService.tsx @@ -1,10 +1,10 @@ import { Flex, H1, Skeleton, styled, Text } from '@traefiklabs/faency' +import { Helmet } from 'react-helmet-async' import { useParams } from 'react-router-dom' import { DetailSectionSkeleton } from 'components/resources/DetailSections' import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection' import { ResourceDetailDataType, useResourceDetail } from 'hooks/use-resource-detail' -import Page from 'layout/Page' import { ServicePanels } from 'pages/http/HttpService' import { NotFound } from 'pages/NotFound' @@ -23,24 +23,30 @@ type UdpServiceRenderProps = { export const UdpServiceRender = ({ data, error, name }: UdpServiceRenderProps) => { if (error) { return ( - + <> + + {name} - Traefik Proxy + Sorry, we could not fetch detail information for this Service right now. Please, try again later. - + ) } if (!data) { return ( - + <> + + {name} - Traefik Proxy + - + ) } @@ -49,11 +55,14 @@ export const UdpServiceRender = ({ data, error, name }: UdpServiceRenderProps) = } return ( - + <> + + {data.name} - Traefik Proxy +

{data.name}

-
+ ) } diff --git a/webui/src/pages/udp/UdpServices.spec.tsx b/webui/src/pages/udp/UdpServices.spec.tsx index 8bb51ef0d..e28d1dba3 100644 --- a/webui/src/pages/udp/UdpServices.spec.tsx +++ b/webui/src/pages/udp/UdpServices.spec.tsx @@ -36,10 +36,13 @@ describe('', () => { .spyOn(useFetchWithPagination, 'default') .mockImplementation(() => useFetchWithPaginationMock({ pages })) - const { container, getByTestId } = renderWithProviders() + const { container, getByTestId } = renderWithProviders(, { + route: '/udp/services', + withPage: true, + }) expect(mock).toHaveBeenCalled() - expect(getByTestId('UDP Services page')).toBeInTheDocument() + expect(getByTestId('/udp/services page')).toBeInTheDocument() const tbody = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[1] expect(tbody.querySelectorAll('a[role="row"]')).toHaveLength(3) @@ -73,6 +76,7 @@ describe('', () => { pageCount={1} pages={[]} />, + { route: '/udp/services', withPage: true }, ) expect(() => getByTestId('loading')).toThrow('Unable to find an element by: [data-testid="loading"]') const tfoot = container.querySelectorAll('div[role="table"] > div[role="rowgroup"]')[2] diff --git a/webui/src/pages/udp/UdpServices.tsx b/webui/src/pages/udp/UdpServices.tsx index b02c2fdcf..5d6b47c66 100644 --- a/webui/src/pages/udp/UdpServices.tsx +++ b/webui/src/pages/udp/UdpServices.tsx @@ -1,5 +1,6 @@ import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex, Text } from '@traefiklabs/faency' import { useMemo } from 'react' +import { Helmet } from 'react-helmet-async' import useInfiniteScroll from 'react-infinite-scroll-hook' import { useSearchParams } from 'react-router-dom' @@ -14,7 +15,6 @@ import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' -import Page from 'layout/Page' export const makeRowRender = (): RenderRowType => { const UdpServicesRenderRow = (row) => ( @@ -108,7 +108,10 @@ export const UdpServices = () => { ) return ( - + <> + + UDP Services - Traefik Proxy + { pageCount={pageCount} pages={pages} /> - + ) } diff --git a/webui/src/types/global.d.ts b/webui/src/types/global.d.ts index e1503ed42..4b024488c 100644 --- a/webui/src/types/global.d.ts +++ b/webui/src/types/global.d.ts @@ -5,5 +5,6 @@ interface Window { declare namespace JSX { interface IntrinsicElements { 'hub-button-app': React.DetailedHTMLProps, HTMLElement> + 'hub-ui-demo-app': { key: string; path: string; theme: 'dark' | 'light'; baseurl: string; containercss: string } } } diff --git a/webui/src/utils/test.tsx b/webui/src/utils/test.tsx index 2c79760cf..995908e9f 100644 --- a/webui/src/utils/test.tsx +++ b/webui/src/utils/test.tsx @@ -1,10 +1,11 @@ import { cleanup, render } from '@testing-library/react' import { FaencyProvider } from '@traefiklabs/faency' import { HelmetProvider } from 'react-helmet-async' -import { BrowserRouter } from 'react-router-dom' +import { MemoryRouter } from 'react-router-dom' import { SWRConfig } from 'swr' import { afterEach } from 'vitest' +import Page from '../layout/Page' import fetch from '../libs/fetch' afterEach(() => { @@ -25,7 +26,7 @@ export { default as userEvent } from '@testing-library/user-event' // override render export export { customRender as render } // eslint-disable-line import/export -export function renderWithProviders(ui: React.ReactElement) { +export function renderWithProviders(ui: React.ReactElement, { route = '/', withPage = false } = {}) { return customRender(ui, { wrapper: ({ children }) => ( @@ -36,7 +37,7 @@ export function renderWithProviders(ui: React.ReactElement) { fetcher: fetch, }} > - {children} + {withPage ? {children} : children} diff --git a/webui/test/setup.ts b/webui/test/setup.ts index 7c53d16b8..fe734b9b7 100644 --- a/webui/test/setup.ts +++ b/webui/test/setup.ts @@ -1,5 +1,6 @@ import '@testing-library/jest-dom' import 'vitest-canvas-mock' +import '@vitest/web-worker' import * as matchers from 'jest-extended' import { expect } from 'vitest' @@ -12,6 +13,7 @@ export class IntersectionObserver { root = null rootMargin = '' thresholds = [] + scrollMargin = '' disconnect() { return null @@ -43,10 +45,10 @@ class ResizeObserver { } beforeAll(() => { - global.IntersectionObserver = IntersectionObserver + globalThis.IntersectionObserver = IntersectionObserver window.IntersectionObserver = IntersectionObserver - global.ResizeObserver = ResizeObserver + globalThis.ResizeObserver = ResizeObserver window.ResizeObserver = ResizeObserver Object.defineProperty(window, 'matchMedia', { diff --git a/webui/yarn.lock b/webui/yarn.lock index 3934fc003..741958b91 100644 --- a/webui/yarn.lock +++ b/webui/yarn.lock @@ -2485,6 +2485,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" @@ -2561,6 +2568,20 @@ __metadata: languageName: node linkType: hard +"@noble/ed25519@npm:^3.0.0": + version: 3.0.0 + resolution: "@noble/ed25519@npm:3.0.0" + checksum: 10c0/6355aed04523d063d3e9a9952926af4a0eaa722e08133f558d44963a3048bf6dae02cae9032d42238d1fcb2a93ef27658f2baf4cf07939457717a4337a89dc26 + languageName: node + linkType: hard + +"@noble/hashes@npm:^2.0.1": + version: 2.0.1 + resolution: "@noble/hashes@npm:2.0.1" + checksum: 10c0/e81769ce21c3b1c80141a3b99bd001f17edea09879aa936692ae39525477386d696101cd573928a304806efb2b9fa751e1dd83241c67d0c84d30091e85c79bdb + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -4113,9 +4134,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.46.1" +"@rollup/rollup-android-arm-eabi@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.52.5" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -4127,9 +4148,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-android-arm64@npm:4.46.1" +"@rollup/rollup-android-arm64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-android-arm64@npm:4.52.5" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -4141,9 +4162,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-darwin-arm64@npm:4.46.1" +"@rollup/rollup-darwin-arm64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-darwin-arm64@npm:4.52.5" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -4155,9 +4176,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-darwin-x64@npm:4.46.1" +"@rollup/rollup-darwin-x64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-darwin-x64@npm:4.52.5" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -4169,9 +4190,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.46.1" +"@rollup/rollup-freebsd-arm64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.52.5" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -4183,9 +4204,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-freebsd-x64@npm:4.46.1" +"@rollup/rollup-freebsd-x64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-freebsd-x64@npm:4.52.5" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -4197,9 +4218,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.46.1" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.52.5" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard @@ -4211,9 +4232,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.46.1" +"@rollup/rollup-linux-arm-musleabihf@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.52.5" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard @@ -4225,9 +4246,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.46.1" +"@rollup/rollup-linux-arm64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.52.5" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard @@ -4239,13 +4260,20 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.46.1" +"@rollup/rollup-linux-arm64-musl@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.52.5" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard +"@rollup/rollup-linux-loong64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.52.5" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-loongarch64-gnu@npm:4.39.0": version: 4.39.0 resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.39.0" @@ -4253,13 +4281,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-loongarch64-gnu@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.46.1" - conditions: os=linux & cpu=loong64 & libc=glibc - languageName: node - linkType: hard - "@rollup/rollup-linux-powerpc64le-gnu@npm:4.39.0": version: 4.39.0 resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.39.0" @@ -4267,9 +4288,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.46.1" +"@rollup/rollup-linux-ppc64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.52.5" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard @@ -4281,9 +4302,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.46.1" +"@rollup/rollup-linux-riscv64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.52.5" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard @@ -4295,9 +4316,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.46.1" +"@rollup/rollup-linux-riscv64-musl@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.52.5" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard @@ -4309,9 +4330,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.46.1" +"@rollup/rollup-linux-s390x-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.52.5" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard @@ -4323,9 +4344,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.46.1" +"@rollup/rollup-linux-x64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.52.5" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard @@ -4337,13 +4358,20 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.46.1" +"@rollup/rollup-linux-x64-musl@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.52.5" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard +"@rollup/rollup-openharmony-arm64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.52.5" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-win32-arm64-msvc@npm:4.39.0": version: 4.39.0 resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.39.0" @@ -4351,9 +4379,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.46.1" +"@rollup/rollup-win32-arm64-msvc@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.52.5" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -4365,13 +4393,20 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.46.1" +"@rollup/rollup-win32-ia32-msvc@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.52.5" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard +"@rollup/rollup-win32-x64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.52.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-win32-x64-msvc@npm:4.39.0": version: 4.39.0 resolution: "@rollup/rollup-win32-x64-msvc@npm:4.39.0" @@ -4379,9 +4414,9 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.46.1": - version: 4.46.1 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.46.1" +"@rollup/rollup-win32-x64-msvc@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.52.5" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -4522,6 +4557,13 @@ __metadata: languageName: node linkType: hard +"@standard-schema/spec@npm:^1.0.0": + version: 1.0.0 + resolution: "@standard-schema/spec@npm:1.0.0" + checksum: 10c0/a1ab9a8bdc09b5b47aa8365d0e0ec40cc2df6437be02853696a0e377321653b0d3ac6f079a8c67d5ddbe9821025584b1fb71d9cc041a6666a96f1fadf2ece15f + languageName: node + linkType: hard + "@stitches/react@npm:1.2.7": version: 1.2.7 resolution: "@stitches/react@npm:1.2.7" @@ -5838,86 +5880,94 @@ __metadata: languageName: node linkType: hard -"@vitest/expect@npm:3.2.4": - version: 3.2.4 - resolution: "@vitest/expect@npm:3.2.4" +"@vitest/expect@npm:4.0.3": + version: 4.0.3 + resolution: "@vitest/expect@npm:4.0.3" dependencies: + "@standard-schema/spec": "npm:^1.0.0" "@types/chai": "npm:^5.2.2" - "@vitest/spy": "npm:3.2.4" - "@vitest/utils": "npm:3.2.4" - chai: "npm:^5.2.0" - tinyrainbow: "npm:^2.0.0" - checksum: 10c0/7586104e3fd31dbe1e6ecaafb9a70131e4197dce2940f727b6a84131eee3decac7b10f9c7c72fa5edbdb68b6f854353bd4c0fa84779e274207fb7379563b10db + "@vitest/spy": "npm:4.0.3" + "@vitest/utils": "npm:4.0.3" + chai: "npm:^6.0.1" + tinyrainbow: "npm:^3.0.3" + checksum: 10c0/36a9ff769387f4475fea273ec3ee39553ed7607bda620eea3a3632cd88a1e8c97173abcce7bc4440c84bf55b8e752edacb519a64f02d7ad5b47df7770cb56883 languageName: node linkType: hard -"@vitest/mocker@npm:3.2.4": - version: 3.2.4 - resolution: "@vitest/mocker@npm:3.2.4" +"@vitest/mocker@npm:4.0.3": + version: 4.0.3 + resolution: "@vitest/mocker@npm:4.0.3" dependencies: - "@vitest/spy": "npm:3.2.4" + "@vitest/spy": "npm:4.0.3" estree-walker: "npm:^3.0.3" - magic-string: "npm:^0.30.17" + magic-string: "npm:^0.30.19" peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true vite: optional: true - checksum: 10c0/f7a4aea19bbbf8f15905847ee9143b6298b2c110f8b64789224cb0ffdc2e96f9802876aa2ca83f1ec1b6e1ff45e822abb34f0054c24d57b29ab18add06536ccd + checksum: 10c0/16c9ef064a55b0fd3e0d7adaddfeef5c41d569d8181c40d8fed75464b43445305b83c017b50c5ea88f49355bef64e32f75cfd35db0682e0a8f5bbcc3acbde264 languageName: node linkType: hard -"@vitest/pretty-format@npm:3.2.4, @vitest/pretty-format@npm:^3.2.4": - version: 3.2.4 - resolution: "@vitest/pretty-format@npm:3.2.4" +"@vitest/pretty-format@npm:4.0.3": + version: 4.0.3 + resolution: "@vitest/pretty-format@npm:4.0.3" dependencies: - tinyrainbow: "npm:^2.0.0" - checksum: 10c0/5ad7d4278e067390d7d633e307fee8103958806a419ca380aec0e33fae71b44a64415f7a9b4bc11635d3c13d4a9186111c581d3cef9c65cc317e68f077456887 + tinyrainbow: "npm:^3.0.3" + checksum: 10c0/031080fbcb16b42c511ef7553a0cdabcb78d0bf9a2bb960a03403ff4553ce05d15e13af4abe7f22bd187f369d3bfa1c59714a0b4d9db33313d7583346511f236 languageName: node linkType: hard -"@vitest/runner@npm:3.2.4": - version: 3.2.4 - resolution: "@vitest/runner@npm:3.2.4" +"@vitest/runner@npm:4.0.3": + version: 4.0.3 + resolution: "@vitest/runner@npm:4.0.3" dependencies: - "@vitest/utils": "npm:3.2.4" + "@vitest/utils": "npm:4.0.3" pathe: "npm:^2.0.3" - strip-literal: "npm:^3.0.0" - checksum: 10c0/e8be51666c72b3668ae3ea348b0196656a4a5adb836cb5e270720885d9517421815b0d6c98bfdf1795ed02b994b7bfb2b21566ee356a40021f5bf4f6ed4e418a + checksum: 10c0/77a4c76890d652115baded6004beb13f5f84e98d0a5175c38b3908f03875d6c02df998430e43b21bf324de9e1499069a54cf773854447607602cab5d9fe8cd60 languageName: node linkType: hard -"@vitest/snapshot@npm:3.2.4": - version: 3.2.4 - resolution: "@vitest/snapshot@npm:3.2.4" +"@vitest/snapshot@npm:4.0.3": + version: 4.0.3 + resolution: "@vitest/snapshot@npm:4.0.3" dependencies: - "@vitest/pretty-format": "npm:3.2.4" - magic-string: "npm:^0.30.17" + "@vitest/pretty-format": "npm:4.0.3" + magic-string: "npm:^0.30.19" pathe: "npm:^2.0.3" - checksum: 10c0/f8301a3d7d1559fd3d59ed51176dd52e1ed5c2d23aa6d8d6aa18787ef46e295056bc726a021698d8454c16ed825ecba163362f42fa90258bb4a98cfd2c9424fc + checksum: 10c0/69e6a3ebdf3852cb949237989aa88c73ead9e6a1debd2227d130e0ec3c33a0bc07ab2d16538a247b8eea0c105f0c9f62419700bbc9230edc99d1df8083bf7b0a languageName: node linkType: hard -"@vitest/spy@npm:3.2.4": - version: 3.2.4 - resolution: "@vitest/spy@npm:3.2.4" - dependencies: - tinyspy: "npm:^4.0.3" - checksum: 10c0/6ebf0b4697dc238476d6b6a60c76ba9eb1dd8167a307e30f08f64149612fd50227682b876420e4c2e09a76334e73f72e3ebf0e350714dc22474258292e202024 +"@vitest/spy@npm:4.0.3": + version: 4.0.3 + resolution: "@vitest/spy@npm:4.0.3" + checksum: 10c0/185fc2621c44c974fe2d89523cc8e20db46b4213fb7f153b63c4091c744b14e1e2c79f6160a3db0aab3e91775c6974214499c643032a02c2261cc68692b4cad8 languageName: node linkType: hard -"@vitest/utils@npm:3.2.4": - version: 3.2.4 - resolution: "@vitest/utils@npm:3.2.4" +"@vitest/utils@npm:4.0.3": + version: 4.0.3 + resolution: "@vitest/utils@npm:4.0.3" dependencies: - "@vitest/pretty-format": "npm:3.2.4" - loupe: "npm:^3.1.4" - tinyrainbow: "npm:^2.0.0" - checksum: 10c0/024a9b8c8bcc12cf40183c246c244b52ecff861c6deb3477cbf487ac8781ad44c68a9c5fd69f8c1361878e55b97c10d99d511f2597f1f7244b5e5101d028ba64 + "@vitest/pretty-format": "npm:4.0.3" + tinyrainbow: "npm:^3.0.3" + checksum: 10c0/b773bb0133307176762a932bab0480e77c9b378406eb92c82f37a16d214b1d873a0378e4c567c4b05bf4628073bc7fc18b519a7c0bdaf75e18311545ca00c5ba + languageName: node + linkType: hard + +"@vitest/web-worker@npm:^4.0.2": + version: 4.0.2 + resolution: "@vitest/web-worker@npm:4.0.2" + dependencies: + debug: "npm:^4.4.3" + peerDependencies: + vitest: 4.0.2 + checksum: 10c0/d30b32b8c7e4df28fbe099125e62f3fc3d0e48a852b3749cbb47963e33070a4c04b3f55bfaf1f90109eacd6bc4d13b4b8028be984033edda532a8372a5abed86 languageName: node linkType: hard @@ -6421,13 +6471,6 @@ __metadata: languageName: node linkType: hard -"assertion-error@npm:^2.0.1": - version: 2.0.1 - resolution: "assertion-error@npm:2.0.1" - checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 - languageName: node - linkType: hard - "ast-types-flow@npm:^0.0.8": version: 0.0.8 resolution: "ast-types-flow@npm:0.0.8" @@ -6756,13 +6799,6 @@ __metadata: languageName: node linkType: hard -"cac@npm:^6.7.14": - version: 6.7.14 - resolution: "cac@npm:6.7.14" - checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 - languageName: node - linkType: hard - "cacache@npm:^16.0.0, cacache@npm:^16.1.0, cacache@npm:^16.1.3": version: 16.1.3 resolution: "cacache@npm:16.1.3" @@ -6950,9 +6986,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001688": - version: 1.0.30001712 - resolution: "caniuse-lite@npm:1.0.30001712" - checksum: 10c0/b3df8bdcc3335969380c2e47acb36c89bfc7f8fb4ef7ee2a5380e30ba46aa69e9d411654bc29894a06c201a1d60d490ab9b92787f3b66d7a7a38d71360e68215 + version: 1.0.30001751 + resolution: "caniuse-lite@npm:1.0.30001751" + checksum: 10c0/c3f2d448f3569004ace160fd9379ea0def8e7a7bc6e65611baadb57d24e1f418258647a6210e46732419f5663e2356c22aa841f92449dd3849eb6471bb7ad592 languageName: node linkType: hard @@ -6968,16 +7004,10 @@ __metadata: languageName: node linkType: hard -"chai@npm:^5.2.0": - version: 5.2.1 - resolution: "chai@npm:5.2.1" - dependencies: - assertion-error: "npm:^2.0.1" - check-error: "npm:^2.1.1" - deep-eql: "npm:^5.0.1" - loupe: "npm:^3.1.0" - pathval: "npm:^2.0.0" - checksum: 10c0/58209c03ae9b2fd97cfa1cb0fbe372b1906e6091311b9ba1b0468cc4923b0766a50a1050a164df3ccefb9464944c9216b632f1477c9e429068013bdbb57220f6 +"chai@npm:^6.0.1": + version: 6.2.0 + resolution: "chai@npm:6.2.0" + checksum: 10c0/a4b7d7f5907187e09f1847afa838d6d1608adc7d822031b7900813c4ed5d9702911ac2468bf290676f22fddb3d727b1be90b57c1d0a69b902534ee29cdc6ff8a languageName: node linkType: hard @@ -7048,13 +7078,6 @@ __metadata: languageName: node linkType: hard -"check-error@npm:^2.1.1": - version: 2.1.1 - resolution: "check-error@npm:2.1.1" - checksum: 10c0/979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e - languageName: node - linkType: hard - "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -7761,6 +7784,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.4.3": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + "debuglog@npm:^1.0.1": version: 1.0.1 resolution: "debuglog@npm:1.0.1" @@ -7815,13 +7850,6 @@ __metadata: languageName: node linkType: hard -"deep-eql@npm:^5.0.1": - version: 5.0.2 - resolution: "deep-eql@npm:5.0.2" - checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 - languageName: node - linkType: hard - "deep-equal@npm:^2.0.5": version: 2.2.3 resolution: "deep-equal@npm:2.2.3" @@ -9193,7 +9221,7 @@ __metadata: languageName: node linkType: hard -"expect-type@npm:^1.2.1": +"expect-type@npm:^1.2.2": version: 1.2.2 resolution: "expect-type@npm:1.2.2" checksum: 10c0/6019019566063bbc7a690d9281d920b1a91284a4a093c2d55d71ffade5ac890cf37a51e1da4602546c4b56569d2ad2fc175a2ccee77d1ae06cb3af91ef84f44b @@ -9293,7 +9321,7 @@ __metadata: languageName: node linkType: hard -"fdir@npm:^6.4.4, fdir@npm:^6.4.6": +"fdir@npm:^6.4.4": version: 6.4.6 resolution: "fdir@npm:6.4.6" peerDependencies: @@ -9305,6 +9333,18 @@ __metadata: languageName: node linkType: hard +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + "figures@npm:^1.7.0": version: 1.7.0 resolution: "figures@npm:1.7.0" @@ -12209,13 +12249,6 @@ __metadata: languageName: node linkType: hard -"loupe@npm:^3.1.0, loupe@npm:^3.1.4": - version: 3.2.0 - resolution: "loupe@npm:3.2.0" - checksum: 10c0/f572fd9e38db8d36ae9eede305480686e310d69bc40394b6842838ebc6c3860a0e35ab30182f33606ab2d8a685d9ff6436649269f8218a1c3385ca329973cb2c - languageName: node - linkType: hard - "lowercase-keys@npm:^2.0.0": version: 2.0.0 resolution: "lowercase-keys@npm:2.0.0" @@ -12289,6 +12322,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.19": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + "magicast@npm:^0.3.5": version: 0.3.5 resolution: "magicast@npm:0.3.5" @@ -14099,13 +14141,6 @@ __metadata: languageName: node linkType: hard -"pathval@npm:^2.0.0": - version: 2.0.1 - resolution: "pathval@npm:2.0.1" - checksum: 10c0/460f4709479fbf2c45903a65655fc8f0a5f6d808f989173aeef5fdea4ff4f303dc13f7870303999add60ec49d4c14733895c0a869392e9866f1091fa64fd7581 - languageName: node - linkType: hard - "picocolors@npm:1.1.1, picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" @@ -15326,30 +15361,32 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^4.40.0": - version: 4.46.1 - resolution: "rollup@npm:4.46.1" +"rollup@npm:^4.43.0": + version: 4.52.5 + resolution: "rollup@npm:4.52.5" dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.46.1" - "@rollup/rollup-android-arm64": "npm:4.46.1" - "@rollup/rollup-darwin-arm64": "npm:4.46.1" - "@rollup/rollup-darwin-x64": "npm:4.46.1" - "@rollup/rollup-freebsd-arm64": "npm:4.46.1" - "@rollup/rollup-freebsd-x64": "npm:4.46.1" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.46.1" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.46.1" - "@rollup/rollup-linux-arm64-gnu": "npm:4.46.1" - "@rollup/rollup-linux-arm64-musl": "npm:4.46.1" - "@rollup/rollup-linux-loongarch64-gnu": "npm:4.46.1" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.46.1" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.46.1" - "@rollup/rollup-linux-riscv64-musl": "npm:4.46.1" - "@rollup/rollup-linux-s390x-gnu": "npm:4.46.1" - "@rollup/rollup-linux-x64-gnu": "npm:4.46.1" - "@rollup/rollup-linux-x64-musl": "npm:4.46.1" - "@rollup/rollup-win32-arm64-msvc": "npm:4.46.1" - "@rollup/rollup-win32-ia32-msvc": "npm:4.46.1" - "@rollup/rollup-win32-x64-msvc": "npm:4.46.1" + "@rollup/rollup-android-arm-eabi": "npm:4.52.5" + "@rollup/rollup-android-arm64": "npm:4.52.5" + "@rollup/rollup-darwin-arm64": "npm:4.52.5" + "@rollup/rollup-darwin-x64": "npm:4.52.5" + "@rollup/rollup-freebsd-arm64": "npm:4.52.5" + "@rollup/rollup-freebsd-x64": "npm:4.52.5" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.52.5" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.52.5" + "@rollup/rollup-linux-arm64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-arm64-musl": "npm:4.52.5" + "@rollup/rollup-linux-loong64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-riscv64-musl": "npm:4.52.5" + "@rollup/rollup-linux-s390x-gnu": "npm:4.52.5" + "@rollup/rollup-linux-x64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-x64-musl": "npm:4.52.5" + "@rollup/rollup-openharmony-arm64": "npm:4.52.5" + "@rollup/rollup-win32-arm64-msvc": "npm:4.52.5" + "@rollup/rollup-win32-ia32-msvc": "npm:4.52.5" + "@rollup/rollup-win32-x64-gnu": "npm:4.52.5" + "@rollup/rollup-win32-x64-msvc": "npm:4.52.5" "@types/estree": "npm:1.0.8" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -15373,7 +15410,7 @@ __metadata: optional: true "@rollup/rollup-linux-arm64-musl": optional: true - "@rollup/rollup-linux-loongarch64-gnu": + "@rollup/rollup-linux-loong64-gnu": optional: true "@rollup/rollup-linux-ppc64-gnu": optional: true @@ -15387,17 +15424,21 @@ __metadata: optional: true "@rollup/rollup-linux-x64-musl": optional: true + "@rollup/rollup-openharmony-arm64": + optional: true "@rollup/rollup-win32-arm64-msvc": optional: true "@rollup/rollup-win32-ia32-msvc": optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true "@rollup/rollup-win32-x64-msvc": optional: true fsevents: optional: true bin: rollup: dist/bin/rollup - checksum: 10c0/84297d63a97bf8fc131039d0f600787ada8baaa38b744820f7d29da601bce9f70a14aa12da80843b2b49eb8660718576f441ee9952954efa16009d7ccebf0000 + checksum: 10c0/faf1697b305d13a149bb64a2bb7378344becc7c8580f56225c4c00adbf493d82480a44b3e3b1cc82a3ac5d1d4cab6dfc89e6635443895a2dc488969075f5b94d languageName: node linkType: hard @@ -16421,15 +16462,6 @@ __metadata: languageName: node linkType: hard -"strip-literal@npm:^3.0.0": - version: 3.0.0 - resolution: "strip-literal@npm:3.0.0" - dependencies: - js-tokens: "npm:^9.0.1" - checksum: 10c0/d81657f84aba42d4bbaf2a677f7e7f34c1f3de5a6726db8bc1797f9c0b303ba54d4660383a74bde43df401cf37cce1dff2c842c55b077a4ceee11f9e31fba828 - languageName: node - linkType: hard - "supports-color@npm:^2.0.0": version: 2.0.0 resolution: "supports-color@npm:2.0.0" @@ -16676,10 +16708,13 @@ __metadata: languageName: node linkType: hard -"tinypool@npm:^1.1.1": - version: 1.1.1 - resolution: "tinypool@npm:1.1.1" - checksum: 10c0/bf26727d01443061b04fa863f571016950888ea994ba0cd8cba3a1c51e2458d84574341ab8dbc3664f1c3ab20885c8cf9ff1cc4b18201f04c2cde7d317fff69b +"tinyglobby@npm:^0.2.15": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 languageName: node linkType: hard @@ -16690,10 +16725,10 @@ __metadata: languageName: node linkType: hard -"tinyspy@npm:^4.0.3": - version: 4.0.3 - resolution: "tinyspy@npm:4.0.3" - checksum: 10c0/0a92a18b5350945cc8a1da3a22c9ad9f4e2945df80aaa0c43e1b3a3cfb64d8501e607ebf0305e048e3c3d3e0e7f8eb10cea27dc17c21effb73e66c4a3be36373 +"tinyrainbow@npm:^3.0.3": + version: 3.0.3 + resolution: "tinyrainbow@npm:3.0.3" + checksum: 10c0/1e799d35cd23cabe02e22550985a3051dc88814a979be02dc632a159c393a998628eacfc558e4c746b3006606d54b00bcdea0c39301133956d10a27aa27e988c languageName: node linkType: hard @@ -16755,6 +16790,8 @@ __metadata: resolution: "traefik-proxy-dashboard@workspace:." dependencies: "@eslint/js": "npm:^9.32.0" + "@noble/ed25519": "npm:^3.0.0" + "@noble/hashes": "npm:^2.0.1" "@testing-library/dom": "npm:^10.4.1" "@testing-library/jest-dom": "npm:^6.4.2" "@testing-library/react": "npm:^14.2.1" @@ -16768,6 +16805,7 @@ __metadata: "@typescript-eslint/parser": "npm:^8.38.0" "@vitejs/plugin-react": "npm:^4.7.0" "@vitest/coverage-v8": "npm:^3.2.4" + "@vitest/web-worker": "npm:^4.0.2" chart.js: "npm:^4.4.1" eslint: "npm:^9.32.0" eslint-config-prettier: "npm:^10.1.8" @@ -16800,7 +16838,7 @@ __metadata: usehooks-ts: "npm:^2.14.0" vite: "npm:^5.4.19" vite-tsconfig-paths: "npm:^5.1.4" - vitest: "npm:^3.2.4" + vitest: "npm:^4.0.3" vitest-canvas-mock: "npm:^0.3.3" languageName: unknown linkType: soft @@ -17519,21 +17557,6 @@ __metadata: languageName: node linkType: hard -"vite-node@npm:3.2.4": - version: 3.2.4 - resolution: "vite-node@npm:3.2.4" - dependencies: - cac: "npm:^6.7.14" - debug: "npm:^4.4.1" - es-module-lexer: "npm:^1.7.0" - pathe: "npm:^2.0.3" - vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" - bin: - vite-node: vite-node.mjs - checksum: 10c0/6ceca67c002f8ef6397d58b9539f80f2b5d79e103a18367288b3f00a8ab55affa3d711d86d9112fce5a7fa658a212a087a005a045eb8f4758947dd99af2a6c6b - languageName: node - linkType: hard - "vite-tsconfig-paths@npm:^5.1.4": version: 5.1.4 resolution: "vite-tsconfig-paths@npm:5.1.4" @@ -17550,61 +17573,6 @@ __metadata: languageName: node linkType: hard -"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0": - version: 7.0.6 - resolution: "vite@npm:7.0.6" - dependencies: - esbuild: "npm:^0.25.0" - fdir: "npm:^6.4.6" - fsevents: "npm:~2.3.3" - picomatch: "npm:^4.0.3" - postcss: "npm:^8.5.6" - rollup: "npm:^4.40.0" - tinyglobby: "npm:^0.2.14" - peerDependencies: - "@types/node": ^20.19.0 || >=22.12.0 - jiti: ">=1.21.0" - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: ">=0.54.8" - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - dependenciesMeta: - fsevents: - optional: true - peerDependenciesMeta: - "@types/node": - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - bin: - vite: bin/vite.js - checksum: 10c0/3b14dfa661281b4843789884199ba2a9cca940a7666970036fe3fb1abff52b88e63e8be5ab419dd04d9f96c0415ee0f1e3ec8ebe357041648af7ccd8e348b6ad - languageName: node - linkType: hard - "vite@npm:^5.1.5": version: 5.4.17 resolution: "vite@npm:5.4.17" @@ -17691,6 +17659,61 @@ __metadata: languageName: node linkType: hard +"vite@npm:^6.0.0 || ^7.0.0": + version: 7.1.12 + resolution: "vite@npm:7.1.12" + dependencies: + esbuild: "npm:^0.25.0" + fdir: "npm:^6.5.0" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rollup: "npm:^4.43.0" + tinyglobby: "npm:^0.2.15" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/cef4d4b4a84e663e09b858964af36e916892ac8540068df42a05ced637ceeae5e9ef71c72d54f3cfc1f3c254af16634230e221b6e2327c2a66d794bb49203262 + languageName: node + linkType: hard + "vitest-canvas-mock@npm:^0.3.3": version: 0.3.3 resolution: "vitest-canvas-mock@npm:0.3.3" @@ -17702,39 +17725,38 @@ __metadata: languageName: node linkType: hard -"vitest@npm:^3.2.4": - version: 3.2.4 - resolution: "vitest@npm:3.2.4" +"vitest@npm:^4.0.3": + version: 4.0.3 + resolution: "vitest@npm:4.0.3" dependencies: - "@types/chai": "npm:^5.2.2" - "@vitest/expect": "npm:3.2.4" - "@vitest/mocker": "npm:3.2.4" - "@vitest/pretty-format": "npm:^3.2.4" - "@vitest/runner": "npm:3.2.4" - "@vitest/snapshot": "npm:3.2.4" - "@vitest/spy": "npm:3.2.4" - "@vitest/utils": "npm:3.2.4" - chai: "npm:^5.2.0" - debug: "npm:^4.4.1" - expect-type: "npm:^1.2.1" - magic-string: "npm:^0.30.17" + "@vitest/expect": "npm:4.0.3" + "@vitest/mocker": "npm:4.0.3" + "@vitest/pretty-format": "npm:4.0.3" + "@vitest/runner": "npm:4.0.3" + "@vitest/snapshot": "npm:4.0.3" + "@vitest/spy": "npm:4.0.3" + "@vitest/utils": "npm:4.0.3" + debug: "npm:^4.4.3" + es-module-lexer: "npm:^1.7.0" + expect-type: "npm:^1.2.2" + magic-string: "npm:^0.30.19" pathe: "npm:^2.0.3" - picomatch: "npm:^4.0.2" + picomatch: "npm:^4.0.3" std-env: "npm:^3.9.0" tinybench: "npm:^2.9.0" tinyexec: "npm:^0.3.2" - tinyglobby: "npm:^0.2.14" - tinypool: "npm:^1.1.1" - tinyrainbow: "npm:^2.0.0" - vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" - vite-node: "npm:3.2.4" + tinyglobby: "npm:^0.2.15" + tinyrainbow: "npm:^3.0.3" + vite: "npm:^6.0.0 || ^7.0.0" why-is-node-running: "npm:^2.3.0" peerDependencies: "@edge-runtime/vm": "*" "@types/debug": ^4.1.12 - "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 - "@vitest/browser": 3.2.4 - "@vitest/ui": 3.2.4 + "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 + "@vitest/browser-playwright": 4.0.3 + "@vitest/browser-preview": 4.0.3 + "@vitest/browser-webdriverio": 4.0.3 + "@vitest/ui": 4.0.3 happy-dom: "*" jsdom: "*" peerDependenciesMeta: @@ -17744,7 +17766,11 @@ __metadata: optional: true "@types/node": optional: true - "@vitest/browser": + "@vitest/browser-playwright": + optional: true + "@vitest/browser-preview": + optional: true + "@vitest/browser-webdriverio": optional: true "@vitest/ui": optional: true @@ -17754,7 +17780,7 @@ __metadata: optional: true bin: vitest: vitest.mjs - checksum: 10c0/5bf53ede3ae6a0e08956d72dab279ae90503f6b5a05298a6a5e6ef47d2fd1ab386aaf48fafa61ed07a0ebfe9e371772f1ccbe5c258dd765206a8218bf2eb79eb + checksum: 10c0/3462e797fc3100d6adae9d5beaa4784a109d4fe5fc1403fb251ebdf7ca95988c1f1447bbaeac3f0046aae555a611f1dcb9da0bcb9d30af7ee99ac5392427fd2b languageName: node linkType: hard From d5a8ff919a95a14c84b8752380a931683f46d994 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Tue, 28 Oct 2025 16:28:05 +0100 Subject: [PATCH 021/134] Prepare release v3.6.0-rc1 --- .github/workflows/release.yaml | 2 +- CHANGELOG.md | 40 +++++ .../getting-started/configuration-overview.md | 2 +- docs/content/getting-started/docker.md | 4 +- .../getting-started/install-traefik.md | 8 +- docs/content/observability/access-logs.md | 2 +- docs/content/providers/docker.md | 2 +- docs/content/providers/kubernetes-crd.md | 4 +- docs/content/providers/kubernetes-gateway.md | 2 +- docs/content/providers/kubernetes-ingress.md | 2 +- .../kubernetes-crd-definition-v1.yml | 152 +++++++++--------- .../kubernetes-gateway-traefik-lb-svc.yml | 2 +- .../traefik.io_ingressroutes.yaml | 32 ++-- .../traefik.io_ingressroutetcps.yaml | 18 +-- .../traefik.io_ingressrouteudps.yaml | 2 +- .../traefik.io_middlewares.yaml | 68 ++++---- .../traefik.io_middlewaretcps.yaml | 6 +- .../traefik.io_serverstransports.yaml | 2 +- .../traefik.io_serverstransporttcps.yaml | 2 +- .../traefik.io_tlsoptions.yaml | 8 +- .../traefik.io_tlsstores.yaml | 2 +- .../traefik.io_traefikservices.yaml | 12 +- .../observability/logs-and-accesslogs.md | 2 +- .../providers/kubernetes/kubernetes-crd.md | 4 +- .../kubernetes/kubernetes-gateway.md | 2 +- .../kubernetes/ingress-nginx.md | 2 +- .../kubernetes/ingress.md | 2 +- .../routing/providers/kubernetes-crd.md | 2 +- .../routing/providers/kubernetes-ingress.md | 6 +- .../user-guides/crd-acme/03-deployments.yml | 2 +- docs/content/user-guides/crd-acme/index.md | 12 +- docs/content/user-guides/crd-acme/k3s.yml | 2 +- .../acme-dns/docker-compose.yml | 2 +- .../acme-dns/docker-compose_secrets.yml | 2 +- .../acme-http/docker-compose.yml | 2 +- .../acme-tls/docker-compose.yml | 2 +- .../basic-example/docker-compose.yml | 2 +- .../docker-compose/basic-example/index.md | 2 +- integration/fixtures/k8s/01-traefik-crd.yml | 152 +++++++++--------- pkg/cli/deprecation.go | 52 +++--- pkg/config/dynamic/middlewares.go | 50 +++--- pkg/config/dynamic/tcp_config.go | 2 +- pkg/config/dynamic/tcp_middlewares.go | 4 +- .../crd/traefikio/v1alpha1/ingressroute.go | 34 ++-- .../crd/traefikio/v1alpha1/ingressroutetcp.go | 20 +-- .../crd/traefikio/v1alpha1/ingressrouteudp.go | 2 +- .../crd/traefikio/v1alpha1/middleware.go | 30 ++-- .../crd/traefikio/v1alpha1/middlewaretcp.go | 6 +- .../traefikio/v1alpha1/serverstransport.go | 2 +- .../traefikio/v1alpha1/serverstransporttcp.go | 2 +- .../crd/traefikio/v1alpha1/service.go | 10 +- .../crd/traefikio/v1alpha1/tlsoption.go | 8 +- .../crd/traefikio/v1alpha1/tlsstore.go | 2 +- script/gcg/traefik-rc-first.toml | 6 +- 54 files changed, 422 insertions(+), 382 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 502377338..37cb43a60 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,7 +10,7 @@ env: CGO_ENABLED: 0 VERSION: ${{ github.ref_name }} TRAEFIKER_EMAIL: "traefiker@traefik.io" - CODENAME: chabichou + CODENAME: ramequin jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index bf46d9ae7..2d64c009a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,43 @@ +## [v3.6.0-rc1](https://github.com/traefik/traefik/tree/v3.6.0-rc1) (2025-10-28) +[All Commits](https://github.com/traefik/traefik/compare/v3.5.0-rc1...v3.6.0-rc1) + +**Enhancements:** +- **[acme]** Add new certificatesresolvers options ([#11977](https://github.com/traefik/traefik/pull/11977) by [ldez](https://github.com/ldez)) +- **[consul,consulcatalog,nomad]** Log provider namespace during startup ([#12002](https://github.com/traefik/traefik/pull/12002) by [shreealt](https://github.com/shreealt)) +- **[docker]** Allow discovering non-running Docker containers ([#10645](https://github.com/traefik/traefik/pull/10645) by [acouvreur](https://github.com/acouvreur)) +- **[ecs]** AWS ECS IPv6 Support ([#12179](https://github.com/traefik/traefik/pull/12179) by [wizbit](https://github.com/wizbit)) +- **[file,k8s/crd,service]** Add least time load balancing strategy ([#12167](https://github.com/traefik/traefik/pull/12167) by [sdelicata](https://github.com/sdelicata)) +- **[healthcheck,tcp]** Add TCP Healthcheck ([#11238](https://github.com/traefik/traefik/pull/11238) by [ddtmachado](https://github.com/ddtmachado)) +- **[healthcheck]** Add passive health checks ([#11351](https://github.com/traefik/traefik/pull/11351) by [Nelwhix](https://github.com/Nelwhix)) +- **[k8s/crd]** Add highest random weight in Kubernetes CRD ([#12061](https://github.com/traefik/traefik/pull/12061) by [lbenguigui](https://github.com/lbenguigui)) +- **[k8s/gatewayapi]** Bump sigs.k8s.io/gateway-api to v1.4.0 ([#12140](https://github.com/traefik/traefik/pull/12140) by [kevinpollet](https://github.com/kevinpollet)) +- **[k8s/ingress]** Allow publishing services with type ExternalName ([#12065](https://github.com/traefik/traefik/pull/12065) by [james-callahan](https://github.com/james-callahan)) +- **[k8s]** Add Knative provider ([#11448](https://github.com/traefik/traefik/pull/11448) by [idurgakalyan](https://github.com/idurgakalyan)) +- **[middleware,authentication]** Add warning when maxBodySize is not set ([#12085](https://github.com/traefik/traefik/pull/12085) by [kianelbo](https://github.com/kianelbo)) +- **[middleware,server]** Multi-layer routing ([#12130](https://github.com/traefik/traefik/pull/12130) by [sdelicata](https://github.com/sdelicata)) +- **[plugins]** Support syscall ([#11939](https://github.com/traefik/traefik/pull/11939) by [david-garcia-garcia](https://github.com/david-garcia-garcia)) +- **[server]** Implement HTTP2 HPACK table size options ([#12050](https://github.com/traefik/traefik/pull/12050) by [GCHQDeveloper548](https://github.com/GCHQDeveloper548)) +- **[service,udp]** Avoid allocations in readLoop by using sync.Pool ([#12029](https://github.com/traefik/traefik/pull/12029) by [arturmelanchyk](https://github.com/arturmelanchyk)) +- **[service]** Add HighestRandomWeight load balancing algorithm ([#9946](https://github.com/traefik/traefik/pull/9946) by [mathieuHa](https://github.com/mathieuHa)) +- **[webui]** Add Traefik Hub demo in dashboard ([#12193](https://github.com/traefik/traefik/pull/12193) by [gndz07](https://github.com/gndz07)) +- **[webui]** Reduce vertical padding in dashboard table rows for more compact layout ([#12145](https://github.com/traefik/traefik/pull/12145) by [leccelecce](https://github.com/leccelecce)) + +**Documentation:** +- Fix broken link to migration guide on readme ([#12021](https://github.com/traefik/traefik/pull/12021) by [0slb](https://github.com/0slb)) + +**Misc:** +- Merge branch v3.5 into master ([#12210](https://github.com/traefik/traefik/pull/12210) by [rtribotte](https://github.com/rtribotte)) +- Merge branch v3.5 into master ([#12191](https://github.com/traefik/traefik/pull/12191) by [rtribotte](https://github.com/rtribotte)) +- Merge branch v3.5 into master ([#12188](https://github.com/traefik/traefik/pull/12188) by [rtribotte](https://github.com/rtribotte)) +- Merge branch v3.5 into master ([#12160](https://github.com/traefik/traefik/pull/12160) by [rtribotte](https://github.com/rtribotte)) +- Merge branch v3.5 into master ([#12136](https://github.com/traefik/traefik/pull/12136) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v3.5 into master ([#12120](https://github.com/traefik/traefik/pull/12120) by [rtribotte](https://github.com/rtribotte)) +- Merge branch v3.5 into master ([#12095](https://github.com/traefik/traefik/pull/12095) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v3.5 into master ([#12051](https://github.com/traefik/traefik/pull/12051) by [rtribotte](https://github.com/rtribotte)) +- Merge branch v3.5 into master ([#11976](https://github.com/traefik/traefik/pull/11976) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v3.5 into master ([#11940](https://github.com/traefik/traefik/pull/11940) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v3.5 into master ([#11900](https://github.com/traefik/traefik/pull/11900) by [kevinpollet](https://github.com/kevinpollet)) + ## [v3.5.4](https://github.com/traefik/traefik/tree/v3.5.4) (2025-10-28) [All Commits](https://github.com/traefik/traefik/compare/v3.5.3...v3.5.4) diff --git a/docs/content/getting-started/configuration-overview.md b/docs/content/getting-started/configuration-overview.md index 728004225..d184a3e2f 100644 --- a/docs/content/getting-started/configuration-overview.md +++ b/docs/content/getting-started/configuration-overview.md @@ -79,7 +79,7 @@ traefik --help # or docker run traefik[:version] --help -# ex: docker run traefik:v3.5 --help +# ex: docker run traefik:v3.6 --help ``` Check the [CLI reference](../reference/install-configuration/configuration-options.md "Link to CLI reference overview") for an overview about all available arguments. diff --git a/docs/content/getting-started/docker.md b/docs/content/getting-started/docker.md index 6cd151b32..4abf0f3ea 100644 --- a/docs/content/getting-started/docker.md +++ b/docs/content/getting-started/docker.md @@ -36,7 +36,7 @@ This configuration: # docker-compose.yml services: traefik: - image: traefik:v3.5 + image: traefik:v3.6 command: - "--api.insecure=true" - "--providers.docker=true" @@ -84,7 +84,7 @@ docker run -d \ -p 8080:8080 \ -v $PWD/traefik.yml:/etc/traefik/traefik.yml \ -v /var/run/docker.sock:/var/run/docker.sock \ - traefik:v3.5 + traefik:v3.6 ``` ## Expose the Dashboard diff --git a/docs/content/getting-started/install-traefik.md b/docs/content/getting-started/install-traefik.md index 920db9530..724eb7ae7 100644 --- a/docs/content/getting-started/install-traefik.md +++ b/docs/content/getting-started/install-traefik.md @@ -16,12 +16,12 @@ You can install Traefik with the following flavors: Choose one of the [official Docker images](https://hub.docker.com/_/traefik) and run it with one sample configuration file: -* [YAML](https://raw.githubusercontent.com/traefik/traefik/v3.5/traefik.sample.yml) -* [TOML](https://raw.githubusercontent.com/traefik/traefik/v3.5/traefik.sample.toml) +* [YAML](https://raw.githubusercontent.com/traefik/traefik/v3.6/traefik.sample.yml) +* [TOML](https://raw.githubusercontent.com/traefik/traefik/v3.6/traefik.sample.toml) ```shell docker run -d -p 8080:8080 -p 80:80 \ - -v $PWD/traefik.yml:/etc/traefik/traefik.yml traefik:v3.5 + -v $PWD/traefik.yml:/etc/traefik/traefik.yml traefik:v3.6 ``` For more details, go to the [Docker provider documentation](../providers/docker.md) @@ -29,7 +29,7 @@ For more details, go to the [Docker provider documentation](../providers/docker. !!! tip * Prefer a fixed version than the latest that could be an unexpected version. - ex: `traefik:v3.5` + ex: `traefik:v3.6` * Docker images are based from the [Alpine Linux Official image](https://hub.docker.com/_/alpine). * Any orchestrator using docker images can fetch the official Traefik docker image. diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 3091df45e..92f393afe 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -306,7 +306,7 @@ Example utilizing Docker Compose: ```yaml services: traefik: - image: traefik:v3.5 + image: traefik:v3.6 environment: - TZ=US/Alaska command: diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index 75840998d..e49557a33 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -163,7 +163,7 @@ See the [Docker API Access](#docker-api-access) section for more information. ```yaml services: traefik: - image: traefik:v3.5 # The official v3 Traefik docker image + image: traefik:v3.6 # The official v3 Traefik docker image ports: - "80:80" volumes: diff --git a/docs/content/providers/kubernetes-crd.md b/docs/content/providers/kubernetes-crd.md index f00b5c68e..855f2bcc2 100644 --- a/docs/content/providers/kubernetes-crd.md +++ b/docs/content/providers/kubernetes-crd.md @@ -31,10 +31,10 @@ the Traefik engineering team developed a [Custom Resource Definition](https://ku ```bash # Install Traefik Resource Definitions: - kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml + kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml # Install RBAC for Traefik: - kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml + kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml ``` ## Resource Configuration diff --git a/docs/content/providers/kubernetes-gateway.md b/docs/content/providers/kubernetes-gateway.md index bf2dff9cb..fb659661a 100644 --- a/docs/content/providers/kubernetes-gateway.md +++ b/docs/content/providers/kubernetes-gateway.md @@ -34,7 +34,7 @@ For more details, check out the conformance [report](https://github.com/kubernet ```bash # Install Traefik RBACs. - kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml + kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml ``` 3. Deploy Traefik and enable the `kubernetesGateway` provider in the static configuration as detailed below: diff --git a/docs/content/providers/kubernetes-ingress.md b/docs/content/providers/kubernetes-ingress.md index ce9276480..4811dd0f1 100644 --- a/docs/content/providers/kubernetes-ingress.md +++ b/docs/content/providers/kubernetes-ingress.md @@ -555,6 +555,6 @@ providers: ### Further To learn more about the various aspects of the Ingress specification that Traefik supports, -many examples of Ingresses definitions are located in the test [examples](https://github.com/traefik/traefik/tree/v3.5/pkg/provider/kubernetes/ingress/fixtures) of the Traefik repository. +many examples of Ingresses definitions are located in the test [examples](https://github.com/traefik/traefik/tree/v3.6/pkg/provider/kubernetes/ingress/fixtures) of the Traefik repository. {!traefik-for-business-applications.md!} diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index 8847677d7..1f2ef1382 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -43,7 +43,7 @@ spec: description: |- EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -52,7 +52,7 @@ spec: description: |- ParentRefs defines references to parent IngressRoute resources for multi-layer routing. When set, this IngressRoute's routers will be children of the referenced parent IngressRoute's routers. - More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#parentrefs + More info: https://doc.traefik.io/traefik/v3.6/routing/routers/#parentrefs items: description: IngressRouteRef is a reference to an IngressRoute resource. properties: @@ -84,12 +84,12 @@ spec: match: description: |- Match defines the router's rule. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/ type: string middlewares: description: |- Middlewares defines the list of references to Middleware resources. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/middleware/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/middleware/ items: description: MiddlewareRef is a reference to a Middleware resource. @@ -109,7 +109,7 @@ spec: observability: description: |- Observability defines the observability configuration for a router. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/observability/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/observability/ properties: accessLogs: description: AccessLogs enables access logs for this router. @@ -132,7 +132,7 @@ spec: priority: description: |- Priority defines the router's priority. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#priority + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/#priority maximum: 9223372036854775000 type: integer services: @@ -302,7 +302,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -373,7 +373,7 @@ spec: syntax: description: |- Syntax defines the router's rule syntax. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#rulesyntax + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/#rulesyntax Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. type: string required: @@ -383,18 +383,18 @@ spec: tls: description: |- TLS defines the TLS configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/router/#tls + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/router/#tls properties: certResolver: description: |- CertResolver defines the name of the certificate resolver to use. Cert resolvers have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/tls/certificate-resolvers/acme/ type: string domains: description: |- Domains defines the list of domains that will be used to issue certificates. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#domains + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#domains items: description: Domain holds a domain name with SANs. properties: @@ -413,17 +413,17 @@ spec: description: |- Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. If not defined, the `default` TLSOption is used. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-options/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-options/ properties: name: description: |- Name defines the name of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsoption/ type: string namespace: description: |- Namespace defines the namespace of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsoption/ type: string required: - name @@ -440,12 +440,12 @@ spec: name: description: |- Name defines the name of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsstore/ type: string namespace: description: |- Namespace defines the namespace of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsstore/ type: string required: - name @@ -505,7 +505,7 @@ spec: description: |- EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -518,7 +518,7 @@ spec: match: description: |- Match defines the router's rule. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/ type: string middlewares: description: Middlewares defines the list of references to MiddlewareTCP @@ -542,7 +542,7 @@ spec: priority: description: |- Priority defines the router's priority. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#priority + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/#priority maximum: 9223372036854775000 type: integer services: @@ -584,7 +584,7 @@ spec: proxyProtocol: description: |- ProxyProtocol defines the PROXY protocol configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/service/#proxy-protocol + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/service/#proxy-protocol Deprecated: ProxyProtocol will not be supported in future APIVersions, please use ServersTransport to configure ProxyProtocol instead. properties: version: @@ -626,7 +626,7 @@ spec: syntax: description: |- Syntax defines the router's rule syntax. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#rulesyntax + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/#rulesyntax Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. enum: - v3 @@ -639,18 +639,18 @@ spec: tls: description: |- TLS defines the TLS configuration on a layer 4 / TCP Route. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/router/#tls + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/router/#tls properties: certResolver: description: |- CertResolver defines the name of the certificate resolver to use. Cert resolvers have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/tls/certificate-resolvers/acme/ type: string domains: description: |- Domains defines the list of domains that will be used to issue certificates. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#domains + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/tls/#domains items: description: Domain holds a domain name with SANs. properties: @@ -669,7 +669,7 @@ spec: description: |- Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. If not defined, the `default` TLSOption is used. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#tls-options + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/tls/#tls-options properties: name: description: Name defines the name of the referenced Traefik @@ -761,7 +761,7 @@ spec: description: |- EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -849,7 +849,7 @@ spec: openAPIV3Schema: description: |- Middleware is the CRD implementation of a Traefik Middleware. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/overview/ properties: apiVersion: description: |- @@ -875,7 +875,7 @@ spec: description: |- AddPrefix holds the add prefix middleware configuration. This middleware updates the path of a request before forwarding it. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/addprefix/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/addprefix/ properties: prefix: description: |- @@ -890,12 +890,12 @@ spec: description: |- BasicAuth holds the basic auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/basicauth/ properties: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/basicauth/#headerfield type: string realm: description: |- @@ -916,7 +916,7 @@ spec: description: |- Buffering holds the buffering middleware configuration. This middleware retries or limits the size of requests that can be forwarded to backends. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/buffering/#maxrequestbodybytes + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/buffering/#maxrequestbodybytes properties: maxRequestBodyBytes: description: |- @@ -948,14 +948,14 @@ spec: description: |- RetryExpression defines the retry conditions. It is a logical combination of functions with operators AND (&&) and OR (||). - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/buffering/#retryexpression + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/buffering/#retryexpression type: string type: object chain: description: |- Chain holds the configuration of the chain middleware. This middleware enables to define reusable combinations of other pieces of middleware. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/chain/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/chain/ properties: middlewares: description: Middlewares is the list of MiddlewareRef which composes @@ -1018,7 +1018,7 @@ spec: description: |- Compress holds the compress middleware configuration. This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/compress/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/compress/ properties: defaultEncoding: description: DefaultEncoding specifies the default encoding if @@ -1068,12 +1068,12 @@ spec: description: |- DigestAuth holds the digest auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/digestauth/ properties: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/digestauth/#headerfield type: string realm: description: |- @@ -1093,7 +1093,7 @@ spec: description: |- ErrorPage holds the custom error middleware configuration. This middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/errorpages/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/errorpages/ properties: query: description: |- @@ -1105,7 +1105,7 @@ spec: service: description: |- Service defines the reference to a Kubernetes Service that will serve the error page. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/errorpages/#service + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/errorpages/#service properties: healthCheck: description: Healthcheck defines health checks for ExternalName @@ -1266,7 +1266,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -1355,7 +1355,7 @@ spec: description: |- ForwardAuth holds the forward auth middleware configuration. This middleware delegates the request authentication to a Service. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/ properties: addAuthCookiesToResponse: description: AddAuthCookiesToResponse defines the list of cookies @@ -1383,7 +1383,7 @@ spec: authResponseHeadersRegex: description: |- AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex type: string forwardBody: description: ForwardBody defines whether to send the request body @@ -1392,7 +1392,7 @@ spec: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/#headerfield type: string maxBodySize: description: MaxBodySize defines the maximum body size in bytes @@ -1454,7 +1454,7 @@ spec: description: |- Headers holds the headers middleware configuration. This middleware manages the requests and responses headers. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/headers/#customrequestheaders + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/headers/#customrequestheaders properties: accessControlAllowCredentials: description: AccessControlAllowCredentials defines whether the @@ -1626,7 +1626,7 @@ spec: description: |- InFlightReq holds the in-flight request middleware configuration. This middleware limits the number of requests being processed and served concurrently. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/inflightreq/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/inflightreq/ properties: amount: description: |- @@ -1640,12 +1640,12 @@ spec: SourceCriterion defines what criterion is used to group requests as originating from a common source. If several strategies are defined at the same time, an error will be raised. If none are set, the default is to use the requestHost. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/inflightreq/#sourcecriterion + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/inflightreq/#sourcecriterion properties: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1681,12 +1681,12 @@ spec: description: |- IPAllowList holds the IP allowlist middleware configuration. This middleware limits allowed requests based on the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/ properties: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1724,7 +1724,7 @@ spec: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1755,7 +1755,7 @@ spec: description: |- PassTLSClientCert holds the pass TLS client cert middleware configuration. This middleware adds the selected data from the passed client TLS certificate to a header. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/passtlsclientcert/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/passtlsclientcert/ properties: info: description: Info selects the specific client certificate details @@ -1858,13 +1858,13 @@ spec: x-kubernetes-preserve-unknown-fields: true description: |- Plugin defines the middleware plugin configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/#community-middlewares + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/overview/#community-middlewares type: object rateLimit: description: |- RateLimit holds the rate limit configuration. This middleware ensures that services will receive a fair amount of requests, and allows one to define what fair is. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/ratelimit/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/ratelimit/ properties: average: description: |- @@ -1983,7 +1983,7 @@ spec: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -2019,7 +2019,7 @@ spec: description: |- RedirectRegex holds the redirect regex middleware configuration. This middleware redirects a request using regex matching and replacement. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/redirectregex/#regex + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectregex/#regex properties: permanent: description: Permanent defines whether the redirection is permanent @@ -2038,7 +2038,7 @@ spec: description: |- RedirectScheme holds the redirect scheme middleware configuration. This middleware redirects requests from a scheme/port to another. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/redirectscheme/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectscheme/ properties: permanent: description: Permanent defines whether the redirection is permanent @@ -2055,7 +2055,7 @@ spec: description: |- ReplacePath holds the replace path middleware configuration. This middleware replaces the path of the request URL and store the original path in an X-Replaced-Path header. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/replacepath/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/replacepath/ properties: path: description: Path defines the path to use as replacement in the @@ -2066,7 +2066,7 @@ spec: description: |- ReplacePathRegex holds the replace path regex middleware configuration. This middleware replaces the path of a URL using regex matching and replacement. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/replacepathregex/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/replacepathregex/ properties: regex: description: Regex defines the regular expression used to match @@ -2082,7 +2082,7 @@ spec: Retry holds the retry middleware configuration. This middleware reissues requests a given number of times to a backend server if that server does not reply. As soon as the server answers, the middleware stops retrying, regardless of the response status. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/retry/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/retry/ properties: attempts: description: Attempts defines how many times the request should @@ -2106,7 +2106,7 @@ spec: description: |- StripPrefix holds the strip prefix middleware configuration. This middleware removes the specified prefixes from the URL path. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/stripprefix/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/stripprefix/ properties: forceSlash: description: |- @@ -2125,7 +2125,7 @@ spec: description: |- StripPrefixRegex holds the strip prefix regex middleware configuration. This middleware removes the matching prefixes from the URL path. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/stripprefixregex/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/stripprefixregex/ properties: regex: description: Regex defines the regular expression to match the @@ -2162,7 +2162,7 @@ spec: openAPIV3Schema: description: |- MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/overview/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/middlewares/overview/ properties: apiVersion: description: |- @@ -2199,7 +2199,7 @@ spec: description: |- IPAllowList defines the IPAllowList middleware configuration. This middleware accepts/refuses connections based on the client IP. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipallowlist/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/middlewares/ipallowlist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -2213,7 +2213,7 @@ spec: IPWhiteList defines the IPWhiteList middleware configuration. This middleware accepts/refuses connections based on the client IP. Deprecated: please use IPAllowList instead. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipwhitelist/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/middlewares/ipwhitelist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -2252,7 +2252,7 @@ spec: ServersTransport is the CRD implementation of a ServersTransport. If no serversTransport is specified, the default@internal will be used. The default@internal serversTransport is created from the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/serverstransport/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/serverstransport/ properties: apiVersion: description: |- @@ -2421,7 +2421,7 @@ spec: ServersTransportTCP is the CRD implementation of a TCPServersTransport. If no tcpServersTransport is specified, a default one named default@internal will be used. The default@internal tcpServersTransport can be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/serverstransport/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/serverstransport/ properties: apiVersion: description: |- @@ -2575,7 +2575,7 @@ spec: openAPIV3Schema: description: |- TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#tls-options + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#tls-options properties: apiVersion: description: |- @@ -2600,14 +2600,14 @@ spec: alpnProtocols: description: |- ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#alpn-protocols + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#alpn-protocols items: type: string type: array cipherSuites: description: |- CipherSuites defines the list of supported cipher suites for TLS versions up to TLS 1.2. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#cipher-suites + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#cipher-suites items: type: string type: array @@ -2635,7 +2635,7 @@ spec: curvePreferences: description: |- CurvePreferences defines the preferred elliptic curves. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#curve-preferences + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#curve-preferences items: type: string type: array @@ -2695,7 +2695,7 @@ spec: TLSStore is the CRD implementation of a Traefik TLS Store. For the time being, only the TLSStore named default is supported. This means that you cannot have two stores that are named default in different Kubernetes namespaces. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#certificates-stores + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#certificates-stores properties: apiVersion: description: |- @@ -2793,7 +2793,7 @@ spec: TraefikService object allows to: - Apply weight to Services on load-balancing - Mirror traffic on services - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/traefikservice/ properties: apiVersion: description: |- @@ -2985,7 +2985,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -3314,7 +3314,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -3463,7 +3463,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -3697,7 +3697,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -3767,7 +3767,7 @@ spec: sticky: description: |- Sticky defines whether sticky sessions are enabled. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/#stickiness-and-load-balancing + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/traefikservice/#stickiness-and-load-balancing properties: cookie: description: Cookie defines the sticky cookie configuration. diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml index 2d09a2a16..7907daadc 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml @@ -25,7 +25,7 @@ spec: serviceAccountName: traefik-controller containers: - name: traefik - image: traefik:v3.5 + image: traefik:v3.6 args: - --entryPoints.web.address=:80 - --entryPoints.websecure.address=:443 diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml index 0a8247d64..9149b8607 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml @@ -43,7 +43,7 @@ spec: description: |- EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -52,7 +52,7 @@ spec: description: |- ParentRefs defines references to parent IngressRoute resources for multi-layer routing. When set, this IngressRoute's routers will be children of the referenced parent IngressRoute's routers. - More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#parentrefs + More info: https://doc.traefik.io/traefik/v3.6/routing/routers/#parentrefs items: description: IngressRouteRef is a reference to an IngressRoute resource. properties: @@ -84,12 +84,12 @@ spec: match: description: |- Match defines the router's rule. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/ type: string middlewares: description: |- Middlewares defines the list of references to Middleware resources. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/middleware/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/middleware/ items: description: MiddlewareRef is a reference to a Middleware resource. @@ -109,7 +109,7 @@ spec: observability: description: |- Observability defines the observability configuration for a router. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/observability/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/observability/ properties: accessLogs: description: AccessLogs enables access logs for this router. @@ -132,7 +132,7 @@ spec: priority: description: |- Priority defines the router's priority. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#priority + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/#priority maximum: 9223372036854775000 type: integer services: @@ -302,7 +302,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -373,7 +373,7 @@ spec: syntax: description: |- Syntax defines the router's rule syntax. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#rulesyntax + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/#rulesyntax Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. type: string required: @@ -383,18 +383,18 @@ spec: tls: description: |- TLS defines the TLS configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/router/#tls + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/router/#tls properties: certResolver: description: |- CertResolver defines the name of the certificate resolver to use. Cert resolvers have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/tls/certificate-resolvers/acme/ type: string domains: description: |- Domains defines the list of domains that will be used to issue certificates. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#domains + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#domains items: description: Domain holds a domain name with SANs. properties: @@ -413,17 +413,17 @@ spec: description: |- Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. If not defined, the `default` TLSOption is used. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-options/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-options/ properties: name: description: |- Name defines the name of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsoption/ type: string namespace: description: |- Namespace defines the namespace of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsoption/ type: string required: - name @@ -440,12 +440,12 @@ spec: name: description: |- Name defines the name of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsstore/ type: string namespace: description: |- Namespace defines the namespace of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsstore/ type: string required: - name diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml index 37a02f2fb..6bbfae936 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml @@ -43,7 +43,7 @@ spec: description: |- EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -56,7 +56,7 @@ spec: match: description: |- Match defines the router's rule. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/ type: string middlewares: description: Middlewares defines the list of references to MiddlewareTCP @@ -80,7 +80,7 @@ spec: priority: description: |- Priority defines the router's priority. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#priority + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/#priority maximum: 9223372036854775000 type: integer services: @@ -122,7 +122,7 @@ spec: proxyProtocol: description: |- ProxyProtocol defines the PROXY protocol configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/service/#proxy-protocol + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/service/#proxy-protocol Deprecated: ProxyProtocol will not be supported in future APIVersions, please use ServersTransport to configure ProxyProtocol instead. properties: version: @@ -164,7 +164,7 @@ spec: syntax: description: |- Syntax defines the router's rule syntax. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#rulesyntax + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/#rulesyntax Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. enum: - v3 @@ -177,18 +177,18 @@ spec: tls: description: |- TLS defines the TLS configuration on a layer 4 / TCP Route. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/router/#tls + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/router/#tls properties: certResolver: description: |- CertResolver defines the name of the certificate resolver to use. Cert resolvers have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/tls/certificate-resolvers/acme/ type: string domains: description: |- Domains defines the list of domains that will be used to issue certificates. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#domains + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/tls/#domains items: description: Domain holds a domain name with SANs. properties: @@ -207,7 +207,7 @@ spec: description: |- Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. If not defined, the `default` TLSOption is used. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#tls-options + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/tls/#tls-options properties: name: description: Name defines the name of the referenced Traefik diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml index dc08ce018..2b7e66887 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml @@ -43,7 +43,7 @@ spec: description: |- EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ Default: all. items: type: string diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index 9abd70ea4..68b9f14f1 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -19,7 +19,7 @@ spec: openAPIV3Schema: description: |- Middleware is the CRD implementation of a Traefik Middleware. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/overview/ properties: apiVersion: description: |- @@ -45,7 +45,7 @@ spec: description: |- AddPrefix holds the add prefix middleware configuration. This middleware updates the path of a request before forwarding it. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/addprefix/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/addprefix/ properties: prefix: description: |- @@ -60,12 +60,12 @@ spec: description: |- BasicAuth holds the basic auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/basicauth/ properties: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/basicauth/#headerfield type: string realm: description: |- @@ -86,7 +86,7 @@ spec: description: |- Buffering holds the buffering middleware configuration. This middleware retries or limits the size of requests that can be forwarded to backends. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/buffering/#maxrequestbodybytes + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/buffering/#maxrequestbodybytes properties: maxRequestBodyBytes: description: |- @@ -118,14 +118,14 @@ spec: description: |- RetryExpression defines the retry conditions. It is a logical combination of functions with operators AND (&&) and OR (||). - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/buffering/#retryexpression + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/buffering/#retryexpression type: string type: object chain: description: |- Chain holds the configuration of the chain middleware. This middleware enables to define reusable combinations of other pieces of middleware. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/chain/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/chain/ properties: middlewares: description: Middlewares is the list of MiddlewareRef which composes @@ -188,7 +188,7 @@ spec: description: |- Compress holds the compress middleware configuration. This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/compress/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/compress/ properties: defaultEncoding: description: DefaultEncoding specifies the default encoding if @@ -238,12 +238,12 @@ spec: description: |- DigestAuth holds the digest auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/digestauth/ properties: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/digestauth/#headerfield type: string realm: description: |- @@ -263,7 +263,7 @@ spec: description: |- ErrorPage holds the custom error middleware configuration. This middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/errorpages/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/errorpages/ properties: query: description: |- @@ -275,7 +275,7 @@ spec: service: description: |- Service defines the reference to a Kubernetes Service that will serve the error page. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/errorpages/#service + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/errorpages/#service properties: healthCheck: description: Healthcheck defines health checks for ExternalName @@ -436,7 +436,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -525,7 +525,7 @@ spec: description: |- ForwardAuth holds the forward auth middleware configuration. This middleware delegates the request authentication to a Service. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/ properties: addAuthCookiesToResponse: description: AddAuthCookiesToResponse defines the list of cookies @@ -553,7 +553,7 @@ spec: authResponseHeadersRegex: description: |- AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex type: string forwardBody: description: ForwardBody defines whether to send the request body @@ -562,7 +562,7 @@ spec: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/#headerfield type: string maxBodySize: description: MaxBodySize defines the maximum body size in bytes @@ -624,7 +624,7 @@ spec: description: |- Headers holds the headers middleware configuration. This middleware manages the requests and responses headers. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/headers/#customrequestheaders + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/headers/#customrequestheaders properties: accessControlAllowCredentials: description: AccessControlAllowCredentials defines whether the @@ -796,7 +796,7 @@ spec: description: |- InFlightReq holds the in-flight request middleware configuration. This middleware limits the number of requests being processed and served concurrently. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/inflightreq/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/inflightreq/ properties: amount: description: |- @@ -810,12 +810,12 @@ spec: SourceCriterion defines what criterion is used to group requests as originating from a common source. If several strategies are defined at the same time, an error will be raised. If none are set, the default is to use the requestHost. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/inflightreq/#sourcecriterion + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/inflightreq/#sourcecriterion properties: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -851,12 +851,12 @@ spec: description: |- IPAllowList holds the IP allowlist middleware configuration. This middleware limits allowed requests based on the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/ properties: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -894,7 +894,7 @@ spec: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -925,7 +925,7 @@ spec: description: |- PassTLSClientCert holds the pass TLS client cert middleware configuration. This middleware adds the selected data from the passed client TLS certificate to a header. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/passtlsclientcert/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/passtlsclientcert/ properties: info: description: Info selects the specific client certificate details @@ -1028,13 +1028,13 @@ spec: x-kubernetes-preserve-unknown-fields: true description: |- Plugin defines the middleware plugin configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/#community-middlewares + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/overview/#community-middlewares type: object rateLimit: description: |- RateLimit holds the rate limit configuration. This middleware ensures that services will receive a fair amount of requests, and allows one to define what fair is. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/ratelimit/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/ratelimit/ properties: average: description: |- @@ -1153,7 +1153,7 @@ spec: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1189,7 +1189,7 @@ spec: description: |- RedirectRegex holds the redirect regex middleware configuration. This middleware redirects a request using regex matching and replacement. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/redirectregex/#regex + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectregex/#regex properties: permanent: description: Permanent defines whether the redirection is permanent @@ -1208,7 +1208,7 @@ spec: description: |- RedirectScheme holds the redirect scheme middleware configuration. This middleware redirects requests from a scheme/port to another. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/redirectscheme/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectscheme/ properties: permanent: description: Permanent defines whether the redirection is permanent @@ -1225,7 +1225,7 @@ spec: description: |- ReplacePath holds the replace path middleware configuration. This middleware replaces the path of the request URL and store the original path in an X-Replaced-Path header. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/replacepath/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/replacepath/ properties: path: description: Path defines the path to use as replacement in the @@ -1236,7 +1236,7 @@ spec: description: |- ReplacePathRegex holds the replace path regex middleware configuration. This middleware replaces the path of a URL using regex matching and replacement. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/replacepathregex/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/replacepathregex/ properties: regex: description: Regex defines the regular expression used to match @@ -1252,7 +1252,7 @@ spec: Retry holds the retry middleware configuration. This middleware reissues requests a given number of times to a backend server if that server does not reply. As soon as the server answers, the middleware stops retrying, regardless of the response status. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/retry/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/retry/ properties: attempts: description: Attempts defines how many times the request should @@ -1276,7 +1276,7 @@ spec: description: |- StripPrefix holds the strip prefix middleware configuration. This middleware removes the specified prefixes from the URL path. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/stripprefix/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/stripprefix/ properties: forceSlash: description: |- @@ -1295,7 +1295,7 @@ spec: description: |- StripPrefixRegex holds the strip prefix regex middleware configuration. This middleware removes the matching prefixes from the URL path. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/stripprefixregex/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/stripprefixregex/ properties: regex: description: Regex defines the regular expression to match the diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml index 5f7604923..9dbf04759 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml @@ -19,7 +19,7 @@ spec: openAPIV3Schema: description: |- MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/overview/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/middlewares/overview/ properties: apiVersion: description: |- @@ -56,7 +56,7 @@ spec: description: |- IPAllowList defines the IPAllowList middleware configuration. This middleware accepts/refuses connections based on the client IP. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipallowlist/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/middlewares/ipallowlist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -70,7 +70,7 @@ spec: IPWhiteList defines the IPWhiteList middleware configuration. This middleware accepts/refuses connections based on the client IP. Deprecated: please use IPAllowList instead. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipwhitelist/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/middlewares/ipwhitelist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of diff --git a/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml b/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml index 0828291ee..3e22e0107 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml @@ -21,7 +21,7 @@ spec: ServersTransport is the CRD implementation of a ServersTransport. If no serversTransport is specified, the default@internal will be used. The default@internal serversTransport is created from the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/serverstransport/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/serverstransport/ properties: apiVersion: description: |- diff --git a/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml index f8be2b1d9..23c39ebae 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml @@ -21,7 +21,7 @@ spec: ServersTransportTCP is the CRD implementation of a TCPServersTransport. If no tcpServersTransport is specified, a default one named default@internal will be used. The default@internal tcpServersTransport can be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/serverstransport/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/serverstransport/ properties: apiVersion: description: |- diff --git a/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml b/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml index c32974fae..50df90027 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml @@ -19,7 +19,7 @@ spec: openAPIV3Schema: description: |- TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#tls-options + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#tls-options properties: apiVersion: description: |- @@ -44,14 +44,14 @@ spec: alpnProtocols: description: |- ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#alpn-protocols + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#alpn-protocols items: type: string type: array cipherSuites: description: |- CipherSuites defines the list of supported cipher suites for TLS versions up to TLS 1.2. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#cipher-suites + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#cipher-suites items: type: string type: array @@ -79,7 +79,7 @@ spec: curvePreferences: description: |- CurvePreferences defines the preferred elliptic curves. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#curve-preferences + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#curve-preferences items: type: string type: array diff --git a/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml b/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml index 779c93908..180f406da 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml @@ -21,7 +21,7 @@ spec: TLSStore is the CRD implementation of a Traefik TLS Store. For the time being, only the TLSStore named default is supported. This means that you cannot have two stores that are named default in different Kubernetes namespaces. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#certificates-stores + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#certificates-stores properties: apiVersion: description: |- diff --git a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml index f0f44c1e0..965fb626a 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml @@ -22,7 +22,7 @@ spec: TraefikService object allows to: - Apply weight to Services on load-balancing - Mirror traffic on services - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/traefikservice/ properties: apiVersion: description: |- @@ -214,7 +214,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -543,7 +543,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -692,7 +692,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -926,7 +926,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -996,7 +996,7 @@ spec: sticky: description: |- Sticky defines whether sticky sessions are enabled. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/#stickiness-and-load-balancing + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/traefikservice/#stickiness-and-load-balancing properties: cookie: description: Cookie defines the sticky cookie configuration. diff --git a/docs/content/reference/install-configuration/observability/logs-and-accesslogs.md b/docs/content/reference/install-configuration/observability/logs-and-accesslogs.md index 7fab52d14..bc4f81439 100644 --- a/docs/content/reference/install-configuration/observability/logs-and-accesslogs.md +++ b/docs/content/reference/install-configuration/observability/logs-and-accesslogs.md @@ -397,7 +397,7 @@ Example utilizing Docker Compose: ```yaml services: traefik: - image: traefik:v3.5 + image: traefik:v3.6 environment: - TZ=US/Alaska command: diff --git a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-crd.md b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-crd.md index 50ea7ff2d..9965dfebf 100644 --- a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-crd.md +++ b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-crd.md @@ -20,10 +20,10 @@ When you install Traefik without using the Helm Chart, or when you are upgrading ```bash # Install Traefik Resource Definitions: -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml # Install RBAC for Traefik: -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml ``` ## Configuration Example diff --git a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md index a1d76eae9..4b4a0a534 100644 --- a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md +++ b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md @@ -34,7 +34,7 @@ For more details, check out the conformance [report](https://github.com/kubernet ```bash # Install Traefik RBACs. - kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml + kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml ``` ## Configuration Example diff --git a/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md b/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md index af98d1b2b..8f0b34a7c 100644 --- a/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md +++ b/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md @@ -138,7 +138,7 @@ which in turn will create the resulting routers, services, handlers, etc. serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v3.5 + image: traefik:v3.6 args: - --entryPoints.web.address=:80 - --providers.kubernetesingressnginx diff --git a/docs/content/reference/routing-configuration/kubernetes/ingress.md b/docs/content/reference/routing-configuration/kubernetes/ingress.md index a9b0aab71..2538031fa 100644 --- a/docs/content/reference/routing-configuration/kubernetes/ingress.md +++ b/docs/content/reference/routing-configuration/kubernetes/ingress.md @@ -402,7 +402,7 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v3.5 + image: traefik:v3.6 args: - --entryPoints.websecure.address=:443 - --entryPoints.websecure.http.tls diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index 095691e14..e9fa28b55 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -48,7 +48,7 @@ The Kubernetes Ingress Controller, The Custom Resource Way. serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v3.5 + image: traefik:v3.6 args: - --log.level=DEBUG - --api diff --git a/docs/content/routing/providers/kubernetes-ingress.md b/docs/content/routing/providers/kubernetes-ingress.md index ff02ae929..99037868b 100644 --- a/docs/content/routing/providers/kubernetes-ingress.md +++ b/docs/content/routing/providers/kubernetes-ingress.md @@ -130,7 +130,7 @@ which in turn will create the resulting routers, services, handlers, etc. serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v3.5 + image: traefik:v3.6 args: - --entryPoints.web.address=:80 - --providers.kubernetesingress @@ -593,7 +593,7 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v3.5 + image: traefik:v3.6 args: - --entryPoints.websecure.address=:443 - --entryPoints.websecure.http.tls @@ -786,7 +786,7 @@ For more options, please refer to the available [annotations](#on-ingress). serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v3.5 + image: traefik:v3.6 args: - --entryPoints.websecure.address=:443 - --providers.kubernetesingress diff --git a/docs/content/user-guides/crd-acme/03-deployments.yml b/docs/content/user-guides/crd-acme/03-deployments.yml index b2d15b3b7..7318b0daf 100644 --- a/docs/content/user-guides/crd-acme/03-deployments.yml +++ b/docs/content/user-guides/crd-acme/03-deployments.yml @@ -26,7 +26,7 @@ spec: serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v3.5 + image: traefik:v3.6 args: - --api.insecure - --accesslog diff --git a/docs/content/user-guides/crd-acme/index.md b/docs/content/user-guides/crd-acme/index.md index 49c9db013..f408b03c5 100644 --- a/docs/content/user-guides/crd-acme/index.md +++ b/docs/content/user-guides/crd-acme/index.md @@ -49,10 +49,10 @@ and the RBAC authorization resources which will be referenced through the `servi ```bash # Install Traefik Resource Definitions: -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml # Install RBAC for Traefik: -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml ``` ### Services @@ -60,7 +60,7 @@ kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/con Then, the services. One for Traefik itself, and one for the app it routes for, i.e. in this case our demo HTTP server: [whoami](https://github.com/traefik/whoami). ```bash -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/user-guides/crd-acme/02-services.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/user-guides/crd-acme/02-services.yml ``` ```yaml @@ -73,7 +73,7 @@ Next, the deployments, i.e. the actual pods behind the services. Again, one pod for Traefik, and one for the whoami app. ```bash -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/user-guides/crd-acme/03-deployments.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/user-guides/crd-acme/03-deployments.yml ``` ```yaml @@ -100,7 +100,7 @@ Look it up. We can now finally apply the actual ingressRoutes, with: ```bash -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/user-guides/crd-acme/04-ingressroutes.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/user-guides/crd-acme/04-ingressroutes.yml ``` ```yaml @@ -126,7 +126,7 @@ Nowadays, TLS v1.0 and v1.1 are deprecated. In order to force TLS v1.2 or later on all your IngressRoute, you can define the `default` TLSOption: ```bash -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/user-guides/crd-acme/05-tlsoption.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/user-guides/crd-acme/05-tlsoption.yml ``` ```yaml diff --git a/docs/content/user-guides/crd-acme/k3s.yml b/docs/content/user-guides/crd-acme/k3s.yml index 23190c461..ecdd5d60d 100644 --- a/docs/content/user-guides/crd-acme/k3s.yml +++ b/docs/content/user-guides/crd-acme/k3s.yml @@ -26,5 +26,5 @@ node: - K3S_CLUSTER_SECRET=somethingtotallyrandom volumes: # this is where you would place a alternative traefik image (saved as a .tar file with - # 'docker save'), if you want to use it, instead of the traefik:v3.5 image. + # 'docker save'), if you want to use it, instead of the traefik:v3.6 image. - /somewhere/on/your/host/custom-image:/var/lib/rancher/k3s/agent/images diff --git a/docs/content/user-guides/docker-compose/acme-dns/docker-compose.yml b/docs/content/user-guides/docker-compose/acme-dns/docker-compose.yml index 2bcb322e6..d2e93d105 100644 --- a/docs/content/user-guides/docker-compose/acme-dns/docker-compose.yml +++ b/docs/content/user-guides/docker-compose/acme-dns/docker-compose.yml @@ -1,7 +1,7 @@ services: traefik: - image: "traefik:v3.5" + image: "traefik:v3.6" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/acme-dns/docker-compose_secrets.yml b/docs/content/user-guides/docker-compose/acme-dns/docker-compose_secrets.yml index 63487e067..752f28755 100644 --- a/docs/content/user-guides/docker-compose/acme-dns/docker-compose_secrets.yml +++ b/docs/content/user-guides/docker-compose/acme-dns/docker-compose_secrets.yml @@ -11,7 +11,7 @@ secrets: services: traefik: - image: "traefik:v3.5" + image: "traefik:v3.6" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/acme-http/docker-compose.yml b/docs/content/user-guides/docker-compose/acme-http/docker-compose.yml index 3deb01ca2..7684098d2 100644 --- a/docs/content/user-guides/docker-compose/acme-http/docker-compose.yml +++ b/docs/content/user-guides/docker-compose/acme-http/docker-compose.yml @@ -1,7 +1,7 @@ services: traefik: - image: "traefik:v3.5" + image: "traefik:v3.6" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/acme-tls/docker-compose.yml b/docs/content/user-guides/docker-compose/acme-tls/docker-compose.yml index c95bb5eb9..b91269fe3 100644 --- a/docs/content/user-guides/docker-compose/acme-tls/docker-compose.yml +++ b/docs/content/user-guides/docker-compose/acme-tls/docker-compose.yml @@ -1,7 +1,7 @@ services: traefik: - image: "traefik:v3.5" + image: "traefik:v3.6" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/basic-example/docker-compose.yml b/docs/content/user-guides/docker-compose/basic-example/docker-compose.yml index 842478ac8..fba201c4a 100644 --- a/docs/content/user-guides/docker-compose/basic-example/docker-compose.yml +++ b/docs/content/user-guides/docker-compose/basic-example/docker-compose.yml @@ -1,7 +1,7 @@ services: traefik: - image: "traefik:v3.5" + image: "traefik:v3.6" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/basic-example/index.md b/docs/content/user-guides/docker-compose/basic-example/index.md index 6aef0c804..60a01516e 100644 --- a/docs/content/user-guides/docker-compose/basic-example/index.md +++ b/docs/content/user-guides/docker-compose/basic-example/index.md @@ -29,7 +29,7 @@ Create a `docker-compose.yml` file with the following content: services: traefik: - image: "traefik:v3.5" + image: "traefik:v3.6" ... networks: - traefiknet diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 8847677d7..1f2ef1382 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -43,7 +43,7 @@ spec: description: |- EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -52,7 +52,7 @@ spec: description: |- ParentRefs defines references to parent IngressRoute resources for multi-layer routing. When set, this IngressRoute's routers will be children of the referenced parent IngressRoute's routers. - More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#parentrefs + More info: https://doc.traefik.io/traefik/v3.6/routing/routers/#parentrefs items: description: IngressRouteRef is a reference to an IngressRoute resource. properties: @@ -84,12 +84,12 @@ spec: match: description: |- Match defines the router's rule. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/ type: string middlewares: description: |- Middlewares defines the list of references to Middleware resources. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/middleware/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/middleware/ items: description: MiddlewareRef is a reference to a Middleware resource. @@ -109,7 +109,7 @@ spec: observability: description: |- Observability defines the observability configuration for a router. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/observability/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/observability/ properties: accessLogs: description: AccessLogs enables access logs for this router. @@ -132,7 +132,7 @@ spec: priority: description: |- Priority defines the router's priority. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#priority + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/#priority maximum: 9223372036854775000 type: integer services: @@ -302,7 +302,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -373,7 +373,7 @@ spec: syntax: description: |- Syntax defines the router's rule syntax. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#rulesyntax + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/#rulesyntax Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. type: string required: @@ -383,18 +383,18 @@ spec: tls: description: |- TLS defines the TLS configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/router/#tls + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/router/#tls properties: certResolver: description: |- CertResolver defines the name of the certificate resolver to use. Cert resolvers have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/tls/certificate-resolvers/acme/ type: string domains: description: |- Domains defines the list of domains that will be used to issue certificates. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#domains + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#domains items: description: Domain holds a domain name with SANs. properties: @@ -413,17 +413,17 @@ spec: description: |- Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. If not defined, the `default` TLSOption is used. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-options/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-options/ properties: name: description: |- Name defines the name of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsoption/ type: string namespace: description: |- Namespace defines the namespace of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsoption/ type: string required: - name @@ -440,12 +440,12 @@ spec: name: description: |- Name defines the name of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsstore/ type: string namespace: description: |- Namespace defines the namespace of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsstore/ type: string required: - name @@ -505,7 +505,7 @@ spec: description: |- EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -518,7 +518,7 @@ spec: match: description: |- Match defines the router's rule. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/ type: string middlewares: description: Middlewares defines the list of references to MiddlewareTCP @@ -542,7 +542,7 @@ spec: priority: description: |- Priority defines the router's priority. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#priority + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/#priority maximum: 9223372036854775000 type: integer services: @@ -584,7 +584,7 @@ spec: proxyProtocol: description: |- ProxyProtocol defines the PROXY protocol configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/service/#proxy-protocol + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/service/#proxy-protocol Deprecated: ProxyProtocol will not be supported in future APIVersions, please use ServersTransport to configure ProxyProtocol instead. properties: version: @@ -626,7 +626,7 @@ spec: syntax: description: |- Syntax defines the router's rule syntax. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#rulesyntax + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/#rulesyntax Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. enum: - v3 @@ -639,18 +639,18 @@ spec: tls: description: |- TLS defines the TLS configuration on a layer 4 / TCP Route. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/router/#tls + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/router/#tls properties: certResolver: description: |- CertResolver defines the name of the certificate resolver to use. Cert resolvers have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/tls/certificate-resolvers/acme/ type: string domains: description: |- Domains defines the list of domains that will be used to issue certificates. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#domains + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/tls/#domains items: description: Domain holds a domain name with SANs. properties: @@ -669,7 +669,7 @@ spec: description: |- Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. If not defined, the `default` TLSOption is used. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#tls-options + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/tls/#tls-options properties: name: description: Name defines the name of the referenced Traefik @@ -761,7 +761,7 @@ spec: description: |- EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -849,7 +849,7 @@ spec: openAPIV3Schema: description: |- Middleware is the CRD implementation of a Traefik Middleware. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/overview/ properties: apiVersion: description: |- @@ -875,7 +875,7 @@ spec: description: |- AddPrefix holds the add prefix middleware configuration. This middleware updates the path of a request before forwarding it. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/addprefix/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/addprefix/ properties: prefix: description: |- @@ -890,12 +890,12 @@ spec: description: |- BasicAuth holds the basic auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/basicauth/ properties: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/basicauth/#headerfield type: string realm: description: |- @@ -916,7 +916,7 @@ spec: description: |- Buffering holds the buffering middleware configuration. This middleware retries or limits the size of requests that can be forwarded to backends. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/buffering/#maxrequestbodybytes + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/buffering/#maxrequestbodybytes properties: maxRequestBodyBytes: description: |- @@ -948,14 +948,14 @@ spec: description: |- RetryExpression defines the retry conditions. It is a logical combination of functions with operators AND (&&) and OR (||). - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/buffering/#retryexpression + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/buffering/#retryexpression type: string type: object chain: description: |- Chain holds the configuration of the chain middleware. This middleware enables to define reusable combinations of other pieces of middleware. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/chain/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/chain/ properties: middlewares: description: Middlewares is the list of MiddlewareRef which composes @@ -1018,7 +1018,7 @@ spec: description: |- Compress holds the compress middleware configuration. This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/compress/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/compress/ properties: defaultEncoding: description: DefaultEncoding specifies the default encoding if @@ -1068,12 +1068,12 @@ spec: description: |- DigestAuth holds the digest auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/digestauth/ properties: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/digestauth/#headerfield type: string realm: description: |- @@ -1093,7 +1093,7 @@ spec: description: |- ErrorPage holds the custom error middleware configuration. This middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/errorpages/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/errorpages/ properties: query: description: |- @@ -1105,7 +1105,7 @@ spec: service: description: |- Service defines the reference to a Kubernetes Service that will serve the error page. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/errorpages/#service + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/errorpages/#service properties: healthCheck: description: Healthcheck defines health checks for ExternalName @@ -1266,7 +1266,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -1355,7 +1355,7 @@ spec: description: |- ForwardAuth holds the forward auth middleware configuration. This middleware delegates the request authentication to a Service. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/ properties: addAuthCookiesToResponse: description: AddAuthCookiesToResponse defines the list of cookies @@ -1383,7 +1383,7 @@ spec: authResponseHeadersRegex: description: |- AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex type: string forwardBody: description: ForwardBody defines whether to send the request body @@ -1392,7 +1392,7 @@ spec: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/#headerfield type: string maxBodySize: description: MaxBodySize defines the maximum body size in bytes @@ -1454,7 +1454,7 @@ spec: description: |- Headers holds the headers middleware configuration. This middleware manages the requests and responses headers. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/headers/#customrequestheaders + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/headers/#customrequestheaders properties: accessControlAllowCredentials: description: AccessControlAllowCredentials defines whether the @@ -1626,7 +1626,7 @@ spec: description: |- InFlightReq holds the in-flight request middleware configuration. This middleware limits the number of requests being processed and served concurrently. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/inflightreq/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/inflightreq/ properties: amount: description: |- @@ -1640,12 +1640,12 @@ spec: SourceCriterion defines what criterion is used to group requests as originating from a common source. If several strategies are defined at the same time, an error will be raised. If none are set, the default is to use the requestHost. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/inflightreq/#sourcecriterion + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/inflightreq/#sourcecriterion properties: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1681,12 +1681,12 @@ spec: description: |- IPAllowList holds the IP allowlist middleware configuration. This middleware limits allowed requests based on the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/ properties: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1724,7 +1724,7 @@ spec: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1755,7 +1755,7 @@ spec: description: |- PassTLSClientCert holds the pass TLS client cert middleware configuration. This middleware adds the selected data from the passed client TLS certificate to a header. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/passtlsclientcert/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/passtlsclientcert/ properties: info: description: Info selects the specific client certificate details @@ -1858,13 +1858,13 @@ spec: x-kubernetes-preserve-unknown-fields: true description: |- Plugin defines the middleware plugin configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/#community-middlewares + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/overview/#community-middlewares type: object rateLimit: description: |- RateLimit holds the rate limit configuration. This middleware ensures that services will receive a fair amount of requests, and allows one to define what fair is. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/ratelimit/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/ratelimit/ properties: average: description: |- @@ -1983,7 +1983,7 @@ spec: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -2019,7 +2019,7 @@ spec: description: |- RedirectRegex holds the redirect regex middleware configuration. This middleware redirects a request using regex matching and replacement. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/redirectregex/#regex + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectregex/#regex properties: permanent: description: Permanent defines whether the redirection is permanent @@ -2038,7 +2038,7 @@ spec: description: |- RedirectScheme holds the redirect scheme middleware configuration. This middleware redirects requests from a scheme/port to another. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/redirectscheme/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectscheme/ properties: permanent: description: Permanent defines whether the redirection is permanent @@ -2055,7 +2055,7 @@ spec: description: |- ReplacePath holds the replace path middleware configuration. This middleware replaces the path of the request URL and store the original path in an X-Replaced-Path header. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/replacepath/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/replacepath/ properties: path: description: Path defines the path to use as replacement in the @@ -2066,7 +2066,7 @@ spec: description: |- ReplacePathRegex holds the replace path regex middleware configuration. This middleware replaces the path of a URL using regex matching and replacement. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/replacepathregex/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/replacepathregex/ properties: regex: description: Regex defines the regular expression used to match @@ -2082,7 +2082,7 @@ spec: Retry holds the retry middleware configuration. This middleware reissues requests a given number of times to a backend server if that server does not reply. As soon as the server answers, the middleware stops retrying, regardless of the response status. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/retry/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/retry/ properties: attempts: description: Attempts defines how many times the request should @@ -2106,7 +2106,7 @@ spec: description: |- StripPrefix holds the strip prefix middleware configuration. This middleware removes the specified prefixes from the URL path. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/stripprefix/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/stripprefix/ properties: forceSlash: description: |- @@ -2125,7 +2125,7 @@ spec: description: |- StripPrefixRegex holds the strip prefix regex middleware configuration. This middleware removes the matching prefixes from the URL path. - More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/stripprefixregex/ + More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/stripprefixregex/ properties: regex: description: Regex defines the regular expression to match the @@ -2162,7 +2162,7 @@ spec: openAPIV3Schema: description: |- MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/overview/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/middlewares/overview/ properties: apiVersion: description: |- @@ -2199,7 +2199,7 @@ spec: description: |- IPAllowList defines the IPAllowList middleware configuration. This middleware accepts/refuses connections based on the client IP. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipallowlist/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/middlewares/ipallowlist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -2213,7 +2213,7 @@ spec: IPWhiteList defines the IPWhiteList middleware configuration. This middleware accepts/refuses connections based on the client IP. Deprecated: please use IPAllowList instead. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipwhitelist/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/middlewares/ipwhitelist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -2252,7 +2252,7 @@ spec: ServersTransport is the CRD implementation of a ServersTransport. If no serversTransport is specified, the default@internal will be used. The default@internal serversTransport is created from the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/serverstransport/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/serverstransport/ properties: apiVersion: description: |- @@ -2421,7 +2421,7 @@ spec: ServersTransportTCP is the CRD implementation of a TCPServersTransport. If no tcpServersTransport is specified, a default one named default@internal will be used. The default@internal tcpServersTransport can be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/serverstransport/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/serverstransport/ properties: apiVersion: description: |- @@ -2575,7 +2575,7 @@ spec: openAPIV3Schema: description: |- TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#tls-options + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#tls-options properties: apiVersion: description: |- @@ -2600,14 +2600,14 @@ spec: alpnProtocols: description: |- ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#alpn-protocols + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#alpn-protocols items: type: string type: array cipherSuites: description: |- CipherSuites defines the list of supported cipher suites for TLS versions up to TLS 1.2. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#cipher-suites + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#cipher-suites items: type: string type: array @@ -2635,7 +2635,7 @@ spec: curvePreferences: description: |- CurvePreferences defines the preferred elliptic curves. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#curve-preferences + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#curve-preferences items: type: string type: array @@ -2695,7 +2695,7 @@ spec: TLSStore is the CRD implementation of a Traefik TLS Store. For the time being, only the TLSStore named default is supported. This means that you cannot have two stores that are named default in different Kubernetes namespaces. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#certificates-stores + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#certificates-stores properties: apiVersion: description: |- @@ -2793,7 +2793,7 @@ spec: TraefikService object allows to: - Apply weight to Services on load-balancing - Mirror traffic on services - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/ + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/traefikservice/ properties: apiVersion: description: |- @@ -2985,7 +2985,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -3314,7 +3314,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -3463,7 +3463,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -3697,7 +3697,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -3767,7 +3767,7 @@ spec: sticky: description: |- Sticky defines whether sticky sessions are enabled. - More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/#stickiness-and-load-balancing + More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/traefikservice/#stickiness-and-load-balancing properties: cookie: description: Cookie defines the sticky cookie configuration. diff --git a/pkg/cli/deprecation.go b/pkg/cli/deprecation.go index c7c262ba7..371cefc5b 100644 --- a/pkg/cli/deprecation.go +++ b/pkg/cli/deprecation.go @@ -195,7 +195,7 @@ func (c *configuration) deprecationNotice(logger zerolog.Logger) bool { if c.Pilot != nil { incompatible = true logger.Error().Msg("Pilot configuration has been removed in v3, please remove all Pilot-related static configuration for Traefik to start." + - " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#pilot") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#pilot") } incompatibleCore := c.Core.deprecationNotice(logger) @@ -213,7 +213,7 @@ func (c *core) deprecationNotice(logger zerolog.Logger) bool { if c != nil && c.DefaultRuleSyntax != "" { logger.Error().Msg("`Core.DefaultRuleSyntax` option has been deprecated in v3.4, and will be removed in the next major version." + " Please consider migrating all router rules to v3 syntax." + - " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#rule-syntax") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v3/#rule-syntax") } return false @@ -243,13 +243,13 @@ func (p *providers) deprecationNotice(logger zerolog.Logger) bool { if p.Marathon != nil { incompatible = true logger.Error().Msg("Marathon provider has been removed in v3, please remove all Marathon-related static configuration for Traefik to start." + - " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#marathon-provider") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#marathon-provider") } if p.Rancher != nil { incompatible = true logger.Error().Msg("Rancher provider has been removed in v3, please remove all Rancher-related static configuration for Traefik to start." + - " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#rancher-v1-provider") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#rancher-v1-provider") } dockerIncompatible := p.Docker.deprecationNotice(logger) @@ -291,14 +291,14 @@ func (d *docker) deprecationNotice(logger zerolog.Logger) bool { if d.SwarmMode != nil { incompatible = true logger.Error().Msg("Docker provider `swarmMode` option has been removed in v3, please use the Swarm Provider instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#docker-docker-swarm") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#docker-docker-swarm") } if d.TLS != nil && d.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("Docker provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#tlscaoptional") } return incompatible @@ -339,7 +339,7 @@ func (e *etcd) deprecationNotice(logger zerolog.Logger) bool { incompatible = true logger.Error().Msg("ETCD provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_3") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#tlscaoptional_3") } return incompatible @@ -360,7 +360,7 @@ func (r *redis) deprecationNotice(logger zerolog.Logger) bool { incompatible = true logger.Error().Msg("Redis provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_4") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#tlscaoptional_4") } return incompatible @@ -381,14 +381,14 @@ func (c *consul) deprecationNotice(logger zerolog.Logger) bool { if c.Namespace != nil { incompatible = true logger.Error().Msg("Consul provider `namespace` option has been removed, please use the `namespaces` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#consul-provider") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#consul-provider") } if c.TLS != nil && c.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("Consul provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_1") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#tlscaoptional_1") } return incompatible @@ -413,14 +413,14 @@ func (c *consulCatalog) deprecationNotice(logger zerolog.Logger) bool { if c.Namespace != nil { incompatible = true logger.Error().Msg("ConsulCatalog provider `namespace` option has been removed, please use the `namespaces` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#consulcatalog-provider") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#consulcatalog-provider") } if c.Endpoint != nil && c.Endpoint.TLS != nil && c.Endpoint.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("ConsulCatalog provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#endpointtlscaoptional") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#endpointtlscaoptional") } return incompatible @@ -441,14 +441,14 @@ func (n *nomad) deprecationNotice(logger zerolog.Logger) bool { if n.Namespace != nil { incompatible = true logger.Error().Msg("Nomad provider `namespace` option has been removed, please use the `namespaces` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#nomad-provider") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#nomad-provider") } if n.Endpoint != nil && n.Endpoint.TLS != nil && n.Endpoint.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("Nomad provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#endpointtlscaoptional_1") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#endpointtlscaoptional_1") } return incompatible @@ -469,7 +469,7 @@ func (h *http) deprecationNotice(logger zerolog.Logger) bool { incompatible = true logger.Error().Msg("HTTP provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_2") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#tlscaoptional_2") } return incompatible @@ -487,7 +487,7 @@ func (i *ingress) deprecationNotice(logger zerolog.Logger) { if i.DisableIngressClassLookup != nil { logger.Error().Msg("Kubernetes Ingress provider `disableIngressClassLookup` option has been deprecated in v3.1, and will be removed in the next major version." + "Please use the `disableClusterScopeResources` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#ingressclasslookup") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v3/#ingressclasslookup") } } @@ -504,7 +504,7 @@ func (e *experimental) deprecationNotice(logger zerolog.Logger) bool { if e.HTTP3 != nil { logger.Error().Msg("HTTP3 is not an experimental feature in v3 and the associated enablement has been removed." + "Please remove its usage from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3-details/#http3") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3-details/#http3") return true } @@ -512,7 +512,7 @@ func (e *experimental) deprecationNotice(logger zerolog.Logger) bool { if e.KubernetesGateway != nil { logger.Error().Msg("KubernetesGateway provider is not an experimental feature starting with v3.1." + "Please remove its usage from the static configuration." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#gateway-api-kubernetesgateway-provider") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v3/#gateway-api-kubernetesgateway-provider") } return false @@ -539,7 +539,7 @@ func (t *tracing) deprecationNotice(logger zerolog.Logger) bool { if t.SpanNameLimit != nil { incompatible = true logger.Error().Msg("SpanNameLimit option for Tracing has been removed in v3, as Span names are now of a fixed length." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#tracing") } if t.GlobalAttributes != nil { @@ -547,49 +547,49 @@ func (t *tracing) deprecationNotice(logger zerolog.Logger) bool { logger.Error().Msg("`tracing.globalAttributes` option has been deprecated in v3.3, and will be removed in the next major version." + "Please use the `tracing.resourceAttributes` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#tracing-global-attributes") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v3/#tracing-global-attributes") } if t.Jaeger != nil { incompatible = true logger.Error().Msg("Jaeger Tracing backend has been removed in v3, please remove all Jaeger-related Tracing static configuration for Traefik to start." + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#tracing") } if t.Zipkin != nil { incompatible = true logger.Error().Msg("Zipkin Tracing backend has been removed in v3, please remove all Zipkin-related Tracing static configuration for Traefik to start." + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#tracing") } if t.Datadog != nil { incompatible = true logger.Error().Msg("Datadog Tracing backend has been removed in v3, please remove all Datadog-related Tracing static configuration for Traefik to start." + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#tracing") } if t.Instana != nil { incompatible = true logger.Error().Msg("Instana Tracing backend has been removed in v3, please remove all Instana-related Tracing static configuration for Traefik to start." + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#tracing") } if t.Haystack != nil { incompatible = true logger.Error().Msg("Haystack Tracing backend has been removed in v3, please remove all Haystack-related Tracing static configuration for Traefik to start." + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#tracing") } if t.Elastic != nil { incompatible = true logger.Error().Msg("Elastic Tracing backend has been removed in v3, please remove all Elastic-related Tracing static configuration for Traefik to start." + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.6/migration/v2-to-v3/#tracing") } return incompatible diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index 02f371948..466b62c52 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -77,7 +77,7 @@ type ContentType struct { // AddPrefix holds the add prefix middleware configuration. // This middleware updates the path of a request before forwarding it. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/addprefix/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/addprefix/ type AddPrefix struct { // Prefix is the string to add before the current path in the requested URL. // It should include a leading slash (/). @@ -89,7 +89,7 @@ type AddPrefix struct { // BasicAuth holds the basic auth middleware configuration. // This middleware restricts access to your services to known users. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/basicauth/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/basicauth/ type BasicAuth struct { // Users is an array of authorized users. // Each user must be declared using the name:hashed-password format. @@ -104,7 +104,7 @@ type BasicAuth struct { // Default: false. RemoveHeader bool `json:"removeHeader,omitempty" toml:"removeHeader,omitempty" yaml:"removeHeader,omitempty" export:"true"` // HeaderField defines a header field to store the authenticated user. - // More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/basicauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/basicauth/#headerfield HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"` } @@ -112,7 +112,7 @@ type BasicAuth struct { // Buffering holds the buffering middleware configuration. // This middleware retries or limits the size of requests that can be forwarded to backends. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/buffering/#maxrequestbodybytes +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/buffering/#maxrequestbodybytes type Buffering struct { // MaxRequestBodyBytes defines the maximum allowed body size for the request (in bytes). // If the request exceeds the allowed size, it is not forwarded to the service, and the client gets a 413 (Request Entity Too Large) response. @@ -130,7 +130,7 @@ type Buffering struct { MemResponseBodyBytes int64 `json:"memResponseBodyBytes,omitempty" toml:"memResponseBodyBytes,omitempty" yaml:"memResponseBodyBytes,omitempty" export:"true"` // RetryExpression defines the retry conditions. // It is a logical combination of functions with operators AND (&&) and OR (||). - // More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/buffering/#retryexpression + // More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/buffering/#retryexpression RetryExpression string `json:"retryExpression,omitempty" toml:"retryExpression,omitempty" yaml:"retryExpression,omitempty" export:"true"` } @@ -147,7 +147,7 @@ type Chain struct { // CircuitBreaker holds the circuit breaker middleware configuration. // This middleware protects the system from stacking requests to unhealthy services, resulting in cascading failures. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/circuitbreaker/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/circuitbreaker/ type CircuitBreaker struct { // Expression defines the expression that, once matched, opens the circuit breaker and applies the fallback mechanism instead of calling the services. Expression string `json:"expression,omitempty" toml:"expression,omitempty" yaml:"expression,omitempty" export:"true"` @@ -197,7 +197,7 @@ func (c *Compress) SetDefaults() { // DigestAuth holds the digest auth middleware configuration. // This middleware restricts access to your services to known users. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/digestauth/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/digestauth/ type DigestAuth struct { // Users defines the authorized users. // Each user should be declared using the name:realm:encoded-password format. @@ -210,7 +210,7 @@ type DigestAuth struct { // Default: traefik. Realm string `json:"realm,omitempty" toml:"realm,omitempty" yaml:"realm,omitempty"` // HeaderField defines a header field to store the authenticated user. - // More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/basicauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/basicauth/#headerfield HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"` } @@ -241,7 +241,7 @@ type ErrorPage struct { // ForwardAuth holds the forward auth middleware configuration. // This middleware delegates the request authentication to a Service. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/forwardauth/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/forwardauth/ type ForwardAuth struct { // Address defines the authentication server address. Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` @@ -252,7 +252,7 @@ type ForwardAuth struct { // AuthResponseHeaders defines the list of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers. AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty" export:"true"` // AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex. - // More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/forwardauth/#authresponseheadersregex + // More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/forwardauth/#authresponseheadersregex AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty" toml:"authResponseHeadersRegex,omitempty" yaml:"authResponseHeadersRegex,omitempty" export:"true"` // AuthRequestHeaders defines the list of the headers to copy from the request to the authentication server. // If not set or empty then all request headers are passed. @@ -260,7 +260,7 @@ type ForwardAuth struct { // AddAuthCookiesToResponse defines the list of cookies to copy from the authentication server response to the response. AddAuthCookiesToResponse []string `json:"addAuthCookiesToResponse,omitempty" toml:"addAuthCookiesToResponse,omitempty" yaml:"addAuthCookiesToResponse,omitempty" export:"true"` // HeaderField defines a header field to store the authenticated user. - // More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/forwardauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/forwardauth/#headerfield HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"` // ForwardBody defines whether to send the request body to the authentication server. ForwardBody bool `json:"forwardBody,omitempty" toml:"forwardBody,omitempty" yaml:"forwardBody,omitempty" export:"true"` @@ -295,7 +295,7 @@ type ClientTLS struct { // Headers holds the headers middleware configuration. // This middleware manages the requests and responses headers. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/headers/#customrequestheaders +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/headers/#customrequestheaders type Headers struct { // CustomRequestHeaders defines the header names and values to apply to the request. CustomRequestHeaders map[string]string `json:"customRequestHeaders,omitempty" toml:"customRequestHeaders,omitempty" yaml:"customRequestHeaders,omitempty" export:"true"` @@ -425,7 +425,7 @@ func (h *Headers) HasSecureHeadersDefined() bool { // +k8s:deepcopy-gen=true // IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/#ipstrategy type IPStrategy struct { // Depth tells Traefik to use the X-Forwarded-For header and take the IP located at the depth position (starting from the right). // +kubebuilder:validation:Minimum=0 @@ -480,7 +480,7 @@ func (s *IPStrategy) Get() (ip.Strategy, error) { // IPWhiteList holds the IP whitelist middleware configuration. // This middleware limits allowed requests based on the client IP. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipwhitelist/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipwhitelist/ // Deprecated: please use IPAllowList instead. type IPWhiteList struct { // SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation). Required. @@ -492,7 +492,7 @@ type IPWhiteList struct { // IPAllowList holds the IP allowlist middleware configuration. // This middleware limits allowed requests based on the client IP. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/ipallowlist/ type IPAllowList struct { // SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation). SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"` @@ -506,7 +506,7 @@ type IPAllowList struct { // InFlightReq holds the in-flight request middleware configuration. // This middleware limits the number of requests being processed and served concurrently. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/inflightreq/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/inflightreq/ type InFlightReq struct { // Amount defines the maximum amount of allowed simultaneous in-flight request. // The middleware responds with HTTP 429 Too Many Requests if there are already amount requests in progress (based on the same sourceCriterion strategy). @@ -515,7 +515,7 @@ type InFlightReq struct { // SourceCriterion defines what criterion is used to group requests as originating from a common source. // If several strategies are defined at the same time, an error will be raised. // If none are set, the default is to use the requestHost. - // More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/inflightreq/#sourcecriterion + // More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/inflightreq/#sourcecriterion SourceCriterion *SourceCriterion `json:"sourceCriterion,omitempty" toml:"sourceCriterion,omitempty" yaml:"sourceCriterion,omitempty" export:"true"` } @@ -523,7 +523,7 @@ type InFlightReq struct { // PassTLSClientCert holds the pass TLS client cert middleware configuration. // This middleware adds the selected data from the passed client TLS certificate to a header. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/passtlsclientcert/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/passtlsclientcert/ type PassTLSClientCert struct { // PEM sets the X-Forwarded-Tls-Client-Cert header with the certificate. PEM bool `json:"pem,omitempty" toml:"pem,omitempty" yaml:"pem,omitempty" export:"true"` @@ -635,7 +635,7 @@ func (r *Redis) SetDefaults() { // RedirectRegex holds the redirect regex middleware configuration. // This middleware redirects a request using regex matching and replacement. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/redirectregex/#regex +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectregex/#regex type RedirectRegex struct { // Regex defines the regex used to match and capture elements from the request URL. Regex string `json:"regex,omitempty" toml:"regex,omitempty" yaml:"regex,omitempty"` @@ -649,7 +649,7 @@ type RedirectRegex struct { // RedirectScheme holds the redirect scheme middleware configuration. // This middleware redirects requests from a scheme/port to another. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/redirectscheme/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectscheme/ type RedirectScheme struct { // Scheme defines the scheme of the new URL. Scheme string `json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,omitempty" export:"true"` @@ -663,7 +663,7 @@ type RedirectScheme struct { // ReplacePath holds the replace path middleware configuration. // This middleware replaces the path of the request URL and store the original path in an X-Replaced-Path header. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/replacepath/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/replacepath/ type ReplacePath struct { // Path defines the path to use as replacement in the request URL. Path string `json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"` @@ -673,7 +673,7 @@ type ReplacePath struct { // ReplacePathRegex holds the replace path regex middleware configuration. // This middleware replaces the path of a URL using regex matching and replacement. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/replacepathregex/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/replacepathregex/ type ReplacePathRegex struct { // Regex defines the regular expression used to match and capture the path from the request URL. Regex string `json:"regex,omitempty" toml:"regex,omitempty" yaml:"regex,omitempty" export:"true"` @@ -686,7 +686,7 @@ type ReplacePathRegex struct { // Retry holds the retry middleware configuration. // This middleware reissues requests a given number of times to a backend server if that server does not reply. // As soon as the server answers, the middleware stops retrying, regardless of the response status. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/retry/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/retry/ type Retry struct { // Attempts defines how many times the request should be retried. Attempts int `json:"attempts,omitempty" toml:"attempts,omitempty" yaml:"attempts,omitempty" export:"true"` @@ -702,7 +702,7 @@ type Retry struct { // StripPrefix holds the strip prefix middleware configuration. // This middleware removes the specified prefixes from the URL path. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/stripprefix/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/stripprefix/ type StripPrefix struct { // Prefixes defines the prefixes to strip from the request URL. Prefixes []string `json:"prefixes,omitempty" toml:"prefixes,omitempty" yaml:"prefixes,omitempty" export:"true"` @@ -717,7 +717,7 @@ type StripPrefix struct { // StripPrefixRegex holds the strip prefix regex middleware configuration. // This middleware removes the matching prefixes from the URL path. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/stripprefixregex/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/stripprefixregex/ type StripPrefixRegex struct { // Regex defines the regular expression to match the path prefix from the request URL. Regex []string `json:"regex,omitempty" toml:"regex,omitempty" yaml:"regex,omitempty" export:"true"` diff --git a/pkg/config/dynamic/tcp_config.go b/pkg/config/dynamic/tcp_config.go index 408516a9f..a68be77b9 100644 --- a/pkg/config/dynamic/tcp_config.go +++ b/pkg/config/dynamic/tcp_config.go @@ -129,7 +129,7 @@ type TCPServer struct { // +k8s:deepcopy-gen=true // ProxyProtocol holds the PROXY Protocol configuration. -// More info: https://doc.traefik.io/traefik/v3.5/routing/services/#proxy-protocol +// More info: https://doc.traefik.io/traefik/v3.6/routing/services/#proxy-protocol type ProxyProtocol struct { // Version defines the PROXY Protocol version to use. // +kubebuilder:validation:Minimum=1 diff --git a/pkg/config/dynamic/tcp_middlewares.go b/pkg/config/dynamic/tcp_middlewares.go index 3d9624d08..1f20e8ca2 100644 --- a/pkg/config/dynamic/tcp_middlewares.go +++ b/pkg/config/dynamic/tcp_middlewares.go @@ -15,7 +15,7 @@ type TCPMiddleware struct { // TCPInFlightConn holds the TCP InFlightConn middleware configuration. // This middleware prevents services from being overwhelmed with high load, // by limiting the number of allowed simultaneous connections for one IP. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/tcp/inflightconn/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/tcp/inflightconn/ type TCPInFlightConn struct { // Amount defines the maximum amount of allowed simultaneous connections. // The middleware closes the connection if there are already amount connections opened. @@ -36,7 +36,7 @@ type TCPIPWhiteList struct { // TCPIPAllowList holds the TCP IPAllowList middleware configuration. // This middleware limits allowed requests based on the client IP. -// More info: https://doc.traefik.io/traefik/v3.5/middlewares/tcp/ipallowlist/ +// More info: https://doc.traefik.io/traefik/v3.6/middlewares/tcp/ipallowlist/ type TCPIPAllowList struct { // SourceRange defines the allowed IPs (or ranges of allowed IPs by using CIDR notation). SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go index c3978e94a..36c50286b 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go @@ -13,22 +13,22 @@ type IngressRouteSpec struct { Routes []Route `json:"routes"` // EntryPoints defines the list of entry point names to bind to. // Entry points have to be configured in the static configuration. - // More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ // Default: all. EntryPoints []string `json:"entryPoints,omitempty"` // TLS defines the TLS configuration. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/router/#tls + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/router/#tls TLS *TLS `json:"tls,omitempty"` // ParentRefs defines references to parent IngressRoute resources for multi-layer routing. // When set, this IngressRoute's routers will be children of the referenced parent IngressRoute's routers. - // More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#parentrefs + // More info: https://doc.traefik.io/traefik/v3.6/routing/routers/#parentrefs ParentRefs []IngressRouteRef `json:"parentRefs,omitempty"` } // Route holds the HTTP route configuration. type Route struct { // Match defines the router's rule. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/ Match string `json:"match"` // Kind defines the kind of the route. // Rule is the only supported kind. @@ -36,62 +36,62 @@ type Route struct { // +kubebuilder:validation:Enum=Rule Kind string `json:"kind,omitempty"` // Priority defines the router's priority. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#priority + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/#priority // +kubebuilder:validation:Maximum=9223372036854774807 Priority int `json:"priority,omitempty"` // Syntax defines the router's rule syntax. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#rulesyntax + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/#rulesyntax // Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. Syntax string `json:"syntax,omitempty"` // Services defines the list of Service. // It can contain any combination of TraefikService and/or reference to a Kubernetes Service. Services []Service `json:"services,omitempty"` // Middlewares defines the list of references to Middleware resources. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/middleware/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/middleware/ Middlewares []MiddlewareRef `json:"middlewares,omitempty"` // Observability defines the observability configuration for a router. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/observability/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/observability/ Observability *dynamic.RouterObservabilityConfig `json:"observability,omitempty"` } // TLS holds the TLS configuration. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/overview/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/overview/ type TLS struct { // SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. SecretName string `json:"secretName,omitempty"` // Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. // If not defined, the `default` TLSOption is used. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-options/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-options/ Options *TLSOptionRef `json:"options,omitempty"` // Store defines the reference to the TLSStore, that will be used to store certificates. // Please note that only `default` TLSStore can be used. Store *TLSStoreRef `json:"store,omitempty"` // CertResolver defines the name of the certificate resolver to use. // Cert resolvers have to be configured in the static configuration. - // More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/tls/certificate-resolvers/acme/ CertResolver string `json:"certResolver,omitempty"` // Domains defines the list of domains that will be used to issue certificates. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#domains + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#domains Domains []types.Domain `json:"domains,omitempty"` } // TLSOptionRef is a reference to a TLSOption resource. type TLSOptionRef struct { // Name defines the name of the referenced TLSOption. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsoption/ Name string `json:"name"` // Namespace defines the namespace of the referenced TLSOption. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsoption/ Namespace string `json:"namespace,omitempty"` } // TLSStoreRef is a reference to a TLSStore resource. type TLSStoreRef struct { // Name defines the name of the referenced TLSStore. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsstore/ Name string `json:"name"` // Namespace defines the namespace of the referenced TLSStore. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/tlsstore/ Namespace string `json:"namespace,omitempty"` } @@ -108,7 +108,7 @@ type LoadBalancerSpec struct { // Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. Namespace string `json:"namespace,omitempty"` // Sticky defines the sticky sessions configuration. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions Sticky *dynamic.Sticky `json:"sticky,omitempty"` // Port defines the port of a Kubernetes Service. // This can be a reference to a named port. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go index 5ef08dc02..1d719ee9a 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go @@ -13,25 +13,25 @@ type IngressRouteTCPSpec struct { Routes []RouteTCP `json:"routes"` // EntryPoints defines the list of entry point names to bind to. // Entry points have to be configured in the static configuration. - // More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ // Default: all. EntryPoints []string `json:"entryPoints,omitempty"` // TLS defines the TLS configuration on a layer 4 / TCP Route. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/router/#tls + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/router/#tls TLS *TLSTCP `json:"tls,omitempty"` } // RouteTCP holds the TCP route configuration. type RouteTCP struct { // Match defines the router's rule. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/ Match string `json:"match"` // Priority defines the router's priority. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#priority + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/#priority // +kubebuilder:validation:Maximum=9223372036854774807 Priority int `json:"priority,omitempty"` // Syntax defines the router's rule syntax. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#rulesyntax + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/#rulesyntax // +kubebuilder:validation:Enum=v3;v2 // Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. Syntax string `json:"syntax,omitempty"` @@ -42,7 +42,7 @@ type RouteTCP struct { } // TLSTCP holds the TLS configuration for an IngressRouteTCP. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/tls/ type TLSTCP struct { // SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. SecretName string `json:"secretName,omitempty"` @@ -50,17 +50,17 @@ type TLSTCP struct { Passthrough bool `json:"passthrough,omitempty"` // Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. // If not defined, the `default` TLSOption is used. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#tls-options + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/tls/#tls-options Options *ObjectReference `json:"options,omitempty"` // Store defines the reference to the TLSStore, that will be used to store certificates. // Please note that only `default` TLSStore can be used. Store *ObjectReference `json:"store,omitempty"` // CertResolver defines the name of the certificate resolver to use. // Cert resolvers have to be configured in the static configuration. - // More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/tls/certificate-resolvers/acme/ CertResolver string `json:"certResolver,omitempty"` // Domains defines the list of domains that will be used to issue certificates. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#domains + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/tls/#domains Domains []types.Domain `json:"domains,omitempty"` } @@ -85,7 +85,7 @@ type ServiceTCP struct { // Deprecated: TerminationDelay will not be supported in future APIVersions, please use ServersTransport to configure the TerminationDelay instead. TerminationDelay *int `json:"terminationDelay,omitempty"` // ProxyProtocol defines the PROXY protocol configuration. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/service/#proxy-protocol + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/service/#proxy-protocol // Deprecated: ProxyProtocol will not be supported in future APIVersions, please use ServersTransport to configure ProxyProtocol instead. ProxyProtocol *dynamic.ProxyProtocol `json:"proxyProtocol,omitempty"` // ServersTransport defines the name of ServersTransportTCP resource to use. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressrouteudp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressrouteudp.go index 6e0038843..e7c8d1c6d 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressrouteudp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressrouteudp.go @@ -11,7 +11,7 @@ type IngressRouteUDPSpec struct { Routes []RouteUDP `json:"routes"` // EntryPoints defines the list of entry point names to bind to. // Entry points have to be configured in the static configuration. - // More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ // Default: all. EntryPoints []string `json:"entryPoints,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go index 32354e2b5..d00e839d1 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go @@ -12,7 +12,7 @@ import ( // +kubebuilder:storageversion // Middleware is the CRD implementation of a Traefik Middleware. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/overview/ type Middleware struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -52,7 +52,7 @@ type MiddlewareSpec struct { ContentType *dynamic.ContentType `json:"contentType,omitempty"` GrpcWeb *dynamic.GrpcWeb `json:"grpcWeb,omitempty"` // Plugin defines the middleware plugin configuration. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/#community-middlewares + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/overview/#community-middlewares Plugin map[string]apiextensionv1.JSON `json:"plugin,omitempty"` } @@ -60,7 +60,7 @@ type MiddlewareSpec struct { // ErrorPage holds the custom error middleware configuration. // This middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/errorpages/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/errorpages/ type ErrorPage struct { // Status defines which status or range of statuses should result in an error page. // It can be either a status code as a number (500), @@ -73,7 +73,7 @@ type ErrorPage struct { // For example: "418": 404 or "410-418": 404 StatusRewrites map[string]int `json:"statusRewrites,omitempty"` // Service defines the reference to a Kubernetes Service that will serve the error page. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/errorpages/#service + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/errorpages/#service Service Service `json:"service,omitempty"` // Query defines the URL for the error page (hosted by service). // The {status} variable can be used in order to insert the status code in the URL. @@ -108,7 +108,7 @@ type CircuitBreaker struct { // Chain holds the configuration of the chain middleware. // This middleware enables to define reusable combinations of other pieces of middleware. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/chain/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/chain/ type Chain struct { // Middlewares is the list of MiddlewareRef which composes the chain. Middlewares []MiddlewareRef `json:"middlewares,omitempty"` @@ -118,7 +118,7 @@ type Chain struct { // BasicAuth holds the basic auth middleware configuration. // This middleware restricts access to your services to known users. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/basicauth/ type BasicAuth struct { // Secret is the name of the referenced Kubernetes Secret containing user credentials. Secret string `json:"secret,omitempty"` @@ -129,7 +129,7 @@ type BasicAuth struct { // Default: false. RemoveHeader bool `json:"removeHeader,omitempty"` // HeaderField defines a header field to store the authenticated user. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/basicauth/#headerfield HeaderField string `json:"headerField,omitempty"` } @@ -137,7 +137,7 @@ type BasicAuth struct { // DigestAuth holds the digest auth middleware configuration. // This middleware restricts access to your services to known users. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/digestauth/ type DigestAuth struct { // Secret is the name of the referenced Kubernetes Secret containing user credentials. Secret string `json:"secret,omitempty"` @@ -147,7 +147,7 @@ type DigestAuth struct { // Default: traefik. Realm string `json:"realm,omitempty"` // HeaderField defines a header field to store the authenticated user. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/digestauth/#headerfield HeaderField string `json:"headerField,omitempty"` } @@ -155,7 +155,7 @@ type DigestAuth struct { // ForwardAuth holds the forward auth middleware configuration. // This middleware delegates the request authentication to a Service. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/ type ForwardAuth struct { // Address defines the authentication server address. Address string `json:"address,omitempty"` @@ -164,7 +164,7 @@ type ForwardAuth struct { // AuthResponseHeaders defines the list of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers. AuthResponseHeaders []string `json:"authResponseHeaders,omitempty"` // AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty"` // AuthRequestHeaders defines the list of the headers to copy from the request to the authentication server. // If not set or empty then all request headers are passed. @@ -174,7 +174,7 @@ type ForwardAuth struct { // AddAuthCookiesToResponse defines the list of cookies to copy from the authentication server response to the response. AddAuthCookiesToResponse []string `json:"addAuthCookiesToResponse,omitempty"` // HeaderField defines a header field to store the authenticated user. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/#headerfield HeaderField string `json:"headerField,omitempty"` // ForwardBody defines whether to send the request body to the authentication server. ForwardBody bool `json:"forwardBody,omitempty"` @@ -201,7 +201,7 @@ type ClientTLSWithCAOptional struct { // RateLimit holds the rate limit configuration. // This middleware ensures that services will receive a fair amount of requests, and allows one to define what fair is. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/ratelimit/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/ratelimit/ type RateLimit struct { // Average is the maximum rate, by default in requests/s, allowed for the given source. // It defaults to 0, which means no rate limiting. @@ -286,7 +286,7 @@ type ClientTLS struct { // Compress holds the compress middleware configuration. // This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/compress/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/compress/ type Compress struct { // ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing. // `application/grpc` is always excluded. @@ -308,7 +308,7 @@ type Compress struct { // Retry holds the retry middleware configuration. // This middleware reissues requests a given number of times to a backend server if that server does not reply. // As soon as the server answers, the middleware stops retrying, regardless of the response status. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/retry/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/retry/ type Retry struct { // Attempts defines how many times the request should be retried. // +kubebuilder:validation:Minimum=0 diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go index 49309b02c..cf0e2a648 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go @@ -9,7 +9,7 @@ import ( // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/overview/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/middlewares/overview/ type MiddlewareTCP struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -28,11 +28,11 @@ type MiddlewareTCPSpec struct { // IPWhiteList defines the IPWhiteList middleware configuration. // This middleware accepts/refuses connections based on the client IP. // Deprecated: please use IPAllowList instead. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipwhitelist/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/middlewares/ipwhitelist/ IPWhiteList *dynamic.TCPIPWhiteList `json:"ipWhiteList,omitempty"` // IPAllowList defines the IPAllowList middleware configuration. // This middleware accepts/refuses connections based on the client IP. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipallowlist/ + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/middlewares/ipallowlist/ IPAllowList *dynamic.TCPIPAllowList `json:"ipAllowList,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go index f797671fc..8cfbb92be 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go @@ -13,7 +13,7 @@ import ( // ServersTransport is the CRD implementation of a ServersTransport. // If no serversTransport is specified, the default@internal will be used. // The default@internal serversTransport is created from the static configuration. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/serverstransport/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/serverstransport/ type ServersTransport struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransporttcp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransporttcp.go index dfb2299be..374108d10 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransporttcp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransporttcp.go @@ -13,7 +13,7 @@ import ( // ServersTransportTCP is the CRD implementation of a TCPServersTransport. // If no tcpServersTransport is specified, a default one named default@internal will be used. // The default@internal tcpServersTransport can be configured in the static configuration. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/serverstransport/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/serverstransport/ type ServersTransportTCP struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go index 35164175d..35d45bcfc 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go @@ -13,7 +13,7 @@ import ( // TraefikService object allows to: // - Apply weight to Services on load-balancing // - Mirror traffic on services -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/ +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/traefikservice/ type TraefikService struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -51,7 +51,7 @@ type TraefikServiceSpec struct { // +k8s:deepcopy-gen=true // Mirroring holds the mirroring service configuration. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#mirroring +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#mirroring type Mirroring struct { LoadBalancerSpec `json:",inline"` @@ -80,19 +80,19 @@ type MirrorService struct { // +k8s:deepcopy-gen=true // WeightedRoundRobin holds the weighted round-robin configuration. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#weighted-round-robin-wrr +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#weighted-round-robin-wrr type WeightedRoundRobin struct { // Services defines the list of Kubernetes Service and/or TraefikService to load-balance, with weight. Services []Service `json:"services,omitempty"` // Sticky defines whether sticky sessions are enabled. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/#stickiness-and-load-balancing + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/kubernetes/crd/http/traefikservice/#stickiness-and-load-balancing Sticky *dynamic.Sticky `json:"sticky,omitempty"` } // +k8s:deepcopy-gen=true // HighestRandomWeight holds the highest random weight configuration. -// More info: https://doc.traefik.io/traefik/v3.5/routing/services/#highest-random-configuration +// More info: https://doc.traefik.io/traefik/v3.6/routing/services/#highest-random-configuration type HighestRandomWeight struct { // Services defines the list of Kubernetes Service and/or TraefikService to load-balance, with weight. Services []Service `json:"services,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go index 07ab9954d..b4eebf6b7 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go @@ -9,7 +9,7 @@ import ( // +kubebuilder:storageversion // TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#tls-options +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#tls-options type TLSOption struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -32,17 +32,17 @@ type TLSOptionSpec struct { // Default: None. MaxVersion string `json:"maxVersion,omitempty"` // CipherSuites defines the list of supported cipher suites for TLS versions up to TLS 1.2. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#cipher-suites + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#cipher-suites CipherSuites []string `json:"cipherSuites,omitempty"` // CurvePreferences defines the preferred elliptic curves. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#curve-preferences + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#curve-preferences CurvePreferences []string `json:"curvePreferences,omitempty"` // ClientAuth defines the server's policy for TLS Client Authentication. ClientAuth ClientAuth `json:"clientAuth,omitempty"` // SniStrict defines whether Traefik allows connections from clients connections that do not specify a server_name extension. SniStrict bool `json:"sniStrict,omitempty"` // ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. - // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#alpn-protocols + // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#alpn-protocols ALPNProtocols []string `json:"alpnProtocols,omitempty"` // DisableSessionTickets disables TLS session resumption via session tickets. DisableSessionTickets bool `json:"disableSessionTickets,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsstore.go index 23a5d1a9a..36a31ca65 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsstore.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsstore.go @@ -12,7 +12,7 @@ import ( // TLSStore is the CRD implementation of a Traefik TLS Store. // For the time being, only the TLSStore named default is supported. // This means that you cannot have two stores that are named default in different Kubernetes namespaces. -// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#certificates-stores +// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#certificates-stores type TLSStore struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. diff --git a/script/gcg/traefik-rc-first.toml b/script/gcg/traefik-rc-first.toml index a946c438d..29e3d0456 100644 --- a/script/gcg/traefik-rc-first.toml +++ b/script/gcg/traefik-rc-first.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example RC1 of v3.5.0-rc1 +# example RC1 of v3.6.0-rc1 CurrentRef = "master" -PreviousRef = "v3.4.0-rc1" +PreviousRef = "v3.5.0-rc1" BaseBranch = "master" -FutureCurrentRefName = "v3.5.0-rc1" +FutureCurrentRefName = "v3.6.0-rc1" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 From 05db0895cb7140c0860d2fdff96c1f686e8a8c8f Mon Sep 17 00:00:00 2001 From: Jesper Noordsij <45041769+jnoordsij@users.noreply.github.com> Date: Wed, 29 Oct 2025 14:20:04 +0100 Subject: [PATCH 022/134] Fix typo in v3.6 migration guide --- docs/content/migrate/v3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/migrate/v3.md b/docs/content/migrate/v3.md index 304287398..85e9fc185 100644 --- a/docs/content/migrate/v3.md +++ b/docs/content/migrate/v3.md @@ -518,7 +518,7 @@ kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/downloa ### Kubernetes CRD Provider -To use the new `leastime` load-balancer algorithm with the Kubernetes CRD provider, you need to update your CRDs. +To use the new `leasttime` load-balancer algorithm with the Kubernetes CRD provider, you need to update your CRDs. **Apply Updated CRDs:** From 2e2fe7a81798c89824e7f46422c6d8f275a2bca5 Mon Sep 17 00:00:00 2001 From: Sheddy Date: Thu, 30 Oct 2025 13:22:04 +0000 Subject: [PATCH 023/134] Update Configuration Overview Page --- .../getting-started/configuration-overview.md | 26 +++++++++---------- docs/mkdocs.yml | 2 +- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/content/getting-started/configuration-overview.md b/docs/content/getting-started/configuration-overview.md index 728004225..d1e939a88 100644 --- a/docs/content/getting-started/configuration-overview.md +++ b/docs/content/getting-started/configuration-overview.md @@ -1,6 +1,6 @@ --- title: "Traefik Configuration Documentation" -description: "Get started with Traefik Proxy. This page will introduce you to the dynamic routing and startup configurations. Read the technical documentation." +description: "Get started with Traefik Proxy. This page will introduce you to the routing and install configurations. Read the technical documentation." --- # Configuration Introduction @@ -8,39 +8,37 @@ description: "Get started with Traefik Proxy. This page will introduce you to th How the Magic Happens {: .subtitle } -![Configuration](../assets/img/static-dynamic-configuration.png) - Configuration in Traefik can refer to two different things: -- The fully dynamic routing configuration (referred to as the _routing configuration_, formerly known as the _dynamic configuration_) -- The startup configuration (referred to as the _install configuration_, formerly known as the _static configuration_) +- The install (startup) configuration (formerly known as the _static configuration_) +- The routing configuration (formerly known as the _dynamic configuration_) -Elements in the install configuration_ set up connections to [providers](../providers/overview.md) and define the [entrypoints](../routing/entrypoints.md) Traefik will listen to (these elements don't change often). +Elements in the _install configuration_ set up connections to [providers](../providers/overview.md) and define the [entrypoints](../routing/entrypoints.md) Traefik will listen to (these elements don't change often). -The _dynamic configuration_ contains everything that defines how the requests are handled by your system. +The _routing configuration_ contains everything that defines how the requests are handled by your system. This configuration can change and is seamlessly hot-reloaded, without any request interruption or connection loss. !!! warning "Incompatible Configuration" Please be aware that the old configurations for Traefik v1.x are NOT compatible with the v2.x config as of now. If you are running v2, please ensure you are using a v2 configuration. -## The Dynamic Configuration +## The Routing Configuration -Traefik gets its _dynamic configuration_ from [providers](../providers/overview.md): whether an orchestrator, a service registry, or a plain old configuration file. +Traefik gets its _routing configuration_ from [providers](../providers/overview.md): whether an orchestrator, a service registry, or a plain old configuration file. Since this configuration is specific to your infrastructure choices, we invite you to refer to the [dedicated section of this documentation](../routing/overview.md). !!! info "" - In the [Quick Start example](../getting-started/quick-start.md), the routing configuration comes from docker in the form of labels attached to your containers. + In the [Quick Start example](../getting-started/docker.md), the whoami application routing configuration comes from docker in the form of a label attached to the whoami container. !!! info "HTTPS Certificates also belong to the routing configuration." You can add / update / remove them without restarting your Traefik instance. -## The Static Configuration +## The Install Configuration -There are three different, **mutually exclusive** (i.e. you can use only one at the same time), ways to define static configuration options in Traefik: +There are three different, **mutually exclusive** (i.e. you can use only one at the same time), ways to define install configuration options in Traefik: 1. In a configuration file 1. In the command-line arguments @@ -56,7 +54,7 @@ Once positioned, this option sets (and resets) all the default values of the sub ### Configuration File -At startup, Traefik searches for static configuration in a file named `traefik.yml` (or `traefik.yaml` or `traefik.toml`) in: +At startup, Traefik searches for install configuration in a file named `traefik.yml` (or `traefik.yaml` or `traefik.toml`) in: - `/etc/traefik/` - `$XDG_CONFIG_HOME/` @@ -86,7 +84,7 @@ Check the [CLI reference](../reference/install-configuration/configuration-optio ### Environment Variables -All available environment variables can be found in the [static configuration environment overview](../reference/install-configuration/configuration-options.md). +All available environment variables can be found in the [install configuration environment overview](../reference/install-configuration/configuration-options.md). ## Available Configuration Options diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index dca15910d..851df16c1 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -187,10 +187,10 @@ nav: - 'What is Traefik': 'index.md' - 'Getting Started': - 'Overview': 'getting-started/index.md' + - 'Configuration Introduction': 'getting-started/configuration-overview.md' - 'Quick Start': - 'Kubernetes': 'getting-started/kubernetes.md' - 'Docker': 'getting-started/docker.md' - - 'Configuration Introduction': 'getting-started/configuration-overview.md' - 'Setup': - 'Kubernetes': 'setup/kubernetes.md' - 'Docker': 'setup/docker.md' From d9deb21eac1ad27dabacff18578d7dbaf124f55b Mon Sep 17 00:00:00 2001 From: Sheddy Date: Fri, 31 Oct 2025 08:22:04 +0000 Subject: [PATCH 024/134] Fix broken links in TCP Service and HTTP Router documentation --- .../reference/routing-configuration/http/routing/router.md | 2 +- docs/content/reference/routing-configuration/tcp/service.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/reference/routing-configuration/http/routing/router.md b/docs/content/reference/routing-configuration/http/routing/router.md index fa8ccb701..da515f937 100644 --- a/docs/content/reference/routing-configuration/http/routing/router.md +++ b/docs/content/reference/routing-configuration/http/routing/router.md @@ -110,7 +110,7 @@ labels: | `tls.options` | The name of the TLS options to use for configuring TLS parameters (cipher suites, min/max TLS version, client authentication, etc.). See [TLS Options](../tls/tls-options.md) for detailed configuration. | `default` | No | | `tls.domains` | List of domains and Subject Alternative Names (SANs) for explicit certificate domain specification. When using ACME certificate resolvers, domains are automatically extracted from router rules, making this option optional. | | No | | `observability` | Observability configuration for the router. Allows fine-grained control over access logs, metrics, and tracing per router. See [Observability](./observability.md) for details. | Inherited from entry points | No | -| `parentRefs` | References to parent router names for multi-layer routing. When specified, this router becomes a child router that processes requests after parent routers have applied their middlewares. See [Multi-Layer Routing](../../../../routing/multi-layer-routing.md) for details. | | No | +| `parentRefs` | References to parent router names for multi-layer routing. When specified, this router becomes a child router that processes requests after parent routers have applied their middlewares. See [Multi-Layer Routing](../routing/multi-layer-routing.md) for details. | | No | | `service` | The name of the service that will handle the matched requests. Services can be load balancer services, weighted round robin, mirroring, or failover services. See [Service](../load-balancing/service.md) for details. | | Yes | ## Router Naming diff --git a/docs/content/reference/routing-configuration/tcp/service.md b/docs/content/reference/routing-configuration/tcp/service.md index 348d83d9a..e719ddf42 100644 --- a/docs/content/reference/routing-configuration/tcp/service.md +++ b/docs/content/reference/routing-configuration/tcp/service.md @@ -165,7 +165,7 @@ In addition, if the parent of this service also has HealthCheck enabled, this se If HealthCheck is enabled for a given service and any of its descendants does not have it enabled, the creation of the service will fail. - HealthCheck on Weighted services can be defined currently only with the [File provider](../../../install-configuration/providers/others/file.md). + HealthCheck on Weighted services can be defined currently only with the [File provider](../../install-configuration/providers/others/file.md). ```yaml tab="Structured (YAML)" ## Dynamic configuration From c4c396810955be7ba164f8573206f590e8758802 Mon Sep 17 00:00:00 2001 From: Antoine Aflalo <197810+Belphemur@users.noreply.github.com> Date: Fri, 31 Oct 2025 04:24:04 -0400 Subject: [PATCH 025/134] Fix default encodings in compress middleware --- .../routing-configuration/http/middlewares/compress.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/reference/routing-configuration/http/middlewares/compress.md b/docs/content/reference/routing-configuration/http/middlewares/compress.md index f67c62c9a..31cf2a4eb 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/compress.md +++ b/docs/content/reference/routing-configuration/http/middlewares/compress.md @@ -53,7 +53,7 @@ spec: |:-----------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| | `excludedContentTypes` | List of content types to compare the `Content-Type` header of the incoming requests and responses before compressing.
The responses with content types defined in `excludedContentTypes` are not compressed.
Content types are compared in a case-insensitive, whitespace-ignored manner.
**The `excludedContentTypes` and `includedContentTypes` options are mutually exclusive.** | "" | No | | `defaultEncoding` | specifies the default encoding if the `Accept-Encoding` header is not in the request or contains a wildcard (`*`). | "" | No | -| `encodings` | Specifies the list of supported compression encodings. At least one encoding value must be specified, and valid entries are `zstd` (Zstandard), `br` (Brotli), and `gzip` (Gzip). The order of the list also sets the priority, the top entry has the highest priority. | zstd, br, gzip | No | +| `encodings` | Specifies the list of supported compression encodings. At least one encoding value must be specified, and valid entries are `zstd` (Zstandard), `br` (Brotli), and `gzip` (Gzip). The order of the list also sets the priority, the top entry has the highest priority. | gzip, br, zstd | No | | `includedContentTypes` | List of content types to compare the `Content-Type` header of the responses before compressing.
The responses with content types defined in `includedContentTypes` are compressed.
Content types are compared in a case-insensitive, whitespace-ignored manner.
**The `excludedContentTypes` and `includedContentTypes` options are mutually exclusive.** | "" | No | | `minResponseBodyBytes` | `Minimum amount of bytes a response body must have to be compressed.
Responses smaller than the specified values will **not** be compressed. | 1024 | No | From 1d8cd5a89b86c5cda2b2e114538f2bd9fbafb960 Mon Sep 17 00:00:00 2001 From: Sheddy Date: Mon, 3 Nov 2025 08:14:04 +0000 Subject: [PATCH 026/134] Add missing ACME options and clean up table for more visibility --- .../tls/certificate-resolvers/acme.md | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/docs/content/reference/install-configuration/tls/certificate-resolvers/acme.md b/docs/content/reference/install-configuration/tls/certificate-resolvers/acme.md index f923e6c72..6a3be9641 100644 --- a/docs/content/reference/install-configuration/tls/certificate-resolvers/acme.md +++ b/docs/content/reference/install-configuration/tls/certificate-resolvers/acme.md @@ -73,30 +73,35 @@ certificatesResolvers: ACME certificate resolvers have the following configuration options: -| Field | Description | Default | Required | -|:--------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------|:---------| -| `acme.email` | Email address used for registration. | "" | Yes | -| `acme.caServer` | CA server to use. | https://acme-v02.api.letsencrypt.org/directory | No | -| `acme.preferredChain` | Preferred chain to use. If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. | "" | No | -| `acme.keyType` | KeyType to use. | "RSA4096" | No | -| `acme.eab` | Enable external account binding. | | No | -| `acme.eab.kid` | Key identifier from External CA. | "" | No | -| `acme.eab.hmacEncoded` | HMAC key from External CA, should be in Base64 URL Encoding without padding format. | "" | No | -| `acme.certificatesDuration` | The certificates' duration in hours, exclusively used to determine renewal dates. | 2160 | No | -| `acme.clientTimeout` | Timeout for HTTP Client used to communicate with the ACME server. | 2m | No | -| `acme.clientResponseHeaderTimeout` | Timeout for response headers for HTTP Client used to communicate with the ACME server. | 30s | No | -| `acme.dnsChallenge` | Enable DNS-01 challenge. More information [here](#dnschallenge). | - | No | -| `acme.dnsChallenge.provider` | DNS provider to use. | "" | No | -| `acme.dnsChallenge.resolvers` | DNS servers to resolve the FQDN authority. | [] | No | -| `acme.dnsChallenge.propagation.delayBeforeChecks` | By default, the provider will verify the TXT DNS challenge record before letting ACME verify. If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds. This is Useful if internal networks block external DNS queries. | 0s | No | -| `acme.dnsChallenge.propagation.disableChecks` | Disables the challenge TXT record propagation checks, before notifying ACME that the DNS challenge is ready. Please note that disabling checks can prevent the challenge from succeeding. | false | No | -| `acme.dnsChallenge.propagation.requireAllRNS` | Enables the challenge TXT record to be propagated to all recursive nameservers. If you have disabled authoritative nameservers checks (with `propagation.disableANSChecks`), it is recommended to check all recursive nameservers instead. | false | No | -| `acme.dnsChallenge.propagation.disableANSChecks` | Disables the challenge TXT record propagation checks against authoritative nameservers. This option will skip the propagation check against the nameservers of the authority (SOA). It should be used only if the nameservers of the authority are not reachable. | false | No | -| `acme.httpChallenge` | Enable HTTP-01 challenge. More information [here](#httpchallenge). | | No | -| `acme.httpChallenge.entryPoint` | EntryPoint to use for the HTTP-01 challenges. Must be reachable by Let's Encrypt through port 80 | "" | Yes | -| `acme.httpChallenge.delay` | The delay between the creation of the challenge and the validation. A value lower than or equal to zero means no delay. | 0 | No | -| `acme.tlsChallenge` | Enable TLS-ALPN-01 challenge. Traefik must be reachable by Let's Encrypt through port 443. More information [here](#tlschallenge). | - | No | -| `acme.storage` | File path used for certificates storage. | "acme.json" | Yes | +| Field | Description | Default | Required | +|:------|:------------|:--------|:---------| +| `acme.email` | Email address used for registration. | "" | Yes | +| `acme.caServer` | CA server to use. | https://acme-v02.api.letsencrypt.org/directory | No | +| `acme.preferredChain` | Preferred chain to use. If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. | "" | No | +| `acme.keyType` | KeyType to use. | "RSA4096" | No | +| `acme.profile` | Certificate profile to use. | "" | No | +| `acme.caCertificates` | Specify the paths to PEM encoded CA Certificates that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list. | [] | No | +| `acme.caSystemCertPool` | Defines if the certificates pool must use a copy of the system cert pool. | false | No | +| `acme.caServerName` | Specify the CA server name that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list. | "" | No | +| `acme.emailAddresses` | CSR email addresses to use. | "" | No | +| `acme.eab` | Enable external account binding. | | No | +| `acme.eab.kid` | Key identifier from External CA. | "" | No | +| `acme.eab.hmacEncoded` | HMAC key from External CA, should be in Base64 URL Encoding without padding format. | "" | No | +| `acme.certificatesDuration` | The certificates' duration in hours, exclusively used to determine renewal dates. | 2160 | No | +| `acme.clientTimeout` | Timeout for HTTP Client used to communicate with the ACME server. | 2m | No | +| `acme.clientResponseHeaderTimeout` | Timeout for response headers for HTTP Client used to communicate with the ACME server. | 30s | No | +| `acme.dnsChallenge` | Enable DNS-01 challenge. More information [here](#dnschallenge). | - | No | +| `acme.dnsChallenge.provider` | DNS provider to use. | "" | No | +| `acme.dnsChallenge.resolvers` | DNS servers to resolve the FQDN authority. | [] | No | +| `acme.dnsChallenge.propagation.delayBeforeChecks` | By default, the provider will verify the TXT DNS challenge record before letting ACME verify. If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds. This is Useful if internal networks block external DNS queries. | 0s | No | +| `acme.dnsChallenge.propagation.disableChecks` | Disables the challenge TXT record propagation checks, before notifying ACME that the DNS challenge is ready. Please note that disabling checks can prevent the challenge from succeeding. | false | No | +| `acme.dnsChallenge.propagation.requireAllRNS` | Enables the challenge TXT record to be propagated to all recursive nameservers. If you have disabled authoritative nameservers checks (with `propagation.disableANSChecks`), it is recommended to check all recursive nameservers instead. | false | No | +| `acme.dnsChallenge.propagation.disableANSChecks` | Disables the challenge TXT record propagation checks against authoritative nameservers. This option will skip the propagation check against the nameservers of the authority (SOA). It should be used only if the nameservers of the authority are not reachable. | false | No | +| `acme.httpChallenge` | Enable HTTP-01 challenge. More information [here](#httpchallenge). | | No | +| `acme.httpChallenge.entryPoint` | EntryPoint to use for the HTTP-01 challenges. Must be reachable by Let's Encrypt through port 80 | "" | Yes | +| `acme.httpChallenge.delay` | The delay between the creation of the challenge and the validation. A value lower than or equal to zero means no delay. | 0 | No | +| `acme.tlsChallenge` | Enable TLS-ALPN-01 challenge. Traefik must be reachable by Let's Encrypt through port 443. More information [here](#tlschallenge). | - | No | +| `acme.storage` | File path used for certificates storage. | "acme.json" | Yes | ## Automatic Certificate Renewal From e1e350f5aa34822d949f66efbb99de132e0c178a Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 4 Nov 2025 11:32:05 +0100 Subject: [PATCH 027/134] Bump github.com/go-acme/lego/v4 to v4.28.0 --- go.mod | 72 ++++++++++++++-------------- go.sum | 145 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 108 insertions(+), 109 deletions(-) diff --git a/go.mod b/go.mod index 3d00ed55b..7bdee03c9 100644 --- a/go.mod +++ b/go.mod @@ -8,13 +8,13 @@ require ( github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000 // No tag on the repo. github.com/andybalholm/brotli v1.1.1 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 - github.com/aws/aws-sdk-go-v2 v1.39.2 - github.com/aws/aws-sdk-go-v2/config v1.31.12 - github.com/aws/aws-sdk-go-v2/credentials v1.18.16 + github.com/aws/aws-sdk-go-v2 v1.39.4 + github.com/aws/aws-sdk-go-v2/config v1.31.15 + github.com/aws/aws-sdk-go-v2/credentials v1.18.19 github.com/aws/aws-sdk-go-v2/service/ec2 v1.203.1 github.com/aws/aws-sdk-go-v2/service/ecs v1.53.15 github.com/aws/aws-sdk-go-v2/service/ssm v1.56.13 - github.com/aws/smithy-go v1.23.0 + github.com/aws/smithy-go v1.23.1 github.com/cenkalti/backoff/v4 v4.3.0 github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd // No tag on the repo. github.com/coreos/go-systemd/v22 v22.5.0 @@ -23,7 +23,7 @@ require ( github.com/docker/go-connections v0.5.0 github.com/fatih/structs v1.1.0 github.com/fsnotify/fsnotify v1.9.0 - github.com/go-acme/lego/v4 v4.27.0 + github.com/go-acme/lego/v4 v4.28.0 github.com/go-kit/kit v0.13.0 github.com/go-kit/log v0.2.1 github.com/golang/protobuf v1.5.4 @@ -103,7 +103,7 @@ require ( golang.org/x/text v0.30.0 golang.org/x/time v0.14.0 golang.org/x/tools v0.37.0 - google.golang.org/grpc v1.75.1 + google.golang.org/grpc v1.76.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.32.3 @@ -146,7 +146,6 @@ require ( github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.13.0 // indirect - github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect github.com/VividCortex/gohistogram v1.0.0 // indirect github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect @@ -156,19 +155,19 @@ require ( github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect github.com/aliyun/credentials-go v1.4.7 // indirect github.com/armon/go-metrics v0.4.1 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 // indirect - github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.0 // indirect - github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect + github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.2 // indirect + github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 // indirect github.com/aziontech/azionapi-go-sdk v0.143.0 // indirect - github.com/baidubce/bce-sdk-go v0.9.248 // indirect + github.com/baidubce/bce-sdk-go v0.9.250 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect @@ -202,7 +201,7 @@ require ( github.com/gin-gonic/gin v1.9.1 // indirect github.com/go-acme/alidns-20150109/v4 v4.6.1 // indirect github.com/go-acme/tencentclouddnspod v1.1.10 // indirect - github.com/go-acme/tencentedgdeone v1.1.19 // indirect + github.com/go-acme/tencentedgdeone v1.1.48 // indirect github.com/go-errors/errors v1.0.1 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect @@ -221,7 +220,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-zookeeper/zk v1.0.3 // indirect github.com/goccy/go-yaml v1.11.3 // indirect - github.com/gofrs/flock v0.12.1 // indirect + github.com/gofrs/flock v0.13.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect @@ -247,7 +246,7 @@ require ( github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/serf v0.10.1 // indirect github.com/huandu/xstrings v1.5.0 // indirect - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.172 // indirect + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.173 // indirect github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect @@ -293,18 +292,19 @@ require ( github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/namedotcom/go/v4 v4.0.2 // indirect github.com/nrdcg/auroradns v1.1.0 // indirect - github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea // indirect - github.com/nrdcg/desec v0.11.0 // indirect + github.com/nrdcg/bunny-go v0.1.0 // indirect + github.com/nrdcg/desec v0.11.1 // indirect github.com/nrdcg/dnspod-go v0.4.0 // indirect github.com/nrdcg/freemyip v0.3.0 // indirect github.com/nrdcg/goacmedns v0.2.0 // indirect github.com/nrdcg/goinwx v0.11.0 // indirect - github.com/nrdcg/mailinabox v0.2.0 // indirect + github.com/nrdcg/mailinabox v0.3.0 // indirect github.com/nrdcg/namesilo v0.5.0 // indirect github.com/nrdcg/nodion v0.1.0 // indirect - github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0 // indirect - github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0 // indirect + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.103.0 // indirect + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.103.0 // indirect github.com/nrdcg/porkbun v0.4.0 // indirect + github.com/nrdcg/vegadns v0.3.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -323,7 +323,7 @@ require ( github.com/rs/cors v1.7.0 // indirect github.com/sacloud/api-client-go v0.3.3 // indirect github.com/sacloud/go-http v0.1.9 // indirect - github.com/sacloud/iaas-api-go v1.19.0 // indirect + github.com/sacloud/iaas-api-go v1.20.0 // indirect github.com/sacloud/packages-go v0.0.11 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -344,7 +344,7 @@ require ( github.com/spf13/viper v1.18.2 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.41 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect @@ -354,12 +354,12 @@ require ( github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect - github.com/volcengine/volc-sdk-golang v1.0.223 // indirect + github.com/volcengine/volc-sdk-golang v1.0.224 // indirect github.com/vultr/govultr/v3 v3.24.0 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/yandex-cloud/go-genproto v0.31.0 // indirect - github.com/yandex-cloud/go-sdk/services/dns v0.0.12 // indirect - github.com/yandex-cloud/go-sdk/v2 v2.19.0 // indirect + github.com/yandex-cloud/go-genproto v0.34.0 // indirect + github.com/yandex-cloud/go-sdk/services/dns v0.0.16 // indirect + github.com/yandex-cloud/go-sdk/v2 v2.24.0 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/errs v1.4.0 // indirect @@ -384,14 +384,14 @@ require ( golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/term v0.36.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/api v0.252.0 // indirect + google.golang.org/api v0.254.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/ns1/ns1-go.v2 v2.15.0 // indirect + gopkg.in/ns1/ns1-go.v2 v2.15.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect diff --git a/go.sum b/go.sum index 01f7b442d..d1f044eaf 100644 --- a/go.sum +++ b/go.sum @@ -115,8 +115,6 @@ github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= -github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= @@ -197,48 +195,48 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I= -github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= -github.com/aws/aws-sdk-go-v2/config v1.31.12 h1:pYM1Qgy0dKZLHX2cXslNacbcEFMkDMl+Bcj5ROuS6p8= -github.com/aws/aws-sdk-go-v2/config v1.31.12/go.mod h1:/MM0dyD7KSDPR+39p9ZNVKaHDLb9qnfDurvVS2KAhN8= -github.com/aws/aws-sdk-go-v2/credentials v1.18.16 h1:4JHirI4zp958zC026Sm+V4pSDwW4pwLefKrc0bF2lwI= -github.com/aws/aws-sdk-go-v2/credentials v1.18.16/go.mod h1:qQMtGx9OSw7ty1yLclzLxXCRbrkjWAM7JnObZjmCB7I= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2 v1.39.4 h1:qTsQKcdQPHnfGYBBs+Btl8QwxJeoWcOcPcixK90mRhg= +github.com/aws/aws-sdk-go-v2 v1.39.4/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= +github.com/aws/aws-sdk-go-v2/config v1.31.15 h1:gE3M4xuNXfC/9bG4hyowGm/35uQTi7bUKeYs5e/6uvU= +github.com/aws/aws-sdk-go-v2/config v1.31.15/go.mod h1:HvnvGJoE2I95KAIW8kkWVPJ4XhdrlvwJpV6pEzFQa8o= +github.com/aws/aws-sdk-go-v2/credentials v1.18.19 h1:Jc1zzwkSY1QbkEcLujwqRTXOdvW8ppND3jRBb/VhBQc= +github.com/aws/aws-sdk-go-v2/credentials v1.18.19/go.mod h1:DIfQ9fAk5H0pGtnqfqkbSIzky82qYnGvh06ASQXXg6A= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 h1:X7X4YKb+c0rkI6d4uJ5tEMxXgCZ+jZ/D6mvkno8c8Uw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11/go.mod h1:EqM6vPZQsZHYvC4Cai35UDg/f5NCEU+vp0WfbVqVcZc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 h1:7AANQZkF3ihM8fbdftpjhken0TP9sBzFbV/Ze/Y4HXA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11/go.mod h1:NTF4QCGkm6fzVwncpkFQqoquQyOolcyXfbpC98urj+c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 h1:ShdtWUZT37LCAA4Mw2kJAJtzaszfSHFb5n25sdcv4YE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11/go.mod h1:7bUb2sSr2MZ3M/N+VyETLTQtInemHXb/Fl3s8CLzm0Y= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= github.com/aws/aws-sdk-go-v2/service/ec2 v1.203.1 h1:ZgY9zeVAe+54Qa7o1GXKRNTez79lffCeJSSinhl+qec= github.com/aws/aws-sdk-go-v2/service/ec2 v1.203.1/go.mod h1:0naMk66LtdeTmE+1CWQTKwtzOQ2t8mavOhMhR0Pv1m0= github.com/aws/aws-sdk-go-v2/service/ecs v1.53.15 h1:uH0DMwDjLGgjjYMk3M1MXHggk37trTiJIvwyJNP17Ig= github.com/aws/aws-sdk-go-v2/service/ecs v1.53.15/go.mod h1:49tE5yYdlAHqZIO8u5+u9Xy9k8IaV0v5cstZrjnX5+c= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.0 h1:JOLRYFWMMKUABCp94HHfo0JBVQDVTLXOvWWphjpBBiQ= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.0/go.mod h1:WEOSRNyfIfvgrD9MuSIGrogKyuFahaVMziVq1pHI0NQ= -github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4 h1:KycXrohD5OxAZ5h02YechO2gevvoHfAPAaJM5l8zqb0= -github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4/go.mod h1:xNLZLn4SusktBQ5moqUOgiDKGz3a7vHwF4W0KD+WBPc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 h1:GpMf3z2KJa4RnJ0ew3Hac+hRFYLZ9DDjfgXjuW+pB54= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11/go.mod h1:6MZP3ZI4QQsgUCFTwMZA2V0sEriNQ8k2hmoHF3qjimQ= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.2 h1:pr1dQ9vamhAf2mYOgiRRC/w9Ht4POFhy6+xXw7hOqwY= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.2/go.mod h1:A4Ch93K7Wam4Qe0Wl0XbPgcgoL5KIJtFIe7wHw6OPWE= +github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 h1:KuoA/cmy/yK8n9v/d6WH36cZwGxFOrn0TmZ4lNN3MKQ= +github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1/go.mod h1:BymbICXBfXQHO6i+yTBhocA9a6DM0uMDQqYelqa9wzs= github.com/aws/aws-sdk-go-v2/service/ssm v1.56.13 h1:JfPeW7F6Y+VqBg6p+8zQv4wlgceguYu5ZT0USEGZ89g= github.com/aws/aws-sdk-go-v2/service/ssm v1.56.13/go.mod h1:EonGQFn66wZkJJrrKXrryrxoS3V30rcHvaWvc6oGHCI= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 h1:A1oRkiSQOWstGh61y4Wc/yQ04sqrQZr1Si/oAXj20/s= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.6/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 h1:M5nimZmugcZUO9wG7iVtROxPhiqyZX6ejS1lxlDPbTU= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.8/go.mod h1:mbef/pgKhtKRwrigPPs7SSSKZgytzP8PQ6P6JAAdqyM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 h1:S5GuJZpYxE0lKeMHKn+BRTz6PTFpgThyJ+5mYfux7BM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3/go.mod h1:X4OF+BTd7HIb3L+tc4UlWHVrpgwZZIVENU15pRDVTI0= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 h1:Ekml5vGg6sHSZLZJQJagefnVe6PmqC2oiRkBq4F7fU0= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.9/go.mod h1:/e15V+o1zFHWdH3u7lpI3rVBcxszktIKuHKCY2/py+k= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= -github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= +github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aziontech/azionapi-go-sdk v0.143.0 h1:4eEBlYT10prgeCVTNR9FIc7f59Crbl2zrH1a4D1BUqU= github.com/aziontech/azionapi-go-sdk v0.143.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= -github.com/baidubce/bce-sdk-go v0.9.248 h1:vB5OMuEC2xnO197M6OWUi24C8mYOZHKXT/8HuKQJUhU= -github.com/baidubce/bce-sdk-go v0.9.248/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/baidubce/bce-sdk-go v0.9.250 h1:fnvV5clsNCAP6pCauj0eNaUnoLVmjQGnco7rcMqp984= +github.com/baidubce/bce-sdk-go v0.9.250/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -415,12 +413,12 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-acme/alidns-20150109/v4 v4.6.1 h1:Dch3aWRcw4U62+jKPjPQN3iW3TPvgIywATbvHzojXeo= github.com/go-acme/alidns-20150109/v4 v4.6.1/go.mod h1:RBcqBA5IvUWtlpjx6dC6EkPVyBNLQ+mR18XoaP38BFY= -github.com/go-acme/lego/v4 v4.27.0 h1:cIhWd7Uj4BNFLEF3IpwuMkukVVRs5qjlp4KdUGa75yU= -github.com/go-acme/lego/v4 v4.27.0/go.mod h1:9FfNZHZmg6hf5CWOp4Lzo4gU8aBEvqZvrwdkBboa+4g= +github.com/go-acme/lego/v4 v4.28.0 h1:URKsCcybo7SjqqZckeBcDN9Vl29/bKS///75tcNkMHQ= +github.com/go-acme/lego/v4 v4.28.0/go.mod h1:bzjilr03IgbaOwlH396hq5W56Bi0/uoRwW/JM8hP7m4= github.com/go-acme/tencentclouddnspod v1.1.10 h1:ERVJ4mc3cT4Nb3+n6H/c1AwZnChGBqLoymE0NVYscKI= github.com/go-acme/tencentclouddnspod v1.1.10/go.mod h1:Bo/0YQJ/99FM+44HmCQkByuptX1tJsJ9V14MGV/2Qco= -github.com/go-acme/tencentedgdeone v1.1.19 h1:1jdEpMITrDuXHnu7QLOy2hpLW0BlDof70/KuRT+EiTo= -github.com/go-acme/tencentedgdeone v1.1.19/go.mod h1:gpu7HvXfcKWBrq5HAEBowZNkdk7JFPkagRzC/infUY0= +github.com/go-acme/tencentedgdeone v1.1.48 h1:WLyLBsRVhSLFmtbEFXk0naLODSQn7X6J0Fc/qR8xVUk= +github.com/go-acme/tencentedgdeone v1.1.48/go.mod h1:mu6tA+bPhlSd+CKUfzRikE0mfxmTlBI6dVTn9LY9dRI= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= @@ -506,8 +504,8 @@ github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXK github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= -github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -714,8 +712,8 @@ github.com/http-wasm/http-wasm-host-go v0.7.0/go.mod h1:adXKcLmL7yuavH/e0kBAp7b3 github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.172 h1:68VUVbLKwBxPh8tjCXwnLO/d8/thdZ+ExpxdFMEdK5A= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.172/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.173 h1:Y4ixGadyrK9xHw6Z+cyiiME3SBXepEcUoiT+B8C5FoQ= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.173/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -969,10 +967,10 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo= github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk= -github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea h1:OSgRS4kqOs/WuxuFOObP2gwrenL4/qiKXQbQugr/Two= -github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea/go.mod h1:IDRRngAngb2eTEaWgpO0hukQFI/vJId46fT1KErMytA= -github.com/nrdcg/desec v0.11.0 h1:XZVHy07sg12y8FozMp+l7XvzPsdzog0AYXuQMaHBsfs= -github.com/nrdcg/desec v0.11.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs= +github.com/nrdcg/bunny-go v0.1.0 h1:GAHTRpHaG/TxfLZlqoJ8OJFzw8rI74+jOTkzxWh0uHA= +github.com/nrdcg/bunny-go v0.1.0/go.mod h1:u+C9dgsspgtWVaAz6QkyV17s9fxD8viwwKoxb9XMz1A= +github.com/nrdcg/desec v0.11.1 h1:ilpKmCr4gGsLcyq3RHfHNmlRzm9fzT2XbWxoVaUCS0s= +github.com/nrdcg/desec v0.11.1/go.mod h1:2LuxHlOcwML/7cntu0eimONmA1U+ZxFDAonoSXr4igQ= github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc= @@ -981,18 +979,20 @@ github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0 github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= github.com/nrdcg/goinwx v0.11.0 h1:GER0SE3POub7rxARt3Y3jRy1OON1hwF1LRxHz5xsFBw= github.com/nrdcg/goinwx v0.11.0/go.mod h1:0BXSC0FxVtU4aTjX0Zw3x0DK32tjugLzeNIAGtwXvPQ= -github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk= -github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc= +github.com/nrdcg/mailinabox v0.3.0 h1:PHkC1elKXKAjEvdx2HHFMgcEGZFqudAl7aU3L2JDhM4= +github.com/nrdcg/mailinabox v0.3.0/go.mod h1:1eFIGcM4lI+AfFOUpbs548SFGz1ZWoMOGbECBmkghw4= github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0 h1:W28ZizQSS2aRWkFA3iAP9eiZS4OLFaiv35nXtq2lW/s= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0/go.mod h1:cVbzGjRhtXgrduaQbR1GR1x+VDU60NcXPMZ3+eQuiiY= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0 h1:gAOs1dkE7LFoWflzqrDqAhOprc0kF1a0fyV8C4HUPj4= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0/go.mod h1:EUBSYwop1K40VpcKy1haIK6kFK/gPT1atEk89OkY0Kg= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.103.0 h1:GPwwX9GFIBjV4u1M3Cr8eKCP6drW01IsfQSDIz6SUk8= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.103.0/go.mod h1:SfDIKzNQ5AGNMMOA3LGqSPnn63F6Gc4E4bsKArqymvg= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.103.0 h1:MjHla6lf1jpjGXORLpzMeo/tSmx0ejmjMjdjTByaDGY= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.103.0/go.mod h1:o1/kMADX0SlB4hJjWtcs3M6VIUOGR78yhPyiBv6oBkk= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= +github.com/nrdcg/vegadns v0.3.0 h1:11FQMw7xVIRUWO9o5+Z/5YZhmPWlm4oxUUH3F6EVqQU= +github.com/nrdcg/vegadns v0.3.0/go.mod h1:NqSyRKZuJlAsv8VI/7rSubfPXN68NwaJ0aG9KxQVFVo= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -1127,8 +1127,8 @@ github.com/sacloud/api-client-go v0.3.3 h1:ZpSAyGpITA8UFO3Hq4qMHZLGuNI1FgxAxo4sq github.com/sacloud/api-client-go v0.3.3/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo= github.com/sacloud/go-http v0.1.9 h1:Xa5PY8/pb7XWhwG9nAeXSrYXPbtfBWqawgzxD5co3VE= github.com/sacloud/go-http v0.1.9/go.mod h1:DpDG+MSyxYaBwPJ7l3aKLMzwYdTVtC5Bo63HActcgoE= -github.com/sacloud/iaas-api-go v1.19.0 h1:Bw4uygqukcvlblhWrITp94nikXqy2fnKoAC6929LkIA= -github.com/sacloud/iaas-api-go v1.19.0/go.mod h1:XV995RM1I7k5AHb7UZrCVyDF/8bZXDxa+uk1EXoj/Zs= +github.com/sacloud/iaas-api-go v1.20.0 h1:L4TfAzoFSwxrD3QXX8UxJa2o+GZrP9b863K+voTy3tQ= +github.com/sacloud/iaas-api-go v1.20.0/go.mod h1:XV995RM1I7k5AHb7UZrCVyDF/8bZXDxa+uk1EXoj/Zs= github.com/sacloud/packages-go v0.0.11 h1:hrRWLmfPM9w7GBs6xb5/ue6pEMl8t1UuDKyR/KfteHo= github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= @@ -1240,9 +1240,8 @@ github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSW github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046 h1:8rUlviSVOEe7TMk7W0gIPrW8MqEzYfZHpsNWSf8s2vg= github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.10/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.19/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.41 h1:XQDGrLX6v4McMP+2myhgQcy5JaPqSgwpLM1qa7ngUII= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.41/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48 h1:aoRUrz2ag27jQWcOKHgeE+toSti6/xPqHKMLruOtJuM= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME= github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E= github.com/testcontainers/testcontainers-go/modules/k3s v0.32.0 h1:Z3DTMveNUqeGJZ+CXZhpvI7OF1BS71Ywi3SwoXLZ4Lc= @@ -1301,8 +1300,8 @@ github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.223 h1:1EEK6VOUaA2Tu0VBD4VC5iSTTFag+KuNo+Vix469Tz4= -github.com/volcengine/volc-sdk-golang v1.0.223/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/volcengine/volc-sdk-golang v1.0.224 h1:k9Vtg64tQAgFTOGWzhyL0b0axuTuExXbLNVlslWlBZI= +github.com/volcengine/volc-sdk-golang v1.0.224/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= github.com/vulcand/oxy/v2 v2.0.3 h1:CPWVPfW4hVZXzwwiQzpFidbnJKpahjPHezM+7TkZRNw= github.com/vulcand/oxy/v2 v2.0.3/go.mod h1:k3t+xjyqmXVh88FdFDbYmUKMEvNpaejvBW14es6H70A= github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50= @@ -1319,12 +1318,12 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/yandex-cloud/go-genproto v0.31.0 h1:mFMS5SD1Lt8qErwefR8ChK3d0jg0tvbDLq57IqenpTg= -github.com/yandex-cloud/go-genproto v0.31.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk/services/dns v0.0.12 h1:c5TNaX7r3DqY37YJFbr7HyQFRcSe1WzCbR81LVwxXyk= -github.com/yandex-cloud/go-sdk/services/dns v0.0.12/go.mod h1:6CRtIkxq6iTSZIOT42EFns54CEr35ncECy4ix9lXUd4= -github.com/yandex-cloud/go-sdk/v2 v2.19.0 h1:Cuzjn6kkOD/KrBF/QyDbKS7b5GAu8fC2ZUjdBjit60A= -github.com/yandex-cloud/go-sdk/v2 v2.19.0/go.mod h1:4SwghU8RB4v2OQzhESgq5SF8XmCXIP80WhgrrNpetJ8= +github.com/yandex-cloud/go-genproto v0.34.0 h1:qhTJpPxOTKQbV44rIqoZSdzxDtZW27fkFjAcipEy8Zs= +github.com/yandex-cloud/go-genproto v0.34.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk/services/dns v0.0.16 h1:0UYrBlQjTO2ct5xcSx6rqkQB95wRBPMVwxfqLQD1sUE= +github.com/yandex-cloud/go-sdk/services/dns v0.0.16/go.mod h1:HlS3aIAdYEmJu2Ska/nzpcuv9LLVSMMXKGhzyLQwf5s= +github.com/yandex-cloud/go-sdk/v2 v2.24.0 h1:G53N/RB5g/jw2xNN0egspnwd2ByHA1OVH6wbTx/tIlo= +github.com/yandex-cloud/go-sdk/v2 v2.24.0/go.mod h1:ZRdpyOig8c/W3bNhwvkeXWWPeDScd9nmXv4AJzKvOsk= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= @@ -1862,8 +1861,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.252.0 h1:xfKJeAJaMwb8OC9fesr369rjciQ704AjU/psjkKURSI= -google.golang.org/api v0.252.0/go.mod h1:dnHOv81x5RAmumZ7BWLShB/u7JZNeyalImxHmtTHxqw= +google.golang.org/api v0.254.0 h1:jl3XrGj7lRjnlUvZAbAdhINTLbsg5dbjmR90+pTQvt4= +google.golang.org/api v0.254.0/go.mod h1:5BkSURm3D9kAqjGvBNgf0EcbX6Rnrf6UArKkwBzAyqQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1906,8 +1905,8 @@ google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuO google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1925,8 +1924,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1965,8 +1964,8 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/ns1/ns1-go.v2 v2.15.0 h1:cE3xSMdSCV8kf9SQldzqgW/Ueh7sv3yO2JwKtYxxz3E= -gopkg.in/ns1/ns1-go.v2 v2.15.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= +gopkg.in/ns1/ns1-go.v2 v2.15.1 h1:8rri2TzAPYcVbBGXn48+dz1Xg30PzHfZ4k8A9JOS0Z0= +gopkg.in/ns1/ns1-go.v2 v2.15.1/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= From 4e3022628d5e93bf8cabba7ef1d01938c20ec809 Mon Sep 17 00:00:00 2001 From: Romain Date: Tue, 4 Nov 2025 17:00:06 +0100 Subject: [PATCH 028/134] Filter unknown nodes with file and env for the deprecation loader Co-authored-by: Kevin Pollet --- pkg/cli/deprecation.go | 298 +++++++++++------- pkg/cli/fixtures/traefik_deprecated.toml | 3 + .../fixtures/traefik_multiple_deprecated.toml | 3 + pkg/cli/fixtures/traefik_no_deprecated.toml | 3 + 4 files changed, 196 insertions(+), 111 deletions(-) diff --git a/pkg/cli/deprecation.go b/pkg/cli/deprecation.go index c7c262ba7..73c4bea50 100644 --- a/pkg/cli/deprecation.go +++ b/pkg/cli/deprecation.go @@ -2,31 +2,39 @@ package cli import ( "errors" + "fmt" "os" "reflect" + "strconv" "strings" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/traefik/paerser/cli" + "github.com/traefik/paerser/env" + "github.com/traefik/paerser/file" "github.com/traefik/paerser/flag" "github.com/traefik/paerser/parser" ) type DeprecationLoader struct{} -func (d DeprecationLoader) Load(args []string, cmd *cli.Command) (bool, error) { - if logDeprecation(cmd.Configuration, args) { - return true, errors.New("incompatible deprecated static option found") +func (d DeprecationLoader) Load(args []string, _ *cli.Command) (bool, error) { + hasIncompatibleOptions, err := logDeprecations(args) + if err != nil { + log.Error().Err(err).Msg("Deprecated install configuration options analysis failed") + return false, nil } + if hasIncompatibleOptions { + return true, errors.New("incompatible deprecated install configuration option found") + } return false, nil } -// logDeprecation prints deprecation hints and returns whether incompatible deprecated options need to be removed. -func logDeprecation(traefikConfiguration interface{}, arguments []string) bool { - // This part doesn't handle properly a flag defined like this: - // --accesslog true +// logDeprecations prints deprecation hints and returns whether incompatible deprecated options need to be removed. +func logDeprecations(arguments []string) (bool, error) { + // This part doesn't handle properly a flag defined like this: --accesslog true // where `true` could be considered as a new argument. // This is not really an issue with the deprecation loader since it will filter the unknown nodes later in this function. var args []string @@ -35,86 +43,153 @@ func logDeprecation(traefikConfiguration interface{}, arguments []string) bool { args = append(args, arg+"=true") continue } - args = append(args, arg) } - labels, err := flag.Parse(args, nil) + // ARGS + // Parse arguments to labels. + argsLabels, err := flag.Parse(args, nil) if err != nil { - log.Error().Err(err).Msg("deprecated static options analysis failed") - return false + return false, fmt.Errorf("parsing arguments to labels: %w", err) } - node, err := parser.DecodeToNode(labels, "traefik") + config, err := parseDeprecatedConfig(argsLabels) if err != nil { - log.Error().Err(err).Msg("deprecated static options analysis failed") - return false + return false, fmt.Errorf("parsing deprecated config from args: %w", err) } - if node != nil && len(node.Children) > 0 { - config := &configuration{} - filterUnknownNodes(reflect.TypeOf(config), node) - - if len(node.Children) > 0 { - // Telling parser to look for the label struct tag to allow empty values. - err = parser.AddMetadata(config, node, parser.MetadataOpts{TagName: "label"}) - if err != nil { - log.Error().Err(err).Msg("deprecated static options analysis failed") - return false - } - - err = parser.Fill(config, node, parser.FillerOpts{}) - if err != nil { - log.Error().Err(err).Msg("deprecated static options analysis failed") - return false - } - - if config.deprecationNotice(log.With().Str("loader", "FLAG").Logger()) { - return true - } - - // No further deprecation parsing and logging, - // as args configuration contains at least one deprecated option. - return false - } + if config.deprecationNotice(log.With().Str("loader", "args").Logger()) { + return true, nil } // FILE - ref, err := flag.Parse(args, traefikConfiguration) + // Find the config file using the same logic as the normal file loader. + finder := cli.Finder{ + BasePaths: []string{"/etc/traefik/traefik", "$XDG_CONFIG_HOME/traefik", "$HOME/.config/traefik", "./traefik"}, + Extensions: []string{"toml", "yaml", "yml"}, + } + + configFile, ok := argsLabels["traefik.configfile"] + if !ok { + configFile = argsLabels["traefik.configFile"] + } + + filePath, err := finder.Find(configFile) if err != nil { - log.Error().Err(err).Msg("deprecated static options analysis failed") - return false + return false, fmt.Errorf("finding configuration file: %w", err) } - configFileFlag := "traefik.configfile" - if _, ok := ref["traefik.configFile"]; ok { - configFileFlag = "traefik.configFile" - } + if filePath != "" { + // We don't rely on the Parser file loader here to avoid issues with unknown fields. + // Parse file content into a generic map. + var fileConfig map[string]interface{} + if err := file.Decode(filePath, &fileConfig); err != nil { + return false, fmt.Errorf("decoding configuration file %s: %w", filePath, err) + } - config := &configuration{} - _, err = loadConfigFiles(ref[configFileFlag], config) + // Convert the file config to label format. + fileLabels := make(map[string]string) + flattenToLabels(fileConfig, "", fileLabels) - if err == nil { - if config.deprecationNotice(log.With().Str("loader", "FILE").Logger()) { - return true + config, err := parseDeprecatedConfig(fileLabels) + if err != nil { + return false, fmt.Errorf("parsing deprecated config from file: %w", err) + } + + if config.deprecationNotice(log.With().Str("loader", "file").Logger()) { + return true, nil } } - config = &configuration{} - l := EnvLoader{} - _, err = l.Load(os.Args, &cli.Command{ - Configuration: config, - }) + // ENV + vars := env.FindPrefixedEnvVars(os.Environ(), env.DefaultNamePrefix, &configuration{}) + if len(vars) > 0 { + // We don't rely on the Parser env loader here to avoid issues with unknown fields. + // Decode environment variables to a generic map. + var envConfig map[string]interface{} + if err := env.Decode(vars, env.DefaultNamePrefix, &envConfig); err != nil { + return false, fmt.Errorf("decoding environment variables: %w", err) + } - if err == nil { - if config.deprecationNotice(log.With().Str("loader", "ENV").Logger()) { - return true + // Convert the env config to label format. + envLabels := make(map[string]string) + flattenToLabels(envConfig, "", envLabels) + + config, err := parseDeprecatedConfig(envLabels) + if err != nil { + return false, fmt.Errorf("parsing deprecated config from environment variables: %w", err) + } + + if config.deprecationNotice(log.With().Str("loader", "env").Logger()) { + return true, nil } } - return false + return false, nil } +// flattenToLabels recursively flattens a nested map into label key-value pairs. +// Example: {"experimental": {"http3": true}} -> {"traefik.experimental.http3": "true"}. +func flattenToLabels(config interface{}, currKey string, labels map[string]string) { + switch v := config.(type) { + case map[string]interface{}: + for key, value := range v { + newKey := key + if currKey != "" { + newKey = currKey + "." + key + } + flattenToLabels(value, newKey, labels) + } + case []interface{}: + for i, item := range v { + newKey := currKey + "[" + strconv.Itoa(i) + "]" + flattenToLabels(item, newKey, labels) + } + default: + // Convert value to string and create label with traefik prefix. + labels["traefik."+currKey] = fmt.Sprintf("%v", v) + } +} + +// parseDeprecatedConfig parses command-line arguments using the deprecation configuration struct, +// filtering unknown nodes and checking for deprecated options. +// Returns true if incompatible deprecated options are found. +func parseDeprecatedConfig(labels map[string]string) (*configuration, error) { + // If no config, we can return without error to allow other loaders to proceed. + if len(labels) == 0 { + return nil, nil + } + + // Convert labels to node tree. + node, err := parser.DecodeToNode(labels, "traefik") + if err != nil { + return nil, fmt.Errorf("decoding to node: %w", err) + } + + // Filter unknown nodes and check for deprecated options. + config := &configuration{} + filterUnknownNodes(reflect.TypeOf(config), node) + + // If no config remains we can return without error, to allow other loaders to proceed. + if node == nil || len(node.Children) == 0 { + return nil, nil + } + + // Telling parser to look for the label struct tag to allow empty values. + err = parser.AddMetadata(config, node, parser.MetadataOpts{TagName: "label"}) + if err != nil { + return nil, fmt.Errorf("adding metadata to node: %w", err) + } + + err = parser.Fill(config, node, parser.FillerOpts{}) + if err != nil { + return nil, fmt.Errorf("filling configuration: %w", err) + } + + return config, nil +} + +// filterUnknownNodes removes from the node tree all nodes that do not correspond to any field in the given type. func filterUnknownNodes(fType reflect.Type, node *parser.Node) bool { var children []*parser.Node for _, child := range node.Children { @@ -127,6 +202,7 @@ func filterUnknownNodes(fType reflect.Type, node *parser.Node) bool { return len(node.Children) > 0 } +// hasKnownNodes checks whether the given node corresponds to a known field in the given type. func hasKnownNodes(rootType reflect.Type, node *parser.Node) bool { rType := rootType if rootType.Kind() == reflect.Pointer { @@ -177,7 +253,7 @@ func findTypedField(rType reflect.Type, node *parser.Node) (reflect.StructField, return reflect.StructField{}, false } -// configuration holds the static configuration removed/deprecated options. +// configuration holds the install configuration removed/deprecated options. type configuration struct { Core *core `json:"core,omitempty" toml:"core,omitempty" yaml:"core,omitempty" label:"allowEmpty" file:"allowEmpty"` Experimental *experimental `json:"experimental,omitempty" toml:"experimental,omitempty" yaml:"experimental,omitempty" label:"allowEmpty" file:"allowEmpty"` @@ -194,7 +270,7 @@ func (c *configuration) deprecationNotice(logger zerolog.Logger) bool { var incompatible bool if c.Pilot != nil { incompatible = true - logger.Error().Msg("Pilot configuration has been removed in v3, please remove all Pilot-related static configuration for Traefik to start." + + logger.Error().Msg("Pilot configuration has been removed in v3, please remove all Pilot-related install configuration for Traefik to start." + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#pilot") } @@ -242,13 +318,13 @@ func (p *providers) deprecationNotice(logger zerolog.Logger) bool { if p.Marathon != nil { incompatible = true - logger.Error().Msg("Marathon provider has been removed in v3, please remove all Marathon-related static configuration for Traefik to start." + + logger.Error().Msg("Marathon provider has been removed in v3, please remove all Marathon-related install configuration for Traefik to start." + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#marathon-provider") } if p.Rancher != nil { incompatible = true - logger.Error().Msg("Rancher provider has been removed in v3, please remove all Rancher-related static configuration for Traefik to start." + + logger.Error().Msg("Rancher provider has been removed in v3, please remove all Rancher-related install configuration for Traefik to start." + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#rancher-v1-provider") } @@ -291,14 +367,14 @@ func (d *docker) deprecationNotice(logger zerolog.Logger) bool { if d.SwarmMode != nil { incompatible = true logger.Error().Msg("Docker provider `swarmMode` option has been removed in v3, please use the Swarm Provider instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#docker-docker-swarm") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#docker-docker-swarm") } if d.TLS != nil && d.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("Docker provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + - "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional") + " Please remove all occurrences from the install configuration for Traefik to start." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional") } return incompatible @@ -318,7 +394,7 @@ func (s *swarm) deprecationNotice(logger zerolog.Logger) bool { if s.TLS != nil && s.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("Swarm provider `tls.CAOptional` option does not exist, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + - "Please remove all occurrences from the static configuration for Traefik to start.") + " Please remove all occurrences from the install configuration for Traefik to start.") } return incompatible @@ -338,8 +414,8 @@ func (e *etcd) deprecationNotice(logger zerolog.Logger) bool { if e.TLS != nil && e.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("ETCD provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + - "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_3") + " Please remove all occurrences from the install configuration for Traefik to start." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_3") } return incompatible @@ -359,8 +435,8 @@ func (r *redis) deprecationNotice(logger zerolog.Logger) bool { if r.TLS != nil && r.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("Redis provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + - "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_4") + " Please remove all occurrences from the install configuration for Traefik to start." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_4") } return incompatible @@ -381,14 +457,14 @@ func (c *consul) deprecationNotice(logger zerolog.Logger) bool { if c.Namespace != nil { incompatible = true logger.Error().Msg("Consul provider `namespace` option has been removed, please use the `namespaces` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#consul-provider") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#consul-provider") } if c.TLS != nil && c.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("Consul provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + - "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_1") + " Please remove all occurrences from the install configuration for Traefik to start." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_1") } return incompatible @@ -413,14 +489,14 @@ func (c *consulCatalog) deprecationNotice(logger zerolog.Logger) bool { if c.Namespace != nil { incompatible = true logger.Error().Msg("ConsulCatalog provider `namespace` option has been removed, please use the `namespaces` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#consulcatalog-provider") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#consulcatalog-provider") } if c.Endpoint != nil && c.Endpoint.TLS != nil && c.Endpoint.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("ConsulCatalog provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + - "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#endpointtlscaoptional") + " Please remove all occurrences from the install configuration for Traefik to start." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#endpointtlscaoptional") } return incompatible @@ -441,14 +517,14 @@ func (n *nomad) deprecationNotice(logger zerolog.Logger) bool { if n.Namespace != nil { incompatible = true logger.Error().Msg("Nomad provider `namespace` option has been removed, please use the `namespaces` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#nomad-provider") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#nomad-provider") } if n.Endpoint != nil && n.Endpoint.TLS != nil && n.Endpoint.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("Nomad provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + - "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#endpointtlscaoptional_1") + " Please remove all occurrences from the install configuration for Traefik to start." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#endpointtlscaoptional_1") } return incompatible @@ -468,8 +544,8 @@ func (h *http) deprecationNotice(logger zerolog.Logger) bool { if h.TLS != nil && h.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("HTTP provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + - "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_2") + " Please remove all occurrences from the install configuration for Traefik to start." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_2") } return incompatible @@ -486,8 +562,8 @@ func (i *ingress) deprecationNotice(logger zerolog.Logger) { if i.DisableIngressClassLookup != nil { logger.Error().Msg("Kubernetes Ingress provider `disableIngressClassLookup` option has been deprecated in v3.1, and will be removed in the next major version." + - "Please use the `disableClusterScopeResources` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#ingressclasslookup") + " Please use the `disableClusterScopeResources` option instead." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#ingressclasslookup") } } @@ -503,16 +579,16 @@ func (e *experimental) deprecationNotice(logger zerolog.Logger) bool { if e.HTTP3 != nil { logger.Error().Msg("HTTP3 is not an experimental feature in v3 and the associated enablement has been removed." + - "Please remove its usage from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3-details/#http3") + " Please remove its usage from the install configuration for Traefik to start." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3-details/#http3") return true } if e.KubernetesGateway != nil { logger.Error().Msg("KubernetesGateway provider is not an experimental feature starting with v3.1." + - "Please remove its usage from the static configuration." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#gateway-api-kubernetesgateway-provider") + " Please remove its usage from the install configuration." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#gateway-api-kubernetesgateway-provider") } return false @@ -539,57 +615,57 @@ func (t *tracing) deprecationNotice(logger zerolog.Logger) bool { if t.SpanNameLimit != nil { incompatible = true logger.Error().Msg("SpanNameLimit option for Tracing has been removed in v3, as Span names are now of a fixed length." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } if t.GlobalAttributes != nil { log.Warn().Msgf("tracing.globalAttributes option is now deprecated, please use tracing.resourceAttributes instead.") logger.Error().Msg("`tracing.globalAttributes` option has been deprecated in v3.3, and will be removed in the next major version." + - "Please use the `tracing.resourceAttributes` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#tracing-global-attributes") + " Please use the `tracing.resourceAttributes` option instead." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#tracing-global-attributes") } if t.Jaeger != nil { incompatible = true - logger.Error().Msg("Jaeger Tracing backend has been removed in v3, please remove all Jaeger-related Tracing static configuration for Traefik to start." + - "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + logger.Error().Msg("Jaeger Tracing backend has been removed in v3, please remove all Jaeger-related Tracing install configuration for Traefik to start." + + " In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } if t.Zipkin != nil { incompatible = true - logger.Error().Msg("Zipkin Tracing backend has been removed in v3, please remove all Zipkin-related Tracing static configuration for Traefik to start." + - "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + logger.Error().Msg("Zipkin Tracing backend has been removed in v3, please remove all Zipkin-related Tracing install configuration for Traefik to start." + + " In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } if t.Datadog != nil { incompatible = true - logger.Error().Msg("Datadog Tracing backend has been removed in v3, please remove all Datadog-related Tracing static configuration for Traefik to start." + - "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + logger.Error().Msg("Datadog Tracing backend has been removed in v3, please remove all Datadog-related Tracing install configuration for Traefik to start." + + " In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } if t.Instana != nil { incompatible = true - logger.Error().Msg("Instana Tracing backend has been removed in v3, please remove all Instana-related Tracing static configuration for Traefik to start." + - "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + logger.Error().Msg("Instana Tracing backend has been removed in v3, please remove all Instana-related Tracing install configuration for Traefik to start." + + " In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } if t.Haystack != nil { incompatible = true - logger.Error().Msg("Haystack Tracing backend has been removed in v3, please remove all Haystack-related Tracing static configuration for Traefik to start." + - "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + logger.Error().Msg("Haystack Tracing backend has been removed in v3, please remove all Haystack-related Tracing install configuration for Traefik to start." + + " In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } if t.Elastic != nil { incompatible = true - logger.Error().Msg("Elastic Tracing backend has been removed in v3, please remove all Elastic-related Tracing static configuration for Traefik to start." + - "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") + logger.Error().Msg("Elastic Tracing backend has been removed in v3, please remove all Elastic-related Tracing install configuration for Traefik to start." + + " In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } return incompatible diff --git a/pkg/cli/fixtures/traefik_deprecated.toml b/pkg/cli/fixtures/traefik_deprecated.toml index 21fa1d1c1..6ac4fdb69 100644 --- a/pkg/cli/fixtures/traefik_deprecated.toml +++ b/pkg/cli/fixtures/traefik_deprecated.toml @@ -3,3 +3,6 @@ [entrypoints.test.http.tls] [providers.marathon] + +[experimental] + futureUnknownFlag = true diff --git a/pkg/cli/fixtures/traefik_multiple_deprecated.toml b/pkg/cli/fixtures/traefik_multiple_deprecated.toml index 0847e9da3..8ab616205 100644 --- a/pkg/cli/fixtures/traefik_multiple_deprecated.toml +++ b/pkg/cli/fixtures/traefik_multiple_deprecated.toml @@ -6,3 +6,6 @@ [pilot] token="xxx" + +[experimental] + futureUnknownFlag = true diff --git a/pkg/cli/fixtures/traefik_no_deprecated.toml b/pkg/cli/fixtures/traefik_no_deprecated.toml index 282d2f873..44cb175d7 100644 --- a/pkg/cli/fixtures/traefik_no_deprecated.toml +++ b/pkg/cli/fixtures/traefik_no_deprecated.toml @@ -1,3 +1,6 @@ [accesslog] [entrypoints.test.http.tls] + +[experimental] + futureUnknownFlag = true From effca0a6039e0828dca92c6bdbacaac5b07c0580 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Fri, 7 Nov 2025 11:58:04 +0100 Subject: [PATCH 029/134] Update letsencrypt/pebble to use ghcr image --- integration/acme_test.go | 120 +++++++++++++---------- integration/resources/compose/pebble.yml | 3 +- 2 files changed, 68 insertions(+), 55 deletions(-) diff --git a/integration/acme_test.go b/integration/acme_test.go index be48e7557..d2fa1a920 100644 --- a/integration/acme_test.go +++ b/integration/acme_test.go @@ -21,6 +21,7 @@ import ( "github.com/traefik/traefik/v2/pkg/provider/acme" "github.com/traefik/traefik/v2/pkg/testhelpers" "github.com/traefik/traefik/v2/pkg/types" + "k8s.io/utils/strings/slices" ) // ACME test suites. @@ -35,9 +36,9 @@ func TestAcmeSuite(t *testing.T) { } type subCases struct { - host string - expectedCommonName string - expectedAlgorithm x509.PublicKeyAlgorithm + host string + expectedDomain string + expectedAlgorithm x509.PublicKeyAlgorithm } type acmeTestCase struct { @@ -142,9 +143,9 @@ func (s *AcmeSuite) TestHTTP01Domains() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_domains.toml", subCases: []subCases{{ - host: acmeDomain, - expectedCommonName: acmeDomain, - expectedAlgorithm: x509.RSA, + host: acmeDomain, + expectedDomain: acmeDomain, + expectedAlgorithm: x509.RSA, }}, template: templateModel{ Domains: []types.Domain{{ @@ -165,9 +166,9 @@ func (s *AcmeSuite) TestHTTP01StoreDomains() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_store_domains.toml", subCases: []subCases{{ - host: acmeDomain, - expectedCommonName: acmeDomain, - expectedAlgorithm: x509.RSA, + host: acmeDomain, + expectedDomain: acmeDomain, + expectedAlgorithm: x509.RSA, }}, template: templateModel{ Domain: types.Domain{ @@ -188,9 +189,9 @@ func (s *AcmeSuite) TestHTTP01DomainsInSAN() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_domains.toml", subCases: []subCases{{ - host: acmeDomain, - expectedCommonName: "acme.wtf", - expectedAlgorithm: x509.RSA, + host: acmeDomain, + expectedDomain: "acme.wtf", + expectedAlgorithm: x509.RSA, }}, template: templateModel{ Domains: []types.Domain{{ @@ -212,9 +213,9 @@ func (s *AcmeSuite) TestHTTP01OnHostRule() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_base.toml", subCases: []subCases{{ - host: acmeDomain, - expectedCommonName: acmeDomain, - expectedAlgorithm: x509.RSA, + host: acmeDomain, + expectedDomain: acmeDomain, + expectedAlgorithm: x509.RSA, }}, template: templateModel{ Acme: map[string]static.CertificateResolver{ @@ -233,14 +234,14 @@ func (s *AcmeSuite) TestMultipleResolver() { traefikConfFilePath: "fixtures/acme/acme_multiple_resolvers.toml", subCases: []subCases{ { - host: acmeDomain, - expectedCommonName: acmeDomain, - expectedAlgorithm: x509.RSA, + host: acmeDomain, + expectedDomain: acmeDomain, + expectedAlgorithm: x509.RSA, }, { - host: "tchouk.acme.wtf", - expectedCommonName: "tchouk.acme.wtf", - expectedAlgorithm: x509.ECDSA, + host: "tchouk.acme.wtf", + expectedDomain: "tchouk.acme.wtf", + expectedAlgorithm: x509.ECDSA, }, }, template: templateModel{ @@ -263,9 +264,9 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleECDSA() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_base.toml", subCases: []subCases{{ - host: acmeDomain, - expectedCommonName: acmeDomain, - expectedAlgorithm: x509.ECDSA, + host: acmeDomain, + expectedDomain: acmeDomain, + expectedAlgorithm: x509.ECDSA, }}, template: templateModel{ Acme: map[string]static.CertificateResolver{ @@ -284,9 +285,9 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleInvalidAlgo() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_base.toml", subCases: []subCases{{ - host: acmeDomain, - expectedCommonName: acmeDomain, - expectedAlgorithm: x509.RSA, + host: acmeDomain, + expectedDomain: acmeDomain, + expectedAlgorithm: x509.RSA, }}, template: templateModel{ Acme: map[string]static.CertificateResolver{ @@ -301,13 +302,14 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleInvalidAlgo() { s.retrieveAcmeCertificate(testCase) } +// TODO: check why this test do not use the ACME cert resolver. func (s *AcmeSuite) TestHTTP01OnHostRuleDefaultDynamicCertificatesWithWildcard() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_tls.toml", subCases: []subCases{{ - host: acmeDomain, - expectedCommonName: wildcardDomain, - expectedAlgorithm: x509.RSA, + host: acmeDomain, + expectedDomain: wildcardDomain, + expectedAlgorithm: x509.RSA, }}, template: templateModel{ Acme: map[string]static.CertificateResolver{ @@ -321,13 +323,14 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleDefaultDynamicCertificatesWithWildcard() s.retrieveAcmeCertificate(testCase) } +// TODO: check why this test do not use the ACME cert resolver. func (s *AcmeSuite) TestHTTP01OnHostRuleDynamicCertificatesWithWildcard() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_tls_dynamic.toml", subCases: []subCases{{ - host: acmeDomain, - expectedCommonName: wildcardDomain, - expectedAlgorithm: x509.RSA, + host: acmeDomain, + expectedDomain: wildcardDomain, + expectedAlgorithm: x509.RSA, }}, template: templateModel{ Acme: map[string]static.CertificateResolver{ @@ -345,9 +348,9 @@ func (s *AcmeSuite) TestTLSALPN01OnHostRuleTCP() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_tcp.toml", subCases: []subCases{{ - host: acmeDomain, - expectedCommonName: acmeDomain, - expectedAlgorithm: x509.RSA, + host: acmeDomain, + expectedDomain: acmeDomain, + expectedAlgorithm: x509.RSA, }}, template: templateModel{ Acme: map[string]static.CertificateResolver{ @@ -365,9 +368,9 @@ func (s *AcmeSuite) TestTLSALPN01OnHostRule() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_base.toml", subCases: []subCases{{ - host: acmeDomain, - expectedCommonName: acmeDomain, - expectedAlgorithm: x509.RSA, + host: acmeDomain, + expectedDomain: acmeDomain, + expectedAlgorithm: x509.RSA, }}, template: templateModel{ Acme: map[string]static.CertificateResolver{ @@ -385,9 +388,9 @@ func (s *AcmeSuite) TestTLSALPN01Domains() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_domains.toml", subCases: []subCases{{ - host: acmeDomain, - expectedCommonName: acmeDomain, - expectedAlgorithm: x509.RSA, + host: acmeDomain, + expectedDomain: acmeDomain, + expectedAlgorithm: x509.RSA, }}, template: templateModel{ Domains: []types.Domain{{ @@ -408,9 +411,9 @@ func (s *AcmeSuite) TestTLSALPN01DomainsInSAN() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_domains.toml", subCases: []subCases{{ - host: acmeDomain, - expectedCommonName: "acme.wtf", - expectedAlgorithm: x509.RSA, + host: acmeDomain, + expectedDomain: "acme.wtf", + expectedAlgorithm: x509.RSA, }}, template: templateModel{ Domains: []types.Domain{{ @@ -502,27 +505,38 @@ func (s *AcmeSuite) retrieveAcmeCertificate(testCase acmeTestCase) { req.Header.Set("Host", sub.host) req.Header.Set("Accept", "*/*") - var resp *http.Response + var ( + gotStatusCode int + gotDomains []string + gotPublicKeyAlgorithm x509.PublicKeyAlgorithm + ) // Retry to send a Request which uses the LE generated certificate err := try.Do(60*time.Second, func() error { - resp, err = client.Do(req) + resp, err := client.Do(req) if err != nil { return err } - cn := resp.TLS.PeerCertificates[0].Subject.CommonName - if cn != sub.expectedCommonName { - return fmt.Errorf("domain %s found instead of %s", cn, sub.expectedCommonName) + gotStatusCode = resp.StatusCode + gotPublicKeyAlgorithm = resp.TLS.PeerCertificates[0].PublicKeyAlgorithm + + // Here we are collecting the common name as it is used in wildcard tests. + gotDomains = append(gotDomains, resp.TLS.PeerCertificates[0].Subject.CommonName) + gotDomains = append(gotDomains, resp.TLS.PeerCertificates[0].DNSNames...) + + if !slices.Contains(gotDomains, sub.expectedDomain) { + return fmt.Errorf("domain name %s not found in domain names: %v", sub.expectedDomain, gotDomains) } return nil }) require.NoError(s.T(), err) - assert.Equal(s.T(), http.StatusOK, resp.StatusCode) + assert.Equal(s.T(), http.StatusOK, gotStatusCode) + // Check Domain into response certificate - assert.Equal(s.T(), sub.expectedCommonName, resp.TLS.PeerCertificates[0].Subject.CommonName) - assert.Equal(s.T(), sub.expectedAlgorithm, resp.TLS.PeerCertificates[0].PublicKeyAlgorithm) + assert.Contains(s.T(), gotDomains, sub.expectedDomain) + assert.Equal(s.T(), sub.expectedAlgorithm, gotPublicKeyAlgorithm) } } diff --git a/integration/resources/compose/pebble.yml b/integration/resources/compose/pebble.yml index f39dc1378..e9031904a 100644 --- a/integration/resources/compose/pebble.yml +++ b/integration/resources/compose/pebble.yml @@ -1,9 +1,8 @@ version: "3.8" services: pebble: - image: letsencrypt/pebble:v2.3.1 + image: ghcr.io/letsencrypt/pebble:2.8.0 command: - - pebble - --dnsserver - host.docker.internal:5053 environment: From b1834122a1ef9161371a1145be5345cd4aa6b2bf Mon Sep 17 00:00:00 2001 From: Romain Date: Fri, 7 Nov 2025 14:16:04 +0100 Subject: [PATCH 030/134] Prepare release v3.5.5 --- CHANGELOG.md | 12 ++++++++++++ script/gcg/traefik-bugfix.toml | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf46d9ae7..7bbc491ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [v3.5.5](https://github.com/traefik/traefik/tree/v3.5.5) (2025-11-07) +[All Commits](https://github.com/traefik/traefik/compare/v3.5.4...v3.5.5) + +**Bug fixes:** +- **[acme]** Bump github.com/go-acme/lego/v4 to v4.28.0 ([#12218](https://github.com/traefik/traefik/pull/12218) by [ldez](https://github.com/ldez)) +- **[server]** Filter unknown nodes with file and env for the deprecation loader ([#12227](https://github.com/traefik/traefik/pull/12227) by [rtribotte](https://github.com/rtribotte)) + +**Documentation:** +- **[acme]** Add missing ACME options and clean up table for more visibility ([#12208](https://github.com/traefik/traefik/pull/12208) by [sheddy-traefik](https://github.com/sheddy-traefik)) +- **[middleware]** Fix default encodings in compress middleware ([#12216](https://github.com/traefik/traefik/pull/12216) by [Belphemur](https://github.com/Belphemur)) +- Update Configuration Overview Page ([#12202](https://github.com/traefik/traefik/pull/12202) by [sheddy-traefik](https://github.com/sheddy-traefik)) + ## [v3.5.4](https://github.com/traefik/traefik/tree/v3.5.4) (2025-10-28) [All Commits](https://github.com/traefik/traefik/compare/v3.5.3...v3.5.4) diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index 36d6242a3..925f3a4cf 100644 --- a/script/gcg/traefik-bugfix.toml +++ b/script/gcg/traefik-bugfix.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example new bugfix v3.5.4 +# example new bugfix v3.5.5 CurrentRef = "v3.5" -PreviousRef = "v3.5.3" +PreviousRef = "v3.5.4" BaseBranch = "v3.5" -FutureCurrentRefName = "v3.5.4" +FutureCurrentRefName = "v3.5.5" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 From bb10b9df91728ebb43b594b70bfd697263bf7166 Mon Sep 17 00:00:00 2001 From: Romain Date: Fri, 7 Nov 2025 14:40:05 +0100 Subject: [PATCH 031/134] Prepare release v3.5.6 --- CHANGELOG.md | 9 +++++++-- script/gcg/traefik-bugfix.toml | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bbc491ce..4343f8351 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -## [v3.5.5](https://github.com/traefik/traefik/tree/v3.5.5) (2025-11-07) -[All Commits](https://github.com/traefik/traefik/compare/v3.5.4...v3.5.5) +## [v3.5.6](https://github.com/traefik/traefik/tree/v3.5.6) (2025-11-07) +[All Commits](https://github.com/traefik/traefik/compare/v3.5.4...v3.5.6) **Bug fixes:** - **[acme]** Bump github.com/go-acme/lego/v4 to v4.28.0 ([#12218](https://github.com/traefik/traefik/pull/12218) by [ldez](https://github.com/ldez)) @@ -10,6 +10,11 @@ - **[middleware]** Fix default encodings in compress middleware ([#12216](https://github.com/traefik/traefik/pull/12216) by [Belphemur](https://github.com/Belphemur)) - Update Configuration Overview Page ([#12202](https://github.com/traefik/traefik/pull/12202) by [sheddy-traefik](https://github.com/sheddy-traefik)) +## [v3.5.5](https://github.com/traefik/traefik/tree/v3.5.5) (2025-11-07) +[All Commits](https://github.com/traefik/traefik/compare/v3.5.4...v3.5.5) + +Release canceled. + ## [v3.5.4](https://github.com/traefik/traefik/tree/v3.5.4) (2025-10-28) [All Commits](https://github.com/traefik/traefik/compare/v3.5.3...v3.5.4) diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index 925f3a4cf..1039a5cde 100644 --- a/script/gcg/traefik-bugfix.toml +++ b/script/gcg/traefik-bugfix.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example new bugfix v3.5.5 +# example new bugfix v3.5.6 CurrentRef = "v3.5" -PreviousRef = "v3.5.4" +PreviousRef = "v3.5.5" BaseBranch = "v3.5" -FutureCurrentRefName = "v3.5.5" +FutureCurrentRefName = "v3.5.6" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 From 9e04dd6a3cca9c7ded649458d7c620891df66559 Mon Sep 17 00:00:00 2001 From: Romain Date: Fri, 7 Nov 2025 15:56:04 +0100 Subject: [PATCH 032/134] Make the aggregator compute provider namespace for router's parentRefs Co-authored-by: Kevin Pollet --- integration/fixtures/routing/multi_layer_auth.toml | 4 ++-- pkg/server/aggregator.go | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/integration/fixtures/routing/multi_layer_auth.toml b/integration/fixtures/routing/multi_layer_auth.toml index 4de17abe6..59d91c90f 100644 --- a/integration/fixtures/routing/multi_layer_auth.toml +++ b/integration/fixtures/routing/multi_layer_auth.toml @@ -42,10 +42,10 @@ [http.routers.admin-router] rule = "Header(`X-User-Role`, `admin`)" service = "admin-service" - parentRefs = ["parent-router@file"] + parentRefs = ["parent-router"] # Child router for developer role [http.routers.developer-router] rule = "Header(`X-User-Role`, `developer`)" service = "developer-service" - parentRefs = ["parent-router@file"] \ No newline at end of file + parentRefs = ["parent-router"] diff --git a/pkg/server/aggregator.go b/pkg/server/aggregator.go index 94006244b..49f311a84 100644 --- a/pkg/server/aggregator.go +++ b/pkg/server/aggregator.go @@ -65,6 +65,16 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint Msg("Router's `ruleSyntax` option is deprecated, please remove any usage of this option.") } + var qualifiedParentRefs []string + for _, parentRef := range router.ParentRefs { + if parts := strings.Split(parentRef, "@"); len(parts) == 1 { + parentRef = provider.MakeQualifiedName(pvd, parentRef) + } + + qualifiedParentRefs = append(qualifiedParentRefs, parentRef) + } + router.ParentRefs = qualifiedParentRefs + conf.HTTP.Routers[provider.MakeQualifiedName(pvd, routerName)] = router } for middlewareName, middleware := range configuration.HTTP.Middlewares { From 06db5168c0d936a0716cbede56bc2cd332be0d4f Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Fri, 7 Nov 2025 16:16:04 +0100 Subject: [PATCH 033/134] Prepare release v3.6.0 --- .github/PULL_REQUEST_TEMPLATE.md | 4 +- CHANGELOG.md | 47 +++++++++++++++++++++ script/gcg/traefik-final-release-part1.toml | 10 ++--- script/gcg/traefik-final-release-part2.toml | 8 ++-- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b4717dc18..8c1dcd419 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,11 +3,11 @@ PLEASE READ THIS MESSAGE. Documentation: - for Traefik v2: use branch v2.11 (fixes only) -- for Traefik v3: use branch v3.5 +- for Traefik v3: use branch v3.6 Bug: - for Traefik v2: use branch v2.11 (security fixes only) -- for Traefik v3: use branch v3.5 +- for Traefik v3: use branch v3.6 Enhancements: - use branch master diff --git a/CHANGELOG.md b/CHANGELOG.md index 51d966914..1cae75d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,50 @@ +## [v3.6.0](https://github.com/traefik/traefik/tree/v3.6.0) (2025-11-07) +[All Commits](https://github.com/traefik/traefik/compare/v3.5.0-rc1...v3.6.0) + +**Enhancements:** +- **[acme]** Add new certificatesresolvers options ([#11977](https://github.com/traefik/traefik/pull/11977) by [ldez](https://github.com/ldez)) +- **[consul,consulcatalog,nomad]** Log provider namespace during startup ([#12002](https://github.com/traefik/traefik/pull/12002) by [shreealt](https://github.com/shreealt)) +- **[docker]** Allow discovering non-running Docker containers ([#10645](https://github.com/traefik/traefik/pull/10645) by [acouvreur](https://github.com/acouvreur)) +- **[ecs]** AWS ECS IPv6 Support ([#12179](https://github.com/traefik/traefik/pull/12179) by [wizbit](https://github.com/wizbit)) +- **[file,k8s/crd,service]** Add least time load balancing strategy ([#12167](https://github.com/traefik/traefik/pull/12167) by [sdelicata](https://github.com/sdelicata)) +- **[healthcheck,tcp]** Add TCP Healthcheck ([#11238](https://github.com/traefik/traefik/pull/11238) by [ddtmachado](https://github.com/ddtmachado)) +- **[healthcheck]** Add passive health checks ([#11351](https://github.com/traefik/traefik/pull/11351) by [Nelwhix](https://github.com/Nelwhix)) +- **[k8s/crd]** Add highest random weight in Kubernetes CRD ([#12061](https://github.com/traefik/traefik/pull/12061) by [lbenguigui](https://github.com/lbenguigui)) +- **[k8s/gatewayapi]** Bump sigs.k8s.io/gateway-api to v1.4.0 ([#12140](https://github.com/traefik/traefik/pull/12140) by [kevinpollet](https://github.com/kevinpollet)) +- **[k8s/ingress]** Allow publishing services with type ExternalName ([#12065](https://github.com/traefik/traefik/pull/12065) by [james-callahan](https://github.com/james-callahan)) +- **[k8s]** Add Knative provider ([#11448](https://github.com/traefik/traefik/pull/11448) by [idurgakalyan](https://github.com/idurgakalyan)) +- **[middleware,authentication]** Add warning when maxBodySize is not set ([#12085](https://github.com/traefik/traefik/pull/12085) by [kianelbo](https://github.com/kianelbo)) +- **[middleware,server]** Multi-layer routing ([#12130](https://github.com/traefik/traefik/pull/12130) by [sdelicata](https://github.com/sdelicata)) +- **[plugins]** Support syscall ([#11939](https://github.com/traefik/traefik/pull/11939) by [david-garcia-garcia](https://github.com/david-garcia-garcia)) +- **[server]** Implement HTTP2 HPACK table size options ([#12050](https://github.com/traefik/traefik/pull/12050) by [GCHQDeveloper548](https://github.com/GCHQDeveloper548)) +- **[service,udp]** Avoid allocations in readLoop by using sync.Pool ([#12029](https://github.com/traefik/traefik/pull/12029) by [arturmelanchyk](https://github.com/arturmelanchyk)) +- **[service]** Add HighestRandomWeight load balancing algorithm ([#9946](https://github.com/traefik/traefik/pull/9946) by [mathieuHa](https://github.com/mathieuHa)) +- **[webui]** Add Traefik Hub demo in dashboard ([#12193](https://github.com/traefik/traefik/pull/12193) by [gndz07](https://github.com/gndz07)) +- **[webui]** Reduce vertical padding in dashboard table rows for more compact layout ([#12145](https://github.com/traefik/traefik/pull/12145) by [leccelecce](https://github.com/leccelecce)) + +**Bug fixes:** +- **[server]** Make the aggregator compute provider namespace for router's parentRefs ([#12235](https://github.com/traefik/traefik/pull/12235) by [rtribotte](https://github.com/rtribotte)) + +**Documentation:** +- Prepare release v3.6.0-rc1 ([#12211](https://github.com/traefik/traefik/pull/12211) by [kevinpollet](https://github.com/kevinpollet)) +- Fix broken link to migration guide on readme ([#12021](https://github.com/traefik/traefik/pull/12021) by [0slb](https://github.com/0slb)) +- Fix broken links in TCP Service and HTTP Router documentation ([#12215](https://github.com/traefik/traefik/pull/12215) by [sheddy-traefik](https://github.com/sheddy-traefik)) +- Fix typo in v3.6 migration guide ([#12212](https://github.com/traefik/traefik/pull/12212) by [jnoordsij](https://github.com/jnoordsij)) + +**Misc:** +- Merge branch v3.5 into master ([#12210](https://github.com/traefik/traefik/pull/12210) by [rtribotte](https://github.com/rtribotte)) +- Merge branch v3.5 into master ([#12191](https://github.com/traefik/traefik/pull/12191) by [rtribotte](https://github.com/rtribotte)) +- Merge branch v3.5 into master ([#12188](https://github.com/traefik/traefik/pull/12188) by [rtribotte](https://github.com/rtribotte)) +- Merge branch v3.5 into master ([#12160](https://github.com/traefik/traefik/pull/12160) by [rtribotte](https://github.com/rtribotte)) +- Merge branch v3.5 into master ([#12136](https://github.com/traefik/traefik/pull/12136) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v3.5 into master ([#12120](https://github.com/traefik/traefik/pull/12120) by [rtribotte](https://github.com/rtribotte)) +- Merge branch v3.5 into master ([#12095](https://github.com/traefik/traefik/pull/12095) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v3.5 into master ([#12051](https://github.com/traefik/traefik/pull/12051) by [rtribotte](https://github.com/rtribotte)) +- Merge branch v3.5 into master ([#11976](https://github.com/traefik/traefik/pull/11976) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v3.5 into master ([#11940](https://github.com/traefik/traefik/pull/11940) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v3.5 into master ([#11900](https://github.com/traefik/traefik/pull/11900) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v3.5 into v3.6 ([#12242](https://github.com/traefik/traefik/pull/12242) by [kevinpollet](https://github.com/kevinpollet)) + ## [v3.5.6](https://github.com/traefik/traefik/tree/v3.5.6) (2025-11-07) [All Commits](https://github.com/traefik/traefik/compare/v3.5.4...v3.5.6) diff --git a/script/gcg/traefik-final-release-part1.toml b/script/gcg/traefik-final-release-part1.toml index 95291aea5..844b5809a 100644 --- a/script/gcg/traefik-final-release-part1.toml +++ b/script/gcg/traefik-final-release-part1.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example final release of v3.5.0 -CurrentRef = "v3.5" -PreviousRef = "v3.5.0-rc1" -BaseBranch = "v3.5" -FutureCurrentRefName = "v3.5.0" +# example final release of v3.6.0 +CurrentRef = "v3.6" +PreviousRef = "v3.6.0-rc1" +BaseBranch = "v3.6" +FutureCurrentRefName = "v3.6.0" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/script/gcg/traefik-final-release-part2.toml b/script/gcg/traefik-final-release-part2.toml index ceaa118de..925566e7f 100644 --- a/script/gcg/traefik-final-release-part2.toml +++ b/script/gcg/traefik-final-release-part2.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example final release of v3.5.0 -CurrentRef = "v3.5.0-rc1" -PreviousRef = "v3.4.0-rc1" +# example final release of v3.6.0 +CurrentRef = "v3.6.0-rc1" +PreviousRef = "v3.5.0-rc1" BaseBranch = "master" -FutureCurrentRefName = "v3.5.0" +FutureCurrentRefName = "v3.6.0" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 From f45317c9c994ade67d843b88a4569934c0bf2db9 Mon Sep 17 00:00:00 2001 From: Nicolas Mengin Date: Wed, 12 Nov 2025 09:48:04 +0100 Subject: [PATCH 034/134] Fix Gateway API version and the list of features supported --- .../providers/kubernetes/kubernetes-gateway.md | 2 +- .../routing-configuration/kubernetes/gateway-api.md | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md index 4b4a0a534..c1ee3c4d5 100644 --- a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md +++ b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md @@ -10,7 +10,7 @@ specification from the Kubernetes Special Interest Groups (SIGs). This provider supports Standard version [v1.4.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.4.0) of the Gateway API specification. -It fully supports all HTTP core and some extended features, as well as the `TCPRoute` and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels). +It fully supports all `HTTPRoute` core and some extended features, like `BackendTLSPolicy`, and `GRPCRoute` resources from the [Standard channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels), as well as `TCPRoute`, and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels). For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.4.0/traefik-traefik). diff --git a/docs/content/reference/routing-configuration/kubernetes/gateway-api.md b/docs/content/reference/routing-configuration/kubernetes/gateway-api.md index a25e147c4..8f5d27c40 100644 --- a/docs/content/reference/routing-configuration/kubernetes/gateway-api.md +++ b/docs/content/reference/routing-configuration/kubernetes/gateway-api.md @@ -8,11 +8,12 @@ description: "The Kubernetes Gateway API can be used as a provider for routing a When using the Kubernetes Gateway API provider, Traefik leverages the Gateway API Custom Resource Definitions (CRDs) to obtain its routing configuration. For detailed information on the Gateway API concepts and resources, refer to the official [documentation](https://gateway-api.sigs.k8s.io/). -The Kubernetes Gateway API provider supports version [v1.2.1](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.2.1) of the specification. +The Kubernetes Gateway API provider supports version [v1.4.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.4.0) of the specification. -It fully supports all `HTTPRoute` core and some extended features, like `GRPCRoute`, as well as the `TCPRoute` and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels). +It fully supports all `HTTPRoute` core and some extended features, like `BackendTLSPolicy`, and `GRPCRoute` resources from the [Standard channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels), as well as `TCPRoute`, and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels). + +For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.4.0/traefik-traefik). -For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.2.1/traefik-traefik). ## Deploying a Gateway From 77b1282570dc80a6bbe7e83742ce44496923be45 Mon Sep 17 00:00:00 2001 From: "Gina A." <70909035+gndz07@users.noreply.github.com> Date: Wed, 12 Nov 2025 10:20:16 +0100 Subject: [PATCH 035/134] Fix blocked navigation on Safari --- webui/src/layout/EmptyPlaceholder.tsx | 15 +++++++++++++-- webui/src/pages/http/HttpMiddlewares.tsx | 6 ++---- webui/src/pages/http/HttpRouters.tsx | 6 ++---- webui/src/pages/http/HttpServices.tsx | 6 ++---- webui/src/pages/tcp/TcpMiddlewares.tsx | 6 ++---- webui/src/pages/tcp/TcpRouters.tsx | 6 ++---- webui/src/pages/tcp/TcpServices.tsx | 6 ++---- webui/src/pages/udp/UdpRouters.tsx | 6 ++---- webui/src/pages/udp/UdpServices.tsx | 6 ++---- 9 files changed, 29 insertions(+), 34 deletions(-) diff --git a/webui/src/layout/EmptyPlaceholder.tsx b/webui/src/layout/EmptyPlaceholder.tsx index e81ace9df..e62fcb112 100644 --- a/webui/src/layout/EmptyPlaceholder.tsx +++ b/webui/src/layout/EmptyPlaceholder.tsx @@ -1,9 +1,20 @@ -import { Flex, Text } from '@traefiklabs/faency' +import { AriaTd, Flex, Text } from '@traefiklabs/faency' import { FiAlertTriangle } from 'react-icons/fi' -export const EmptyPlaceholder = ({ message = 'No data available' }: { message?: string }) => ( +type EmptyPlaceholderProps = { + message?: string +} +export const EmptyPlaceholder = ({ message = 'No data available' }: EmptyPlaceholderProps) => ( {message} ) + +export const EmptyPlaceholderTd = (props: EmptyPlaceholderProps) => { + return ( + + + + ) +} diff --git a/webui/src/pages/http/HttpMiddlewares.tsx b/webui/src/pages/http/HttpMiddlewares.tsx index e3274009e..4ec86d841 100644 --- a/webui/src/pages/http/HttpMiddlewares.tsx +++ b/webui/src/pages/http/HttpMiddlewares.tsx @@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh' import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' -import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' +import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder' import { parseMiddlewareType } from 'libs/parsers' export const makeRowRender = (): RenderRowType => { @@ -79,9 +79,7 @@ export const HttpMiddlewaresRender = ({ {(isEmpty || !!error) && ( - - - + )} diff --git a/webui/src/pages/http/HttpRouters.tsx b/webui/src/pages/http/HttpRouters.tsx index 06c2c255f..7896ca38a 100644 --- a/webui/src/pages/http/HttpRouters.tsx +++ b/webui/src/pages/http/HttpRouters.tsx @@ -16,7 +16,7 @@ import SortableTh from 'components/tables/SortableTh' import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' -import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' +import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder' export const makeRowRender = (protocol = 'http'): RenderRowType => { const HttpRoutersRenderRow = (row) => ( @@ -100,9 +100,7 @@ export const HttpRoutersRender = ({ {(isEmpty || !!error) && ( - - - + )} diff --git a/webui/src/pages/http/HttpServices.tsx b/webui/src/pages/http/HttpServices.tsx index 8c41badcd..e49bc82b6 100644 --- a/webui/src/pages/http/HttpServices.tsx +++ b/webui/src/pages/http/HttpServices.tsx @@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh' import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' -import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' +import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder' export const makeRowRender = (): RenderRowType => { const HttpServicesRenderRow = (row) => ( @@ -78,9 +78,7 @@ export const HttpServicesRender = ({ {(isEmpty || !!error) && ( - - - + )} diff --git a/webui/src/pages/tcp/TcpMiddlewares.tsx b/webui/src/pages/tcp/TcpMiddlewares.tsx index b0189a2e5..25bca597b 100644 --- a/webui/src/pages/tcp/TcpMiddlewares.tsx +++ b/webui/src/pages/tcp/TcpMiddlewares.tsx @@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh' import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' -import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' +import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder' import { parseMiddlewareType } from 'libs/parsers' export const makeRowRender = (): RenderRowType => { @@ -79,9 +79,7 @@ export const TcpMiddlewaresRender = ({ {(isEmpty || !!error) && ( - - - + )} diff --git a/webui/src/pages/tcp/TcpRouters.tsx b/webui/src/pages/tcp/TcpRouters.tsx index f3cd3d497..8a8f638ac 100644 --- a/webui/src/pages/tcp/TcpRouters.tsx +++ b/webui/src/pages/tcp/TcpRouters.tsx @@ -16,7 +16,7 @@ import SortableTh from 'components/tables/SortableTh' import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' -import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' +import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder' export const makeRowRender = (): RenderRowType => { const TcpRoutersRenderRow = (row) => ( @@ -96,9 +96,7 @@ export const TcpRoutersRender = ({ {(isEmpty || !!error) && ( - - - + )} diff --git a/webui/src/pages/tcp/TcpServices.tsx b/webui/src/pages/tcp/TcpServices.tsx index 77480fd4e..13df8792b 100644 --- a/webui/src/pages/tcp/TcpServices.tsx +++ b/webui/src/pages/tcp/TcpServices.tsx @@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh' import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' -import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' +import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder' export const makeRowRender = (): RenderRowType => { const TcpServicesRenderRow = (row) => ( @@ -78,9 +78,7 @@ export const TcpServicesRender = ({ {(isEmpty || !!error) && ( - - - + )} diff --git a/webui/src/pages/udp/UdpRouters.tsx b/webui/src/pages/udp/UdpRouters.tsx index ce6348b75..b468630ce 100644 --- a/webui/src/pages/udp/UdpRouters.tsx +++ b/webui/src/pages/udp/UdpRouters.tsx @@ -15,7 +15,7 @@ import SortableTh from 'components/tables/SortableTh' import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' -import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' +import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder' export const makeRowRender = (): RenderRowType => { const UdpRoutersRenderRow = (row) => ( @@ -81,9 +81,7 @@ export const UdpRoutersRender = ({ {(isEmpty || !!error) && ( - - - + )} diff --git a/webui/src/pages/udp/UdpServices.tsx b/webui/src/pages/udp/UdpServices.tsx index 5d6b47c66..76abc3d02 100644 --- a/webui/src/pages/udp/UdpServices.tsx +++ b/webui/src/pages/udp/UdpServices.tsx @@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh' import Tooltip from 'components/Tooltip' import TooltipText from 'components/TooltipText' import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination' -import { EmptyPlaceholder } from 'layout/EmptyPlaceholder' +import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder' export const makeRowRender = (): RenderRowType => { const UdpServicesRenderRow = (row) => ( @@ -78,9 +78,7 @@ export const UdpServicesRender = ({ {(isEmpty || !!error) && ( - - - + )} From a01c73d506e4ba186f882ef53a281862095f778f Mon Sep 17 00:00:00 2001 From: "Gina A." <70909035+gndz07@users.noreply.github.com> Date: Wed, 12 Nov 2025 12:16:06 +0100 Subject: [PATCH 036/134] Restore remote Upgrade to Hub button web component --- webui/package.json | 2 +- .../traefiklabs-hub-button-app/main-v1.js | 23 - .../traefiklabs-hub-button-app/main-v1.js.map | 1 - webui/src/components/TableFilter.tsx | 4 +- .../src/hooks/use-hub-upgrade-button.spec.tsx | 131 ++ webui/src/hooks/use-hub-upgrade-button.tsx | 61 + webui/src/layout/Navigation.spec.tsx | 75 + webui/src/layout/Navigation.tsx | 147 +- .../src/pages/hub-demo/HubDashboard.spec.tsx | 8 +- webui/src/pages/hub-demo/HubDashboard.tsx | 6 +- webui/src/pages/hub-demo/constants.ts | 1 + .../src/pages/hub-demo/use-hub-demo.spec.tsx | 5 +- webui/src/pages/hub-demo/use-hub-demo.tsx | 10 +- .../scriptVerification.integration.spec.ts | 0 .../workers/scriptVerification.spec.ts | 22 +- .../workers/scriptVerification.ts | 4 +- .../workers/scriptVerificationWorker.ts | 0 webui/yarn.lock | 1445 +++++++++++++++-- 18 files changed, 1642 insertions(+), 303 deletions(-) delete mode 100644 webui/public/traefiklabs-hub-button-app/main-v1.js delete mode 100644 webui/public/traefiklabs-hub-button-app/main-v1.js.map create mode 100644 webui/src/hooks/use-hub-upgrade-button.spec.tsx create mode 100644 webui/src/hooks/use-hub-upgrade-button.tsx create mode 100644 webui/src/pages/hub-demo/constants.ts rename webui/src/{pages/hub-demo => utils}/workers/scriptVerification.integration.spec.ts (100%) rename webui/src/{pages/hub-demo => utils}/workers/scriptVerification.spec.ts (82%) rename webui/src/{pages/hub-demo => utils}/workers/scriptVerification.ts (92%) rename webui/src/{pages/hub-demo => utils}/workers/scriptVerificationWorker.ts (100%) diff --git a/webui/package.json b/webui/package.json index d2972a97c..b75ac6d1b 100644 --- a/webui/package.json +++ b/webui/package.json @@ -49,7 +49,7 @@ "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.1", "@testing-library/user-event": "^14.5.2", - "@traefiklabs/faency": "11.1.4", + "@traefiklabs/faency": "12.0.4", "@types/lodash": "^4.17.16", "@types/node": "^22.15.18", "@types/react": "^18.2.0", diff --git a/webui/public/traefiklabs-hub-button-app/main-v1.js b/webui/public/traefiklabs-hub-button-app/main-v1.js deleted file mode 100644 index e140dab34..000000000 --- a/webui/public/traefiklabs-hub-button-app/main-v1.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -(()=>{var e={110:(e,t,n)=>{"use strict";var r=n(309),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},l={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},o={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},i={};function u(e){return r.isMemo(e)?o:i[e.$$typeof]||a}i[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},i[r.Memo]=o;var s=Object.defineProperty,c=Object.getOwnPropertyNames,f=Object.getOwnPropertySymbols,d=Object.getOwnPropertyDescriptor,p=Object.getPrototypeOf,h=Object.prototype;e.exports=function e(t,n,r){if("string"!==typeof n){if(h){var a=p(n);a&&a!==h&&e(t,a,r)}var o=c(n);f&&(o=o.concat(f(n)));for(var i=u(t),m=u(n),g=0;g{"use strict";var n="function"===typeof Symbol&&Symbol.for,r=n?Symbol.for("react.element"):60103,a=n?Symbol.for("react.portal"):60106,l=n?Symbol.for("react.fragment"):60107,o=n?Symbol.for("react.strict_mode"):60108,i=n?Symbol.for("react.profiler"):60114,u=n?Symbol.for("react.provider"):60109,s=n?Symbol.for("react.context"):60110,c=n?Symbol.for("react.async_mode"):60111,f=n?Symbol.for("react.concurrent_mode"):60111,d=n?Symbol.for("react.forward_ref"):60112,p=n?Symbol.for("react.suspense"):60113,h=n?Symbol.for("react.suspense_list"):60120,m=n?Symbol.for("react.memo"):60115,g=n?Symbol.for("react.lazy"):60116,v=n?Symbol.for("react.block"):60121,y=n?Symbol.for("react.fundamental"):60117,b=n?Symbol.for("react.responder"):60118,S=n?Symbol.for("react.scope"):60119;function k(e){if("object"===typeof e&&null!==e){var t=e.$$typeof;switch(t){case r:switch(e=e.type){case c:case f:case l:case i:case o:case p:return e;default:switch(e=e&&e.$$typeof){case s:case d:case g:case m:case u:return e;default:return t}}case a:return t}}}function w(e){return k(e)===f}t.AsyncMode=c,t.ConcurrentMode=f,t.ContextConsumer=s,t.ContextProvider=u,t.Element=r,t.ForwardRef=d,t.Fragment=l,t.Lazy=g,t.Memo=m,t.Portal=a,t.Profiler=i,t.StrictMode=o,t.Suspense=p,t.isAsyncMode=function(e){return w(e)||k(e)===c},t.isConcurrentMode=w,t.isContextConsumer=function(e){return k(e)===s},t.isContextProvider=function(e){return k(e)===u},t.isElement=function(e){return"object"===typeof e&&null!==e&&e.$$typeof===r},t.isForwardRef=function(e){return k(e)===d},t.isFragment=function(e){return k(e)===l},t.isLazy=function(e){return k(e)===g},t.isMemo=function(e){return k(e)===m},t.isPortal=function(e){return k(e)===a},t.isProfiler=function(e){return k(e)===i},t.isStrictMode=function(e){return k(e)===o},t.isSuspense=function(e){return k(e)===p},t.isValidElementType=function(e){return"string"===typeof e||"function"===typeof e||e===l||e===f||e===i||e===o||e===p||e===h||"object"===typeof e&&null!==e&&(e.$$typeof===g||e.$$typeof===m||e.$$typeof===u||e.$$typeof===s||e.$$typeof===d||e.$$typeof===y||e.$$typeof===b||e.$$typeof===S||e.$$typeof===v)},t.typeOf=k},309:(e,t,n)=>{"use strict";e.exports=n(746)},463:(e,t,n)=>{"use strict";var r=n(791),a=n(296);function l(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n