diff --git a/.golangci.yml b/.golangci.yml index 8d811008d..359e07d7a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,8 +1,5 @@ run: timeout: 10m - skip-files: [] - skip-dirs: - - pkg/provider/kubernetes/crd/generated/ linters-settings: govet: @@ -209,11 +206,16 @@ linters: - maintidx # kind of duplicate of gocyclo - nonamedreturns # Too strict - gosmopolitan # not relevant + - exportloopref # Useless with go1.22 + - musttag + - intrange # bug (fixed in golangci-lint v1.58) issues: exclude-use-default: false max-issues-per-linter: 0 max-same-issues: 0 + exclude-dirs: + - pkg/provider/kubernetes/crd/generated/ exclude: - 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked' - "should have a package comment, unless it's in another file for this package" diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index b664bbe3f..d59457ec7 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -25,7 +25,7 @@ global_job_config: - export "PATH=${GOPATH}/bin:${PATH}" - mkdir -vp "${SEMAPHORE_GIT_DIR}" "${GOPATH}/bin" - export GOPROXY=https://proxy.golang.org,direct - - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.56.2 + - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.57.0 - curl -sSfL https://gist.githubusercontent.com/traefiker/6d7ac019c11d011e4f131bb2cca8900e/raw/goreleaser.sh | bash -s -- -b "${GOPATH}/bin" - checkout - cache restore traefik-$(checksum go.sum) diff --git a/cmd/traefik/traefik_test.go b/cmd/traefik/traefik_test.go index 079b3e3a5..fd521b5e5 100644 --- a/cmd/traefik/traefik_test.go +++ b/cmd/traefik/traefik_test.go @@ -95,7 +95,6 @@ func TestAppendCertMetric(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/docs/content/contributing/maintainers.md b/docs/content/contributing/maintainers.md index d379677e8..e91bfcc89 100644 --- a/docs/content/contributing/maintainers.md +++ b/docs/content/contributing/maintainers.md @@ -21,6 +21,8 @@ description: "Traefik Proxy is an open source software with a thriving community * Harold Ozouf [@jspdown](https://github.com/jspdown) * Tom Moulard [@tommoulard](https://github.com/tommoulard) * Landry Benguigui [@lbenguigui](https://github.com/lbenguigui) +* Simon Delicata [@sdelicata](https://github.com/sdelicata) +* Baptiste Mayelle [@youkoulayley](https://github.com/youkoulayley) ## Past Maintainers diff --git a/docs/content/migration/v2-to-v3.md b/docs/content/migration/v2-to-v3.md index f2ec92c1a..61a60d990 100644 --- a/docs/content/migration/v2-to-v3.md +++ b/docs/content/migration/v2-to-v3.md @@ -92,6 +92,34 @@ Docker provider `tls.CAOptional` option has been removed in v3, as TLS client au The `tls.caOptional` option should be removed from the Docker provider static configuration. +### Kubernetes Gateway API + +#### Experimental Channel Resources (TLSRoute and TCPRoute) + +In v3, the Kubernetes Gateway API provider does not enable support for the experimental channel API resources by default. + +##### Remediation + +The `experimentalChannel` option should be used to enable the support for the experimental channel API resources. + +??? example "An example usage of the Kubernetes Gateway API provider with experimental channel support enabled" + + ```yaml tab="File (YAML)" + providers: + kubernetesGateway: + experimentalChannel: true + ``` + + ```toml tab="File (TOML)" + [providers.kubernetesGateway] + experimentalChannel = true + # ... + ``` + + ```bash tab="CLI" + --providers.kubernetesgateway.experimentalchannel=true + ``` + ### Experimental Configuration #### HTTP3 @@ -696,7 +724,7 @@ Here are two possible transition strategies: Please check the [OpenTelemetry Tracing provider documention](../observability/tracing/opentelemetry.md) for more information. -#### Internal Resources Observability (AccessLogs, Metrics and Tracing) +#### Internal Resources Observability In v3, observability for internal routers or services (e.g.: `ping@internal`) is disabled by default. To enable it one should use the new `addInternals` option for AccessLogs, Metrics or Tracing. @@ -704,4 +732,4 @@ Please take a look at the observability documentation for more information: - [AccessLogs](../observability/access-logs.md#addinternals) - [Metrics](../observability/metrics/overview.md#addinternals) -- [AccessLogs](../observability/tracing/overview.md#addinternals) +- [Tracing](../observability/tracing/overview.md#addinternals) diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index ed38e5263..626e9da45 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -563,3 +563,20 @@ To enable these ciphers, please set the option `CipherSuites` in your [TLS confi > (https://go.dev/doc/go1.22#crypto/tls) To enable TLS 1.0, please set the option `MinVersion` to `VersionTLS10` in your [TLS configuration](https://doc.traefik.io/traefik/https/tls/#cipher-suites) or set the environment variable `GODEBUG=tls10server=1`. + +## v2.11.1 + +### Maximum Router Priority Value + +Before v2.11.1, the maximum user-defined router priority value is: + +- `MaxInt32` for 32-bit platforms, +- `MaxInt64` for 64-bit platforms. + +Please check out the [go documentation](https://pkg.go.dev/math#pkg-constants) for more information. + +In v2.11.1, Traefik reserves a range of priorities for its internal routers and now, +the maximum user-defined router priority value is: + +- `(MaxInt32 - 1000)` for 32-bit platforms, +- `(MaxInt64 - 1000)` for 64-bit platforms. diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index beb114db1..2105cc030 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -30,7 +30,7 @@ accessLog: {} _Optional, Default="false"_ -Enables accessLogs for internal resources. +Enables accessLogs for internal resources (e.g.: `ping@internal`). ```yaml tab="File (YAML)" accesslog: @@ -187,7 +187,7 @@ accessLog: [accessLog.fields] defaultMode = "keep" - + [accessLog.fields.names] "ClientUsername" = "drop" diff --git a/docs/content/observability/metrics/overview.md b/docs/content/observability/metrics/overview.md index 082d4aed0..a49c5053c 100644 --- a/docs/content/observability/metrics/overview.md +++ b/docs/content/observability/metrics/overview.md @@ -21,7 +21,7 @@ and [Kubernetes](https://grafana.com/grafana/dashboards/17347) deployments. _Optional, Default="false"_ -Enables metrics for internal resources. +Enables metrics for internal resources (e.g.: `ping@internals`). ```yaml tab="File (YAML)" metrics: diff --git a/docs/content/observability/tracing/overview.md b/docs/content/observability/tracing/overview.md index 72dd20cd6..e81f63edf 100644 --- a/docs/content/observability/tracing/overview.md +++ b/docs/content/observability/tracing/overview.md @@ -36,7 +36,7 @@ tracing: {} _Optional, Default="false"_ -Enables tracing for internal resources. +Enables tracing for internal resources (e.g.: `ping@internal`). ```yaml tab="File (YAML)" tracing: @@ -159,4 +159,4 @@ tracing: ```bash tab="CLI" --tracing.capturedResponseHeaders[0]=X-CustomHeader -``` \ No newline at end of file +``` diff --git a/docs/content/providers/kubernetes-gateway.md b/docs/content/providers/kubernetes-gateway.md index 95b5ffd02..fe49dbe75 100644 --- a/docs/content/providers/kubernetes-gateway.md +++ b/docs/content/providers/kubernetes-gateway.md @@ -212,6 +212,29 @@ providers: --providers.kubernetesgateway.namespaces=default,production ``` +### `experimentalChannel` + +_Optional, Default: false_ + +Toggles support for the Experimental Channel resources ([Gateway API release channels documentation](https://gateway-api.sigs.k8s.io/concepts/versioning/#release-channels)). +This option currently enables support for `TCPRoute` and `TLSRoute`. + +```yaml tab="File (YAML)" +providers: + kubernetesGateway: + experimentalChannel: true +``` + +```toml tab="File (TOML)" +[providers.kubernetesGateway] + experimentalChannel = true + # ... +``` + +```bash tab="CLI" +--providers.kubernetesgateway.experimentalchannel=true +``` + ### `labelselector` _Optional, Default: ""_ diff --git a/docs/content/providers/nomad.md b/docs/content/providers/nomad.md index f1d0d1fe1..85b3ac033 100644 --- a/docs/content/providers/nomad.md +++ b/docs/content/providers/nomad.md @@ -511,3 +511,27 @@ providers: --providers.nomad.namespaces=ns1,ns2 # ... ``` + +### `allowEmptyServices` + +_Optional, Default: false_ + +If the parameter is set to `true`, +it allows the creation of an empty [servers load balancer](../routing/services/index.md#servers-load-balancer) if the targeted Nomad service has no endpoints available. This results in a `503` HTTP response instead of a `404`. + +```yaml tab="File (YAML)" +providers: + nomad: + allowEmptyServices: true + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + allowEmptyServices = true + # ... +``` + +```bash tab="CLI" +--providers.nomad.allowEmptyServices=true +``` diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 45cd217a5..6194dfbfe 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -729,6 +729,9 @@ Kubernetes certificate authority file path (not needed for in-cluster client). `--providers.kubernetesgateway.endpoint`: Kubernetes server endpoint (required for external cluster client). +`--providers.kubernetesgateway.experimentalchannel`: +Toggles Experimental Channel resources support (TCPRoute, TLSRoute...). (Default: ```false```) + `--providers.kubernetesgateway.labelselector`: Kubernetes label selector to select specific GatewayClasses. @@ -786,6 +789,9 @@ Kubernetes bearer token (not needed for in-cluster client). It accepts either a `--providers.nomad`: Enable Nomad backend with default settings. (Default: ```false```) +`--providers.nomad.allowemptyservices`: +Allow the creation of services without endpoints. (Default: ```false```) + `--providers.nomad.constraints`: Constraints is an expression that Traefik matches against the Nomad service's tags to determine whether to create route(s) for that service. diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index f7ab1bd79..7fcde07fc 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -729,6 +729,9 @@ Kubernetes certificate authority file path (not needed for in-cluster client). `TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_ENDPOINT`: Kubernetes server endpoint (required for external cluster client). +`TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_EXPERIMENTALCHANNEL`: +Toggles Experimental Channel resources support (TCPRoute, TLSRoute...). (Default: ```false```) + `TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_LABELSELECTOR`: Kubernetes label selector to select specific GatewayClasses. @@ -786,6 +789,9 @@ Kubernetes bearer token (not needed for in-cluster client). It accepts either a `TRAEFIK_PROVIDERS_NOMAD`: Enable Nomad backend with default settings. (Default: ```false```) +`TRAEFIK_PROVIDERS_NOMAD_ALLOWEMPTYSERVICES`: +Allow the creation of services without endpoints. (Default: ```false```) + `TRAEFIK_PROVIDERS_NOMAD_CONSTRAINTS`: Constraints is an expression that Traefik matches against the Nomad service's tags to determine whether to create route(s) for that service. diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index cef7d56e9..e5a6e379e 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -146,6 +146,7 @@ namespaces = ["foobar", "foobar"] labelSelector = "foobar" throttleDuration = "42s" + experimentalChannel = true [providers.rest] insecure = true [providers.consulCatalog] @@ -184,6 +185,7 @@ stale = true exposedByDefault = true refreshInterval = "42s" + allowEmptyServices = true namespaces = ["foobar", "foobar"] [providers.nomad.endpoint] address = "foobar" diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 84404356d..362418fbc 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -163,6 +163,7 @@ providers: - foobar labelSelector: foobar throttleDuration: 42s + experimentalChannel: true rest: insecure: true consulCatalog: @@ -215,6 +216,7 @@ providers: stale: true exposedByDefault: true refreshInterval: 42s + allowEmptyServices: true namespaces: - foobar - foobar diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index 73929cfe8..0166667e4 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -374,7 +374,7 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne | [4] | `routes[n].priority` | Defines the [priority](../routers/index.md#priority) to disambiguate rules of the same length, for route matching | | [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) | | [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name | -| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace | +| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace. It can be omitted when the Middleware is in the IngressRoute namespace. | | [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) | | [9] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. | | [10] | `services[n].serversTransport` | Defines the reference to a [ServersTransport](#kind-serverstransport). The ServersTransport namespace is assumed to be the [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) namespace (see [ServersTransport reference](#serverstransport-reference)). | diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index 04d979dda..884487693 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -442,6 +442,14 @@ The priority is directly equal to the length of the rule, and so the longest len A value of `0` for the priority is ignored: `priority = 0` means that the default rules length sorting is used. +??? warning "Maximum Value" + + Traefik reserves a range of priorities for its internal routers, + the maximum user-defined router priority value is: + + - `(MaxInt32 - 1000)` for 32-bit platforms, + - `(MaxInt64 - 1000)` for 64-bit platforms. + ??? info "How default priorities are computed" ```yaml tab="File (YAML)" @@ -1148,6 +1156,14 @@ The priority is directly equal to the length of the rule, and so the longest len A value of `0` for the priority is ignored: `priority = 0` means that the default rules length sorting is used. +??? warning "Maximum Value" + + Traefik reserves a range of priorities for its internal routers, + the maximum user-defined router priority value is: + + - `(MaxInt32 - 1000)` for 32-bit platforms, + - `(MaxInt64 - 1000)` for 64-bit platforms. + ??? info "How default priorities are computed" ```yaml tab="File (YAML)" diff --git a/go.mod b/go.mod index 513d968c9..6c04b1d7c 100644 --- a/go.mod +++ b/go.mod @@ -61,7 +61,7 @@ require ( github.com/tidwall/gjson v1.17.0 github.com/traefik/grpc-web v0.16.0 github.com/traefik/paerser v0.2.0 - github.com/traefik/yaegi v0.15.1 + github.com/traefik/yaegi v0.16.1 github.com/unrolled/render v1.0.2 github.com/unrolled/secure v1.0.9 github.com/vulcand/oxy/v2 v2.0.0-20230427132221-be5cf38f3c1c diff --git a/go.sum b/go.sum index 1ff19c374..1faff9087 100644 --- a/go.sum +++ b/go.sum @@ -1097,8 +1097,8 @@ github.com/traefik/grpc-web v0.16.0 h1:eeUWZaFg6ZU0I9dWOYE2D5qkNzRBmXzzuRlxdltas github.com/traefik/grpc-web v0.16.0/go.mod h1:2ttniSv7pTgBWIU2HZLokxRfFX3SA60c/DTmQQgVml4= github.com/traefik/paerser v0.2.0 h1:zqCLGSXoNlcBd+mzqSCLjon/I6phqIjeJL2xFB2ysgQ= github.com/traefik/paerser v0.2.0/go.mod h1:afzaVcgF8A+MpTnPG4wBr4whjanCSYA6vK5RwaYVtRc= -github.com/traefik/yaegi v0.15.1 h1:YA5SbaL6HZA0Exh9T/oArRHqGN2HQ+zgmCY7dkoTXu4= -github.com/traefik/yaegi v0.15.1/go.mod h1:AVRxhaI2G+nUsaM1zyktzwXn69G3t/AuTDrCiTds9p0= +github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E= +github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY= github.com/transip/gotransip/v6 v6.23.0 h1:PsTdjortrEZ8IFFifEryzjVjOy9SgK4ahlnhKBBIQgA= github.com/transip/gotransip/v6 v6.23.0/go.mod h1:nzv9eN2tdsUrm5nG5ZX6AugYIU4qgsMwIn2c0EZLk8c= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= diff --git a/integration/fixtures/k8s_gateway.toml b/integration/fixtures/k8s_gateway.toml index 0649ab249..4db5bde6a 100644 --- a/integration/fixtures/k8s_gateway.toml +++ b/integration/fixtures/k8s_gateway.toml @@ -27,3 +27,4 @@ address = ":8443" [providers.kubernetesGateway] + experimentalChannel = true diff --git a/integration/fixtures/tcp/service_errors.toml b/integration/fixtures/tcp/service_errors.toml index 98e552f6b..922df2bd1 100644 --- a/integration/fixtures/tcp/service_errors.toml +++ b/integration/fixtures/tcp/service_errors.toml @@ -24,10 +24,12 @@ [tcp.routers.router1] service = "service1" rule = "HostSNI(`snitest.net`)" + [tcp.routers.router1.tls] [tcp.routers.router2] service = "service2" rule = "HostSNI(`snitest.com`)" + [tcp.routers.router2.tls] [tcp.services] [tcp.services.service1] diff --git a/integration/https_test.go b/integration/https_test.go index e4a68dfb8..39914d016 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -1134,8 +1134,6 @@ func (s *HTTPSSuite) TestWithDomainFronting() { } for _, test := range testCases { - test := test - req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443", nil) require.NoError(s.T(), err) req.Host = test.hostHeader @@ -1179,8 +1177,6 @@ func (s *HTTPSSuite) TestWithInvalidTLSOption() { } for _, test := range testCases { - test := test - tlsConfig := &tls.Config{ InsecureSkipVerify: true, } diff --git a/pkg/api/dashboard/dashboard.go b/pkg/api/dashboard/dashboard.go index b42d04247..d1da0eeef 100644 --- a/pkg/api/dashboard/dashboard.go +++ b/pkg/api/dashboard/dashboard.go @@ -39,7 +39,7 @@ func Append(router *mux.Router, customAssets fs.FS) { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options w.Header().Del("Content-Type") - http.StripPrefix("/dashboard/", http.FileServer(http.FS(assets))).ServeHTTP(w, r) + http.StripPrefix("/dashboard/", http.FileServerFS(assets)).ServeHTTP(w, r) }) } @@ -56,7 +56,7 @@ func (g Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options w.Header().Del("Content-Type") - http.FileServer(http.FS(assets)).ServeHTTP(w, r) + http.FileServerFS(assets).ServeHTTP(w, r) } func safePrefix(req *http.Request) string { diff --git a/pkg/api/dashboard/dashboard_test.go b/pkg/api/dashboard/dashboard_test.go index ef0ffc635..28c734d94 100644 --- a/pkg/api/dashboard/dashboard_test.go +++ b/pkg/api/dashboard/dashboard_test.go @@ -42,7 +42,6 @@ func Test_safePrefix(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -91,7 +90,6 @@ func Test_ContentSecurityPolicy(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/api/handler_entrypoint_test.go b/pkg/api/handler_entrypoint_test.go index 18bf5cc4e..768cd3dcd 100644 --- a/pkg/api/handler_entrypoint_test.go +++ b/pkg/api/handler_entrypoint_test.go @@ -210,7 +210,6 @@ func TestHandler_EntryPoints(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/api/handler_http_test.go b/pkg/api/handler_http_test.go index 5d5f6d25e..f605c4ac4 100644 --- a/pkg/api/handler_http_test.go +++ b/pkg/api/handler_http_test.go @@ -998,7 +998,6 @@ func TestHandler_HTTP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/api/handler_overview_test.go b/pkg/api/handler_overview_test.go index 3e8743a81..d3ef01f72 100644 --- a/pkg/api/handler_overview_test.go +++ b/pkg/api/handler_overview_test.go @@ -269,7 +269,6 @@ func TestHandler_Overview(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/api/handler_tcp_test.go b/pkg/api/handler_tcp_test.go index 2bf5e5706..e08897880 100644 --- a/pkg/api/handler_tcp_test.go +++ b/pkg/api/handler_tcp_test.go @@ -874,7 +874,6 @@ func TestHandler_TCP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/api/handler_test.go b/pkg/api/handler_test.go index 01ee922ef..5a8125315 100644 --- a/pkg/api/handler_test.go +++ b/pkg/api/handler_test.go @@ -127,7 +127,6 @@ func TestHandler_RawData(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -252,7 +251,6 @@ func TestHandler_GetMiddleware(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/api/handler_udp_test.go b/pkg/api/handler_udp_test.go index 741239940..dd3067314 100644 --- a/pkg/api/handler_udp_test.go +++ b/pkg/api/handler_udp_test.go @@ -564,7 +564,6 @@ func TestHandler_UDP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/api/sort_test.go b/pkg/api/sort_test.go index 30a9c3a62..74436b7f0 100644 --- a/pkg/api/sort_test.go +++ b/pkg/api/sort_test.go @@ -828,7 +828,6 @@ func TestSortRouters(t *testing.T) { }, } for _, test := range testCases { - test := test t.Run(fmt.Sprintf("%s-%s", test.direction, test.sortBy), func(t *testing.T) { t.Parallel() @@ -1339,7 +1338,6 @@ func TestSortServices(t *testing.T) { }, } for _, test := range testCases { - test := test t.Run(fmt.Sprintf("%s-%s", test.direction, test.sortBy), func(t *testing.T) { t.Parallel() @@ -1674,7 +1672,6 @@ func TestSortMiddlewares(t *testing.T) { }, } for _, test := range testCases { - test := test t.Run(fmt.Sprintf("%s-%s", test.direction, test.sortBy), func(t *testing.T) { t.Parallel() diff --git a/pkg/cli/deprecation_test.go b/pkg/cli/deprecation_test.go index c225c0e7b..b33987024 100644 --- a/pkg/cli/deprecation_test.go +++ b/pkg/cli/deprecation_test.go @@ -267,7 +267,6 @@ func TestDeprecationNotice(t *testing.T) { } for _, test := range tests { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -385,7 +384,6 @@ func TestLoad(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { tconfig := cmd.NewTraefikConfiguration() c := &cli.Command{Configuration: tconfig} diff --git a/pkg/collector/collector.go b/pkg/collector/collector.go index 9b974cc5c..4db377e4f 100644 --- a/pkg/collector/collector.go +++ b/pkg/collector/collector.go @@ -65,7 +65,7 @@ func createBody(staticConfiguration *static.Configuration) (*bytes.Buffer, error } buf := new(bytes.Buffer) - err = json.NewEncoder(buf).Encode(data) //nolint:musttag // cannot be changed for historical reasons. + err = json.NewEncoder(buf).Encode(data) if err != nil { return nil, err } diff --git a/pkg/config/kv/kv_node_test.go b/pkg/config/kv/kv_node_test.go index c4a76b843..86b7d6ea2 100644 --- a/pkg/config/kv/kv_node_test.go +++ b/pkg/config/kv/kv_node_test.go @@ -244,7 +244,6 @@ func TestDecodeToNode(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/config/kv/kv_test.go b/pkg/config/kv/kv_test.go index 9dafcaab4..2a951cc51 100644 --- a/pkg/config/kv/kv_test.go +++ b/pkg/config/kv/kv_test.go @@ -87,7 +87,6 @@ func TestDecode(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/config/runtime/runtime_http_test.go b/pkg/config/runtime/runtime_http_test.go index 6080e770f..57938b616 100644 --- a/pkg/config/runtime/runtime_http_test.go +++ b/pkg/config/runtime/runtime_http_test.go @@ -208,7 +208,6 @@ func TestGetRoutersByEntryPoints(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() runtimeConfig := NewConfig(test.conf) diff --git a/pkg/config/runtime/runtime_tcp_test.go b/pkg/config/runtime/runtime_tcp_test.go index 8263d5626..c4726ac72 100644 --- a/pkg/config/runtime/runtime_tcp_test.go +++ b/pkg/config/runtime/runtime_tcp_test.go @@ -208,7 +208,6 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() runtimeConfig := NewConfig(test.conf) diff --git a/pkg/config/runtime/runtime_test.go b/pkg/config/runtime/runtime_test.go index 54e34e854..f09edbedb 100644 --- a/pkg/config/runtime/runtime_test.go +++ b/pkg/config/runtime/runtime_test.go @@ -665,8 +665,6 @@ func TestPopulateUsedBy(t *testing.T) { }, } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/config/runtime/runtime_udp_test.go b/pkg/config/runtime/runtime_udp_test.go index 5e83dff05..5531bb934 100644 --- a/pkg/config/runtime/runtime_udp_test.go +++ b/pkg/config/runtime/runtime_udp_test.go @@ -189,7 +189,6 @@ func TestGetUDPRoutersByEntryPoints(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() runtimeConfig := NewConfig(test.conf) diff --git a/pkg/config/static/static_config_test.go b/pkg/config/static/static_config_test.go index 0fbd83056..691540580 100644 --- a/pkg/config/static/static_config_test.go +++ b/pkg/config/static/static_config_test.go @@ -26,7 +26,6 @@ func TestHasEntrypoint(t *testing.T) { } for _, test := range tests { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go index fe8f4b7a4..3eb276308 100644 --- a/pkg/healthcheck/healthcheck_test.go +++ b/pkg/healthcheck/healthcheck_test.go @@ -243,7 +243,6 @@ func TestServiceHealthChecker_newRequest(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -405,7 +404,6 @@ func TestServiceHealthChecker_Launch(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/ip/checker_test.go b/pkg/ip/checker_test.go index 69ab9c1da..1b044e049 100644 --- a/pkg/ip/checker_test.go +++ b/pkg/ip/checker_test.go @@ -36,7 +36,6 @@ func TestIsAuthorized(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -117,7 +116,6 @@ func TestNew(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -289,7 +287,6 @@ func TestContainsIsAllowed(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/ip/strategy_test.go b/pkg/ip/strategy_test.go index 1409b1d54..8bc5d285e 100644 --- a/pkg/ip/strategy_test.go +++ b/pkg/ip/strategy_test.go @@ -21,7 +21,6 @@ func TestRemoteAddrStrategy_GetIP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -61,7 +60,6 @@ func TestDepthStrategy_GetIP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -109,7 +107,6 @@ func TestTrustedIPsStrategy_GetIP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/metrics/datadog_test.go b/pkg/metrics/datadog_test.go index cd4b99433..0b0dfd3df 100644 --- a/pkg/metrics/datadog_test.go +++ b/pkg/metrics/datadog_test.go @@ -67,7 +67,6 @@ func TestDatadog_parseDatadogAddress(t *testing.T) { } for _, test := range tests { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/metrics/opentelemetry_test.go b/pkg/metrics/opentelemetry_test.go index 9d8da3cd6..c778f2fec 100644 --- a/pkg/metrics/opentelemetry_test.go +++ b/pkg/metrics/opentelemetry_test.go @@ -89,8 +89,6 @@ func TestOpenTelemetry_labels(t *testing.T) { } for _, test := range tests { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -181,8 +179,6 @@ func TestOpenTelemetry_GaugeCollectorAdd(t *testing.T) { } for _, test := range tests { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -275,8 +271,6 @@ func TestOpenTelemetry_GaugeCollectorSet(t *testing.T) { } for _, test := range tests { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/metrics/prometheus_test.go b/pkg/metrics/prometheus_test.go index 4fcb56b90..91e5a56fe 100644 --- a/pkg/metrics/prometheus_test.go +++ b/pkg/metrics/prometheus_test.go @@ -64,7 +64,6 @@ func TestRegisterPromState(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { actualNbRegistries := 0 for _, prom := range test.prometheusSlice { @@ -381,7 +380,6 @@ func TestPrometheus(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.name, func(t *testing.T) { family := findMetricFamily(test.name, metricsFamilies) if family == nil { diff --git a/pkg/middlewares/accesslog/logger_formatters_test.go b/pkg/middlewares/accesslog/logger_formatters_test.go index cfa4281ec..028e3dbf7 100644 --- a/pkg/middlewares/accesslog/logger_formatters_test.go +++ b/pkg/middlewares/accesslog/logger_formatters_test.go @@ -86,7 +86,6 @@ func TestCommonLogFormatter_Format(t *testing.T) { t.Setenv("TZ", "Etc/GMT+9") for _, test := range testCases { - test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -150,7 +149,6 @@ func Test_toLog(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index dddcb26a7..c5a6102b7 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -180,7 +180,6 @@ func TestLoggerHeaderFields(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { logFile, err := os.CreateTemp(t.TempDir(), "*.log") require.NoError(t, err) @@ -469,7 +468,6 @@ func TestLoggerJSON(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -687,7 +685,6 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { // NOTE: It is not possible to run these cases in parallel because we capture Stdout diff --git a/pkg/middlewares/accesslog/parser_test.go b/pkg/middlewares/accesslog/parser_test.go index 1712f1a8c..1e194dd7e 100644 --- a/pkg/middlewares/accesslog/parser_test.go +++ b/pkg/middlewares/accesslog/parser_test.go @@ -60,7 +60,6 @@ func TestParseAccessLog(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/accesslog/save_retries_test.go b/pkg/middlewares/accesslog/save_retries_test.go index add4cc28f..ca296b2a5 100644 --- a/pkg/middlewares/accesslog/save_retries_test.go +++ b/pkg/middlewares/accesslog/save_retries_test.go @@ -28,8 +28,6 @@ func TestSaveRetries(t *testing.T) { } for _, test := range tests { - test := test - t.Run(fmt.Sprintf("%d retries", test.requestAttempt), func(t *testing.T) { t.Parallel() saveRetries := &SaveRetries{} diff --git a/pkg/middlewares/addprefix/add_prefix_test.go b/pkg/middlewares/addprefix/add_prefix_test.go index b7bbeb697..2bcc2ad7f 100644 --- a/pkg/middlewares/addprefix/add_prefix_test.go +++ b/pkg/middlewares/addprefix/add_prefix_test.go @@ -29,7 +29,6 @@ func TestNewAddPrefix(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -75,7 +74,6 @@ func TestAddPrefix(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/auth/basic_auth_test.go b/pkg/middlewares/auth/basic_auth_test.go index c8128bf05..6198988f6 100644 --- a/pkg/middlewares/auth/basic_auth_test.go +++ b/pkg/middlewares/auth/basic_auth_test.go @@ -210,7 +210,6 @@ func TestBasicAuthUsersFromFile(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/auth/digest_auth_test.go b/pkg/middlewares/auth/digest_auth_test.go index be554aa17..ae472e3d0 100644 --- a/pkg/middlewares/auth/digest_auth_test.go +++ b/pkg/middlewares/auth/digest_auth_test.go @@ -88,7 +88,6 @@ func TestDigestAuthUsersFromFile(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/buffering/buffering_test.go b/pkg/middlewares/buffering/buffering_test.go index 3d40aee5f..de6f50170 100644 --- a/pkg/middlewares/buffering/buffering_test.go +++ b/pkg/middlewares/buffering/buffering_test.go @@ -48,7 +48,6 @@ func TestBuffering(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/compress/brotli/brotli_test.go b/pkg/middlewares/compress/brotli/brotli_test.go index 445268f85..2e1ef3e17 100644 --- a/pkg/middlewares/compress/brotli/brotli_test.go +++ b/pkg/middlewares/compress/brotli/brotli_test.go @@ -88,7 +88,6 @@ func Test_NoBody(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -347,7 +346,6 @@ func Test_ExcludedContentTypes(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -452,7 +450,6 @@ func Test_IncludedContentTypes(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -557,7 +554,6 @@ func Test_FlushExcludedContentTypes(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -676,7 +672,6 @@ func Test_FlushIncludedContentTypes(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -831,8 +826,6 @@ func TestParseContentType_equals(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/compress/compress_test.go b/pkg/middlewares/compress/compress_test.go index 34e1ea7fc..1ea6afb45 100644 --- a/pkg/middlewares/compress/compress_test.go +++ b/pkg/middlewares/compress/compress_test.go @@ -74,8 +74,6 @@ func TestNegotiation(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -299,7 +297,6 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -351,7 +348,6 @@ func TestShouldCompressWhenSpecificContentType(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -547,8 +543,6 @@ func TestMinResponseBodyBytes(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/connectionheader/connectionheader_test.go b/pkg/middlewares/connectionheader/connectionheader_test.go index 7ee047bd0..bd41d58d8 100644 --- a/pkg/middlewares/connectionheader/connectionheader_test.go +++ b/pkg/middlewares/connectionheader/connectionheader_test.go @@ -47,7 +47,6 @@ func TestRemover(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/contenttype/content_type_test.go b/pkg/middlewares/contenttype/content_type_test.go index 9f311b5bc..cfe8ba502 100644 --- a/pkg/middlewares/contenttype/content_type_test.go +++ b/pkg/middlewares/contenttype/content_type_test.go @@ -46,7 +46,6 @@ func TestAutoDetection(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/customerrors/custom_errors_test.go b/pkg/middlewares/customerrors/custom_errors_test.go index 8a6c65882..37c13e53d 100644 --- a/pkg/middlewares/customerrors/custom_errors_test.go +++ b/pkg/middlewares/customerrors/custom_errors_test.go @@ -157,7 +157,6 @@ func TestHandler(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/forwardedheaders/forwarded_header_test.go b/pkg/middlewares/forwardedheaders/forwarded_header_test.go index 4d2dab2fc..8e1d10925 100644 --- a/pkg/middlewares/forwardedheaders/forwarded_header_test.go +++ b/pkg/middlewares/forwardedheaders/forwarded_header_test.go @@ -272,7 +272,6 @@ func TestServeHTTP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -369,7 +368,6 @@ func Test_isWebsocketRequest(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/headers/header_test.go b/pkg/middlewares/headers/header_test.go index b10a748e7..4b1d14762 100644 --- a/pkg/middlewares/headers/header_test.go +++ b/pkg/middlewares/headers/header_test.go @@ -52,7 +52,6 @@ func TestNewHeader_customRequestHeader(t *testing.T) { emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/headers/headers_test.go b/pkg/middlewares/headers/headers_test.go index 8e365ab54..194975e36 100644 --- a/pkg/middlewares/headers/headers_test.go +++ b/pkg/middlewares/headers/headers_test.go @@ -59,7 +59,6 @@ func TestNew_allowedHosts(t *testing.T) { require.NoError(t, err) for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/headers/secure_test.go b/pkg/middlewares/headers/secure_test.go index 24e38564e..d16c8c273 100644 --- a/pkg/middlewares/headers/secure_test.go +++ b/pkg/middlewares/headers/secure_test.go @@ -67,7 +67,6 @@ func Test_newSecure_modifyResponse(t *testing.T) { emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/ipallowlist/ip_allowlist_test.go b/pkg/middlewares/ipallowlist/ip_allowlist_test.go index cea46e401..3dddcc353 100644 --- a/pkg/middlewares/ipallowlist/ip_allowlist_test.go +++ b/pkg/middlewares/ipallowlist/ip_allowlist_test.go @@ -41,7 +41,6 @@ func TestNewIPAllowLister(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -102,7 +101,6 @@ func TestIPAllowLister_ServeHTTP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/ipwhitelist/ip_whitelist_test.go b/pkg/middlewares/ipwhitelist/ip_whitelist_test.go index 5e040016a..9cf88ef32 100644 --- a/pkg/middlewares/ipwhitelist/ip_whitelist_test.go +++ b/pkg/middlewares/ipwhitelist/ip_whitelist_test.go @@ -33,7 +33,6 @@ func TestNewIPWhiteLister(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -76,7 +75,6 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/metrics/metrics_test.go b/pkg/middlewares/metrics/metrics_test.go index 541c34170..bbadfde41 100644 --- a/pkg/middlewares/metrics/metrics_test.go +++ b/pkg/middlewares/metrics/metrics_test.go @@ -80,7 +80,6 @@ func Test_getMethod(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.method, func(t *testing.T) { t.Parallel() @@ -132,7 +131,6 @@ func Test_getRequestProtocol(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -175,7 +173,6 @@ func Test_grpcStatusCode(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/observability/entrypoint_test.go b/pkg/middlewares/observability/entrypoint_test.go index 5fc9dde08..f02180901 100644 --- a/pkg/middlewares/observability/entrypoint_test.go +++ b/pkg/middlewares/observability/entrypoint_test.go @@ -122,7 +122,6 @@ func TestEntryPointMiddleware_metrics(t *testing.T) { } for _, test := range tests { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go index 47f6b451f..dde36280d 100644 --- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go +++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go @@ -310,7 +310,6 @@ func TestPassTLSClientCert_PEM(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -533,7 +532,6 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -604,7 +602,6 @@ WqeUSNGYV//RunTeuRDAf5OxehERb1srzBXhRZ3cZdzXbgR/`, } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -645,7 +642,6 @@ func Test_getSANs(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/ratelimiter/rate_limiter_test.go b/pkg/middlewares/ratelimiter/rate_limiter_test.go index 411879c40..d61e4243c 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter_test.go +++ b/pkg/middlewares/ratelimiter/rate_limiter_test.go @@ -89,7 +89,6 @@ func TestNewRateLimiter(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -248,7 +247,6 @@ func TestRateLimit(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { if test.loadDuration >= time.Minute && testing.Short() { t.Skip("skipping test in short mode.") diff --git a/pkg/middlewares/redirect/redirect_regex_test.go b/pkg/middlewares/redirect/redirect_regex_test.go index a93c78844..4239c1ae0 100644 --- a/pkg/middlewares/redirect/redirect_regex_test.go +++ b/pkg/middlewares/redirect/redirect_regex_test.go @@ -154,7 +154,6 @@ func TestRedirectRegexHandler(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/redirect/redirect_scheme_test.go b/pkg/middlewares/redirect/redirect_scheme_test.go index 02b0451c4..258bf6c04 100644 --- a/pkg/middlewares/redirect/redirect_scheme_test.go +++ b/pkg/middlewares/redirect/redirect_scheme_test.go @@ -283,8 +283,6 @@ func TestRedirectSchemeHandler(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/replacepathregex/replace_path_regex.go b/pkg/middlewares/replacepathregex/replace_path_regex.go index 16c131f8c..1e0d0708b 100644 --- a/pkg/middlewares/replacepathregex/replace_path_regex.go +++ b/pkg/middlewares/replacepathregex/replace_path_regex.go @@ -52,7 +52,7 @@ func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) currentPath = req.URL.EscapedPath() } - if rp.regexp != nil && len(rp.replacement) > 0 && rp.regexp.MatchString(currentPath) { + if rp.regexp != nil && rp.regexp.MatchString(currentPath) { req.Header.Add(replacepath.ReplacedPathHeader, currentPath) req.URL.RawPath = rp.regexp.ReplaceAllString(currentPath, rp.replacement) diff --git a/pkg/middlewares/replacepathregex/replace_path_regex_test.go b/pkg/middlewares/replacepathregex/replace_path_regex_test.go index 7e83f3862..065b53d80 100644 --- a/pkg/middlewares/replacepathregex/replace_path_regex_test.go +++ b/pkg/middlewares/replacepathregex/replace_path_regex_test.go @@ -44,6 +44,28 @@ func TestReplacePathRegex(t *testing.T) { expectedRawPath: "/who-am-i/and/who-am-i", expectedHeader: "/whoami/and/whoami", }, + { + desc: "empty replacement", + path: "/whoami/and/whoami", + config: dynamic.ReplacePathRegex{ + Replacement: "", + Regex: `/whoami`, + }, + expectedPath: "/and", + expectedRawPath: "/and", + expectedHeader: "/whoami/and/whoami", + }, + { + desc: "empty trimmed replacement", + path: "/whoami/and/whoami", + config: dynamic.ReplacePathRegex{ + Replacement: " ", + Regex: `/whoami`, + }, + expectedPath: "/and", + expectedRawPath: "/and", + expectedHeader: "/whoami/and/whoami", + }, { desc: "no match", path: "/whoami/and/whoami", diff --git a/pkg/middlewares/requestdecorator/hostresolver_test.go b/pkg/middlewares/requestdecorator/hostresolver_test.go index ed199c387..f1c8c39a2 100644 --- a/pkg/middlewares/requestdecorator/hostresolver_test.go +++ b/pkg/middlewares/requestdecorator/hostresolver_test.go @@ -35,7 +35,6 @@ func TestCNAMEFlatten(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/requestdecorator/request_decorator_test.go b/pkg/middlewares/requestdecorator/request_decorator_test.go index 75ccc0139..c95980826 100644 --- a/pkg/middlewares/requestdecorator/request_decorator_test.go +++ b/pkg/middlewares/requestdecorator/request_decorator_test.go @@ -38,7 +38,6 @@ func TestRequestHost(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -80,7 +79,6 @@ func TestRequestFlattening(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -173,7 +171,6 @@ func Test_parseHost(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/retry/retry_test.go b/pkg/middlewares/retry/retry_test.go index 010376b86..302415866 100644 --- a/pkg/middlewares/retry/retry_test.go +++ b/pkg/middlewares/retry/retry_test.go @@ -101,7 +101,6 @@ func TestRetry(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -272,7 +271,6 @@ func TestRetryWebsocket(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/snicheck/snicheck_test.go b/pkg/middlewares/snicheck/snicheck_test.go index a9d57bdf6..d7411e555 100644 --- a/pkg/middlewares/snicheck/snicheck_test.go +++ b/pkg/middlewares/snicheck/snicheck_test.go @@ -37,7 +37,6 @@ func TestSNICheck_ServeHTTP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/stripprefix/strip_prefix_test.go b/pkg/middlewares/stripprefix/strip_prefix_test.go index cb2c58826..83310ff8b 100644 --- a/pkg/middlewares/stripprefix/strip_prefix_test.go +++ b/pkg/middlewares/stripprefix/strip_prefix_test.go @@ -134,7 +134,6 @@ func TestStripPrefix(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go b/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go index 64db6b842..9f2c02548 100644 --- a/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go +++ b/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go @@ -108,7 +108,6 @@ func TestStripPrefixRegex(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.path, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/tcp/ipallowlist/ip_allowlist_test.go b/pkg/middlewares/tcp/ipallowlist/ip_allowlist_test.go index 8da65c1ad..5c918e8cf 100644 --- a/pkg/middlewares/tcp/ipallowlist/ip_allowlist_test.go +++ b/pkg/middlewares/tcp/ipallowlist/ip_allowlist_test.go @@ -39,7 +39,6 @@ func TestNewIPAllowLister(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -81,7 +80,6 @@ func TestIPAllowLister_ServeHTTP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go index 72fa33d0c..f0bf631fb 100644 --- a/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go +++ b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go @@ -39,7 +39,6 @@ func TestNewIPWhiteLister(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -81,7 +80,6 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/muxer/http/matcher_test.go b/pkg/muxer/http/matcher_test.go index c4e601d01..f3a8f262c 100644 --- a/pkg/muxer/http/matcher_test.go +++ b/pkg/muxer/http/matcher_test.go @@ -65,7 +65,6 @@ func TestClientIPMatcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -139,7 +138,6 @@ func TestMethodMatcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -257,7 +255,6 @@ func TestHostMatcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -357,7 +354,6 @@ func TestHostRegexpMatcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -431,7 +427,6 @@ func TestPathMatcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -524,7 +519,6 @@ func TestPathRegexpMatcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -596,7 +590,6 @@ func TestPathPrefixMatcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -683,8 +676,6 @@ func TestHeaderMatcher(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -792,7 +783,6 @@ func TestHeaderRegexpMatcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -881,7 +871,6 @@ func TestQueryMatcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -995,7 +984,6 @@ func TestQueryRegexpMatcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/muxer/http/matcher_v2.go b/pkg/muxer/http/matcher_v2.go index 72d5d826a..d87b8e41d 100644 --- a/pkg/muxer/http/matcher_v2.go +++ b/pkg/muxer/http/matcher_v2.go @@ -25,15 +25,21 @@ var httpFuncsV2 = map[string]func(*matchersTree, ...string) error{ } func pathV2(tree *matchersTree, paths ...string) error { + var routes []*mux.Route + for _, path := range paths { - if !strings.HasPrefix(path, "/") { - return fmt.Errorf("path %q does not start with a '/'", path) + route := mux.NewRouter().NewRoute() + + if err := route.Path(path).GetError(); err != nil { + return err } + + routes = append(routes, route) } tree.matcher = func(req *http.Request) bool { - for _, path := range paths { - if req.URL.Path == path { + for _, route := range routes { + if route.Match(req, &mux.RouteMatch{}) { return true } } @@ -45,15 +51,21 @@ func pathV2(tree *matchersTree, paths ...string) error { } func pathPrefixV2(tree *matchersTree, paths ...string) error { + var routes []*mux.Route + for _, path := range paths { - if !strings.HasPrefix(path, "/") { - return fmt.Errorf("path %q does not start with a '/'", path) + route := mux.NewRouter().NewRoute() + + if err := route.PathPrefix(path).GetError(); err != nil { + return err } + + routes = append(routes, route) } tree.matcher = func(req *http.Request) bool { - for _, path := range paths { - if strings.HasPrefix(req.URL.Path, path) { + for _, route := range routes { + if route.Match(req, &mux.RouteMatch{}) { return true } } diff --git a/pkg/muxer/http/matcher_v2_test.go b/pkg/muxer/http/matcher_v2_test.go index eac62a005..6799fb7da 100644 --- a/pkg/muxer/http/matcher_v2_test.go +++ b/pkg/muxer/http/matcher_v2_test.go @@ -69,7 +69,6 @@ func TestClientIPV2Matcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -146,7 +145,6 @@ func TestMethodV2Matcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -271,7 +269,6 @@ func TestHostV2Matcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -374,7 +371,6 @@ func TestHostRegexpV2Matcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -454,10 +450,21 @@ func TestPathV2Matcher(t *testing.T) { "https://example.com/css/main.css": http.StatusNotFound, }, }, + { + desc: "valid Path matcher with regexp", + rule: "Path(`/css{path:(/.*)?}`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com/css/main.css": http.StatusOK, + "https://example.org/css/main.css": http.StatusOK, + "https://example.com/css/components/component.css": http.StatusOK, + "https://example.com/css.css": http.StatusNotFound, + "https://example.com/js/main.js": http.StatusNotFound, + }, + }, } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -535,10 +542,21 @@ func TestPathPrefixV2Matcher(t *testing.T) { "https://example.com/css/main.css": http.StatusOK, }, }, + { + desc: "valid PathPrefix matcher with regexp", + rule: "PathPrefix(`/css-{name:[0-9]?}`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com/css-1/main.css": http.StatusOK, + "https://example.org/css-222/main.css": http.StatusOK, + "https://example.com/css-333333/components/component.css": http.StatusOK, + "https://example.com/css.css": http.StatusNotFound, + "https://example.com/js/main.js": http.StatusNotFound, + }, + }, } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -625,8 +643,6 @@ func TestHeadersMatcher(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -734,7 +750,6 @@ func TestHeaderRegexpV2Matcher(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -827,7 +842,6 @@ func TestHostRegexp(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -1495,8 +1509,6 @@ func Test_addRoute(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/muxer/http/mux_test.go b/pkg/muxer/http/mux_test.go index efa8486a3..ddfcf361f 100644 --- a/pkg/muxer/http/mux_test.go +++ b/pkg/muxer/http/mux_test.go @@ -222,8 +222,6 @@ func TestMuxer(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -378,14 +376,12 @@ func Test_addRoutePriority(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() muxer, err := NewMuxer() require.NoError(t, err) for _, route := range test.cases { - route := route handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-From", route.xFrom) }) @@ -446,7 +442,6 @@ func TestParseDomains(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.expression, func(t *testing.T) { t.Parallel() @@ -511,7 +506,6 @@ func TestEmptyHost(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -550,7 +544,6 @@ func TestGetRulePriority(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/muxer/tcp/matcher_test.go b/pkg/muxer/tcp/matcher_test.go index 531492e89..dd6aac008 100644 --- a/pkg/muxer/tcp/matcher_test.go +++ b/pkg/muxer/tcp/matcher_test.go @@ -31,7 +31,6 @@ func Test_HostSNICatchAll(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -137,7 +136,6 @@ func Test_HostSNI(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -220,7 +218,6 @@ func Test_HostSNIRegexp(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -292,7 +289,6 @@ func Test_ClientIP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -356,7 +352,6 @@ func Test_ALPN(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/muxer/tcp/matcher_v2_test.go b/pkg/muxer/tcp/matcher_v2_test.go index 74ffcff80..254ac515e 100644 --- a/pkg/muxer/tcp/matcher_v2_test.go +++ b/pkg/muxer/tcp/matcher_v2_test.go @@ -461,8 +461,6 @@ func Test_addTCPRouteV2(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -561,7 +559,6 @@ func TestParseHostSNIV2(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.expression, func(t *testing.T) { t.Parallel() @@ -606,8 +603,6 @@ func Test_HostSNICatchAllV2(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -708,8 +703,6 @@ func Test_HostSNIV2(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -823,8 +816,6 @@ func Test_HostSNIRegexpV2(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -916,8 +907,6 @@ func Test_ClientIPV2(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -985,8 +974,6 @@ func Test_ALPNV2(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/muxer/tcp/mux_test.go b/pkg/muxer/tcp/mux_test.go index 3b108b7ea..d2bafd21e 100644 --- a/pkg/muxer/tcp/mux_test.go +++ b/pkg/muxer/tcp/mux_test.go @@ -263,8 +263,6 @@ func Test_addTCPRoute(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -373,7 +371,6 @@ func TestParseHostSNI(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -436,8 +433,6 @@ func Test_Priority(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -446,7 +441,6 @@ func Test_Priority(t *testing.T) { matchedRule := "" for rule, priority := range test.rules { - rule := rule err := muxer.AddRoute(rule, "", priority, tcp.HandlerFunc(func(conn tcp.WriteCloser) { matchedRule = rule })) @@ -488,7 +482,6 @@ func TestGetRulePriority(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/acme/local_store_test.go b/pkg/provider/acme/local_store_test.go index 44d4ab0fc..4614078c0 100644 --- a/pkg/provider/acme/local_store_test.go +++ b/pkg/provider/acme/local_store_test.go @@ -44,7 +44,6 @@ func TestLocalStore_GetAccount(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { s := NewLocalStore(test.filename) diff --git a/pkg/provider/acme/provider_test.go b/pkg/provider/acme/provider_test.go index 762ca3e9e..0285e5d74 100644 --- a/pkg/provider/acme/provider_test.go +++ b/pkg/provider/acme/provider_test.go @@ -166,7 +166,6 @@ func TestGetUncheckedCertificates(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -241,7 +240,6 @@ func TestProvider_sanitizeDomains(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -423,7 +421,6 @@ func TestDeleteUnnecessaryDomains(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -497,7 +494,6 @@ func TestIsAccountMatchingCaServer(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -573,7 +569,6 @@ func TestInitAccount(t *testing.T) { }, } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -632,7 +627,6 @@ func Test_getCertificateRenewDurations(t *testing.T) { }, } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/aggregator/aggregator.go b/pkg/provider/aggregator/aggregator.go index 9aebb5c34..574ced911 100644 --- a/pkg/provider/aggregator/aggregator.go +++ b/pkg/provider/aggregator/aggregator.go @@ -179,7 +179,6 @@ func (p ProviderAggregator) Provide(configurationChan chan<- dynamic.Message, po } for _, prd := range p.providers { - prd := prd safe.Go(func() { p.launchProvider(configurationChan, pool, prd) }) diff --git a/pkg/provider/constraints/constraints_labels_test.go b/pkg/provider/constraints/constraints_labels_test.go index 8068735eb..8cba52480 100644 --- a/pkg/provider/constraints/constraints_labels_test.go +++ b/pkg/provider/constraints/constraints_labels_test.go @@ -161,7 +161,6 @@ func TestMatchLabels(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.expr, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/constraints/constraints_tags_test.go b/pkg/provider/constraints/constraints_tags_test.go index a563b0b30..531c715c3 100644 --- a/pkg/provider/constraints/constraints_tags_test.go +++ b/pkg/provider/constraints/constraints_tags_test.go @@ -95,7 +95,6 @@ func TestMatchTags(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.expr, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/consulcatalog/config_test.go b/pkg/provider/consulcatalog/config_test.go index 56568c29a..d96867919 100644 --- a/pkg/provider/consulcatalog/config_test.go +++ b/pkg/provider/consulcatalog/config_test.go @@ -298,7 +298,6 @@ func TestDefaultRule(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -3347,8 +3346,6 @@ func Test_buildConfiguration(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -3414,8 +3411,6 @@ func TestNamespaces(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -3864,7 +3859,6 @@ func TestFilterHealthStatuses(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/consulcatalog/convert_types_test.go b/pkg/provider/consulcatalog/convert_types_test.go index 8dece14c2..e8ac70367 100644 --- a/pkg/provider/consulcatalog/convert_types_test.go +++ b/pkg/provider/consulcatalog/convert_types_test.go @@ -52,7 +52,6 @@ func Test_tagsToNeutralLabels(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/docker/config_test.go b/pkg/provider/docker/config_test.go index fb198053c..940a01aec 100644 --- a/pkg/provider/docker/config_test.go +++ b/pkg/provider/docker/config_test.go @@ -395,7 +395,6 @@ func TestDynConfBuilder_DefaultRule(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -3649,8 +3648,6 @@ func TestDynConfBuilder_build(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -3837,7 +3834,6 @@ func TestDynConfBuilder_getIPPort_docker(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -3950,7 +3946,6 @@ func TestDynConfBuilder_getIPAddress_docker(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -4020,7 +4015,6 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) { } for serviceID, test := range testCases { - test := test t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/docker/pswarm.go b/pkg/provider/docker/pswarm.go index fd513dad2..07f5dbd0b 100644 --- a/pkg/provider/docker/pswarm.go +++ b/pkg/provider/docker/pswarm.go @@ -187,8 +187,7 @@ func (p *SwarmProvider) listServices(ctx context.Context, dockerClient client.AP networkMap := make(map[string]*dockertypes.NetworkResource) for _, network := range networkList { - networkToAdd := network - networkMap[network.ID] = &networkToAdd + networkMap[network.ID] = &network } var dockerDataList []dockerData diff --git a/pkg/provider/docker/pswarm_test.go b/pkg/provider/docker/pswarm_test.go index d13e3e7c3..b24c868d9 100644 --- a/pkg/provider/docker/pswarm_test.go +++ b/pkg/provider/docker/pswarm_test.go @@ -63,7 +63,6 @@ func TestListTasks(t *testing.T) { } for caseID, test := range testCases { - test := test t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Parallel() @@ -230,7 +229,6 @@ func TestSwarmProvider_listServices(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -351,7 +349,6 @@ func TestSwarmProvider_parseService_task(t *testing.T) { } for caseID, test := range testCases { - test := test t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/docker/shared_test.go b/pkg/provider/docker/shared_test.go index 324363e84..630a7fee9 100644 --- a/pkg/provider/docker/shared_test.go +++ b/pkg/provider/docker/shared_test.go @@ -66,7 +66,6 @@ func Test_getPort_docker(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -96,7 +95,6 @@ func Test_getPort_swarm(t *testing.T) { } for serviceID, test := range testCases { - test := test t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/ecs/config_test.go b/pkg/provider/ecs/config_test.go index a20dc3542..a030f9b4a 100644 --- a/pkg/provider/ecs/config_test.go +++ b/pkg/provider/ecs/config_test.go @@ -366,7 +366,6 @@ func TestDefaultRule(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -3234,8 +3233,6 @@ func Test_buildConfiguration(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/ecs/ecs_test.go b/pkg/provider/ecs/ecs_test.go index ede115a41..b05bd2165 100644 --- a/pkg/provider/ecs/ecs_test.go +++ b/pkg/provider/ecs/ecs_test.go @@ -68,7 +68,6 @@ func TestChunkIDs(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 99fc278d1..092814ffb 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -1570,8 +1570,6 @@ func TestLoadIngressRouteTCPs(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -4588,7 +4586,6 @@ func TestLoadIngressRoutes(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -5098,8 +5095,6 @@ func TestLoadIngressRouteUDPs(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -5191,8 +5186,6 @@ func TestParseServiceProtocol(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -5423,7 +5416,6 @@ func TestGetServicePort(t *testing.T) { }, } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -6537,8 +6529,6 @@ func TestCrossNamespace(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -6808,8 +6798,6 @@ func TestExternalNameService(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -6991,8 +6979,6 @@ func TestNativeLB(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -7289,8 +7275,6 @@ func TestFillExtensionBuilderRegistry(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/kubernetes/gateway/client.go b/pkg/provider/kubernetes/gateway/client.go index 9bf57a5f8..05ae47f44 100644 --- a/pkg/provider/kubernetes/gateway/client.go +++ b/pkg/provider/kubernetes/gateway/client.go @@ -75,7 +75,8 @@ type clientWrapper struct { isNamespaceAll bool watchedNamespaces []string - labelSelector string + labelSelector string + experimentalChannel bool } func createClientFromConfig(c *rest.Config) (*clientWrapper, error) { @@ -196,19 +197,22 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< if err != nil { return nil, err } - _, err = factoryGateway.Gateway().V1alpha2().TCPRoutes().Informer().AddEventHandler(eventHandler) - if err != nil { - return nil, err - } - _, err = factoryGateway.Gateway().V1alpha2().TLSRoutes().Informer().AddEventHandler(eventHandler) - if err != nil { - return nil, err - } _, err = factoryGateway.Gateway().V1beta1().ReferenceGrants().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err } + if c.experimentalChannel { + _, err = factoryGateway.Gateway().V1alpha2().TCPRoutes().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + _, err = factoryGateway.Gateway().V1alpha2().TLSRoutes().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 { diff --git a/pkg/provider/kubernetes/gateway/client_test.go b/pkg/provider/kubernetes/gateway/client_test.go index e6e751959..d4bd4582b 100644 --- a/pkg/provider/kubernetes/gateway/client_test.go +++ b/pkg/provider/kubernetes/gateway/client_test.go @@ -233,8 +233,6 @@ func TestStatusEquals(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index 360ae1b8d..01401d750 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -51,13 +51,15 @@ const ( // 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 types.FileOrContent `description:"Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"` - 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 select specific GatewayClasses." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` - ThrottleDuration ptypes.Duration `description:"Kubernetes refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` - EntryPoints map[string]Entrypoint `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` + Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Token types.FileOrContent `description:"Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"` + 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 select specific GatewayClasses." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` + ThrottleDuration ptypes.Duration `description:"Kubernetes refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` + ExperimentalChannel bool `description:"Toggles Experimental Channel resources support (TCPRoute, TLSRoute...)." json:"experimentalChannel,omitempty" toml:"experimentalChannel,omitempty" yaml:"experimentalChannel,omitempty" export:"true"` + + EntryPoints map[string]Entrypoint `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` // groupKindFilterFuncs is the list of allowed Group and Kinds for the Filter ExtensionRef objects. groupKindFilterFuncs map[string]map[string]BuildFilterFunc @@ -155,6 +157,7 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { } client.labelSelector = p.LabelSelector + client.experimentalChannel = p.ExperimentalChannel return client, nil } @@ -396,7 +399,7 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * // AttachedRoutes: 0 TODO Set to number of Routes associated with a Listener regardless of Gateway or Route status } - supportedKinds, conditions := supportedRouteKinds(listener.Protocol) + supportedKinds, conditions := supportedRouteKinds(listener.Protocol, p.ExperimentalChannel) if len(conditions) > 0 { listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, conditions...) continue @@ -716,21 +719,41 @@ func (p *Provider) entryPointName(port gatev1.PortNumber, protocol gatev1.Protoc return "", fmt.Errorf("no matching entryPoint for port %d and protocol %q", port, protocol) } -func supportedRouteKinds(protocol gatev1.ProtocolType) ([]gatev1.RouteGroupKind, []metav1.Condition) { +func supportedRouteKinds(protocol gatev1.ProtocolType, experimentalChannel bool) ([]gatev1.RouteGroupKind, []metav1.Condition) { group := gatev1.Group(gatev1.GroupName) switch protocol { case gatev1.TCPProtocolType: - return []gatev1.RouteGroupKind{{Kind: kindTCPRoute, Group: &group}}, nil + if experimentalChannel { + return []gatev1.RouteGroupKind{{Kind: kindTCPRoute, Group: &group}}, nil + } + + return nil, []metav1.Condition{{ + Type: string(gatev1.ListenerConditionConflicted), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.ListenerReasonInvalidRouteKinds), + Message: fmt.Sprintf("Protocol %q requires the experimental channel support to be enabled, please use the `experimentalChannel` option", protocol), + }} case gatev1.HTTPProtocolType, gatev1.HTTPSProtocolType: return []gatev1.RouteGroupKind{{Kind: kindHTTPRoute, Group: &group}}, nil case gatev1.TLSProtocolType: - return []gatev1.RouteGroupKind{ - {Kind: kindTCPRoute, Group: &group}, - {Kind: kindTLSRoute, Group: &group}, - }, nil + if experimentalChannel { + return []gatev1.RouteGroupKind{ + {Kind: kindTCPRoute, Group: &group}, + {Kind: kindTLSRoute, Group: &group}, + }, nil + } + + return nil, []metav1.Condition{{ + Type: string(gatev1.ListenerConditionConflicted), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.ListenerReasonInvalidRouteKinds), + Message: fmt.Sprintf("Protocol %q requires the experimental channel support to be enabled, please use the `experimentalChannel` option", protocol), + }} } return nil, []metav1.Condition{{ diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 8146523f9..f3c6f8c00 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -24,12 +24,12 @@ var _ provider.Provider = (*Provider)(nil) func TestLoadHTTPRoutes(t *testing.T) { testCases := []struct { - desc string - ingressClass string - paths []string - namespaces []string - expected *dynamic.Configuration - entryPoints map[string]Entrypoint + desc string + ingressClass string + paths []string + expected *dynamic.Configuration + entryPoints map[string]Entrypoint + experimentalChannel bool }{ { desc: "Empty", @@ -476,6 +476,7 @@ func TestLoadHTTPRoutes(t *testing.T) { entryPoints: map[string]Entrypoint{"TCP": { Address: ":8080", }}, + experimentalChannel: true, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -1656,7 +1657,6 @@ func TestLoadHTTPRoutes(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -1664,7 +1664,7 @@ func TestLoadHTTPRoutes(t *testing.T) { return } - p := Provider{EntryPoints: test.entryPoints} + p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: test.experimentalChannel} conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) @@ -1915,7 +1915,6 @@ func TestLoadHTTPRoutes_backendExtensionRef(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -2132,7 +2131,6 @@ func TestLoadHTTPRoutes_filterExtensionRef(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -2897,7 +2895,6 @@ func TestLoadTCPRoutes(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -2905,7 +2902,7 @@ func TestLoadTCPRoutes(t *testing.T) { return } - p := Provider{EntryPoints: test.entryPoints} + p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: true} conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) }) @@ -4026,7 +4023,6 @@ func TestLoadTLSRoutes(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -4034,7 +4030,7 @@ func TestLoadTLSRoutes(t *testing.T) { return } - p := Provider{EntryPoints: test.entryPoints} + p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: true} conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) }) @@ -4043,11 +4039,12 @@ func TestLoadTLSRoutes(t *testing.T) { func TestLoadMixedRoutes(t *testing.T) { testCases := []struct { - desc string - ingressClass string - paths []string - expected *dynamic.Configuration - entryPoints map[string]Entrypoint + desc string + ingressClass string + paths []string + expected *dynamic.Configuration + entryPoints map[string]Entrypoint + experimentalChannel bool }{ { desc: "Empty", @@ -4159,6 +4156,7 @@ func TestLoadMixedRoutes(t *testing.T) { "tls-1": {Address: ":10000"}, "tls-2": {Address: ":11000"}, }, + experimentalChannel: true, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -4343,6 +4341,7 @@ func TestLoadMixedRoutes(t *testing.T) { "tls-1": {Address: ":10000"}, "tls-2": {Address: ":11000"}, }, + experimentalChannel: true, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -4499,6 +4498,7 @@ func TestLoadMixedRoutes(t *testing.T) { "tls-1": {Address: ":10000"}, "tls-2": {Address: ":11000"}, }, + experimentalChannel: true, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -4749,6 +4749,7 @@ func TestLoadMixedRoutes(t *testing.T) { "tls-1": {Address: ":10000"}, "tls-2": {Address: ":11000"}, }, + experimentalChannel: true, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -4904,6 +4905,7 @@ func TestLoadMixedRoutes(t *testing.T) { "tcp": {Address: ":9000"}, "tls": {Address: ":10000"}, }, + experimentalChannel: true, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -5034,7 +5036,6 @@ func TestLoadMixedRoutes(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -5042,7 +5043,7 @@ func TestLoadMixedRoutes(t *testing.T) { return } - p := Provider{EntryPoints: test.entryPoints} + p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: test.experimentalChannel} conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) }) @@ -5051,11 +5052,12 @@ func TestLoadMixedRoutes(t *testing.T) { func TestLoadRoutesWithReferenceGrants(t *testing.T) { testCases := []struct { - desc string - ingressClass string - paths []string - expected *dynamic.Configuration - entryPoints map[string]Entrypoint + desc string + ingressClass string + paths []string + expected *dynamic.Configuration + entryPoints map[string]Entrypoint + experimentalChannel bool }{ { desc: "Empty", @@ -5163,6 +5165,7 @@ func TestLoadRoutesWithReferenceGrants(t *testing.T) { entryPoints: map[string]Entrypoint{ "tls": {Address: ":9000"}, }, + experimentalChannel: true, expected: &dynamic.Configuration{ UDP: &dynamic.UDPConfiguration{ Routers: map[string]*dynamic.UDPRouter{}, @@ -5224,7 +5227,6 @@ func TestLoadRoutesWithReferenceGrants(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -5232,7 +5234,7 @@ func TestLoadRoutesWithReferenceGrants(t *testing.T) { return } - p := Provider{EntryPoints: test.entryPoints} + p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: test.experimentalChannel} conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) }) @@ -5334,7 +5336,6 @@ func Test_hostRule(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() rule, err := hostRule(test.hostnames) @@ -5565,7 +5566,6 @@ func Test_extractRule(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -5640,7 +5640,6 @@ func Test_hostSNIRule(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -5961,7 +5960,6 @@ func Test_shouldAttach(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -6065,7 +6063,6 @@ func Test_matchingHostnames(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -6177,7 +6174,6 @@ func Test_getAllowedRoutes(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -6223,7 +6219,6 @@ func Test_makeListenerKey(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -6358,7 +6353,6 @@ func Test_referenceGrantMatchesFrom(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -6487,7 +6481,6 @@ func Test_referenceGrantMatchesTo(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/kubernetes/ingress/annotations_test.go b/pkg/provider/kubernetes/ingress/annotations_test.go index ee77647f3..cab70357f 100644 --- a/pkg/provider/kubernetes/ingress/annotations_test.go +++ b/pkg/provider/kubernetes/ingress/annotations_test.go @@ -80,7 +80,6 @@ func Test_parseRouterConfig(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -155,7 +154,6 @@ func Test_parseServiceConfig(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -239,7 +237,6 @@ func Test_convertAnnotations(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/kubernetes/ingress/client_test.go b/pkg/provider/kubernetes/ingress/client_test.go index 0a162e877..27e1bbd37 100644 --- a/pkg/provider/kubernetes/ingress/client_test.go +++ b/pkg/provider/kubernetes/ingress/client_test.go @@ -46,7 +46,6 @@ func TestTranslateNotFoundError(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -124,7 +123,6 @@ func TestIsLoadBalancerIngressEquals(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index 739417738..28e4b338a 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -1495,8 +1495,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -1626,8 +1624,6 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -1679,8 +1675,6 @@ func TestLoadConfigurationFromIngressesWithNativeLB(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -1907,7 +1901,6 @@ func TestGetCertificates(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/kubernetes/k8s/event_handler_test.go b/pkg/provider/kubernetes/k8s/event_handler_test.go index e8b7a31a2..323b4f550 100644 --- a/pkg/provider/kubernetes/k8s/event_handler_test.go +++ b/pkg/provider/kubernetes/k8s/event_handler_test.go @@ -507,7 +507,6 @@ func Test_detectChanges(t *testing.T) { }, } for _, test := range tests { - test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/kv/consul/consul_test.go b/pkg/provider/kv/consul/consul_test.go index 7ecfe111b..60ee6b27a 100644 --- a/pkg/provider/kv/consul/consul_test.go +++ b/pkg/provider/kv/consul/consul_test.go @@ -29,8 +29,6 @@ func TestNamespaces(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/nomad/config.go b/pkg/provider/nomad/config.go index b6caa8cf1..83022f31d 100644 --- a/pkg/provider/nomad/config.go +++ b/pkg/provider/nomad/config.go @@ -97,8 +97,11 @@ func (p *Provider) buildTCPConfig(i item, configuration *dynamic.TCPConfiguratio } for _, service := range configuration.Services { - if err := p.addServerTCP(i, service.LoadBalancer); err != nil { - return err + // Leave load balancer empty when no address and allowEmptyServices = true + if !(i.Address == "" && p.AllowEmptyServices) { + if err := p.addServerTCP(i, service.LoadBalancer); err != nil { + return err + } } } @@ -115,8 +118,11 @@ func (p *Provider) buildUDPConfig(i item, configuration *dynamic.UDPConfiguratio } for _, service := range configuration.Services { - if err := p.addServerUDP(i, service.LoadBalancer); err != nil { - return err + // Leave load balancer empty when no address and allowEmptyServices = true + if !(i.Address == "" && p.AllowEmptyServices) { + if err := p.addServerUDP(i, service.LoadBalancer); err != nil { + return err + } } } @@ -136,8 +142,11 @@ func (p *Provider) buildServiceConfig(i item, configuration *dynamic.HTTPConfigu } for _, service := range configuration.Services { - if err := p.addServer(i, service.LoadBalancer); err != nil { - return err + // Leave load balancer empty when no address and allowEmptyServices = true + if !(i.Address == "" && p.AllowEmptyServices) { + if err := p.addServer(i, service.LoadBalancer); err != nil { + return err + } } } diff --git a/pkg/provider/nomad/config_test.go b/pkg/provider/nomad/config_test.go index ef13dee71..2f036e929 100644 --- a/pkg/provider/nomad/config_test.go +++ b/pkg/provider/nomad/config_test.go @@ -706,6 +706,42 @@ func Test_buildConfig(t *testing.T) { }, }, }, + { + desc: "empty service", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.enable=true", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + 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: "one service with rule label", items: []item{ @@ -2825,6 +2861,307 @@ func Test_buildConfig(t *testing.T) { } } +func Test_buildConfigAllowEmptyServicesTrue(t *testing.T) { + testCases := []struct { + desc string + items []item + constraints string + expected *dynamic.Configuration + }{ + { + desc: "empty service http", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.enable=true", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + 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.traefik.test`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: nil, + PassHostHeader: Bool(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "empty service tcp", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tcp.routers.test.rule = HostSNI(`foobar`)", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "test": { + Rule: "HostSNI(`foobar`)", + Service: "Test", + }, + }, + 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: "empty service udp", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.udp.routers.test.entrypoints = udp", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + 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": { + EntryPoints: []string{"udp"}, + Service: "Test", + }, + }, + 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) { + p := new(Provider) + p.SetDefaults() + p.AllowEmptyServices = true + p.DefaultRule = "Host(`{{ normalize .Name }}.traefik.test`)" + p.Constraints = test.constraints + err := p.Init() + require.NoError(t, err) + + ctx := context.TODO() + c := p.buildConfig(ctx, test.items) + require.Equal(t, test.expected, c) + }) + } +} + +func Test_buildConfigAllowEmptyServicesFalseDefault(t *testing.T) { + testCases := []struct { + desc string + items []item + constraints string + expected *dynamic.Configuration + }{ + { + desc: "empty service http", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.enable=true", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + 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: "empty service tcp", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tcp.routers.test.rule = HostSNI(`foobar`)", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + 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: "empty service udp", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.udp.routers.test.entrypoints = udp", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + 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{}, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + p := new(Provider) + p.SetDefaults() + p.DefaultRule = "Host(`{{ normalize .Name }}.traefik.test`)" + p.Constraints = test.constraints + err := p.Init() + require.NoError(t, err) + + ctx := context.TODO() + c := p.buildConfig(ctx, test.items) + require.Equal(t, test.expected, c) + }) + } +} + func Test_keepItem(t *testing.T) { testCases := []struct { name string @@ -2897,8 +3234,6 @@ func TestNamespaces(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/nomad/fixtures/job_job1_WithGroupService_Scaling1.json b/pkg/provider/nomad/fixtures/job_job1_WithGroupService_Scaling1.json new file mode 100644 index 000000000..9044736c6 --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job1_WithGroupService_Scaling1.json @@ -0,0 +1,220 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job1", + "ParentID": "", + "Name": "job1", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "654cb8ee-9c81-4fe8-02a0-2aecdea35ae3", + "Type": "horizontal", + "Target": { + "Job": "job1", + "Group": "group1", + "Namespace": "default" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "http" + ] + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job1", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "running", + "StatusDescription": "", + "Stable": true, + "Version": 11, + "SubmitTime": 1705690395733241600, + "CreateIndex": 493, + "ModifyIndex": 9961, + "JobModifyIndex": 9955 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job2_WithGroupService_Scaling0.json b/pkg/provider/nomad/fixtures/job_job2_WithGroupService_Scaling0.json new file mode 100644 index 000000000..0cf8656f7 --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job2_WithGroupService_Scaling0.json @@ -0,0 +1,220 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job2", + "ParentID": "", + "Name": "job2", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 0, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "0ae1d8fa-aa84-0b1b-941f-82dc71bc4664", + "Type": "horizontal", + "Target": { + "Group": "group1", + "Namespace": "default", + "Job": "job2" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 9975, + "ModifyIndex": 9975 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "ports": [ + "http" + ], + "image": "nginx" + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job2", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "dead", + "StatusDescription": "", + "Stable": true, + "Version": 10, + "SubmitTime": 1705690880440177400, + "CreateIndex": 2923, + "ModifyIndex": 10048, + "JobModifyIndex": 10044 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job3_WithGroupService_ScalingDisabled.json b/pkg/provider/nomad/fixtures/job_job3_WithGroupService_ScalingDisabled.json new file mode 100644 index 000000000..a0efc5e16 --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job3_WithGroupService_ScalingDisabled.json @@ -0,0 +1,206 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job3", + "ParentID": "", + "Name": "job3", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": null, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "ports": [ + "http" + ], + "image": "nginx" + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job3", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "running", + "StatusDescription": "", + "Stable": true, + "Version": 4, + "SubmitTime": 1705690892484556300, + "CreateIndex": 2932, + "ModifyIndex": 9992, + "JobModifyIndex": 9983 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job4_WithGroupService_ScalingDisabled_Stopped.json b/pkg/provider/nomad/fixtures/job_job4_WithGroupService_ScalingDisabled_Stopped.json new file mode 100644 index 000000000..55d312dcd --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job4_WithGroupService_ScalingDisabled_Stopped.json @@ -0,0 +1,206 @@ +{ + "Stop": true, + "Region": "global", + "Namespace": "default", + "ID": "job4", + "ParentID": "", + "Name": "job4", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": null, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "http" + ] + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job4", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "dead", + "StatusDescription": "", + "Stable": true, + "Version": 11, + "SubmitTime": 1705690905317339000, + "CreateIndex": 3079, + "ModifyIndex": 10054, + "JobModifyIndex": 10052 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job5_WithGroupTaskService_Scaling1.json b/pkg/provider/nomad/fixtures/job_job5_WithGroupTaskService_Scaling1.json new file mode 100644 index 000000000..f0171814d --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job5_WithGroupTaskService_Scaling1.json @@ -0,0 +1,299 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job5", + "ParentID": "", + "Name": "job5", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "56d57f83-7b9e-fbfa-af96-e2c7f5dc698c", + "Type": "horizontal", + "Target": { + "Namespace": "default", + "Job": "job5", + "Group": "group1" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "http" + ] + }, + "Env": null, + "Services": [ + { + "Name": "job5task1", + "TaskName": "task1", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + }, + { + "Name": "task2", + "Driver": "docker", + "User": "", + "Config": { + "ports": [ + "other" + ], + "image": "nginx" + }, + "Env": null, + "Services": [ + { + "Name": "job5task2", + "TaskName": "task2", + "PortLabel": "other", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + }, + { + "Label": "other", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": null, + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "running", + "StatusDescription": "", + "Stable": true, + "Version": 7, + "SubmitTime": 1705690918813110300, + "CreateIndex": 3095, + "ModifyIndex": 10018, + "JobModifyIndex": 10008 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job6_WithGroupTaskService_Scaling0.json b/pkg/provider/nomad/fixtures/job_job6_WithGroupTaskService_Scaling0.json new file mode 100644 index 000000000..a4528e4dd --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job6_WithGroupTaskService_Scaling0.json @@ -0,0 +1,299 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job6", + "ParentID": "", + "Name": "job6", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 0, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "d18b02ae-5a68-e635-8100-06b110910b5f", + "Type": "horizontal", + "Target": { + "Job": "job6", + "Group": "group1", + "Namespace": "default" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 10020, + "ModifyIndex": 10020 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "http" + ] + }, + "Env": null, + "Services": [ + { + "Name": "job6task1", + "TaskName": "task1", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + }, + { + "Name": "task2", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "other" + ] + }, + "Env": null, + "Services": [ + { + "Name": "job6task2", + "TaskName": "task2", + "PortLabel": "other", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + }, + { + "Label": "other", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": null, + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "dead", + "StatusDescription": "", + "Stable": true, + "Version": 3, + "SubmitTime": 1705690931854150700, + "CreateIndex": 9941, + "ModifyIndex": 10078, + "JobModifyIndex": 10074 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job7_TCP.json b/pkg/provider/nomad/fixtures/job_job7_TCP.json new file mode 100644 index 000000000..3bf39a776 --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job7_TCP.json @@ -0,0 +1,222 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job7", + "ParentID": "", + "Name": "job7", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "e3b82307-1b7f-5fc6-901a-d9174418461f", + "Type": "horizontal", + "Target": { + "Namespace": "default", + "Job": "job7", + "Group": "group1" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "http" + ] + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job7", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true", + "traefik.tcp.routers.job7.rule=HostSNI(`job7`)", + "traefik.tcp.routers.job7.tls.passthrough=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "running", + "StatusDescription": "", + "Stable": true, + "Version": 1, + "SubmitTime": 1705752438438572000, + "CreateIndex": 11221, + "ModifyIndex": 11238, + "JobModifyIndex": 11232 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job8_UDP.json b/pkg/provider/nomad/fixtures/job_job8_UDP.json new file mode 100644 index 000000000..eda3df6f0 --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job8_UDP.json @@ -0,0 +1,221 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job8", + "ParentID": "", + "Name": "job8", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "3828aaa9-4e16-cde5-cc80-f0cec4a7c82d", + "Type": "horizontal", + "Target": { + "Namespace": "default", + "Job": "job8", + "Group": "group1" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "ports": [ + "http" + ], + "image": "nginx" + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job8", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true", + "traefik.udp.routers.job8.service=job8" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "running", + "StatusDescription": "", + "Stable": true, + "Version": 2, + "SubmitTime": 1705753285493029600, + "CreateIndex": 11276, + "ModifyIndex": 11303, + "JobModifyIndex": 11297 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job9_ScalingEnabled_Stopped.json b/pkg/provider/nomad/fixtures/job_job9_ScalingEnabled_Stopped.json new file mode 100644 index 000000000..596072a67 --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job9_ScalingEnabled_Stopped.json @@ -0,0 +1,220 @@ +{ + "Stop": true, + "Region": "global", + "Namespace": "default", + "ID": "job9", + "ParentID": "", + "Name": "job9", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "094d38a7-24cf-d3a4-3a82-cfc50ef7c597", + "Type": "horizontal", + "Target": { + "Group": "group1", + "Namespace": "default", + "Job": "job9" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "http" + ] + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job9", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "dead", + "StatusDescription": "", + "Stable": true, + "Version": 3, + "SubmitTime": 1705753979676559600, + "CreateIndex": 11317, + "ModifyIndex": 11346, + "JobModifyIndex": 11344 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job1.json b/pkg/provider/nomad/fixtures/jobs_job1.json new file mode 100644 index 000000000..66016a6a9 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job1.json @@ -0,0 +1,56 @@ +[ + { + "ID": "job1", + "ParentID": "", + "Name": "job1", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "running", + "StatusDescription": "", + "JobSummary": { + "JobID": "job1", + "Namespace": "default", + "Summary": { + "job1": { + "Queued": 0, + "Complete": 1, + "Failed": 0, + "Running": 0, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + }, + "group1": { + "Queued": 0, + "Complete": 6, + "Failed": 1, + "Running": 1, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 493, + "ModifyIndex": 9909 + }, + "CreateIndex": 493, + "ModifyIndex": 9961, + "JobModifyIndex": 9955, + "SubmitTime": 1705690395733241600, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job2.json b/pkg/provider/nomad/fixtures/jobs_job2.json new file mode 100644 index 000000000..ee817aa0a --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job2.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job2", + "ParentID": "", + "Name": "job2", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "dead", + "StatusDescription": "", + "JobSummary": { + "JobID": "job2", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 6, + "Failed": 6, + "Running": 0, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 2923, + "ModifyIndex": 10051 + }, + "CreateIndex": 2923, + "ModifyIndex": 10048, + "JobModifyIndex": 10044, + "SubmitTime": 1705690880440177400, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job3.json b/pkg/provider/nomad/fixtures/jobs_job3.json new file mode 100644 index 000000000..bf69bc069 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job3.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job3", + "ParentID": "", + "Name": "job3", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "running", + "StatusDescription": "", + "JobSummary": { + "JobID": "job3", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 1, + "Failed": 0, + "Running": 1, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 2932, + "ModifyIndex": 9989 + }, + "CreateIndex": 2932, + "ModifyIndex": 9992, + "JobModifyIndex": 9983, + "SubmitTime": 1705690892484556300, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job4.json b/pkg/provider/nomad/fixtures/jobs_job4.json new file mode 100644 index 000000000..dfd0f3a11 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job4.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job4", + "ParentID": "", + "Name": "job4", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": true, + "Status": "dead", + "StatusDescription": "", + "JobSummary": { + "JobID": "job4", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 6, + "Failed": 0, + "Running": 0, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 3079, + "ModifyIndex": 10057 + }, + "CreateIndex": 3079, + "ModifyIndex": 10054, + "JobModifyIndex": 10052, + "SubmitTime": 1705690905317339000, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job5.json b/pkg/provider/nomad/fixtures/jobs_job5.json new file mode 100644 index 000000000..bf846ed21 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job5.json @@ -0,0 +1,56 @@ +[ + { + "ID": "job5", + "ParentID": "", + "Name": "job5", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "running", + "StatusDescription": "", + "JobSummary": { + "JobID": "job5", + "Namespace": "default", + "Summary": { + "job5": { + "Queued": 0, + "Complete": 2, + "Failed": 0, + "Running": 0, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + }, + "group1": { + "Queued": 0, + "Complete": 4, + "Failed": 0, + "Running": 1, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 3095, + "ModifyIndex": 10015 + }, + "CreateIndex": 3095, + "ModifyIndex": 10018, + "JobModifyIndex": 10008, + "SubmitTime": 1705690918813110300, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job6.json b/pkg/provider/nomad/fixtures/jobs_job6.json new file mode 100644 index 000000000..e4c34e238 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job6.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job6", + "ParentID": "", + "Name": "job6", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "dead", + "StatusDescription": "", + "JobSummary": { + "JobID": "job6", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 3, + "Failed": 0, + "Running": 0, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 9941, + "ModifyIndex": 10082 + }, + "CreateIndex": 9941, + "ModifyIndex": 10078, + "JobModifyIndex": 10074, + "SubmitTime": 1705690931854150700, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job7.json b/pkg/provider/nomad/fixtures/jobs_job7.json new file mode 100644 index 000000000..f3de421e2 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job7.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job7", + "ParentID": "", + "Name": "job7", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "running", + "StatusDescription": "", + "JobSummary": { + "JobID": "job7", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 0, + "Failed": 0, + "Running": 1, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 11221, + "ModifyIndex": 11225 + }, + "CreateIndex": 11221, + "ModifyIndex": 11238, + "JobModifyIndex": 11232, + "SubmitTime": 1705752438438572000, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job8.json b/pkg/provider/nomad/fixtures/jobs_job8.json new file mode 100644 index 000000000..e77e2c718 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job8.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job8", + "ParentID": "", + "Name": "job8", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "running", + "StatusDescription": "", + "JobSummary": { + "JobID": "job8", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 0, + "Failed": 0, + "Running": 1, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 11276, + "ModifyIndex": 11281 + }, + "CreateIndex": 11276, + "ModifyIndex": 11303, + "JobModifyIndex": 11297, + "SubmitTime": 1705753285493029600, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job9.json b/pkg/provider/nomad/fixtures/jobs_job9.json new file mode 100644 index 000000000..2f45e4ae7 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job9.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job9", + "ParentID": "", + "Name": "job9", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": true, + "Status": "dead", + "StatusDescription": "", + "JobSummary": { + "JobID": "job9", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 1, + "Failed": 0, + "Running": 0, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 11317, + "ModifyIndex": 11349 + }, + "CreateIndex": 11317, + "ModifyIndex": 11346, + "JobModifyIndex": 11344, + "SubmitTime": 1705753979676559600, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_hello.json b/pkg/provider/nomad/fixtures/service_hello.json new file mode 100644 index 000000000..e4f675b16 --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_hello.json @@ -0,0 +1,20 @@ +[ + { + "Address": "127.0.0.1", + "AllocID": "71a63a80-a98a-93ee-4fd7-73b808577c20", + "CreateIndex": 18, + "Datacenter": "dc1", + "ID": "_nomad-task-71a63a80-a98a-93ee-4fd7-73b808577c20-group-hello-nomad-hello-nomad-http", + "JobID": "echo", + "ModifyIndex": 18, + "Namespace": "default", + "NodeID": "6d7f412e-e7ff-2e66-d47b-867b0e9d8726", + "Port": 20627, + "ServiceName": "hello-nomad", + "Tags": [ + "traefik.enable=true", + "traefik.http.routers.hellon.entrypoints=web", + "traefik.http.routers.hellon.service=hello-nomad" + ] + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job1.json b/pkg/provider/nomad/fixtures/service_job1.json new file mode 100644 index 000000000..18a6c2ba0 --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job1.json @@ -0,0 +1,18 @@ +[ + { + "ID": "_nomad-task-03c7270c-f475-5981-1932-87c0a8a5aa24-group-group1-job1-http", + "ServiceName": "job1", + "Namespace": "default", + "NodeID": "e262ecb6-a7ac-7c3e-4de0-06445559e596", + "Datacenter": "dc1", + "JobID": "job1", + "AllocID": "03c7270c-f475-5981-1932-87c0a8a5aa24", + "Tags": [ + "traefik.enable=true" + ], + "Address": "192.168.1.21", + "Port": 29916, + "CreateIndex": 5753, + "ModifyIndex": 9958 + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job2.json b/pkg/provider/nomad/fixtures/service_job2.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job2.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job3.json b/pkg/provider/nomad/fixtures/service_job3.json new file mode 100644 index 000000000..ece0f4235 --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job3.json @@ -0,0 +1,18 @@ +[ + { + "ID": "_nomad-task-dd945b55-70fa-0efc-6512-b88fb55ce33f-group-group1-job3-http", + "ServiceName": "job3", + "Namespace": "default", + "NodeID": "e262ecb6-a7ac-7c3e-4de0-06445559e596", + "Datacenter": "dc1", + "JobID": "job3", + "AllocID": "dd945b55-70fa-0efc-6512-b88fb55ce33f", + "Tags": [ + "traefik.enable=true" + ], + "Address": "192.168.1.21", + "Port": 29983, + "CreateIndex": 9987, + "ModifyIndex": 9987 + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job4.json b/pkg/provider/nomad/fixtures/service_job4.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job4.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job5task1.json b/pkg/provider/nomad/fixtures/service_job5task1.json new file mode 100644 index 000000000..a438e607f --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job5task1.json @@ -0,0 +1,18 @@ +[ + { + "ID": "_nomad-task-a98bac3d-5382-3032-1954-57aff58b20c1-task1-job5task1-http", + "ServiceName": "job5task1", + "Namespace": "default", + "NodeID": "e262ecb6-a7ac-7c3e-4de0-06445559e596", + "Datacenter": "dc1", + "JobID": "job5", + "AllocID": "a98bac3d-5382-3032-1954-57aff58b20c1", + "Tags": [ + "traefik.enable=true" + ], + "Address": "192.168.1.21", + "Port": 24542, + "CreateIndex": 10013, + "ModifyIndex": 10013 + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job5task2.json b/pkg/provider/nomad/fixtures/service_job5task2.json new file mode 100644 index 000000000..a9d32113f --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job5task2.json @@ -0,0 +1,18 @@ +[ + { + "ID": "_nomad-task-a98bac3d-5382-3032-1954-57aff58b20c1-task2-job5task2-other", + "ServiceName": "job5task2", + "Namespace": "default", + "NodeID": "e262ecb6-a7ac-7c3e-4de0-06445559e596", + "Datacenter": "dc1", + "JobID": "job5", + "AllocID": "a98bac3d-5382-3032-1954-57aff58b20c1", + "Tags": [ + "traefik.enable=true" + ], + "Address": "192.168.1.21", + "Port": 30165, + "CreateIndex": 10014, + "ModifyIndex": 10014 + } +] diff --git a/pkg/provider/nomad/fixtures/service_job6task1.json b/pkg/provider/nomad/fixtures/service_job6task1.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job6task1.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job6task2.json b/pkg/provider/nomad/fixtures/service_job6task2.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job6task2.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job7.json b/pkg/provider/nomad/fixtures/service_job7.json new file mode 100644 index 000000000..35ab344cf --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job7.json @@ -0,0 +1,20 @@ +[ + { + "ID": "_nomad-task-3e1cc853-f6b1-2c46-6f20-332a6e91794b-group-group1-job7-http", + "ServiceName": "job7", + "Namespace": "default", + "NodeID": "e262ecb6-a7ac-7c3e-4de0-06445559e596", + "Datacenter": "dc1", + "JobID": "job7", + "AllocID": "3e1cc853-f6b1-2c46-6f20-332a6e91794b", + "Tags": [ + "traefik.enable=true", + "traefik.tcp.routers.job7.rule=HostSNI(`job7`)", + "traefik.tcp.routers.job7.tls.passthrough=true" + ], + "Address": "192.168.1.21", + "Port": 22899, + "CreateIndex": 11224, + "ModifyIndex": 11271 + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job8.json b/pkg/provider/nomad/fixtures/service_job8.json new file mode 100644 index 000000000..d670c568a --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job8.json @@ -0,0 +1,19 @@ +[ + { + "ID": "_nomad-task-fffd3d81-66ac-ed6d-d9a1-fc41b2a26d07-group-group1-job8-http", + "ServiceName": "job8", + "Namespace": "default", + "NodeID": "e262ecb6-a7ac-7c3e-4de0-06445559e596", + "Datacenter": "dc1", + "JobID": "job8", + "AllocID": "fffd3d81-66ac-ed6d-d9a1-fc41b2a26d07", + "Tags": [ + "traefik.enable=true", + "traefik.udp.routers.job8.service=job8" + ], + "Address": "192.168.1.21", + "Port": 24268, + "CreateIndex": 11279, + "ModifyIndex": 11300 + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job9.json b/pkg/provider/nomad/fixtures/service_job9.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job9.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_redis.json b/pkg/provider/nomad/fixtures/service_redis.json new file mode 100644 index 000000000..b1f4e752e --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_redis.json @@ -0,0 +1,18 @@ +[ + { + "Address": "127.0.0.1", + "AllocID": "07501480-8175-8071-7da6-133bd1ff890f", + "CreateIndex": 46, + "Datacenter": "dc1", + "ID": "_nomad-task-07501480-8175-8071-7da6-133bd1ff890f-group-redis-redis-redis", + "JobID": "echo", + "ModifyIndex": 46, + "Namespace": "default", + "NodeID": "6d7f412e-e7ff-2e66-d47b-867b0e9d8726", + "Port": 30826, + "ServiceName": "redis", + "Tags": [ + "traefik.enable=true" + ] + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/services.json b/pkg/provider/nomad/fixtures/services.json new file mode 100644 index 000000000..dfe303183 --- /dev/null +++ b/pkg/provider/nomad/fixtures/services.json @@ -0,0 +1,21 @@ +[ + { + "Namespace": "default", + "Services": [ + { + "ServiceName": "redis", + "Tags": [ + "traefik.enable=true" + ] + }, + { + "ServiceName": "hello-nomad", + "Tags": [ + "traefik.enable=true", + "traefik.http.routers.hellon.entrypoints=web", + "traefik.http.routers.hellon.service=hello-nomad" + ] + } + ] + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/nomad.go b/pkg/provider/nomad/nomad.go index b836707eb..5a595dca6 100644 --- a/pkg/provider/nomad/nomad.go +++ b/pkg/provider/nomad/nomad.go @@ -85,13 +85,14 @@ func (p *ProviderBuilder) BuildProviders() []*Provider { // Configuration represents the Nomad provider configuration. type Configuration struct { - DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"` - Constraints string `description:"Constraints is an expression that Traefik matches against the Nomad service's tags to determine whether to create route(s) for that service." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"` - Endpoint *EndpointConfig `description:"Nomad endpoint settings" json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" export:"true"` - Prefix string `description:"Prefix for nomad service tags." json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"` - Stale bool `description:"Use stale consistency for catalog reads." json:"stale,omitempty" toml:"stale,omitempty" yaml:"stale,omitempty" export:"true"` - ExposedByDefault bool `description:"Expose Nomad services by default." json:"exposedByDefault,omitempty" toml:"exposedByDefault,omitempty" yaml:"exposedByDefault,omitempty" export:"true"` - RefreshInterval ptypes.Duration `description:"Interval for polling Nomad API." json:"refreshInterval,omitempty" toml:"refreshInterval,omitempty" yaml:"refreshInterval,omitempty" export:"true"` + DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"` + Constraints string `description:"Constraints is an expression that Traefik matches against the Nomad service's tags to determine whether to create route(s) for that service." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"` + Endpoint *EndpointConfig `description:"Nomad endpoint settings" json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" export:"true"` + Prefix string `description:"Prefix for nomad service tags." json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"` + Stale bool `description:"Use stale consistency for catalog reads." json:"stale,omitempty" toml:"stale,omitempty" yaml:"stale,omitempty" export:"true"` + ExposedByDefault bool `description:"Expose Nomad services by default." json:"exposedByDefault,omitempty" toml:"exposedByDefault,omitempty" yaml:"exposedByDefault,omitempty" export:"true"` + RefreshInterval ptypes.Duration `description:"Interval for polling Nomad API." json:"refreshInterval,omitempty" toml:"refreshInterval,omitempty" yaml:"refreshInterval,omitempty" export:"true"` + AllowEmptyServices bool `description:"Allow the creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"` } // SetDefaults sets the default values for the Nomad Traefik Provider Configuration. @@ -116,6 +117,7 @@ func (c *Configuration) SetDefaults() { c.ExposedByDefault = true c.RefreshInterval = ptypes.Duration(15 * time.Second) c.DefaultRule = defaultTemplateRule + c.AllowEmptyServices = false } type EndpointConfig struct { @@ -222,10 +224,20 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } func (p *Provider) loadConfiguration(ctx context.Context, configurationC chan<- dynamic.Message) error { - items, err := p.getNomadServiceData(ctx) - if err != nil { - return err + var items []item + var err error + if p.AllowEmptyServices { + items, err = p.getNomadServiceDataWithEmptyServices(ctx) + if err != nil { + return err + } + } else { + items, err = p.getNomadServiceData(ctx) + if err != nil { + return err + } } + configurationC <- dynamic.Message{ ProviderName: p.name, Configuration: p.buildConfig(ctx, items), @@ -291,6 +303,98 @@ func (p *Provider) getNomadServiceData(ctx context.Context) ([]item, error) { return items, nil } +func (p *Provider) getNomadServiceDataWithEmptyServices(ctx context.Context) ([]item, error) { + jobsOpts := &api.QueryOptions{AllowStale: p.Stale} + jobsOpts = jobsOpts.WithContext(ctx) + + jobStubs, _, err := p.client.Jobs().List(jobsOpts) + if err != nil { + return nil, err + } + + var items []item + + // Get Services even when they are scaled down to zero. Currently the nomad service interface does not support this. https://github.com/hashicorp/nomad/issues/19731 + for _, jobStub := range jobStubs { + jobInfoOpts := &api.QueryOptions{} + jobInfoOpts = jobInfoOpts.WithContext(ctx) + + job, _, err := p.client.Jobs().Info(jobStub.ID, jobInfoOpts) + if err != nil { + return nil, err + } + + for _, taskGroup := range job.TaskGroups { + services := []*api.Service{} + // Get all services in job -> taskgroup + services = append(services, taskGroup.Services...) + + // Get all services in job -> taskgroup -> tasks + for _, task := range taskGroup.Tasks { + services = append(services, task.Services...) + } + + for _, service := range services { + logger := log.Ctx(ctx).With().Str("serviceName", service.TaskName).Logger() + + extraConf := p.getExtraConf(service.Tags) + if !extraConf.Enable { + logger.Debug().Msg("Filter Nomad service that is not enabled") + continue + } + + matches, err := constraints.MatchTags(service.Tags, p.Constraints) + if err != nil { + logger.Error().Err(err).Msg("Error matching constraint expressions") + continue + } + + if !matches { + logger.Debug().Msgf("Filter Nomad service not matching constraints: %q", p.Constraints) + continue + } + + if nil != taskGroup.Scaling && *taskGroup.Scaling.Enabled && *taskGroup.Count == 0 { + // Add items without address + items = append(items, item{ + // Create a unique id for non registered services + ID: fmt.Sprintf("%s-%s-%s-%s-%s", *job.Namespace, *job.Name, *taskGroup.Name, service.TaskName, service.Name), + Name: service.Name, + Namespace: *job.Namespace, + Node: "", + Datacenter: "", + Address: "", + Port: -1, + Tags: service.Tags, + ExtraConf: p.getExtraConf(service.Tags), + }) + } else { + instances, err := p.fetchService(ctx, service.Name) + if err != nil { + return nil, err + } + + for _, i := range instances { + items = append(items, item{ + ID: i.ID, + Name: i.ServiceName, + Namespace: i.Namespace, + Node: i.NodeID, + Datacenter: i.Datacenter, + Address: i.Address, + Port: i.Port, + Tags: i.Tags, + ExtraConf: p.getExtraConf(i.Tags), + }) + } + } + } + } + } + + return items, nil +} + // getExtraConf returns a configuration with settings which are not part of the dynamic configuration (e.g. ".enable"). func (p *Provider) getExtraConf(tags []string) configuration { labels := tagsToLabels(tags, p.Prefix) diff --git a/pkg/provider/nomad/nomad_test.go b/pkg/provider/nomad/nomad_test.go index b83c7727c..2794fab0c 100644 --- a/pkg/provider/nomad/nomad_test.go +++ b/pkg/provider/nomad/nomad_test.go @@ -2,8 +2,11 @@ package nomad import ( "context" + "fmt" "net/http" "net/http/httptest" + "os" + "path/filepath" "strings" "testing" @@ -12,6 +15,17 @@ import ( "github.com/traefik/traefik/v3/pkg/types" ) +var responses = map[string][]byte{} + +func TestMain(m *testing.M) { + err := setup() + if err != nil { + fmt.Fprintf(os.Stderr, "%s", err.Error()) + os.Exit(1) + } + m.Run() +} + func Test_globalConfig(t *testing.T) { cases := []struct { Name string @@ -118,7 +132,6 @@ func TestProvider_SetDefaults_Endpoint(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { for k, v := range test.envs { t.Setenv(k, v) @@ -132,15 +145,311 @@ func TestProvider_SetDefaults_Endpoint(t *testing.T) { } } +func Test_getNomadServiceDataWithEmptyServices_GroupService_Scaling1(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job1"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job1"): + _, _ = w.Write(responses["job_job1_WithGroupService_Scaling1"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job1"): + _, _ = w.Write(responses["service_job1"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 1) +} + +func Test_getNomadServiceDataWithEmptyServices_GroupService_Scaling0(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job2"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job2"): + _, _ = w.Write(responses["job_job2_WithGroupService_Scaling0"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job2"): + _, _ = w.Write(responses["service_job2"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 1) +} + +func Test_getNomadServiceDataWithEmptyServices_GroupService_ScalingDisabled(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job3"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job3"): + _, _ = w.Write(responses["job_job3_WithGroupService_ScalingDisabled"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job3"): + _, _ = w.Write(responses["service_job3"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 1) +} + +func Test_getNomadServiceDataWithEmptyServices_GroupService_ScalingDisabled_Stopped(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job4"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job4"): + _, _ = w.Write(responses["job_job4_WithGroupService_ScalingDisabled_Stopped"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job4"): + _, _ = w.Write(responses["service_job4"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + + // Should not be listed as job is stopped + require.Empty(t, items) +} + +func Test_getNomadServiceDataWithEmptyServices_GroupTaskService_Scaling1(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job5"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job5"): + _, _ = w.Write(responses["job_job5_WithGroupTaskService_Scaling1"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job5task1"): + _, _ = w.Write(responses["service_job5task1"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job5task2"): + _, _ = w.Write(responses["service_job5task2"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 2) +} + +func Test_getNomadServiceDataWithEmptyServices_GroupTaskService_Scaling0(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job6"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job6"): + _, _ = w.Write(responses["job_job6_WithGroupTaskService_Scaling0"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job6task1"): + _, _ = w.Write(responses["service_job6task1"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job6task2"): + _, _ = w.Write(responses["service_job6task2"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 2) +} + +func Test_getNomadServiceDataWithEmptyServices_TCP(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job7"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job7"): + _, _ = w.Write(responses["job_job7_TCP"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job7"): + _, _ = w.Write(responses["service_job7"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 1) +} + +func Test_getNomadServiceDataWithEmptyServices_UDP(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job8"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job8"): + _, _ = w.Write(responses["job_job8_UDP"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job8"): + _, _ = w.Write(responses["service_job8"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 1) +} + +func Test_getNomadServiceDataWithEmptyServices_ScalingEnabled_Stopped(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job9"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job9"): + _, _ = w.Write(responses["job_job9_ScalingEnabled_Stopped"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job9"): + _, _ = w.Write(responses["service_job9"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + + // Should not be listed as job is stopped + require.Empty(t, items) +} + +func setup() error { + responsesDir := "./fixtures" + files, err := os.ReadDir(responsesDir) + if err != nil { + return err + } + for _, file := range files { + if !file.IsDir() && filepath.Ext(file.Name()) == ".json" { + content, err := os.ReadFile(filepath.Join(responsesDir, file.Name())) + if err != nil { + return err + } + responses[strings.TrimSuffix(filepath.Base(file.Name()), filepath.Ext(file.Name()))] = content + } + } + return nil +} + func Test_getNomadServiceData(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case strings.HasSuffix(r.RequestURI, "/v1/services"): - _, _ = w.Write([]byte(services)) + _, _ = w.Write(responses["services"]) case strings.HasSuffix(r.RequestURI, "/v1/service/redis"): - _, _ = w.Write([]byte(redis)) + _, _ = w.Write(responses["service_redis"]) case strings.HasSuffix(r.RequestURI, "/v1/service/hello-nomad"): - _, _ = w.Write([]byte(hello)) + _, _ = w.Write(responses["service_hello"]) } })) t.Cleanup(ts.Close) @@ -160,71 +469,3 @@ func Test_getNomadServiceData(t *testing.T) { require.NoError(t, err) require.Len(t, items, 2) } - -const services = ` -[ - { - "Namespace": "default", - "Services": [ - { - "ServiceName": "redis", - "Tags": [ - "traefik.enable=true" - ] - }, - { - "ServiceName": "hello-nomad", - "Tags": [ - "traefik.enable=true", - "traefik.http.routers.hellon.entrypoints=web", - "traefik.http.routers.hellon.service=hello-nomad" - ] - } - ] - } -] -` - -const redis = ` -[ - { - "Address": "127.0.0.1", - "AllocID": "07501480-8175-8071-7da6-133bd1ff890f", - "CreateIndex": 46, - "Datacenter": "dc1", - "ID": "_nomad-task-07501480-8175-8071-7da6-133bd1ff890f-group-redis-redis-redis", - "JobID": "echo", - "ModifyIndex": 46, - "Namespace": "default", - "NodeID": "6d7f412e-e7ff-2e66-d47b-867b0e9d8726", - "Port": 30826, - "ServiceName": "redis", - "Tags": [ - "traefik.enable=true" - ] - } -] -` - -const hello = ` -[ - { - "Address": "127.0.0.1", - "AllocID": "71a63a80-a98a-93ee-4fd7-73b808577c20", - "CreateIndex": 18, - "Datacenter": "dc1", - "ID": "_nomad-task-71a63a80-a98a-93ee-4fd7-73b808577c20-group-hello-nomad-hello-nomad-http", - "JobID": "echo", - "ModifyIndex": 18, - "Namespace": "default", - "NodeID": "6d7f412e-e7ff-2e66-d47b-867b0e9d8726", - "Port": 20627, - "ServiceName": "hello-nomad", - "Tags": [ - "traefik.enable=true", - "traefik.http.routers.hellon.entrypoints=web", - "traefik.http.routers.hellon.service=hello-nomad" - ] - } -] -` diff --git a/pkg/provider/nomad/tag_test.go b/pkg/provider/nomad/tag_test.go index e15f5486c..69be3addb 100644 --- a/pkg/provider/nomad/tag_test.go +++ b/pkg/provider/nomad/tag_test.go @@ -97,7 +97,6 @@ func Test_tagsToLabels(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/tailscale/provider_test.go b/pkg/provider/tailscale/provider_test.go index 614b42ed3..2e6e1b962 100644 --- a/pkg/provider/tailscale/provider_test.go +++ b/pkg/provider/tailscale/provider_test.go @@ -120,7 +120,6 @@ func TestProvider_findDomains(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -189,7 +188,6 @@ func TestProvider_sendDynamicConfig(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -229,7 +227,6 @@ func Test_sanitizeDomains(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -268,7 +265,6 @@ func Test_isTailscaleDomain(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/provider/traefik/internal_test.go b/pkg/provider/traefik/internal_test.go index 915bbf6b6..c8d64f6be 100644 --- a/pkg/provider/traefik/internal_test.go +++ b/pkg/provider/traefik/internal_test.go @@ -257,7 +257,6 @@ func Test_createConfiguration(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/redactor/redactor_doOnJSON_test.go b/pkg/redactor/redactor_doOnJSON_test.go index ddae9c579..03e4082a1 100644 --- a/pkg/redactor/redactor_doOnJSON_test.go +++ b/pkg/redactor/redactor_doOnJSON_test.go @@ -59,7 +59,6 @@ func Test_doOnJSON_simple(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.name, func(t *testing.T) { t.Parallel() output := doOnJSON(test.input) diff --git a/pkg/redactor/redactor_doOnStruct_test.go b/pkg/redactor/redactor_doOnStruct_test.go index 96fe5edc9..af1f1faad 100644 --- a/pkg/redactor/redactor_doOnStruct_test.go +++ b/pkg/redactor/redactor_doOnStruct_test.go @@ -369,7 +369,6 @@ func Test_doOnStruct(t *testing.T) { } for _, test := range testCase { - test := test t.Run(test.name, func(t *testing.T) { t.Parallel() val := reflect.ValueOf(test.base).Elem() diff --git a/pkg/rules/parser.go b/pkg/rules/parser.go index 061895d3c..2555c01b9 100644 --- a/pkg/rules/parser.go +++ b/pkg/rules/parser.go @@ -31,7 +31,6 @@ func NewParser(matchers []string) (predicate.Parser, error) { parserFuncs := make(map[string]interface{}) for _, matcherName := range matchers { - matcherName := matcherName fn := func(value ...string) TreeBuilder { return func() *Tree { return &Tree{ diff --git a/pkg/rules/parser_test.go b/pkg/rules/parser_test.go index 503ab4298..b8aed5d5a 100644 --- a/pkg/rules/parser_test.go +++ b/pkg/rules/parser_test.go @@ -250,8 +250,6 @@ func TestRuleMatch(t *testing.T) { require.NoError(t, err) for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/safe/routine_test.go b/pkg/safe/routine_test.go index 1880b7c4e..12875972c 100644 --- a/pkg/safe/routine_test.go +++ b/pkg/safe/routine_test.go @@ -63,7 +63,6 @@ func TestPoolWithCtx(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { // These subtests cannot be run in parallel, since the testRoutine // is shared across the subtests. diff --git a/pkg/server/aggregator_test.go b/pkg/server/aggregator_test.go index 1ef3bd8ac..ba5fe4418 100644 --- a/pkg/server/aggregator_test.go +++ b/pkg/server/aggregator_test.go @@ -113,7 +113,6 @@ func Test_mergeConfiguration(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -162,7 +161,6 @@ func Test_mergeConfiguration_tlsCertificates(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -346,8 +344,6 @@ func Test_mergeConfiguration_tlsOptions(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -439,7 +435,6 @@ func Test_mergeConfiguration_tlsStore(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -664,7 +659,6 @@ func Test_applyModel(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/cookie/cookie_test.go b/pkg/server/cookie/cookie_test.go index 125e9dce1..452f0f221 100644 --- a/pkg/server/cookie/cookie_test.go +++ b/pkg/server/cookie/cookie_test.go @@ -34,7 +34,6 @@ func TestGetName(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -64,7 +63,6 @@ func Test_sanitizeName(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/middleware/middlewares_test.go b/pkg/server/middleware/middlewares_test.go index a0b97d1b9..89ea08963 100644 --- a/pkg/server/middleware/middlewares_test.go +++ b/pkg/server/middleware/middlewares_test.go @@ -256,7 +256,6 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -364,7 +363,6 @@ func TestBuilder_buildConstructor(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/provider/provider_test.go b/pkg/server/provider/provider_test.go index dc229e532..7686d1bb5 100644 --- a/pkg/server/provider/provider_test.go +++ b/pkg/server/provider/provider_test.go @@ -47,7 +47,6 @@ func TestAddInContext(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -103,7 +102,6 @@ func TestGetQualifiedName(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index 369aaa7ab..e3af98f33 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -4,7 +4,9 @@ import ( "context" "errors" "fmt" + "math" "net/http" + "strings" "github.com/containous/alice" "github.com/rs/zerolog/log" @@ -21,6 +23,8 @@ import ( "github.com/traefik/traefik/v3/pkg/tls" ) +const maxUserPriority = math.MaxInt - 1000 + type middlewareBuilder interface { BuildChain(ctx context.Context, names []string) *alice.Chain } @@ -65,8 +69,6 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t entryPointHandlers := make(map[string]http.Handler) for entryPointName, routers := range m.getHTTPRouters(rootCtx, entryPoints, tls) { - entryPointName := entryPointName - logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger() ctx := logger.WithContext(rootCtx) @@ -121,6 +123,13 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str routerConfig.Priority = httpmuxer.GetRulePriority(routerConfig.Rule) } + if routerConfig.Priority > maxUserPriority && !strings.HasSuffix(routerName, "@internal") { + err = fmt.Errorf("the router priority %d exceeds the max user-defined priority %d", routerConfig.Priority, maxUserPriority) + routerConfig.AddError(err, true) + logger.Error().Err(err).Send() + continue + } + handler, err := m.buildRouterHandler(ctxRouter, routerName, routerConfig) if err != nil { routerConfig.AddError(err, true) diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index b52172cce..c1ae3436e 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -3,6 +3,7 @@ package router import ( "context" "io" + "math" "net/http" "net/http/httptest" "strings" @@ -297,7 +298,6 @@ func TestRouterManager_Get(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -574,6 +574,32 @@ func TestRuntimeConfiguration(t *testing.T) { }, expectedError: 2, }, + { + desc: "Router priority exceeding max user-defined priority", + serviceConfig: map[string]*dynamic.Service{ + "foo-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1", + }, + }, + }, + }, + }, + middlewareConfig: map[string]*dynamic.Middleware{}, + routerConfig: map[string]*dynamic.Router{ + "bar": { + EntryPoints: []string{"web"}, + Service: "foo-service", + Rule: "Host(`foo.bar`)", + Priority: math.MaxInt, + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + tlsOptions: map[string]tls.Options{}, + expectedError: 1, + }, { desc: "Router with broken tlsOption", serviceConfig: map[string]*dynamic.Service{ @@ -640,7 +666,6 @@ func TestRuntimeConfiguration(t *testing.T) { }, } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/router/tcp/manager.go b/pkg/server/router/tcp/manager.go index cd10d5ccf..5966462cc 100644 --- a/pkg/server/router/tcp/manager.go +++ b/pkg/server/router/tcp/manager.go @@ -5,7 +5,9 @@ import ( "crypto/tls" "errors" "fmt" + "math" "net/http" + "strings" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" @@ -19,6 +21,8 @@ import ( traefiktls "github.com/traefik/traefik/v3/pkg/tls" ) +const maxUserPriority = math.MaxInt - 1000 + type middlewareBuilder interface { BuildChain(ctx context.Context, names []string) *tcp.Chain } @@ -74,8 +78,6 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m entryPointHandlers := make(map[string]*Router) for _, entryPointName := range entryPoints { - entryPointName := entryPointName - routers := entryPointsRouters[entryPointName] logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger() @@ -296,6 +298,14 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim routerErr := fmt.Errorf("invalid rule: %q , has HostSNI matcher, but no TLS on router", routerConfig.Rule) routerConfig.AddError(routerErr, true) logger.Error().Err(routerErr).Send() + continue + } + + if routerConfig.Priority > maxUserPriority && !strings.HasSuffix(routerName, "@internal") { + routerErr := fmt.Errorf("the router priority %d exceeds the max user-defined priority %d", routerConfig.Priority, maxUserPriority) + routerConfig.AddError(routerErr, true) + logger.Error().Err(routerErr).Send() + continue } var handler tcp.Handler diff --git a/pkg/server/router/tcp/manager_test.go b/pkg/server/router/tcp/manager_test.go index 29af7783c..97cec0444 100644 --- a/pkg/server/router/tcp/manager_test.go +++ b/pkg/server/router/tcp/manager_test.go @@ -3,6 +3,7 @@ package tcp import ( "context" "crypto/tls" + "math" "net/http" "net/http/httptest" "testing" @@ -265,11 +266,45 @@ func TestRuntimeConfiguration(t *testing.T) { EntryPoints: []string{"web"}, Service: "foo-service", Rule: "HostSNI(`foo.bar`)", + TLS: &dynamic.RouterTCPTLSConfig{}, }, }, }, expectedError: 2, }, + { + desc: "Router with priority exceeding the max user-defined priority", + tcpServiceConfig: map[string]*runtime.TCPServiceInfo{ + "foo-service": { + TCPService: &dynamic.TCPService{ + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Port: "8085", + Address: "127.0.0.1:8085", + }, + { + Address: "127.0.0.1:8086", + Port: "8086", + }, + }, + }, + }, + }, + }, + tcpRouterConfig: map[string]*runtime.TCPRouterInfo{ + "bar": { + TCPRouter: &dynamic.TCPRouter{ + EntryPoints: []string{"web"}, + Service: "foo-service", + Rule: "HostSNI(`foo.bar`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + Priority: math.MaxInt, + }, + }, + }, + expectedError: 1, + }, { desc: "Router with HostSNI but no TLS", tcpServiceConfig: map[string]*runtime.TCPServiceInfo{ @@ -299,8 +334,6 @@ func TestRuntimeConfiguration(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index 74f563486..4522a4532 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -8,8 +8,10 @@ import ( "io" "net" "net/http" + "slices" "time" + "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/rs/zerolog/log" tcpmuxer "github.com/traefik/traefik/v3/pkg/muxer/tcp" "github.com/traefik/traefik/v3/pkg/tcp" @@ -157,6 +159,12 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) { return } + // Handling ACME-TLS/1 challenges. + if slices.Contains(hello.protos, tlsalpn01.ACMETLS1Protocol) { + r.acmeTLSALPNHandler().ServeTCP(r.GetConn(conn, hello.peeked)) + return + } + // For real, the handler eventually used for HTTPS is (almost) always the same: // it is the httpsForwarder that is used for all HTTPS connections that match // (which is also incidentally the same used in the last block below for 404s). @@ -201,6 +209,17 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) { conn.Close() } +// acmeTLSALPNHandler returns a special handler to solve ACME-TLS/1 challenges. +func (r *Router) acmeTLSALPNHandler() tcp.Handler { + if r.httpsTLSConfig == nil { + return &brokenTLSRouter{} + } + + return tcp.HandlerFunc(func(conn tcp.WriteCloser) { + _ = tls.Server(conn, r.httpsTLSConfig).Handshake() + }) +} + // AddTCPRoute defines a handler for the given rule. func (r *Router) AddTCPRoute(rule string, priority int, target tcp.Handler) error { return r.muxerTCP.AddRoute(rule, "", priority, target) diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go index 64f32f7f7..b95f0d867 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -14,6 +14,7 @@ import ( "testing" "time" + "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/dynamic" @@ -22,6 +23,8 @@ import ( "github.com/traefik/traefik/v3/pkg/server/service/tcp" tcp2 "github.com/traefik/traefik/v3/pkg/tcp" traefiktls "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/tls/generate" + "github.com/traefik/traefik/v3/pkg/types" ) type applyRouter func(conf *runtime.Configuration) @@ -166,11 +169,16 @@ func Test_Routing(t *testing.T) { dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) serviceManager := tcp.NewManager(conf, dialerManager) + certPEM, keyPEM, err := generate.KeyPair("foo.bar", time.Time{}) + require.NoError(t, err) + // Creates the tlsManager and defines the TLS 1.0 and 1.2 TLSOptions. tlsManager := traefiktls.NewManager() tlsManager.UpdateConfigs( context.Background(), - map[string]traefiktls.Store{}, + map[string]traefiktls.Store{ + tlsalpn01.ACMETLS1Protocol: {}, + }, map[string]traefiktls.Options{ "default": { MinVersion: "VersionTLS10", @@ -185,7 +193,10 @@ func Test_Routing(t *testing.T) { MaxVersion: "VersionTLS12", }, }, - []*traefiktls.CertAndStores{}) + []*traefiktls.CertAndStores{{ + Certificate: traefiktls.Certificate{CertFile: types.FileOrContent(certPEM), KeyFile: types.FileOrContent(keyPEM)}, + Stores: []string{tlsalpn01.ACMETLS1Protocol}, + }}) middlewaresBuilder := tcpmiddleware.NewBuilder(conf.TCPMiddlewares) @@ -209,6 +220,10 @@ func Test_Routing(t *testing.T) { desc: "No routers", routers: []applyRouter{}, checks: []checkCase{ + { + desc: "ACME TLS Challenge", + checkRouter: checkACMETLS, + }, { desc: "TCP with client sending first bytes should fail", checkRouter: checkTCPClientFirst, @@ -246,6 +261,16 @@ func Test_Routing(t *testing.T) { }, }, }, + { + desc: "TCP TLS passthrough does not catch ACME TLS", + routers: []applyRouter{routerTCPTLSCatchAllPassthrough}, + checks: []checkCase{ + { + desc: "ACME TLS Challenge", + checkRouter: checkACMETLS, + }, + }, + }, { desc: "Single TCP CatchAll router", routers: []applyRouter{routerTCPCatchAll}, @@ -556,8 +581,6 @@ func Test_Routing(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -694,6 +717,21 @@ func routerTCPTLSCatchAll(conf *runtime.Configuration) { } } +// routerTCPTLSCatchAllPassthrough a TCP TLS CatchAll Passthrough - HostSNI(`*`) router with TLS 1.0 config. +func routerTCPTLSCatchAllPassthrough(conf *runtime.Configuration) { + conf.TCPRouters["tcp-tls-catchall-passthrough"] = &runtime.TCPRouterInfo{ + TCPRouter: &dynamic.TCPRouter{ + EntryPoints: []string{"web"}, + Service: "tcp", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Options: "tls12", + Passthrough: true, + }, + }, + } +} + // routerTCPTLS configures a TCP TLS - HostSNI(`foo.bar`) router with TLS 1.2 config. func routerTCPTLS(conf *runtime.Configuration) { conf.TCPRouters["tcp-tls"] = &runtime.TCPRouterInfo{ @@ -736,6 +774,33 @@ func routerHTTPS(conf *runtime.Configuration) { } } +// checkACMETLS simulates a ACME TLS Challenge client connection. +// It returns an error if TLS handshake fails. +func checkACMETLS(addr string, _ time.Duration) (err error) { + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + ServerName: "foo.bar", + MinVersion: tls.VersionTLS10, + NextProtos: []string{tlsalpn01.ACMETLS1Protocol}, + } + conn, err := tls.Dial("tcp", addr, tlsConfig) + if err != nil { + return err + } + defer func() { + closeErr := conn.Close() + if closeErr != nil && err == nil { + err = closeErr + } + }() + + if conn.ConnectionState().Version != tls.VersionTLS10 { + return fmt.Errorf("wrong TLS version. wanted %X, got %X", tls.VersionTLS10, conn.ConnectionState().Version) + } + + return nil +} + // checkTCPClientFirst simulates a TCP client sending first bytes first. // It returns an error if it doesn't receive the expected response. func checkTCPClientFirst(addr string, timeout time.Duration) (err error) { diff --git a/pkg/server/router/udp/router.go b/pkg/server/router/udp/router.go index 091d77ed5..8fb0f70d6 100644 --- a/pkg/server/router/udp/router.go +++ b/pkg/server/router/udp/router.go @@ -43,8 +43,6 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m entryPointHandlers := make(map[string]udp.Handler) for _, entryPointName := range entryPoints { - entryPointName := entryPointName - routers := entryPointsRouters[entryPointName] logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger() diff --git a/pkg/server/router/udp/router_test.go b/pkg/server/router/udp/router_test.go index d6be07491..eaa99e174 100644 --- a/pkg/server/router/udp/router_test.go +++ b/pkg/server/router/udp/router_test.go @@ -106,8 +106,6 @@ func TestRuntimeConfiguration(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/routerfactory_test.go b/pkg/server/routerfactory_test.go index 971b58c99..836885781 100644 --- a/pkg/server/routerfactory_test.go +++ b/pkg/server/routerfactory_test.go @@ -170,8 +170,6 @@ func TestServerResponseEmptyBackend(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/service/observability_roundtripper_test.go b/pkg/server/service/observability_roundtripper_test.go index afbac748e..837839543 100644 --- a/pkg/server/service/observability_roundtripper_test.go +++ b/pkg/server/service/observability_roundtripper_test.go @@ -56,7 +56,6 @@ func TestObservabilityRoundTripper_metrics(t *testing.T) { } for _, test := range tests { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/service/roundtripper_test.go b/pkg/server/service/roundtripper_test.go index 0f4a9c6e5..600fc1457 100644 --- a/pkg/server/service/roundtripper_test.go +++ b/pkg/server/service/roundtripper_test.go @@ -405,7 +405,6 @@ func TestDisableHTTP2(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -589,7 +588,6 @@ func TestKerberosRoundTripper(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go index 5c26770e6..786a1aded 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -62,7 +62,6 @@ func TestGetLoadBalancer(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -306,7 +305,6 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { serviceInfo := &runtime.ServiceInfo{Service: &dynamic.Service{LoadBalancer: test.service}} handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, serviceInfo) @@ -498,7 +496,6 @@ func TestManager_Build(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/service/tcp/service_test.go b/pkg/server/service/tcp/service_test.go index cb94becb4..6f06d296c 100644 --- a/pkg/server/service/tcp/service_test.go +++ b/pkg/server/service/tcp/service_test.go @@ -237,7 +237,6 @@ func TestManager_BuildTCP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/service/udp/service_test.go b/pkg/server/service/udp/service_test.go index 6517c66a1..ec1794bc1 100644 --- a/pkg/server/service/udp/service_test.go +++ b/pkg/server/service/udp/service_test.go @@ -174,7 +174,6 @@ func TestManager_BuildUDP(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/tcp/proxy_test.go b/pkg/tcp/proxy_test.go index 21176300c..e2874e8aa 100644 --- a/pkg/tcp/proxy_test.go +++ b/pkg/tcp/proxy_test.go @@ -104,8 +104,6 @@ func TestProxyProtocol(t *testing.T) { } for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { backendListener, err := net.Listen("tcp", ":0") require.NoError(t, err) diff --git a/pkg/tcp/wrr_load_balancer_test.go b/pkg/tcp/wrr_load_balancer_test.go index 5ee18caad..fc3438ac6 100644 --- a/pkg/tcp/wrr_load_balancer_test.go +++ b/pkg/tcp/wrr_load_balancer_test.go @@ -121,13 +121,11 @@ func TestLoadBalancing(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() balancer := NewWRRLoadBalancer() for server, weight := range test.serversWeight { - server := server balancer.AddWeightServer(HandlerFunc(func(conn WriteCloser) { _, err := conn.Write([]byte(server)) require.NoError(t, err) diff --git a/pkg/tls/certificate_store_test.go b/pkg/tls/certificate_store_test.go index 5a948d302..aacc9b759 100644 --- a/pkg/tls/certificate_store_test.go +++ b/pkg/tls/certificate_store_test.go @@ -56,7 +56,6 @@ func TestGetBestCertificate(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() dynamicMap := map[string]*CertificateData{} diff --git a/pkg/tls/tlsmanager_test.go b/pkg/tls/tlsmanager_test.go index 9103aac32..cfe325e9a 100644 --- a/pkg/tls/tlsmanager_test.go +++ b/pkg/tls/tlsmanager_test.go @@ -162,7 +162,6 @@ func TestManager_Get(t *testing.T) { tlsManager.UpdateConfigs(context.Background(), nil, tlsConfigs, dynamicConfigs) for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -302,7 +301,6 @@ func TestClientAuth(t *testing.T) { tlsManager.UpdateConfigs(context.Background(), nil, tlsConfigs, nil) for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/types/domain_test.go b/pkg/types/domain_test.go index dc97c7971..f9f7d42cb 100644 --- a/pkg/types/domain_test.go +++ b/pkg/types/domain_test.go @@ -37,7 +37,6 @@ func TestDomain_ToStrArray(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -77,7 +76,6 @@ func TestDomain_Set(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -171,7 +169,6 @@ func TestMatchDomain(t *testing.T) { } for _, test := range testCases { - test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() diff --git a/pkg/types/tls_test.go b/pkg/types/tls_test.go index d3e93f408..b123aba53 100644 --- a/pkg/types/tls_test.go +++ b/pkg/types/tls_test.go @@ -104,8 +104,6 @@ func TestClientTLS_CreateTLSConfig(t *testing.T) { } for _, test := range tests { - test := test - t.Run(test.desc, func(t *testing.T) { tlsConfig, err := test.clientTLS.CreateTLSConfig(context.Background()) if test.wantErr {