From 51343bc15fd54a68c520746657801d26376e14a7 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 14 Jan 2026 17:26:08 +0100 Subject: [PATCH 01/13] Upgrade golangci-lint --- .github/workflows/validate.yaml | 4 +- .golangci.yml | 22 ++- cmd/configuration.go | 1 + cmd/internal/gen/main.go | 2 +- .../kubernetes-crd-definition-v1.yml | 10 +- .../traefik.containo.us_middlewares.yaml | 1 + .../traefik.containo.us_middlewaretcps.yaml | 3 +- .../traefik.containo.us_tlsoptions.yaml | 1 + .../traefik.io_middlewares.yaml | 1 + .../traefik.io_middlewaretcps.yaml | 3 +- .../traefik.io_tlsoptions.yaml | 1 + docs/mkdocs.yml | 2 +- integration/access_log_test.go | 3 +- integration/acme_test.go | 11 +- integration/consul_catalog_test.go | 83 +++++----- integration/consul_test.go | 21 +-- integration/docker_test.go | 6 +- integration/error_pages_test.go | 1 + integration/etcd_test.go | 1 + integration/fixtures/k8s/01-traefik-crd.yml | 10 +- integration/healthcheck_test.go | 7 +- integration/helloworld/helloworld.pb.go | 6 +- integration/https_test.go | 68 ++++---- integration/integration_test.go | 73 ++++----- integration/log_rotation_test.go | 1 - integration/marathon15_test.go | 1 + integration/marathon_test.go | 1 + integration/proxy_protocol_test.go | 9 +- integration/ratelimit_test.go | 1 + integration/redis_sentinel_test.go | 61 ++++---- integration/redis_test.go | 1 + integration/rest_test.go | 1 + integration/retry_test.go | 1 + integration/tracing_test.go | 37 ++--- integration/try/try.go | 8 +- integration/zk_test.go | 1 + internal/gendoc.go | 26 ++-- internal/parser.go | 8 +- pkg/api/criterion.go | 5 +- pkg/api/debug.go | 2 +- pkg/api/handler.go | 5 +- pkg/api/handler_entrypoint.go | 1 + pkg/api/handler_entrypoint_test.go | 2 +- pkg/api/handler_http.go | 3 + pkg/api/handler_http_test.go | 2 +- pkg/api/handler_overview.go | 2 +- pkg/api/handler_overview_test.go | 4 +- pkg/api/handler_tcp.go | 3 + pkg/api/handler_tcp_test.go | 2 +- pkg/api/handler_test.go | 2 +- pkg/api/handler_udp.go | 2 + pkg/api/handler_udp_test.go | 2 +- pkg/cli/loader_file.go | 2 +- pkg/collector/hydratation/hydration.go | 6 +- pkg/config/dynamic/config_test.go | 4 +- pkg/config/dynamic/middlewares.go | 1 + pkg/config/dynamic/plugins_test.go | 6 +- pkg/config/dynamic/tcp_middlewares.go | 1 + pkg/config/kv/kv.go | 4 +- pkg/config/label/label.go | 2 +- pkg/config/runtime/runtime_http.go | 28 ++-- pkg/config/runtime/runtime_tcp.go | 29 ++-- pkg/config/runtime/runtime_udp.go | 22 ++- pkg/config/static/plugins.go | 2 +- pkg/config/static/static_config.go | 28 ++-- pkg/healthcheck/healthcheck.go | 36 +++-- pkg/healthcheck/healthcheck_test.go | 1 + pkg/job/job.go | 1 + pkg/log/deprecated.go | 37 +++-- pkg/metrics/datadog.go | 2 +- pkg/metrics/headers.go | 10 +- pkg/metrics/influxdb.go | 2 +- pkg/metrics/influxdb2.go | 2 +- pkg/metrics/statsd.go | 2 +- pkg/middlewares/accesslog/logdata.go | 2 +- .../accesslog/logger_formatters.go | 2 +- .../accesslog/logger_formatters_test.go | 10 +- pkg/middlewares/accesslog/logger_test.go | 40 ++--- pkg/middlewares/auth/connectionheader.go | 2 +- pkg/middlewares/capture/capture.go | 28 ++-- pkg/middlewares/customerrors/custom_errors.go | 39 ++--- .../forwardedheaders/forwarded_header.go | 46 +++--- pkg/middlewares/headers/header.go | 42 ++--- pkg/middlewares/headers/secure_test.go | 3 +- pkg/middlewares/metrics/recorder.go | 9 +- pkg/middlewares/ratelimiter/rate_limiter.go | 5 +- .../ratelimiter/rate_limiter_test.go | 7 +- pkg/middlewares/recovery/recovery.go | 2 +- pkg/middlewares/retry/retry.go | 6 +- pkg/middlewares/tracing/entrypoint.go | 1 + pkg/middlewares/tracing/entrypoint_test.go | 10 +- pkg/middlewares/tracing/forwarder_test.go | 18 +-- pkg/middlewares/tracing/mock_tracing_test.go | 16 +- pkg/middlewares/tracing/status_code.go | 1 + pkg/muxer/http/mux.go | 1 + pkg/plugins/client.go | 58 +++---- pkg/plugins/middlewares.go | 6 +- pkg/plugins/providers.go | 6 +- pkg/plugins/types.go | 14 +- pkg/provider/acme/local_store.go | 102 ++++++------ pkg/provider/acme/local_store_unix.go | 1 - pkg/provider/acme/provider.go | 4 +- pkg/provider/aggregator/aggregator.go | 14 +- pkg/provider/configuration.go | 6 +- .../constraints/constraints_labels.go | 2 +- pkg/provider/constraints/constraints_tags.go | 2 +- pkg/provider/consulcatalog/consul_catalog.go | 20 +-- pkg/provider/docker/docker.go | 146 +++++++++--------- pkg/provider/docker/swarm_test.go | 2 + pkg/provider/ecs/ecs.go | 74 ++++----- pkg/provider/file/file.go | 93 ++++++----- .../kubernetes/crd/client-containous.go | 56 +++---- pkg/provider/kubernetes/crd/client.go | 24 +-- .../kubernetes/crd/client_mock_test.go | 4 +- pkg/provider/kubernetes/crd/kubernetes.go | 98 ++++++------ .../kubernetes/crd/kubernetes_test.go | 22 +-- pkg/provider/kubernetes/crd/scheme.go | 4 +- .../v1alpha1/middlewaretcp.go | 3 +- .../traefikcontainous/v1alpha1/tlsoption.go | 1 + .../crd/traefikio/v1alpha1/middlewaretcp.go | 3 +- .../crd/traefikio/v1alpha1/tlsoption.go | 1 + pkg/provider/kubernetes/gateway/client.go | 16 +- .../kubernetes/gateway/client_mock_test.go | 4 +- pkg/provider/kubernetes/gateway/kubernetes.go | 113 +++++++------- pkg/provider/kubernetes/ingress/client.go | 106 ++++++------- .../kubernetes/ingress/client_mock_test.go | 4 +- pkg/provider/kubernetes/ingress/kubernetes.go | 96 ++++++------ pkg/provider/kubernetes/k8s/event_handler.go | 12 +- .../kubernetes/k8s/event_handler_test.go | 4 +- pkg/provider/marathon/config.go | 6 +- pkg/provider/marathon/marathon.go | 4 +- pkg/provider/marathon/readiness.go | 6 +- pkg/provider/rancher/rancher.go | 22 +-- pkg/redactor/redactor.go | 16 +- pkg/rules/parser.go | 9 +- pkg/safe/routine.go | 4 +- pkg/safe/routine_test.go | 1 + pkg/safe/safe.go | 8 +- pkg/server/middleware/plugins.go | 6 +- pkg/server/router/router.go | 16 +- pkg/server/router/router_test.go | 4 +- pkg/server/router/tcp/manager.go | 52 +++---- pkg/server/router/tcp/router.go | 55 +++---- pkg/server/router/tcp/router_test.go | 1 + pkg/server/router/udp/router.go | 28 ++-- pkg/server/server_entrypoint_tcp.go | 17 +- pkg/server/server_entrypoint_tcp_http3.go | 10 +- pkg/server/server_signals.go | 1 - pkg/server/server_signals_windows.go | 1 - pkg/server/service/bufferpool.go | 2 +- .../loadbalancer/failover/failover_test.go | 1 + .../service/loadbalancer/mirror/mirror.go | 49 +++--- pkg/server/service/loadbalancer/wrr/wrr.go | 65 ++++---- .../service/loadbalancer/wrr/wrr_test.go | 1 + pkg/server/service/proxy.go | 2 +- pkg/server/service/proxy_test.go | 2 +- pkg/server/service/roundtripper.go | 14 +- pkg/server/service/service.go | 74 ++++----- pkg/tcp/proxy_unix.go | 1 - pkg/tcp/proxy_windows.go | 1 - pkg/tcp/wrr_load_balancer.go | 1 + pkg/testhelpers/metrics.go | 10 +- pkg/tls/certificate.go | 7 +- pkg/tls/certificate_store.go | 56 +++---- pkg/tls/tls.go | 3 +- pkg/tls/tlsmanager.go | 16 +- pkg/tracing/haystack/logger.go | 6 +- pkg/tracing/jaeger/logger.go | 2 +- pkg/tracing/tracing.go | 17 +- pkg/udp/conn.go | 107 +++++++------ pkg/udp/wrr_load_balancer.go | 1 + 171 files changed, 1463 insertions(+), 1416 deletions(-) diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 60c212d3c..317653e13 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -7,8 +7,8 @@ on: env: GO_VERSION: '1.24' - GOLANGCI_LINT_VERSION: v2.0.2 - MISSPELL_VERSION: v0.6.0 + GOLANGCI_LINT_VERSION: v2.7.2 + MISSPELL_VERSION: v0.7.0 jobs: diff --git a/.golangci.yml b/.golangci.yml index 605a27512..a0fcc0112 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -36,6 +36,7 @@ linters: - nilnil # Not relevant - nlreturn # Not relevant - noctx # Too strict + - noinlineerr # Too strict - nonamedreturns # Too strict - paralleltest # Not relevant - prealloc # Too many false-positive. @@ -47,6 +48,7 @@ linters: - varnamelen # Not relevant - wrapcheck # Too strict - wsl # Too strict + - wsl_v5 # Too strict settings: depguard: @@ -292,15 +294,31 @@ linters: source: 'errors.New\("Nomad provider' text: 'ST1005: error strings should not be capitalized' - path: (.+)\.go - text: 'struct-tag: unknown option ''inline'' in JSON tag' + text: 'omitzero: Omitempty has no effect on nested struct field' + linters: + - modernize + - path: (.+)\.go + text: 'struct-tag: unknown option "inline" in json tag' linters: - revive - path: (.+)\.go - text: 'struct-tag: unknown option ''omitzero'' in TOML tag' + text: 'struct-tag: unknown option "omitzero" in toml tag' + linters: + - revive + - path: (pkg/types/.+|pkg/api/.+)\.go + text: 'var-naming: avoid meaningless package names' + linters: + - revive + - path: (pkg/muxer/http/.+|pkg/provider/http/.+)\.go + text: 'var-naming: avoid package names that conflict with Go standard library package names' linters: - revive - path: (.+)\.go$ text: 'SA1019: http.CloseNotifier has been deprecated' # FIXME must be fixed + - path: (.+)\.go$ + text: 'SA1019: dynamic.(TCPIPWhiteList|IPWhiteList) is deprecated: please use IPAllowList instead.' + - path: (.+)\.go$ + text: 'SA1019: middlewareTCP.Spec.IPWhiteList is deprecated: please use IPAllowList instead.' - path: (.+)\.go$ text: 'SA1019: cfg.(SSLRedirect|SSLTemporaryRedirect|SSLHost|SSLForceHost|FeaturePolicy) is deprecated' - path: (.+)\.go$ diff --git a/cmd/configuration.go b/cmd/configuration.go index 3e4f38f92..811245e51 100644 --- a/cmd/configuration.go +++ b/cmd/configuration.go @@ -10,6 +10,7 @@ import ( // TraefikCmdConfiguration wraps the static configuration and extra parameters. type TraefikCmdConfiguration struct { static.Configuration `export:"true"` + // ConfigFile is the path to the configuration file. ConfigFile string `description:"Configuration file to use. If specified all other flags are ignored." export:"true"` } diff --git a/cmd/internal/gen/main.go b/cmd/internal/gen/main.go index 8e9c3735a..52bd3f789 100644 --- a/cmd/internal/gen/main.go +++ b/cmd/internal/gen/main.go @@ -83,7 +83,7 @@ func run(dest string) error { return err } - return os.WriteFile(filepath.Join(dest, "marshaler.go"), []byte(fmt.Sprintf(marsh, destPkg)), 0o666) + return os.WriteFile(filepath.Join(dest, "marshaler.go"), fmt.Appendf(nil, marsh, destPkg), 0o666) } func cleanType(typ types.Type, base string) string { diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index b5c41bb8c..9de679ae1 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -1273,6 +1273,7 @@ spec: IPWhiteList holds the IP whitelist middleware configuration. This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ + Deprecated: please use IPAllowList instead. properties: ipStrategy: @@ -1663,8 +1664,9 @@ spec: description: |- IPWhiteList defines the IPWhiteList middleware configuration. This middleware accepts/refuses connections based on the client IP. - Deprecated: please use IPAllowList instead. More info: https://doc.traefik.io/traefik/v2.11/middlewares/tcp/ipwhitelist/ + + Deprecated: please use IPAllowList instead. properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -1907,6 +1909,7 @@ spec: description: |- PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. It is enabled automatically when minVersion or maxVersion is set. + Deprecated: https://github.com/golang/go/issues/45430 type: boolean sniStrict: @@ -3703,6 +3706,7 @@ spec: IPWhiteList holds the IP whitelist middleware configuration. This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ + Deprecated: please use IPAllowList instead. properties: ipStrategy: @@ -4093,8 +4097,9 @@ spec: description: |- IPWhiteList defines the IPWhiteList middleware configuration. This middleware accepts/refuses connections based on the client IP. - Deprecated: please use IPAllowList instead. More info: https://doc.traefik.io/traefik/v2.11/middlewares/tcp/ipwhitelist/ + + Deprecated: please use IPAllowList instead. properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -4337,6 +4342,7 @@ spec: description: |- PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. It is enabled automatically when minVersion or maxVersion is set. + Deprecated: https://github.com/golang/go/issues/45430 type: boolean sniStrict: diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml index f822b08f5..6d61dcb3f 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml @@ -658,6 +658,7 @@ spec: IPWhiteList holds the IP whitelist middleware configuration. This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ + Deprecated: please use IPAllowList instead. properties: ipStrategy: diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewaretcps.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewaretcps.yaml index 829a9c85a..f52ef185e 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewaretcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewaretcps.yaml @@ -68,8 +68,9 @@ spec: description: |- IPWhiteList defines the IPWhiteList middleware configuration. This middleware accepts/refuses connections based on the client IP. - Deprecated: please use IPAllowList instead. More info: https://doc.traefik.io/traefik/v2.11/middlewares/tcp/ipwhitelist/ + + Deprecated: please use IPAllowList instead. properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml index 6c7fdc914..06f4ff242 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml @@ -99,6 +99,7 @@ spec: description: |- PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. It is enabled automatically when minVersion or maxVersion is set. + Deprecated: https://github.com/golang/go/issues/45430 type: boolean sniStrict: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index 0dee0fa2c..99cc5ac87 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -658,6 +658,7 @@ spec: IPWhiteList holds the IP whitelist middleware configuration. This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ + Deprecated: please use IPAllowList instead. properties: ipStrategy: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml index dc435fdf5..d4f739cb9 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml @@ -68,8 +68,9 @@ spec: description: |- IPWhiteList defines the IPWhiteList middleware configuration. This middleware accepts/refuses connections based on the client IP. - Deprecated: please use IPAllowList instead. More info: https://doc.traefik.io/traefik/v2.11/middlewares/tcp/ipwhitelist/ + + Deprecated: please use IPAllowList instead. properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of diff --git a/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml b/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml index 20f817125..6e4c90cf7 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml @@ -99,6 +99,7 @@ spec: description: |- PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. It is enabled automatically when minVersion or maxVersion is set. + Deprecated: https://github.com/golang/go/issues/45430 type: boolean sniStrict: diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 71d83858f..4e2110641 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -28,7 +28,7 @@ theme: prev: 'Previous' next: 'Next' -copyright: 'Traefik Labs • Copyright © 2016-2025' +copyright: 'Traefik Labs • Copyright © 2016-2026' extra_javascript: - assets/js/hljs/highlight.pack.js # Download from https://highlightjs.org/download/ and enable YAML, TOML and Dockerfile diff --git a/integration/access_log_test.go b/integration/access_log_test.go index 1361deaa0..684f167d8 100644 --- a/integration/access_log_test.go +++ b/integration/access_log_test.go @@ -245,8 +245,7 @@ func digestParts(resp *http.Response) map[string]string { result := map[string]string{} if len(resp.Header["Www-Authenticate"]) > 0 { wantedHeaders := []string{"nonce", "realm", "qop", "opaque"} - responseHeaders := strings.Split(resp.Header["Www-Authenticate"][0], ",") - for _, r := range responseHeaders { + for r := range strings.SplitSeq(resp.Header["Www-Authenticate"][0], ",") { for _, w := range wantedHeaders { if strings.Contains(r, w) { result[w] = strings.Split(r, `"`)[1] diff --git a/integration/acme_test.go b/integration/acme_test.go index d2fa1a920..f5cf2c6bc 100644 --- a/integration/acme_test.go +++ b/integration/acme_test.go @@ -27,6 +27,7 @@ import ( // ACME test suites. type AcmeSuite struct { BaseSuite + pebbleIP string fakeDNSServer *dns.Server } @@ -63,11 +64,6 @@ const ( wildcardDomain = "*.acme.wtf" ) -func (s *AcmeSuite) getAcmeURL() string { - return fmt.Sprintf("https://%s/dir", - net.JoinHostPort(s.pebbleIP, "14000")) -} - func setupPebbleRootCA() (*http.Transport, error) { path, err := filepath.Abs("fixtures/acme/ssl/pebble.minica.pem") if err != nil { @@ -540,3 +536,8 @@ func (s *AcmeSuite) retrieveAcmeCertificate(testCase acmeTestCase) { assert.Equal(s.T(), sub.expectedAlgorithm, gotPublicKeyAlgorithm) } } + +func (s *AcmeSuite) getAcmeURL() string { + return fmt.Sprintf("https://%s/dir", + net.JoinHostPort(s.pebbleIP, "14000")) +} diff --git a/integration/consul_catalog_test.go b/integration/consul_catalog_test.go index 8043d5bab..cc257e31c 100644 --- a/integration/consul_catalog_test.go +++ b/integration/consul_catalog_test.go @@ -16,6 +16,7 @@ import ( type ConsulCatalogSuite struct { BaseSuite + consulClient *api.Client consulAgentClient *api.Client consulURL string @@ -53,47 +54,6 @@ func (s *ConsulCatalogSuite) TearDownSuite() { s.BaseSuite.TearDownSuite() } -func (s *ConsulCatalogSuite) waitToElectConsulLeader() error { - return try.Do(15*time.Second, func() error { - leader, err := s.consulClient.Status().Leader() - - if err != nil || len(leader) == 0 { - return fmt.Errorf("leader not found. %w", err) - } - - return nil - }) -} - -func (s *ConsulCatalogSuite) waitForConnectCA() error { - return try.Do(15*time.Second, func() error { - caroots, _, err := s.consulClient.Connect().CARoots(nil) - - if err != nil || len(caroots.Roots) == 0 { - return fmt.Errorf("connect CA not fully initialized. %w", err) - } - - return nil - }) -} - -func (s *ConsulCatalogSuite) registerService(reg *api.AgentServiceRegistration, onAgent bool) error { - client := s.consulClient - if onAgent { - client = s.consulAgentClient - } - - return client.Agent().ServiceRegister(reg) -} - -func (s *ConsulCatalogSuite) deregisterService(id string, onAgent bool) error { - client := s.consulClient - if onAgent { - client = s.consulAgentClient - } - return client.Agent().ServiceDeregister(id) -} - func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings() { reg1 := &api.AgentServiceRegistration{ ID: "whoami1", @@ -847,3 +807,44 @@ func (s *ConsulCatalogSuite) TestConsulConnect_NotAware() { err = s.deregisterService("whoami1", false) require.NoError(s.T(), err) } + +func (s *ConsulCatalogSuite) waitToElectConsulLeader() error { + return try.Do(15*time.Second, func() error { + leader, err := s.consulClient.Status().Leader() + + if err != nil || len(leader) == 0 { + return fmt.Errorf("leader not found. %w", err) + } + + return nil + }) +} + +func (s *ConsulCatalogSuite) waitForConnectCA() error { + return try.Do(15*time.Second, func() error { + caroots, _, err := s.consulClient.Connect().CARoots(nil) + + if err != nil || len(caroots.Roots) == 0 { + return fmt.Errorf("connect CA not fully initialized. %w", err) + } + + return nil + }) +} + +func (s *ConsulCatalogSuite) registerService(reg *api.AgentServiceRegistration, onAgent bool) error { + client := s.consulClient + if onAgent { + client = s.consulAgentClient + } + + return client.Agent().ServiceRegister(reg) +} + +func (s *ConsulCatalogSuite) deregisterService(id string, onAgent bool) error { + client := s.consulClient + if onAgent { + client = s.consulAgentClient + } + return client.Agent().ServiceDeregister(id) +} diff --git a/integration/consul_test.go b/integration/consul_test.go index 176ab7587..1daa4efc4 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -26,6 +26,7 @@ import ( // Consul test suites. type ConsulSuite struct { BaseSuite + kvClient store.Store consulURL string } @@ -164,16 +165,6 @@ func (s *ConsulSuite) TestSimpleConfiguration() { } } -func (s *ConsulSuite) assertWhoami(host string, expectedStatusCode int) { - req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) - require.NoError(s.T(), err) - req.Host = host - - resp, err := try.ResponseUntilStatusCode(req, 15*time.Second, expectedStatusCode) - require.NoError(s.T(), err) - resp.Body.Close() -} - func (s *ConsulSuite) TestDeleteRootKey() { // This test case reproduce the issue: https://github.com/traefik/traefik/issues/8092 @@ -222,3 +213,13 @@ func (s *ConsulSuite) TestDeleteRootKey() { s.assertWhoami("kv1.localhost", http.StatusNotFound) s.assertWhoami("kv2.localhost", http.StatusNotFound) } + +func (s *ConsulSuite) assertWhoami(host string, expectedStatusCode int) { + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) + require.NoError(s.T(), err) + req.Host = host + + resp, err := try.ResponseUntilStatusCode(req, 15*time.Second, expectedStatusCode) + require.NoError(s.T(), err) + resp.Body.Close() +} diff --git a/integration/docker_test.go b/integration/docker_test.go index f9060ec71..70d38878e 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -81,7 +81,7 @@ func (s *DockerSuite) TestDefaultDockerContainers() { body, err := io.ReadAll(resp.Body) require.NoError(s.T(), err) - var version map[string]interface{} + var version map[string]any assert.NoError(s.T(), json.Unmarshal(body, &version)) assert.Equal(s.T(), "swarm/1.0.0", version["Version"]) @@ -145,7 +145,7 @@ func (s *DockerSuite) TestDockerContainersWithLabels() { body, err := io.ReadAll(resp.Body) require.NoError(s.T(), err) - var version map[string]interface{} + var version map[string]any assert.NoError(s.T(), json.Unmarshal(body, &version)) assert.Equal(s.T(), "swarm/1.0.0", version["Version"]) @@ -203,7 +203,7 @@ func (s *DockerSuite) TestRestartDockerContainers() { body, err := io.ReadAll(resp.Body) require.NoError(s.T(), err) - var version map[string]interface{} + var version map[string]any assert.NoError(s.T(), json.Unmarshal(body, &version)) assert.Equal(s.T(), "swarm/1.0.0", version["Version"]) diff --git a/integration/error_pages_test.go b/integration/error_pages_test.go index acc31fec2..58c05dd7d 100644 --- a/integration/error_pages_test.go +++ b/integration/error_pages_test.go @@ -14,6 +14,7 @@ import ( // ErrorPagesSuite test suites. type ErrorPagesSuite struct { BaseSuite + ErrorPageIP string BackendIP string } diff --git a/integration/etcd_test.go b/integration/etcd_test.go index 0f1ca5d1a..5e3219591 100644 --- a/integration/etcd_test.go +++ b/integration/etcd_test.go @@ -24,6 +24,7 @@ import ( // etcd test suites. type EtcdSuite struct { BaseSuite + kvClient store.Store etcdAddr string } diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index b5c41bb8c..9de679ae1 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -1273,6 +1273,7 @@ spec: IPWhiteList holds the IP whitelist middleware configuration. This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ + Deprecated: please use IPAllowList instead. properties: ipStrategy: @@ -1663,8 +1664,9 @@ spec: description: |- IPWhiteList defines the IPWhiteList middleware configuration. This middleware accepts/refuses connections based on the client IP. - Deprecated: please use IPAllowList instead. More info: https://doc.traefik.io/traefik/v2.11/middlewares/tcp/ipwhitelist/ + + Deprecated: please use IPAllowList instead. properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -1907,6 +1909,7 @@ spec: description: |- PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. It is enabled automatically when minVersion or maxVersion is set. + Deprecated: https://github.com/golang/go/issues/45430 type: boolean sniStrict: @@ -3703,6 +3706,7 @@ spec: IPWhiteList holds the IP whitelist middleware configuration. This middleware limits allowed requests based on the client IP. More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ + Deprecated: please use IPAllowList instead. properties: ipStrategy: @@ -4093,8 +4097,9 @@ spec: description: |- IPWhiteList defines the IPWhiteList middleware configuration. This middleware accepts/refuses connections based on the client IP. - Deprecated: please use IPAllowList instead. More info: https://doc.traefik.io/traefik/v2.11/middlewares/tcp/ipwhitelist/ + + Deprecated: please use IPAllowList instead. properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -4337,6 +4342,7 @@ spec: description: |- PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. It is enabled automatically when minVersion or maxVersion is set. + Deprecated: https://github.com/golang/go/issues/45430 type: boolean sniStrict: diff --git a/integration/healthcheck_test.go b/integration/healthcheck_test.go index 6f14b048b..406429fe8 100644 --- a/integration/healthcheck_test.go +++ b/integration/healthcheck_test.go @@ -19,6 +19,7 @@ import ( // HealthCheck test suites. type HealthCheckSuite struct { BaseSuite + whoami1IP string whoami2IP string whoami3IP string @@ -305,7 +306,7 @@ func (s *HealthCheckSuite) TestPropagate() { require.NoError(s.T(), err) } - try.Sleep(time.Second) + try.Sleep(time.Second) //nolint:staticcheck // Intentional use for integration test timing. want2 := `IP: ` + s.whoami2IP want4 := `IP: ` + s.whoami4IP @@ -387,7 +388,7 @@ func (s *HealthCheckSuite) TestPropagate() { require.NoError(s.T(), err) } - try.Sleep(time.Second) + try.Sleep(time.Second) //nolint:staticcheck // Intentional use for integration test timing. // Verify that everything is down, and that we get 503s everywhere. for range 2 { @@ -413,7 +414,7 @@ func (s *HealthCheckSuite) TestPropagate() { require.NoError(s.T(), err) } - try.Sleep(time.Second) + try.Sleep(time.Second) //nolint:staticcheck // Intentional use for integration test timing. // Verify everything is up on root router. reachedServers = make(map[string]int) diff --git a/integration/helloworld/helloworld.pb.go b/integration/helloworld/helloworld.pb.go index d3f26398c..6c65d422b 100644 --- a/integration/helloworld/helloworld.pb.go +++ b/integration/helloworld/helloworld.pb.go @@ -187,7 +187,7 @@ func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { s.RegisterService(&_Greeter_serviceDesc, srv) } -func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _Greeter_SayHello_Handler(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { in := new(HelloRequest) if err := dec(in); err != nil { return nil, err @@ -199,13 +199,13 @@ func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(in Server: srv, FullMethod: "/helloworld.Greeter/SayHello", } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { + handler := func(ctx context.Context, req any) (any, error) { return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) } return interceptor(ctx, in, info, handler) } -func _Greeter_StreamExample_Handler(srv interface{}, stream grpc.ServerStream) error { +func _Greeter_StreamExample_Handler(srv any, stream grpc.ServerStream) error { m := new(StreamExampleRequest) if err := stream.RecvMsg(m); err != nil { return err diff --git a/integration/https_test.go b/integration/https_test.go index 6e98e61d5..a0ab2c1bc 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -876,40 +876,6 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion() require.NoError(s.T(), err) } -// modifyCertificateConfFileContent replaces the content of a HTTPS configuration file. -func (s *HTTPSSuite) modifyCertificateConfFileContent(certFileName, confFileName string) { - file, err := os.OpenFile("./"+confFileName, os.O_WRONLY, os.ModeExclusive) - require.NoError(s.T(), err) - defer func() { - file.Close() - }() - err = file.Truncate(0) - require.NoError(s.T(), err) - - // If certificate file is not provided, just truncate the configuration file - if len(certFileName) > 0 { - tlsConf := dynamic.Configuration{ - TLS: &dynamic.TLSConfiguration{ - Certificates: []*traefiktls.CertAndStores{ - { - Certificate: traefiktls.Certificate{ - CertFile: traefiktls.FileOrContent("fixtures/https/" + certFileName + ".cert"), - KeyFile: traefiktls.FileOrContent("fixtures/https/" + certFileName + ".key"), - }, - }, - }, - }, - } - - var confBuffer bytes.Buffer - err := toml.NewEncoder(&confBuffer).Encode(tlsConf) - require.NoError(s.T(), err) - - _, err = file.Write(confBuffer.Bytes()) - require.NoError(s.T(), err) - } -} - func (s *HTTPSSuite) TestEntryPointHttpsRedirectAndPathModification() { file := s.adaptFile("fixtures/https/https_redirect.toml", struct{}{}) s.traefikCmd(withConfigFile(file)) @@ -1176,6 +1142,40 @@ func (s *HTTPSSuite) TestWithInvalidTLSOption() { } } +// modifyCertificateConfFileContent replaces the content of a HTTPS configuration file. +func (s *HTTPSSuite) modifyCertificateConfFileContent(certFileName, confFileName string) { + file, err := os.OpenFile("./"+confFileName, os.O_WRONLY, os.ModeExclusive) + require.NoError(s.T(), err) + defer func() { + file.Close() + }() + err = file.Truncate(0) + require.NoError(s.T(), err) + + // If certificate file is not provided, just truncate the configuration file + if len(certFileName) > 0 { + tlsConf := dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{ + Certificates: []*traefiktls.CertAndStores{ + { + Certificate: traefiktls.Certificate{ + CertFile: traefiktls.FileOrContent("fixtures/https/" + certFileName + ".cert"), + KeyFile: traefiktls.FileOrContent("fixtures/https/" + certFileName + ".key"), + }, + }, + }, + }, + } + + var confBuffer bytes.Buffer + err := toml.NewEncoder(&confBuffer).Encode(tlsConf) + require.NoError(s.T(), err) + + _, err = file.Write(confBuffer.Bytes()) + require.NoError(s.T(), err) + } +} + func (s *SimpleSuite) TestMaxConcurrentStream() { file := s.adaptFile("fixtures/https/max_concurrent_stream.toml", struct{}{}) diff --git a/integration/integration_test.go b/integration/integration_test.go index 12a76064d..12861fd47 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -61,45 +61,12 @@ type composeDeploy struct { type BaseSuite struct { suite.Suite + containers map[string]testcontainers.Container network *testcontainers.DockerNetwork hostIP string } -func (s *BaseSuite) waitForTraefik(containerName string) { - time.Sleep(1 * time.Second) - - // Wait for Traefik to turn ready. - req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil) - require.NoError(s.T(), err) - - err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains(containerName)) - require.NoError(s.T(), err) -} - -func (s *BaseSuite) displayTraefikLogFile(path string) { - if s.T().Failed() { - if _, err := os.Stat(path); !os.IsNotExist(err) { - content, errRead := os.ReadFile(path) - // TODO TestName - // fmt.Printf("%s: Traefik logs: \n", c.TestName()) - fmt.Print("Traefik logs: \n") - if errRead == nil { - fmt.Println(string(content)) - } else { - fmt.Println(errRead) - } - } else { - // fmt.Printf("%s: No Traefik logs.\n", c.TestName()) - fmt.Print("No Traefik logs.\n") - } - errRemove := os.Remove(path) - if errRemove != nil { - fmt.Println(errRemove) - } - } -} - func (s *BaseSuite) SetupSuite() { if isDockerDesktop(context.Background(), s.T()) { _, err := os.Stat(tailscaleSecretFilePath) @@ -400,7 +367,7 @@ func (s *BaseSuite) displayTraefikLog(output *bytes.Buffer) { if output == nil || output.Len() == 0 { log.WithoutContext().Info("No Traefik logs.") } else { - for _, line := range strings.Split(output.String(), "\n") { + for line := range strings.SplitSeq(output.String(), "\n") { log.WithoutContext().Info(line) } } @@ -416,7 +383,7 @@ func (s *BaseSuite) getDockerHost() string { return dockerHost } -func (s *BaseSuite) adaptFile(path string, tempObjects interface{}) string { +func (s *BaseSuite) adaptFile(path string, tempObjects any) string { // Load file tmpl, err := template.ParseFiles(path) require.NoError(s.T(), err) @@ -504,3 +471,37 @@ func (s *BaseSuite) composeExec(service string, args ...string) string { return string(content) } + +func (s *BaseSuite) waitForTraefik(containerName string) { + time.Sleep(1 * time.Second) + + // Wait for Traefik to turn ready. + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil) + require.NoError(s.T(), err) + + err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains(containerName)) + require.NoError(s.T(), err) +} + +func (s *BaseSuite) displayTraefikLogFile(path string) { + if s.T().Failed() { + if _, err := os.Stat(path); !os.IsNotExist(err) { + content, errRead := os.ReadFile(path) + // TODO TestName + // fmt.Printf("%s: Traefik logs: \n", c.TestName()) + fmt.Print("Traefik logs: \n") + if errRead == nil { + fmt.Println(string(content)) + } else { + fmt.Println(errRead) + } + } else { + // fmt.Printf("%s: No Traefik logs.\n", c.TestName()) + fmt.Print("No Traefik logs.\n") + } + errRemove := os.Remove(path) + if errRemove != nil { + fmt.Println(errRemove) + } + } +} diff --git a/integration/log_rotation_test.go b/integration/log_rotation_test.go index e5979e50d..7cbd246b8 100644 --- a/integration/log_rotation_test.go +++ b/integration/log_rotation_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows package integration diff --git a/integration/marathon15_test.go b/integration/marathon15_test.go index 255aba819..97201e111 100644 --- a/integration/marathon15_test.go +++ b/integration/marathon15_test.go @@ -14,6 +14,7 @@ import ( // Marathon test suites. type MarathonSuite15 struct { BaseSuite + marathonURL string } diff --git a/integration/marathon_test.go b/integration/marathon_test.go index c26c01780..f15229693 100644 --- a/integration/marathon_test.go +++ b/integration/marathon_test.go @@ -16,6 +16,7 @@ const containerNameMarathon = "marathon" // Marathon test suites. type MarathonSuite struct { BaseSuite + marathonURL string } diff --git a/integration/proxy_protocol_test.go b/integration/proxy_protocol_test.go index 35d694f65..275e47790 100644 --- a/integration/proxy_protocol_test.go +++ b/integration/proxy_protocol_test.go @@ -3,6 +3,7 @@ package integration import ( "bufio" "net" + "strings" "testing" "time" @@ -15,6 +16,7 @@ import ( type ProxyProtocolSuite struct { BaseSuite + whoamiIP string } @@ -124,15 +126,16 @@ func proxyProtoRequest(address string, version byte) (string, error) { } // Read the response from the server - var content string + var content strings.Builder scanner := bufio.NewScanner(conn) for scanner.Scan() { - content += scanner.Text() + "\n" + content.WriteString(scanner.Text()) + content.WriteString("\n") } if scanner.Err() != nil { return "", err } - return content, nil + return content.String(), nil } diff --git a/integration/ratelimit_test.go b/integration/ratelimit_test.go index 95aa3b297..e63d1044c 100644 --- a/integration/ratelimit_test.go +++ b/integration/ratelimit_test.go @@ -12,6 +12,7 @@ import ( type RateLimitSuite struct { BaseSuite + ServerIP string } diff --git a/integration/redis_sentinel_test.go b/integration/redis_sentinel_test.go index 6b452c0f9..4a9db2ba8 100644 --- a/integration/redis_sentinel_test.go +++ b/integration/redis_sentinel_test.go @@ -29,6 +29,7 @@ import ( // Redis test suites. type RedisSentinelSuite struct { BaseSuite + kvClient store.Store redisEndpoints []string } @@ -76,36 +77,6 @@ func (s *RedisSentinelSuite) TearDownSuite() { } } -func (s *RedisSentinelSuite) setupSentinelConfiguration(ports []string) { - for i, port := range ports { - templateValue := struct{ SentinelPort string }{SentinelPort: port} - - // Load file - templateFile := "resources/compose/config/sentinel_template.conf" - tmpl, err := template.ParseFiles(templateFile) - require.NoError(s.T(), err) - - folder, prefix := filepath.Split(templateFile) - - fileName := fmt.Sprintf("%s/sentinel%d.conf", folder, i+1) - tmpFile, err := os.Create(fileName) - require.NoError(s.T(), err) - defer tmpFile.Close() - - err = tmpFile.Chmod(0o666) - require.NoError(s.T(), err) - - model := structs.Map(templateValue) - model["SelfFilename"] = tmpFile.Name() - - err = tmpl.ExecuteTemplate(tmpFile, prefix, model) - require.NoError(s.T(), err) - - err = tmpFile.Sync() - require.NoError(s.T(), err) - } -} - func (s *RedisSentinelSuite) TestSentinelConfiguration() { file := s.adaptFile("fixtures/redis/sentinel.toml", struct{ RedisAddress string }{ RedisAddress: strings.Join(s.redisEndpoints, `","`), @@ -202,3 +173,33 @@ func (s *RedisSentinelSuite) TestSentinelConfiguration() { log.WithoutContext().Info(text) } } + +func (s *RedisSentinelSuite) setupSentinelConfiguration(ports []string) { + for i, port := range ports { + templateValue := struct{ SentinelPort string }{SentinelPort: port} + + // Load file + templateFile := "resources/compose/config/sentinel_template.conf" + tmpl, err := template.ParseFiles(templateFile) + require.NoError(s.T(), err) + + folder, prefix := filepath.Split(templateFile) + + fileName := fmt.Sprintf("%s/sentinel%d.conf", folder, i+1) + tmpFile, err := os.Create(fileName) + require.NoError(s.T(), err) + defer tmpFile.Close() + + err = tmpFile.Chmod(0o666) + require.NoError(s.T(), err) + + model := structs.Map(templateValue) + model["SelfFilename"] = tmpFile.Name() + + err = tmpl.ExecuteTemplate(tmpFile, prefix, model) + require.NoError(s.T(), err) + + err = tmpFile.Sync() + require.NoError(s.T(), err) + } +} diff --git a/integration/redis_test.go b/integration/redis_test.go index 3faaffec3..81d53c319 100644 --- a/integration/redis_test.go +++ b/integration/redis_test.go @@ -25,6 +25,7 @@ import ( // Redis test suites. type RedisSuite struct { BaseSuite + kvClient store.Store redisEndpoints []string } diff --git a/integration/rest_test.go b/integration/rest_test.go index b1034955f..88eb4811f 100644 --- a/integration/rest_test.go +++ b/integration/rest_test.go @@ -18,6 +18,7 @@ import ( type RestSuite struct { BaseSuite + whoamiAddr string } diff --git a/integration/retry_test.go b/integration/retry_test.go index 6804bbeba..0011cdbce 100644 --- a/integration/retry_test.go +++ b/integration/retry_test.go @@ -15,6 +15,7 @@ import ( type RetrySuite struct { BaseSuite + whoamiIP string } diff --git a/integration/tracing_test.go b/integration/tracing_test.go index 1c069bacd..1a779915e 100644 --- a/integration/tracing_test.go +++ b/integration/tracing_test.go @@ -12,6 +12,7 @@ import ( type TracingSuite struct { BaseSuite + whoamiIP string whoamiPort int tracerZipkinIP string @@ -43,15 +44,6 @@ func (s *TracingSuite) TearDownSuite() { s.BaseSuite.TearDownSuite() } -func (s *TracingSuite) startZipkin() { - s.composeUp("zipkin") - s.tracerZipkinIP = s.getComposeServiceIP("zipkin") - - // Wait for Zipkin to turn ready. - err := try.GetRequest("http://"+s.tracerZipkinIP+":9411/api/v2/services", 20*time.Second, try.StatusCodeIs(http.StatusOK)) - require.NoError(s.T(), err) -} - func (s *TracingSuite) TestZipkinRateLimit() { s.startZipkin() @@ -141,15 +133,6 @@ func (s *TracingSuite) TestZipkinAuth() { require.NoError(s.T(), err) } -func (s *TracingSuite) startJaeger() { - s.composeUp("jaeger", "whoami") - s.tracerJaegerIP = s.getComposeServiceIP("jaeger") - - // Wait for Jaeger to turn ready. - err := try.GetRequest("http://"+s.tracerJaegerIP+":16686/api/services", 20*time.Second, try.StatusCodeIs(http.StatusOK)) - require.NoError(s.T(), err) -} - func (s *TracingSuite) TestJaegerRateLimit() { s.startJaeger() // defer s.composeStop(c, "jaeger") @@ -290,3 +273,21 @@ func (s *TracingSuite) TestJaegerAuthCollector() { err = try.GetRequest("http://"+s.tracerJaegerIP+":16686/api/traces?service=tracing", 20*time.Second, try.BodyContains("EntryPoint web", "basic-auth@file")) require.NoError(s.T(), err) } + +func (s *TracingSuite) startZipkin() { + s.composeUp("zipkin") + s.tracerZipkinIP = s.getComposeServiceIP("zipkin") + + // Wait for Zipkin to turn ready. + err := try.GetRequest("http://"+s.tracerZipkinIP+":9411/api/v2/services", 20*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) +} + +func (s *TracingSuite) startJaeger() { + s.composeUp("jaeger", "whoami") + s.tracerJaegerIP = s.getComposeServiceIP("jaeger") + + // Wait for Jaeger to turn ready. + err := try.GetRequest("http://"+s.tracerJaegerIP+":16686/api/services", 20*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) +} diff --git a/integration/try/try.go b/integration/try/try.go index 9f17267e0..20bc8bc1a 100644 --- a/integration/try/try.go +++ b/integration/try/try.go @@ -19,6 +19,7 @@ const ( type timedAction func(timeout time.Duration, operation DoCondition) error // Sleep pauses the current goroutine for at least the duration d. +// // Deprecated: Use only when use another Try[...] functions is not possible. func Sleep(d time.Duration) { d = applyCIMultiplier(d) @@ -92,10 +93,7 @@ func Do(timeout time.Duration, operation DoCondition) error { panic("timeout must be larger than zero") } - interval := time.Duration(math.Ceil(float64(timeout) / 15.0)) - if interval > maxInterval { - interval = maxInterval - } + interval := min(time.Duration(math.Ceil(float64(timeout)/15.0)), maxInterval) timeout = applyCIMultiplier(timeout) @@ -166,7 +164,7 @@ func doRequest(action timedAction, timeout time.Duration, request *http.Request, func applyCIMultiplier(timeout time.Duration) time.Duration { ci := os.Getenv("CI") if len(ci) > 0 { - log.Debug("Apply CI multiplier:", CITimeoutMultiplier) + log.WithoutContext().Debug("Apply CI multiplier:", CITimeoutMultiplier) return time.Duration(float64(timeout) * CITimeoutMultiplier) } return timeout diff --git a/integration/zk_test.go b/integration/zk_test.go index 6eb62a9f0..48fefdc54 100644 --- a/integration/zk_test.go +++ b/integration/zk_test.go @@ -25,6 +25,7 @@ import ( // Zk test suites. type ZookeeperSuite struct { BaseSuite + kvClient store.Store zookeeperAddr string } diff --git a/internal/gendoc.go b/internal/gendoc.go index e2362faf5..116040b1e 100644 --- a/internal/gendoc.go +++ b/internal/gendoc.go @@ -78,7 +78,7 @@ func main() { logger.Fatal(err) } - genStaticConfDoc("./docs/content/reference/static-configuration/env-ref.md", "", func(i interface{}) ([]parser.Flat, error) { + genStaticConfDoc("./docs/content/reference/static-configuration/env-ref.md", "", func(i any) ([]parser.Flat, error) { return env.Encode(env.DefaultNamePrefix, i) }) genStaticConfDoc("./docs/content/reference/static-configuration/cli-ref.md", "--", flag.Encode) @@ -240,7 +240,7 @@ func clean(element any) { valSvcs.SetMapIndex(reflect.ValueOf(fmt.Sprintf("%s1", valueSvcRoot.Type().Name())), reflect.Value{}) } -func genStaticConfDoc(outputFile, prefix string, encodeFn func(interface{}) ([]parser.Flat, error)) { +func genStaticConfDoc(outputFile, prefix string, encodeFn func(any) ([]parser.Flat, error)) { logger := log.WithoutContext().WithField("file", outputFile) element := &cmd.NewTraefikConfiguration().Configuration @@ -309,7 +309,7 @@ type errWriter struct { err error } -func (ew *errWriter) writeln(a ...interface{}) { +func (ew *errWriter) writeln(a ...any) { if ew.err != nil { return } @@ -319,15 +319,15 @@ func (ew *errWriter) writeln(a ...interface{}) { func genKVDynConfDoc(outputFile string) { dynConfPath := "./docs/content/reference/dynamic-configuration/file.toml" - conf := map[string]interface{}{} + conf := map[string]any{} _, err := toml.DecodeFile(dynConfPath, &conf) if err != nil { - log.Fatal(err) + log.WithoutContext().Fatal(err) } file, err := os.Create(outputFile) if err != nil { - log.Fatal(err) + log.WithoutContext().Fatal(err) } store := storeWriter{data: map[string]string{}} @@ -335,7 +335,7 @@ func genKVDynConfDoc(outputFile string) { c := client{store: store} err = c.load("traefik", conf) if err != nil { - log.Fatal(err) + log.WithoutContext().Fatal(err) } var keys []string @@ -374,10 +374,10 @@ type client struct { store storeWriter } -func (c client) load(parentKey string, conf map[string]interface{}) error { +func (c client) load(parentKey string, conf map[string]any) error { for k, v := range conf { switch entry := v.(type) { - case map[string]interface{}: + case map[string]any: key := path.Join(parentKey, k) if len(entry) == 0 { @@ -391,7 +391,7 @@ func (c client) load(parentKey string, conf map[string]interface{}) error { return err } } - case []map[string]interface{}: + case []map[string]any: for i, o := range entry { key := path.Join(parentKey, k, strconv.Itoa(i)) @@ -399,11 +399,11 @@ func (c client) load(parentKey string, conf map[string]interface{}) error { return err } } - case []interface{}: + case []any: for i, o := range entry { key := path.Join(parentKey, k, strconv.Itoa(i)) - err := c.store.Put(key, []byte(fmt.Sprintf("%v", o)), nil) + err := c.store.Put(key, fmt.Appendf(nil, "%v", o), nil) if err != nil { return err } @@ -411,7 +411,7 @@ func (c client) load(parentKey string, conf map[string]interface{}) error { default: key := path.Join(parentKey, k) - err := c.store.Put(key, []byte(fmt.Sprintf("%v", v)), nil) + err := c.store.Put(key, fmt.Appendf(nil, "%v", v), nil) if err != nil { return err } diff --git a/internal/parser.go b/internal/parser.go index 3fa85617e..c99cc075d 100644 --- a/internal/parser.go +++ b/internal/parser.go @@ -38,7 +38,7 @@ func encodeNode(labels map[string]string, root string, node *parser.Node) { } } -func encodeRawValue(labels map[string]string, root string, rawValue interface{}) { +func encodeRawValue(labels map[string]string, root string, rawValue any) { if rawValue == nil { return } @@ -47,14 +47,14 @@ func encodeRawValue(labels map[string]string, root string, rawValue interface{}) if tValue.Kind() == reflect.Map && tValue.Elem().Kind() == reflect.Interface { r := reflect.ValueOf(rawValue). - Convert(reflect.TypeOf((map[string]interface{})(nil))). - Interface().(map[string]interface{}) + Convert(reflect.TypeFor[map[string]any]()). + Interface().(map[string]any) for k, v := range r { switch tv := v.(type) { case string: labels[root+"."+k] = tv - case []interface{}: + case []any: for i, e := range tv { encodeRawValue(labels, fmt.Sprintf("%s.%s[%d]", root, k, i), e) } diff --git a/pkg/api/criterion.go b/pkg/api/criterion.go index 4a3c35612..b9ff70ef7 100644 --- a/pkg/api/criterion.go +++ b/pkg/api/criterion.go @@ -72,10 +72,7 @@ func pagination(request *http.Request, maximum int) (pageInfo, error) { return pageInfo{}, fmt.Errorf("invalid request: page: %d, per_page: %d", page, perPage) } - endIndex := startIndex + perPage - if endIndex >= maximum { - endIndex = maximum - } + endIndex := min(startIndex+perPage, maximum) nextPage := 1 if page*perPage < maximum { diff --git a/pkg/api/debug.go b/pkg/api/debug.go index b207043ef..f5fdfb74c 100644 --- a/pkg/api/debug.go +++ b/pkg/api/debug.go @@ -15,7 +15,7 @@ func init() { expvar.Publish("Goroutines2", expvar.Func(goroutines)) } -func goroutines() interface{} { +func goroutines() any { return runtime.NumGoroutine() } diff --git a/pkg/api/handler.go b/pkg/api/handler.go index 18cc7c7ba..bcea78504 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -30,6 +30,7 @@ func writeError(rw http.ResponseWriter, msg string, code int) { type serviceInfoRepresentation struct { *runtime.ServiceInfo + ServerStatus map[string]string `json:"serverStatus,omitempty"` } @@ -147,12 +148,12 @@ func getProviderName(id string) string { return strings.SplitN(id, "@", 2)[1] } -func extractType(element interface{}) string { +func extractType(element any) string { v := reflect.ValueOf(element).Elem() for i := range v.NumField() { field := v.Field(i) - if field.Kind() == reflect.Map && field.Type().Elem() == reflect.TypeOf(dynamic.PluginConf{}) { + if field.Kind() == reflect.Map && field.Type().Elem() == reflect.TypeFor[dynamic.PluginConf]() { if keys := field.MapKeys(); len(keys) == 1 { return keys[0].String() } diff --git a/pkg/api/handler_entrypoint.go b/pkg/api/handler_entrypoint.go index 2a7aee691..0464f1b02 100644 --- a/pkg/api/handler_entrypoint.go +++ b/pkg/api/handler_entrypoint.go @@ -15,6 +15,7 @@ import ( type entryPointRepresentation struct { *static.EntryPoint + Name string `json:"name,omitempty"` } diff --git a/pkg/api/handler_entrypoint_test.go b/pkg/api/handler_entrypoint_test.go index d5d6a5d1f..a12ffb8ad 100644 --- a/pkg/api/handler_entrypoint_test.go +++ b/pkg/api/handler_entrypoint_test.go @@ -235,7 +235,7 @@ func TestHandler_EntryPoints(t *testing.T) { require.NoError(t, err) if *updateExpected { - var results interface{} + var results any err := json.Unmarshal(contents, &results) require.NoError(t, err) diff --git a/pkg/api/handler_http.go b/pkg/api/handler_http.go index 7afbc7f43..693d2aacb 100644 --- a/pkg/api/handler_http.go +++ b/pkg/api/handler_http.go @@ -17,6 +17,7 @@ import ( type routerRepresentation struct { *runtime.RouterInfo + Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` } @@ -35,6 +36,7 @@ func newRouterRepresentation(name string, rt *runtime.RouterInfo) routerRepresen type serviceRepresentation struct { *runtime.ServiceInfo + ServerStatus map[string]string `json:"serverStatus,omitempty"` Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` @@ -53,6 +55,7 @@ func newServiceRepresentation(name string, si *runtime.ServiceInfo) serviceRepre type middlewareRepresentation struct { *runtime.MiddlewareInfo + Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` Type string `json:"type,omitempty"` diff --git a/pkg/api/handler_http_test.go b/pkg/api/handler_http_test.go index 0e5079f61..838e5b154 100644 --- a/pkg/api/handler_http_test.go +++ b/pkg/api/handler_http_test.go @@ -950,7 +950,7 @@ func TestHandler_HTTP(t *testing.T) { require.NoError(t, err) if *updateExpected { - var results interface{} + var results any err := json.Unmarshal(contents, &results) require.NoError(t, err) diff --git a/pkg/api/handler_overview.go b/pkg/api/handler_overview.go index 98c350dfb..817837e7d 100644 --- a/pkg/api/handler_overview.go +++ b/pkg/api/handler_overview.go @@ -232,7 +232,7 @@ func getProviders(conf static.Configuration) []string { if !field.IsNil() { providers = append(providers, v.Type().Field(i).Name) } - } else if field.Kind() == reflect.Map && field.Type().Elem() == reflect.TypeOf(static.PluginConf{}) { + } else if field.Kind() == reflect.Map && field.Type().Elem() == reflect.TypeFor[static.PluginConf]() { for _, value := range field.MapKeys() { providers = append(providers, "plugin-"+value.String()) } diff --git a/pkg/api/handler_overview_test.go b/pkg/api/handler_overview_test.go index 422c35f21..4234f383b 100644 --- a/pkg/api/handler_overview_test.go +++ b/pkg/api/handler_overview_test.go @@ -243,7 +243,7 @@ func TestHandler_Overview(t *testing.T) { Rest: &rest.Provider{}, Rancher: &rancher.Provider{}, Plugin: map[string]static.PluginConf{ - "test": map[string]interface{}{}, + "test": map[string]any{}, }, }, }, @@ -298,7 +298,7 @@ func TestHandler_Overview(t *testing.T) { require.NoError(t, err) if *updateExpected { - var results interface{} + var results any err := json.Unmarshal(contents, &results) require.NoError(t, err) diff --git a/pkg/api/handler_tcp.go b/pkg/api/handler_tcp.go index b738bf953..dec6cfb7e 100644 --- a/pkg/api/handler_tcp.go +++ b/pkg/api/handler_tcp.go @@ -16,6 +16,7 @@ import ( type tcpRouterRepresentation struct { *runtime.TCPRouterInfo + Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` } @@ -30,6 +31,7 @@ func newTCPRouterRepresentation(name string, rt *runtime.TCPRouterInfo) tcpRoute type tcpServiceRepresentation struct { *runtime.TCPServiceInfo + Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` Type string `json:"type,omitempty"` @@ -46,6 +48,7 @@ func newTCPServiceRepresentation(name string, si *runtime.TCPServiceInfo) tcpSer type tcpMiddlewareRepresentation struct { *runtime.TCPMiddlewareInfo + Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` Type string `json:"type,omitempty"` diff --git a/pkg/api/handler_tcp_test.go b/pkg/api/handler_tcp_test.go index 7566e4dd5..3bf7752a5 100644 --- a/pkg/api/handler_tcp_test.go +++ b/pkg/api/handler_tcp_test.go @@ -821,7 +821,7 @@ func TestHandler_TCP(t *testing.T) { require.NoError(t, err) if *updateExpected { - var results interface{} + var results any err := json.Unmarshal(contents, &results) require.NoError(t, err) diff --git a/pkg/api/handler_test.go b/pkg/api/handler_test.go index b2d5395ca..2485e35ae 100644 --- a/pkg/api/handler_test.go +++ b/pkg/api/handler_test.go @@ -178,7 +178,7 @@ func TestHandler_GetMiddleware(t *testing.T) { middlewareName string conf runtime.Configuration expectedStatus int - expected interface{} + expected any }{ { desc: "Middleware not found", diff --git a/pkg/api/handler_udp.go b/pkg/api/handler_udp.go index c3fe44a8a..271975cfe 100644 --- a/pkg/api/handler_udp.go +++ b/pkg/api/handler_udp.go @@ -16,6 +16,7 @@ import ( type udpRouterRepresentation struct { *runtime.UDPRouterInfo + Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` } @@ -30,6 +31,7 @@ func newUDPRouterRepresentation(name string, rt *runtime.UDPRouterInfo) udpRoute type udpServiceRepresentation struct { *runtime.UDPServiceInfo + Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` Type string `json:"type,omitempty"` diff --git a/pkg/api/handler_udp_test.go b/pkg/api/handler_udp_test.go index 04429fc95..0b91a6521 100644 --- a/pkg/api/handler_udp_test.go +++ b/pkg/api/handler_udp_test.go @@ -560,7 +560,7 @@ func TestHandler_UDP(t *testing.T) { require.NoError(t, err) if *updateExpected { - var results interface{} + var results any err := json.Unmarshal(contents, &results) require.NoError(t, err) diff --git a/pkg/cli/loader_file.go b/pkg/cli/loader_file.go index f01eb763f..e8fbb3ace 100644 --- a/pkg/cli/loader_file.go +++ b/pkg/cli/loader_file.go @@ -63,7 +63,7 @@ func (f *FileLoader) Load(args []string, cmd *cli.Command) (bool, error) { // loadConfigFiles tries to decode the given configuration file and all default locations for the configuration file. // It stops as soon as decoding one of them is successful. -func loadConfigFiles(configFile string, element interface{}) (string, error) { +func loadConfigFiles(configFile string, element any) (string, error) { finder := cli.Finder{ BasePaths: []string{"/etc/traefik/traefik", "$XDG_CONFIG_HOME/traefik", "$HOME/.config/traefik", "./traefik"}, Extensions: []string{"toml", "yaml", "yml"}, diff --git a/pkg/collector/hydratation/hydration.go b/pkg/collector/hydratation/hydration.go index c45cfcfbb..eff17ae58 100644 --- a/pkg/collector/hydratation/hydration.go +++ b/pkg/collector/hydratation/hydration.go @@ -18,7 +18,7 @@ const ( ) // Hydrate hydrates a configuration. -func Hydrate(element interface{}) error { +func Hydrate(element any) error { field := reflect.ValueOf(element) return fill(field) } @@ -55,7 +55,7 @@ func fill(field reflect.Value) error { setTyped(field, int32(defaultNumber)) case reflect.Int64: switch field.Type() { - case reflect.TypeOf(types.Duration(time.Second)): + case reflect.TypeFor[types.Duration](): setTyped(field, types.Duration(defaultNumber*time.Second)) default: setTyped(field, int64(defaultNumber)) @@ -81,7 +81,7 @@ func fill(field reflect.Value) error { return nil } -func setTyped(field reflect.Value, i interface{}) { +func setTyped(field reflect.Value, i any) { baseValue := reflect.ValueOf(i) if field.Kind().String() == field.Type().String() { field.Set(baseValue) diff --git a/pkg/config/dynamic/config_test.go b/pkg/config/dynamic/config_test.go index 4a04509c4..368a211da 100644 --- a/pkg/config/dynamic/config_test.go +++ b/pkg/config/dynamic/config_test.go @@ -21,7 +21,7 @@ func TestDeepCopy(t *testing.T) { cfgDeepCopy := cfg.DeepCopy() assert.NotEqual(t, reflect.ValueOf(cfgDeepCopy), reflect.ValueOf(cfg)) - assert.Equal(t, reflect.TypeOf(cfgDeepCopy), reflect.TypeOf(cfg)) + assert.Equal(t, reflect.TypeOf(cfgDeepCopy), reflect.TypeOf(cfg)) //nolint:modernize // Comparing runtime types of two values. assert.Equal(t, cfgDeepCopy, cfg) // Update cfg @@ -32,6 +32,6 @@ func TestDeepCopy(t *testing.T) { assert.Equal(t, cfgCopy, cfg) assert.NotEqual(t, reflect.ValueOf(cfgDeepCopy), reflect.ValueOf(cfg)) - assert.Equal(t, reflect.TypeOf(cfgDeepCopy), reflect.TypeOf(cfg)) + assert.Equal(t, reflect.TypeOf(cfgDeepCopy), reflect.TypeOf(cfg)) //nolint:modernize // Comparing runtime types of two values. assert.NotEqual(t, cfgDeepCopy, cfg) } diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index bb5f95f9b..8af6944a0 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -389,6 +389,7 @@ func (s *IPStrategy) Get() (ip.Strategy, error) { // IPWhiteList holds the IP whitelist middleware configuration. // This middleware limits allowed requests based on the client IP. // More info: https://doc.traefik.io/traefik/v2.11/middlewares/http/ipwhitelist/ +// // Deprecated: please use IPAllowList instead. type IPWhiteList struct { // SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation). Required. diff --git a/pkg/config/dynamic/plugins_test.go b/pkg/config/dynamic/plugins_test.go index 6021362dd..83b05dcc6 100644 --- a/pkg/config/dynamic/plugins_test.go +++ b/pkg/config/dynamic/plugins_test.go @@ -44,11 +44,11 @@ func TestPluginConf_DeepCopy_mapOfStruct(t *testing.T) { } func TestPluginConf_DeepCopy_map(t *testing.T) { - m := map[string]interface{}{ + m := map[string]any{ "name": "bar", } p := PluginConf{ - "config": map[string]interface{}{ + "config": map[string]any{ "foo": m, }, } @@ -64,7 +64,7 @@ func TestPluginConf_DeepCopy_map(t *testing.T) { func TestPluginConf_DeepCopy_panic(t *testing.T) { p := &PluginConf{ - "config": map[string]interface{}{ + "config": map[string]any{ "foo": &Foo{Name: "gigi"}, }, } diff --git a/pkg/config/dynamic/tcp_middlewares.go b/pkg/config/dynamic/tcp_middlewares.go index e98390c64..1fc80c44a 100644 --- a/pkg/config/dynamic/tcp_middlewares.go +++ b/pkg/config/dynamic/tcp_middlewares.go @@ -26,6 +26,7 @@ type TCPInFlightConn struct { // TCPIPWhiteList holds the TCP IPWhiteList middleware configuration. // This middleware limits allowed requests based on the client IP. // More info: https://doc.traefik.io/traefik/v2.11/middlewares/tcp/ipwhitelist/ +// // Deprecated: please use IPAllowList instead. type TCPIPWhiteList struct { // SourceRange defines the allowed IPs (or ranges of allowed IPs by using CIDR notation). diff --git a/pkg/config/kv/kv.go b/pkg/config/kv/kv.go index ad467b00d..eb71dee0c 100644 --- a/pkg/config/kv/kv.go +++ b/pkg/config/kv/kv.go @@ -13,7 +13,7 @@ import ( // KV pairs -> tree of untyped nodes // untyped nodes -> nodes augmented with metadata such as kind (inferred from element) // "typed" nodes -> typed element. -func Decode(pairs []*store.KVPair, element interface{}, rootName string) error { +func Decode(pairs []*store.KVPair, element any, rootName string) error { if element == nil { return nil } @@ -34,7 +34,7 @@ func Decode(pairs []*store.KVPair, element interface{}, rootName string) error { return parser.Fill(element, node, parser.FillerOpts{AllowSliceAsStruct: false}) } -func getRootFieldNames(rootName string, element interface{}) []string { +func getRootFieldNames(rootName string, element any) []string { if element == nil { return nil } diff --git a/pkg/config/label/label.go b/pkg/config/label/label.go index 0222f9a14..66822f1f0 100644 --- a/pkg/config/label/label.go +++ b/pkg/config/label/label.go @@ -31,6 +31,6 @@ func EncodeConfiguration(conf *dynamic.Configuration) (map[string]string, error) // Decode converts the labels to an element. // labels -> [ node -> node + metadata (type) ] -> element (node). -func Decode(labels map[string]string, element interface{}, filters ...string) error { +func Decode(labels map[string]string, element any, filters ...string) error { return parser.Decode(labels, element, parser.DefaultRootName, filters...) } diff --git a/pkg/config/runtime/runtime_http.go b/pkg/config/runtime/runtime_http.go index 6f78abf25..24d3878b6 100644 --- a/pkg/config/runtime/runtime_http.go +++ b/pkg/config/runtime/runtime_http.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "maps" "slices" "sort" "sync" @@ -72,6 +73,7 @@ func unique(src []string) []string { // RouterInfo holds information about a currently running HTTP router. type RouterInfo struct { *dynamic.Router // dynamic configuration + // Err contains all the errors that occurred during router's creation. Err []string `json:"error,omitempty"` // Status reports whether the router is disabled, in a warning state, or all good (enabled). @@ -84,10 +86,8 @@ type RouterInfo struct { // AddError adds err to r.Err, if it does not already exist. // If critical is set, r is marked as disabled. func (r *RouterInfo) AddError(err error, critical bool) { - for _, value := range r.Err { - if value == err.Error() { - return - } + if slices.Contains(r.Err, err.Error()) { + return } r.Err = append(r.Err, err.Error()) @@ -105,6 +105,7 @@ func (r *RouterInfo) AddError(err error, critical bool) { // MiddlewareInfo holds information about a currently running middleware. type MiddlewareInfo struct { *dynamic.Middleware // dynamic configuration + // Err contains all the errors that occurred during service creation. Err []string `json:"error,omitempty"` Status string `json:"status,omitempty"` @@ -114,10 +115,8 @@ type MiddlewareInfo struct { // AddError adds err to s.Err, if it does not already exist. // If critical is set, m is marked as disabled. func (m *MiddlewareInfo) AddError(err error, critical bool) { - for _, value := range m.Err { - if value == err.Error() { - return - } + if slices.Contains(m.Err, err.Error()) { + return } m.Err = append(m.Err, err.Error()) @@ -135,6 +134,7 @@ func (m *MiddlewareInfo) AddError(err error, critical bool) { // ServiceInfo holds information about a currently running service. type ServiceInfo struct { *dynamic.Service // dynamic configuration + // Err contains all the errors that occurred during service creation. Err []string `json:"error,omitempty"` // Status reports whether the service is disabled, in a warning state, or all good (enabled). @@ -150,10 +150,8 @@ type ServiceInfo struct { // AddError adds err to s.Err, if it does not already exist. // If critical is set, s is marked as disabled. func (s *ServiceInfo) AddError(err error, critical bool) { - for _, value := range s.Err { - if value == err.Error() { - return - } + if slices.Contains(s.Err, err.Error()) { + return } s.Err = append(s.Err, err.Error()) @@ -190,9 +188,5 @@ func (s *ServiceInfo) GetAllStatus() map[string]string { return nil } - allStatus := make(map[string]string, len(s.serverStatus)) - for k, v := range s.serverStatus { - allStatus[k] = v - } - return allStatus + return maps.Clone(s.serverStatus) } diff --git a/pkg/config/runtime/runtime_tcp.go b/pkg/config/runtime/runtime_tcp.go index 4dd70dedf..825573c3f 100644 --- a/pkg/config/runtime/runtime_tcp.go +++ b/pkg/config/runtime/runtime_tcp.go @@ -47,8 +47,9 @@ func (c *Configuration) GetTCPRoutersByEntryPoints(ctx context.Context, entryPoi // TCPRouterInfo holds information about a currently running TCP router. type TCPRouterInfo struct { - *dynamic.TCPRouter // dynamic configuration - Err []string `json:"error,omitempty"` // initialization error + *dynamic.TCPRouter // dynamic configuration + + Err []string `json:"error,omitempty"` // initialization error // Status reports whether the router is disabled, in a warning state, or all good (enabled). // If not in "enabled" state, the reason for it should be in the list of Err. // It is the caller's responsibility to set the initial status. @@ -59,10 +60,8 @@ type TCPRouterInfo struct { // AddError adds err to r.Err, if it does not already exist. // If critical is set, r is marked as disabled. func (r *TCPRouterInfo) AddError(err error, critical bool) { - for _, value := range r.Err { - if value == err.Error() { - return - } + if slices.Contains(r.Err, err.Error()) { + return } r.Err = append(r.Err, err.Error()) @@ -79,8 +78,9 @@ func (r *TCPRouterInfo) AddError(err error, critical bool) { // TCPServiceInfo holds information about a currently running TCP service. type TCPServiceInfo struct { - *dynamic.TCPService // dynamic configuration - Err []string `json:"error,omitempty"` // initialization error + *dynamic.TCPService // dynamic configuration + + Err []string `json:"error,omitempty"` // initialization error // Status reports whether the service is disabled, in a warning state, or all good (enabled). // If not in "enabled" state, the reason for it should be in the list of Err. // It is the caller's responsibility to set the initial status. @@ -91,10 +91,8 @@ type TCPServiceInfo struct { // AddError adds err to s.Err, if it does not already exist. // If critical is set, s is marked as disabled. func (s *TCPServiceInfo) AddError(err error, critical bool) { - for _, value := range s.Err { - if value == err.Error() { - return - } + if slices.Contains(s.Err, err.Error()) { + return } s.Err = append(s.Err, err.Error()) @@ -112,6 +110,7 @@ func (s *TCPServiceInfo) AddError(err error, critical bool) { // TCPMiddlewareInfo holds information about a currently running middleware. type TCPMiddlewareInfo struct { *dynamic.TCPMiddleware // dynamic configuration + // Err contains all the errors that occurred during service creation. Err []string `json:"error,omitempty"` Status string `json:"status,omitempty"` @@ -121,10 +120,8 @@ type TCPMiddlewareInfo struct { // AddError adds err to s.Err, if it does not already exist. // If critical is set, m is marked as disabled. func (m *TCPMiddlewareInfo) AddError(err error, critical bool) { - for _, value := range m.Err { - if value == err.Error() { - return - } + if slices.Contains(m.Err, err.Error()) { + return } m.Err = append(m.Err, err.Error()) diff --git a/pkg/config/runtime/runtime_udp.go b/pkg/config/runtime/runtime_udp.go index 7a6dfde33..5fb7ee83b 100644 --- a/pkg/config/runtime/runtime_udp.go +++ b/pkg/config/runtime/runtime_udp.go @@ -53,8 +53,9 @@ func (c *Configuration) GetUDPRoutersByEntryPoints(ctx context.Context, entryPoi // UDPRouterInfo holds information about a currently running UDP router. type UDPRouterInfo struct { - *dynamic.UDPRouter // dynamic configuration - Err []string `json:"error,omitempty"` // initialization error + *dynamic.UDPRouter // dynamic configuration + + Err []string `json:"error,omitempty"` // initialization error // Status reports whether the router is disabled, in a warning state, or all good (enabled). // If not in "enabled" state, the reason for it should be in the list of Err. // It is the caller's responsibility to set the initial status. @@ -65,10 +66,8 @@ type UDPRouterInfo struct { // AddError adds err to r.Err, if it does not already exist. // If critical is set, r is marked as disabled. func (r *UDPRouterInfo) AddError(err error, critical bool) { - for _, value := range r.Err { - if value == err.Error() { - return - } + if slices.Contains(r.Err, err.Error()) { + return } r.Err = append(r.Err, err.Error()) @@ -85,8 +84,9 @@ func (r *UDPRouterInfo) AddError(err error, critical bool) { // UDPServiceInfo holds information about a currently running UDP service. type UDPServiceInfo struct { - *dynamic.UDPService // dynamic configuration - Err []string `json:"error,omitempty"` // initialization error + *dynamic.UDPService // dynamic configuration + + Err []string `json:"error,omitempty"` // initialization error // Status reports whether the service is disabled, in a warning state, or all good (enabled). // If not in "enabled" state, the reason for it should be in the list of Err. // It is the caller's responsibility to set the initial status. @@ -97,10 +97,8 @@ type UDPServiceInfo struct { // AddError adds err to s.Err, if it does not already exist. // If critical is set, s is marked as disabled. func (s *UDPServiceInfo) AddError(err error, critical bool) { - for _, value := range s.Err { - if value == err.Error() { - return - } + if slices.Contains(s.Err, err.Error()) { + return } s.Err = append(s.Err, err.Error()) diff --git a/pkg/config/static/plugins.go b/pkg/config/static/plugins.go index 5b2577e4e..ddf2f9cc1 100644 --- a/pkg/config/static/plugins.go +++ b/pkg/config/static/plugins.go @@ -1,4 +1,4 @@ package static // PluginConf holds the plugin configuration. -type PluginConf map[string]interface{} +type PluginConf map[string]any diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 30af400cb..d1e400411 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -295,20 +295,6 @@ func (c *Configuration) SetEffectiveConfiguration() { c.initACMEProvider() } -func (c *Configuration) hasUserDefinedEntrypoint() bool { - return len(c.EntryPoints) != 0 -} - -func (c *Configuration) initACMEProvider() { - for _, resolver := range c.CertificatesResolvers { - if resolver.ACME != nil { - resolver.ACME.CAServer = getSafeACMECAServer(resolver.ACME.CAServer) - } - } - - legolog.Logger = stdlog.New(log.WithoutContext().WriterLevel(logrus.DebugLevel), "legolog: ", 0) -} - // ValidateConfiguration validate that configuration is coherent. func (c *Configuration) ValidateConfiguration() error { var acmeEmail string @@ -342,6 +328,20 @@ func (c *Configuration) ValidateConfiguration() error { return nil } +func (c *Configuration) hasUserDefinedEntrypoint() bool { + return len(c.EntryPoints) != 0 +} + +func (c *Configuration) initACMEProvider() { + for _, resolver := range c.CertificatesResolvers { + if resolver.ACME != nil { + resolver.ACME.CAServer = getSafeACMECAServer(resolver.ACME.CAServer) + } + } + + legolog.Logger = stdlog.New(log.WithoutContext().WriterLevel(logrus.DebugLevel), "legolog: ", 0) +} + func getSafeACMECAServer(caServerSrc string) string { if len(caServerSrc) == 0 { return DefaultAcmeCAServer diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go index 8bd8d1e17..576a76392 100644 --- a/pkg/healthcheck/healthcheck.go +++ b/pkg/healthcheck/healthcheck.go @@ -82,10 +82,19 @@ type backendURL struct { // BackendConfig HealthCheck configuration for a backend. type BackendConfig struct { Options + name string disabledURLs []backendURL } +// NewBackendConfig Instantiate a new BackendConfig. +func NewBackendConfig(options Options, backendName string) *BackendConfig { + return &BackendConfig{ + Options: options, + name: backendName, + } +} + func (b *BackendConfig) newRequest(serverURL *url.URL) (*http.Request, error) { u, err := serverURL.Parse(b.Path) if err != nil { @@ -236,14 +245,6 @@ func newHealthCheck(registry metrics.Registry) *HealthCheck { } } -// NewBackendConfig Instantiate a new BackendConfig. -func NewBackendConfig(options Options, backendName string) *BackendConfig { - return &BackendConfig{ - Options: options, - name: backendName, - } -} - // checkHealth returns a nil error in case it was successful and otherwise // a non-nil error with a meaningful description why the health check failed. func checkHealth(serverURL *url.URL, backend *BackendConfig) error { @@ -286,6 +287,16 @@ type StatusUpdater interface { RegisterStatusUpdater(fn func(up bool)) error } +// LbStatusUpdater wraps a BalancerHandler and a ServiceInfo, +// so it can keep track of the status of a server in the ServiceInfo. +type LbStatusUpdater struct { + BalancerHandler + + serviceInfo *runtime.ServiceInfo // can be nil + updaters []func(up bool) + wantsHealthCheck bool +} + // NewLBStatusUpdater returns a new LbStatusUpdater. func NewLBStatusUpdater(bh BalancerHandler, info *runtime.ServiceInfo, hc *dynamic.ServerHealthCheck) *LbStatusUpdater { return &LbStatusUpdater{ @@ -295,15 +306,6 @@ func NewLBStatusUpdater(bh BalancerHandler, info *runtime.ServiceInfo, hc *dynam } } -// LbStatusUpdater wraps a BalancerHandler and a ServiceInfo, -// so it can keep track of the status of a server in the ServiceInfo. -type LbStatusUpdater struct { - BalancerHandler - serviceInfo *runtime.ServiceInfo // can be nil - updaters []func(up bool) - wantsHealthCheck bool -} - // RegisterStatusUpdater adds fn to the list of hooks that are run when the // status of the Balancer changes. // Not thread safe. diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go index e90d05526..1ce6b9469 100644 --- a/pkg/healthcheck/healthcheck_test.go +++ b/pkg/healthcheck/healthcheck_test.go @@ -456,6 +456,7 @@ type testLoadBalancer struct { // RWMutex needed due to parallel test execution: Both the system-under-test // and the test assertions reference the counters. *sync.RWMutex + numRemovedServers int numUpsertedServers int servers []*url.URL diff --git a/pkg/job/job.go b/pkg/job/job.go index 752f7b905..c16146fca 100644 --- a/pkg/job/job.go +++ b/pkg/job/job.go @@ -17,6 +17,7 @@ const ( // If operation() takes more than MinJobInterval, Reset() is called in NextBackOff(). type BackOff struct { *backoff.ExponentialBackOff + MinJobInterval time.Duration } diff --git a/pkg/log/deprecated.go b/pkg/log/deprecated.go index 3bde3f841..94d8cafdb 100644 --- a/pkg/log/deprecated.go +++ b/pkg/log/deprecated.go @@ -9,68 +9,79 @@ import ( ) // Debug logs a message at level Debug on the standard logger. +// // Deprecated: use log.FromContext(ctx).Debug(...) instead. -func Debug(args ...interface{}) { +func Debug(args ...any) { mainLogger.Debug(args...) } // Debugf logs a message at level Debug on the standard logger. +// // Deprecated: use log.FromContext(ctx).Debugf(...) instead. -func Debugf(format string, args ...interface{}) { +func Debugf(format string, args ...any) { mainLogger.Debugf(format, args...) } // Info logs a message at level Info on the standard logger. +// // Deprecated: use log.FromContext(ctx).Info(...) instead. -func Info(args ...interface{}) { +func Info(args ...any) { mainLogger.Info(args...) } // Infof logs a message at level Info on the standard logger. +// // Deprecated: use log.FromContext(ctx).Infof(...) instead. -func Infof(format string, args ...interface{}) { +func Infof(format string, args ...any) { mainLogger.Infof(format, args...) } // Warn logs a message at level Warn on the standard logger. +// // Deprecated: use log.FromContext(ctx).Warn(...) instead. -func Warn(args ...interface{}) { +func Warn(args ...any) { mainLogger.Warn(args...) } // Warnf logs a message at level Warn on the standard logger. +// // Deprecated: use log.FromContext(ctx).Warnf(...) instead. -func Warnf(format string, args ...interface{}) { +func Warnf(format string, args ...any) { mainLogger.Warnf(format, args...) } // Error logs a message at level Error on the standard logger. +// // Deprecated: use log.FromContext(ctx).Error(...) instead. -func Error(args ...interface{}) { +func Error(args ...any) { mainLogger.Error(args...) } // Errorf logs a message at level Error on the standard logger. +// // Deprecated: use log.FromContext(ctx).Errorf(...) instead. -func Errorf(format string, args ...interface{}) { +func Errorf(format string, args ...any) { mainLogger.Errorf(format, args...) } // Panic logs a message at level Panic on the standard logger. +// // Deprecated: use log.FromContext(ctx).Panic(...) instead. -func Panic(args ...interface{}) { +func Panic(args ...any) { mainLogger.Panic(args...) } // Fatal logs a message at level Fatal on the standard logger. +// // Deprecated: use log.FromContext(ctx).Fatal(...) instead. -func Fatal(args ...interface{}) { +func Fatal(args ...any) { mainLogger.Fatal(args...) } // Fatalf logs a message at level Fatal on the standard logger. +// // Deprecated: use log.FromContext(ctx).Fatalf(...) instead. -func Fatalf(format string, args ...interface{}) { +func Fatalf(format string, args ...any) { mainLogger.Fatalf(format, args...) } @@ -84,7 +95,7 @@ func AddHook(hook logrus.Hook) { func CustomWriterLevel(level logrus.Level, maxScanTokenSize int) *io.PipeWriter { reader, writer := io.Pipe() - var printFunc func(args ...interface{}) + var printFunc func(args ...any) switch level { case logrus.DebugLevel: @@ -111,7 +122,7 @@ func CustomWriterLevel(level logrus.Level, maxScanTokenSize int) *io.PipeWriter // extract from github.com/Sirupsen/logrus/writer.go // Hack the buffer size. -func writerScanner(reader io.ReadCloser, scanTokenSize int, printFunc func(args ...interface{})) { +func writerScanner(reader io.ReadCloser, scanTokenSize int, printFunc func(args ...any)) { scanner := bufio.NewScanner(reader) if scanTokenSize > bufio.MaxScanTokenSize { diff --git a/pkg/metrics/datadog.go b/pkg/metrics/datadog.go index 10676e789..4262e8e19 100644 --- a/pkg/metrics/datadog.go +++ b/pkg/metrics/datadog.go @@ -58,7 +58,7 @@ func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry { config.Prefix = defaultMetricsPrefix } - datadogClient = dogstatsd.New(config.Prefix+".", kitlog.LoggerFunc(func(keyvals ...interface{}) error { + datadogClient = dogstatsd.New(config.Prefix+".", kitlog.LoggerFunc(func(keyvals ...any) error { log.WithoutContext().WithField(log.MetricsProviderName, "datadog").Info(keyvals...) return nil })) diff --git a/pkg/metrics/headers.go b/pkg/metrics/headers.go index 65443cd29..ed165ae96 100644 --- a/pkg/metrics/headers.go +++ b/pkg/metrics/headers.go @@ -36,16 +36,16 @@ func (c MultiCounterWithHeaders) With(headers http.Header, labelValues ...string return next } -// NewCounterWithNoopHeaders returns a CounterWithNoopHeaders. -func NewCounterWithNoopHeaders(counter metrics.Counter) CounterWithNoopHeaders { - return CounterWithNoopHeaders{counter: counter} -} - // CounterWithNoopHeaders is a counter that satisfies CounterWithHeaders but ignores the given http.Header. type CounterWithNoopHeaders struct { counter metrics.Counter } +// NewCounterWithNoopHeaders returns a CounterWithNoopHeaders. +func NewCounterWithNoopHeaders(counter metrics.Counter) CounterWithNoopHeaders { + return CounterWithNoopHeaders{counter: counter} +} + // Add adds the given delta value to the counter value. func (c CounterWithNoopHeaders) Add(delta float64) { c.counter.Add(delta) diff --git a/pkg/metrics/influxdb.go b/pkg/metrics/influxdb.go index 3690a40cb..5b375627e 100644 --- a/pkg/metrics/influxdb.go +++ b/pkg/metrics/influxdb.go @@ -147,7 +147,7 @@ func initInfluxDBClient(ctx context.Context, config *types.InfluxDB) *influx.Inf Database: config.Database, RetentionPolicy: config.RetentionPolicy, }, - kitlog.LoggerFunc(func(keyvals ...interface{}) error { + kitlog.LoggerFunc(func(keyvals ...any) error { log.WithoutContext().WithField(log.MetricsProviderName, "influxdb").Info(keyvals...) return nil })) diff --git a/pkg/metrics/influxdb2.go b/pkg/metrics/influxdb2.go index bb184c893..15272302f 100644 --- a/pkg/metrics/influxdb2.go +++ b/pkg/metrics/influxdb2.go @@ -37,7 +37,7 @@ func RegisterInfluxDB2(ctx context.Context, config *types.InfluxDB2) Registry { influxDB2Store = influx.New( config.AdditionalLabels, influxdb.BatchPointsConfig{}, - kitlog.LoggerFunc(func(kv ...interface{}) error { + kitlog.LoggerFunc(func(kv ...any) error { log.FromContext(ctx).Error(kv...) return nil }), diff --git a/pkg/metrics/statsd.go b/pkg/metrics/statsd.go index 424b12bb7..f55307422 100644 --- a/pkg/metrics/statsd.go +++ b/pkg/metrics/statsd.go @@ -55,7 +55,7 @@ func RegisterStatsd(ctx context.Context, config *types.Statsd) Registry { config.Prefix = defaultMetricsPrefix } - statsdClient = statsd.New(config.Prefix+".", kitlog.LoggerFunc(func(keyvals ...interface{}) error { + statsdClient = statsd.New(config.Prefix+".", kitlog.LoggerFunc(func(keyvals ...any) error { log.WithoutContext().WithField(log.MetricsProviderName, "statsd").Info(keyvals...) return nil })) diff --git a/pkg/middlewares/accesslog/logdata.go b/pkg/middlewares/accesslog/logdata.go index 1db168446..5e4f37b28 100644 --- a/pkg/middlewares/accesslog/logdata.go +++ b/pkg/middlewares/accesslog/logdata.go @@ -121,7 +121,7 @@ func init() { } // CoreLogData holds the fields computed from the request/response. -type CoreLogData map[string]interface{} +type CoreLogData map[string]any // LogData is the data captured by the middleware so that it can be logged. type LogData struct { diff --git a/pkg/middlewares/accesslog/logger_formatters.go b/pkg/middlewares/accesslog/logger_formatters.go index c714ef146..7524944e9 100644 --- a/pkg/middlewares/accesslog/logger_formatters.go +++ b/pkg/middlewares/accesslog/logger_formatters.go @@ -52,7 +52,7 @@ func (f *CommonLogFormatter) Format(entry *logrus.Entry) ([]byte, error) { return b.Bytes(), err } -func toLog(fields logrus.Fields, key, defaultValue string, quoted bool) interface{} { +func toLog(fields logrus.Fields, key, defaultValue string, quoted bool) any { if v, ok := fields[key]; ok { if v == nil { return defaultValue diff --git a/pkg/middlewares/accesslog/logger_formatters_test.go b/pkg/middlewares/accesslog/logger_formatters_test.go index 028e3dbf7..e536954f8 100644 --- a/pkg/middlewares/accesslog/logger_formatters_test.go +++ b/pkg/middlewares/accesslog/logger_formatters_test.go @@ -14,12 +14,12 @@ func TestCommonLogFormatter_Format(t *testing.T) { testCases := []struct { name string - data map[string]interface{} + data map[string]any expectedLog string }{ { name: "DownstreamStatus & DownstreamContentSize are nil", - data: map[string]interface{}{ + data: map[string]any{ StartUTC: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), Duration: 123 * time.Second, ClientHost: "10.0.0.1", @@ -40,7 +40,7 @@ func TestCommonLogFormatter_Format(t *testing.T) { }, { name: "all data", - data: map[string]interface{}{ + data: map[string]any{ StartUTC: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), Duration: 123 * time.Second, ClientHost: "10.0.0.1", @@ -61,7 +61,7 @@ func TestCommonLogFormatter_Format(t *testing.T) { }, { name: "all data with local time", - data: map[string]interface{}{ + data: map[string]any{ StartLocal: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), Duration: 123 * time.Second, ClientHost: "10.0.0.1", @@ -106,7 +106,7 @@ func Test_toLog(t *testing.T) { fieldName string defaultValue string quoted bool - expectedLog interface{} + expectedLog any }{ { desc: "Should return int 1", diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index 634ed72c7..43803ca65 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -116,7 +116,7 @@ func lineCount(t *testing.T, fileName string) int { } count := 0 - for _, line := range strings.Split(string(fileContents), "\n") { + for line := range strings.SplitSeq(string(fileContents), "\n") { if strings.TrimSpace(line) == "" { continue } @@ -259,32 +259,32 @@ func TestLoggerCLFWithBufferingSize(t *testing.T) { assertValidLogData(t, expectedLog, logData) } -func assertString(exp string) func(t *testing.T, actual interface{}) { - return func(t *testing.T, actual interface{}) { +func assertString(exp string) func(t *testing.T, actual any) { + return func(t *testing.T, actual any) { t.Helper() assert.Equal(t, exp, actual) } } -func assertNotEmpty() func(t *testing.T, actual interface{}) { - return func(t *testing.T, actual interface{}) { +func assertNotEmpty() func(t *testing.T, actual any) { + return func(t *testing.T, actual any) { t.Helper() assert.NotEmpty(t, actual) } } -func assertFloat64(exp float64) func(t *testing.T, actual interface{}) { - return func(t *testing.T, actual interface{}) { +func assertFloat64(exp float64) func(t *testing.T, actual any) { + return func(t *testing.T, actual any) { t.Helper() assert.InDelta(t, exp, actual, delta) } } -func assertFloat64NotZero() func(t *testing.T, actual interface{}) { - return func(t *testing.T, actual interface{}) { +func assertFloat64NotZero() func(t *testing.T, actual any) { + return func(t *testing.T, actual any) { t.Helper() assert.NotZero(t, actual) @@ -296,7 +296,7 @@ func TestLoggerJSON(t *testing.T) { desc string config *types.AccessLog tls bool - expected map[string]func(t *testing.T, value interface{}) + expected map[string]func(t *testing.T, value any) }{ { desc: "default config", @@ -304,7 +304,7 @@ func TestLoggerJSON(t *testing.T) { FilePath: "", Format: JSONFormat, }, - expected: map[string]func(t *testing.T, value interface{}){ + expected: map[string]func(t *testing.T, value any){ RequestContentSize: assertFloat64(0), RequestHost: assertString(testHostname), RequestAddr: assertString(testHostname), @@ -344,7 +344,7 @@ func TestLoggerJSON(t *testing.T) { Format: JSONFormat, }, tls: true, - expected: map[string]func(t *testing.T, value interface{}){ + expected: map[string]func(t *testing.T, value any){ RequestContentSize: assertFloat64(0), RequestHost: assertString(testHostname), RequestAddr: assertString(testHostname), @@ -388,7 +388,7 @@ func TestLoggerJSON(t *testing.T) { DefaultMode: "drop", }, }, - expected: map[string]func(t *testing.T, value interface{}){ + expected: map[string]func(t *testing.T, value any){ "level": assertString("info"), "msg": assertString(""), "time": assertNotEmpty(), @@ -409,7 +409,7 @@ func TestLoggerJSON(t *testing.T) { }, }, }, - expected: map[string]func(t *testing.T, value interface{}){ + expected: map[string]func(t *testing.T, value any){ "level": assertString("info"), "msg": assertString(""), "time": assertNotEmpty(), @@ -427,7 +427,7 @@ func TestLoggerJSON(t *testing.T) { }, }, }, - expected: map[string]func(t *testing.T, value interface{}){ + expected: map[string]func(t *testing.T, value any){ "level": assertString("info"), "msg": assertString(""), "time": assertNotEmpty(), @@ -454,7 +454,7 @@ func TestLoggerJSON(t *testing.T) { }, }, }, - expected: map[string]func(t *testing.T, value interface{}){ + expected: map[string]func(t *testing.T, value any){ RequestHost: assertString(testHostname), "level": assertString("info"), "msg": assertString(""), @@ -480,7 +480,7 @@ func TestLoggerJSON(t *testing.T) { }, }, }, - expected: map[string]func(t *testing.T, value interface{}){ + expected: map[string]func(t *testing.T, value any){ RequestHost: assertString(testHostname), "level": assertString("info"), "msg": assertString(""), @@ -506,7 +506,7 @@ func TestLoggerJSON(t *testing.T) { logData, err := os.ReadFile(logFilePath) require.NoError(t, err) - jsonData := make(map[string]interface{}) + jsonData := make(map[string]any) err = json.Unmarshal(logData, &jsonData) require.NoError(t, err) @@ -520,7 +520,7 @@ func TestLoggerJSON(t *testing.T) { } func TestLogger_AbortedRequest(t *testing.T) { - expected := map[string]func(t *testing.T, value interface{}){ + expected := map[string]func(t *testing.T, value any){ RequestContentSize: assertFloat64(0), RequestHost: assertString(testHostname), RequestAddr: assertString(testHostname), @@ -563,7 +563,7 @@ func TestLogger_AbortedRequest(t *testing.T) { logData, err := os.ReadFile(config.FilePath) require.NoError(t, err) - jsonData := make(map[string]interface{}) + jsonData := make(map[string]any) err = json.Unmarshal(logData, &jsonData) require.NoError(t, err) diff --git a/pkg/middlewares/auth/connectionheader.go b/pkg/middlewares/auth/connectionheader.go index 8b78b9430..d5d4e4653 100644 --- a/pkg/middlewares/auth/connectionheader.go +++ b/pkg/middlewares/auth/connectionheader.go @@ -22,7 +22,7 @@ func RemoveConnectionHeaders(req *http.Request) { } for _, f := range req.Header[connectionHeader] { - for _, sf := range strings.Split(f, ",") { + for sf := range strings.SplitSeq(f, ",") { if sf = textproto.TrimString(sf); sf != "" { req.Header.Del(sf) } diff --git a/pkg/middlewares/capture/capture.go b/pkg/middlewares/capture/capture.go index c541cde29..74b990157 100644 --- a/pkg/middlewares/capture/capture.go +++ b/pkg/middlewares/capture/capture.go @@ -84,20 +84,6 @@ func (c *Capture) Reset(next http.Handler) http.Handler { }) } -func (c *Capture) renew(rw http.ResponseWriter, req *http.Request) (http.ResponseWriter, *http.Request) { - ctx := context.WithValue(req.Context(), capturedData, c) - newReq := req.WithContext(ctx) - - if newReq.Body != nil { - readCounter := &readCounter{source: newReq.Body} - c.rr = readCounter - newReq.Body = readCounter - } - c.rw = newResponseWriter(rw) - - return c.rw, newReq -} - func (c *Capture) ResponseSize() int64 { return c.rw.Size() } @@ -115,6 +101,20 @@ func (c *Capture) RequestSize() int64 { return c.rr.size } +func (c *Capture) renew(rw http.ResponseWriter, req *http.Request) (http.ResponseWriter, *http.Request) { + ctx := context.WithValue(req.Context(), capturedData, c) + newReq := req.WithContext(ctx) + + if newReq.Body != nil { + readCounter := &readCounter{source: newReq.Body} + c.rr = readCounter + newReq.Body = readCounter + } + c.rw = newResponseWriter(rw) + + return c.rw, newReq +} + type readCounter struct { // source ReadCloser from where the request body is read. source io.ReadCloser diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go index bd5b78baa..3ee2ed0a2 100644 --- a/pkg/middlewares/customerrors/custom_errors.go +++ b/pkg/middlewares/customerrors/custom_errors.go @@ -4,6 +4,7 @@ import ( "bufio" "context" "fmt" + "maps" "net" "net/http" "net/url" @@ -169,16 +170,6 @@ func (cc *codeCatcher) Header() http.Header { return cc.headerMap } -func (cc *codeCatcher) getCode() int { - return cc.code -} - -// isFilteredCode returns whether the codeCatcher received a response code among the ones it is watching, -// and for which the response should be deferred to the error handler. -func (cc *codeCatcher) isFilteredCode() bool { - return cc.caughtFilteredCode -} - func (cc *codeCatcher) Write(buf []byte) (int, error) { // If WriteHeader was already called from the caller, this is a NOOP. // Otherwise, cc.code is actually a 200 here. @@ -204,9 +195,7 @@ func (cc *codeCatcher) WriteHeader(code int) { if code >= 100 && code <= 199 { // Multiple informational status codes can be used, // so here the copy is not appending the values to not repeat them. - for k, v := range cc.Header() { - cc.responseWriter.Header()[k] = v - } + maps.Copy(cc.responseWriter.Header(), cc.Header()) cc.responseWriter.WriteHeader(code) return @@ -224,9 +213,8 @@ func (cc *codeCatcher) WriteHeader(code int) { // The copy is not appending the values, // to not repeat them in case any informational status code has been written. - for k, v := range cc.Header() { - cc.responseWriter.Header()[k] = v - } + maps.Copy(cc.responseWriter.Header(), cc.Header()) + cc.responseWriter.WriteHeader(cc.code) cc.headersSent = true } @@ -259,6 +247,16 @@ func (cc *codeCatcher) Flush() { } } +func (cc *codeCatcher) getCode() int { + return cc.code +} + +// isFilteredCode returns whether the codeCatcher received a response code among the ones it is watching, +// and for which the response should be deferred to the error handler. +func (cc *codeCatcher) isFilteredCode() bool { + return cc.caughtFilteredCode +} + type codeCatcherWithCloseNotify struct { *codeCatcher } @@ -332,17 +330,14 @@ func (r *codeModifierWithoutCloseNotify) WriteHeader(code int) { if code >= 100 && code <= 199 { // Multiple informational status codes can be used, // so here the copy is not appending the values to not repeat them. - for k, v := range r.headerMap { - r.responseWriter.Header()[k] = v - } + maps.Copy(r.responseWriter.Header(), r.headerMap) r.responseWriter.WriteHeader(code) return } - for k, v := range r.headerMap { - r.responseWriter.Header()[k] = v - } + maps.Copy(r.responseWriter.Header(), r.headerMap) + r.responseWriter.WriteHeader(r.code) r.headerSent = true } diff --git a/pkg/middlewares/forwardedheaders/forwarded_header.go b/pkg/middlewares/forwardedheaders/forwarded_header.go index 4b037a089..d051a4f9a 100644 --- a/pkg/middlewares/forwardedheaders/forwarded_header.go +++ b/pkg/middlewares/forwardedheaders/forwarded_header.go @@ -81,13 +81,6 @@ func NewXForwarded(insecure bool, trustedIPs []string, connectionHeaders []strin }, nil } -func (x *XForwarded) isTrustedIP(ip string) bool { - if x.ipChecker == nil { - return false - } - return x.ipChecker.IsAuthorized(ip) == nil -} - // removeIPv6Zone removes the zone if the given IP is an ipv6 address and it has {zone} information in it, // like "[fe80::d806:a55d:eb1b:49cc%vEthernet (vmxnet3 Ethernet Adapter - Virtual Switch)]:64692". func removeIPv6Zone(clientIP string) string { @@ -138,6 +131,28 @@ func forwardedPort(req *http.Request) string { return "80" } +// ServeHTTP implements http.Handler. +func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if !x.insecure && !x.isTrustedIP(r.RemoteAddr) { + for _, h := range xHeaders { + unsafeHeader(r.Header).Del(h) + } + } + + x.rewrite(r) + + x.removeConnectionHeaders(r) + + x.next.ServeHTTP(w, r) +} + +func (x *XForwarded) isTrustedIP(ip string) bool { + if x.ipChecker == nil { + return false + } + return x.ipChecker.IsAuthorized(ip) == nil +} + func (x *XForwarded) rewrite(outreq *http.Request) { if clientIP, _, err := net.SplitHostPort(outreq.RemoteAddr); err == nil { clientIP = removeIPv6Zone(clientIP) @@ -186,21 +201,6 @@ func (x *XForwarded) rewrite(outreq *http.Request) { } } -// ServeHTTP implements http.Handler. -func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if !x.insecure && !x.isTrustedIP(r.RemoteAddr) { - for _, h := range xHeaders { - unsafeHeader(r.Header).Del(h) - } - } - - x.rewrite(r) - - x.removeConnectionHeaders(r) - - x.next.ServeHTTP(w, r) -} - func (x *XForwarded) removeConnectionHeaders(req *http.Request) { var reqUpType string if httpguts.HeaderValuesContainsToken(req.Header[connection], upgrade) { @@ -209,7 +209,7 @@ func (x *XForwarded) removeConnectionHeaders(req *http.Request) { var connectionHopByHopHeaders []string for _, f := range req.Header[connection] { - for _, sf := range strings.Split(f, ",") { + for sf := range strings.SplitSeq(f, ",") { if sf = textproto.TrimString(sf); sf != "" { // Connection header cannot dictate to remove X- headers managed by Traefik, // as per rfc7230 https://datatracker.ietf.org/doc/html/rfc7230#section-6.1, diff --git a/pkg/middlewares/headers/header.go b/pkg/middlewares/headers/header.go index a9e0ae52b..36e9eda77 100644 --- a/pkg/middlewares/headers/header.go +++ b/pkg/middlewares/headers/header.go @@ -68,27 +68,6 @@ func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } -// modifyCustomRequestHeaders sets or deletes custom request headers. -func (s *Header) modifyCustomRequestHeaders(req *http.Request) { - // Loop through Custom request headers - for header, value := range s.headers.CustomRequestHeaders { - switch { - // Handling https://github.com/golang/go/commit/ecdbffd4ec68b509998792f120868fec319de59b. - case value == "" && header == forward.XForwardedFor: - req.Header[header] = nil - - case value == "": - req.Header.Del(header) - - case strings.EqualFold(header, "Host"): - req.Host = value - - default: - req.Header.Set(header, value) - } - } -} - // PostRequestModifyResponseHeaders set or delete response headers. // This method is called AFTER the response is generated from the backend // and can merge/override headers from the backend response. @@ -138,6 +117,27 @@ func (s *Header) PostRequestModifyResponseHeaders(res *http.Response) error { return nil } +// modifyCustomRequestHeaders sets or deletes custom request headers. +func (s *Header) modifyCustomRequestHeaders(req *http.Request) { + // Loop through Custom request headers + for header, value := range s.headers.CustomRequestHeaders { + switch { + // Handling https://github.com/golang/go/commit/ecdbffd4ec68b509998792f120868fec319de59b. + case value == "" && header == forward.XForwardedFor: + req.Header[header] = nil + + case value == "": + req.Header.Del(header) + + case strings.EqualFold(header, "Host"): + req.Host = value + + default: + req.Header.Set(header, value) + } + } +} + // processCorsHeaders processes the incoming request, // and returns if it is a preflight request. // If not a preflight, it handles the preRequestModifyCorsResponseHeaders. diff --git a/pkg/middlewares/headers/secure_test.go b/pkg/middlewares/headers/secure_test.go index 050288dfa..9a035b845 100644 --- a/pkg/middlewares/headers/secure_test.go +++ b/pkg/middlewares/headers/secure_test.go @@ -18,10 +18,11 @@ func Test_newSecure_sslForceHost(t *testing.T) { } testCases := []struct { + expected + desc string host string cfg dynamic.Headers - expected }{ { desc: "http should return a 301", diff --git a/pkg/middlewares/metrics/recorder.go b/pkg/middlewares/metrics/recorder.go index 66de624b4..19805f0db 100644 --- a/pkg/middlewares/metrics/recorder.go +++ b/pkg/middlewares/metrics/recorder.go @@ -27,6 +27,7 @@ func newResponseRecorder(rw http.ResponseWriter) recorder { // later analysis. type responseRecorder struct { http.ResponseWriter + statusCode int } @@ -40,10 +41,6 @@ func (r *responseRecorderWithCloseNotify) CloseNotify() <-chan bool { return r.ResponseWriter.(http.CloseNotifier).CloseNotify() } -func (r *responseRecorder) getCode() int { - return r.statusCode -} - // WriteHeader captures the status code for later retrieval. func (r *responseRecorder) WriteHeader(status int) { r.ResponseWriter.WriteHeader(status) @@ -61,3 +58,7 @@ func (r *responseRecorder) Flush() { f.Flush() } } + +func (r *responseRecorder) getCode() int { + return r.statusCode +} diff --git a/pkg/middlewares/ratelimiter/rate_limiter.go b/pkg/middlewares/ratelimiter/rate_limiter.go index 390163463..5fbdc21a3 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter.go +++ b/pkg/middlewares/ratelimiter/rate_limiter.go @@ -66,10 +66,7 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name return nil, err } - burst := config.Burst - if burst < 1 { - burst = 1 - } + burst := max(config.Burst, 1) period := time.Duration(config.Period) if period < 0 { diff --git a/pkg/middlewares/ratelimiter/rate_limiter_test.go b/pkg/middlewares/ratelimiter/rate_limiter_test.go index 0b8927bf1..ab7cb78fb 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter_test.go +++ b/pkg/middlewares/ratelimiter/rate_limiter_test.go @@ -283,11 +283,8 @@ func TestRateLimit(t *testing.T) { stop := time.Now() elapsed := stop.Sub(start) - burst := test.config.Burst - if burst < 1 { - // actual default value - burst = 1 - } + // actual default value if burst < 1 + burst := max(test.config.Burst, 1) period := time.Duration(test.config.Period) if period == 0 { diff --git a/pkg/middlewares/recovery/recovery.go b/pkg/middlewares/recovery/recovery.go index 2415c3ea3..8f4e7ed74 100644 --- a/pkg/middlewares/recovery/recovery.go +++ b/pkg/middlewares/recovery/recovery.go @@ -57,7 +57,7 @@ func recoverFunc(rw recoveryResponseWriter, r *http.Request) { // https://github.com/golang/go/blob/a0d6420d8be2ae7164797051ec74fa2a2df466a1/src/net/http/server.go#L1761-L1775 // https://github.com/golang/go/blob/c33153f7b416c03983324b3e8f869ce1116d84bc/src/net/http/httputil/reverseproxy.go#L284 -func shouldLogPanic(panicValue interface{}) bool { +func shouldLogPanic(panicValue any) bool { //nolint:errorlint // false-positive because panicValue is an interface. return panicValue != nil && panicValue != http.ErrAbortHandler } diff --git a/pkg/middlewares/retry/retry.go b/pkg/middlewares/retry/retry.go index 3e8167ba9..9613679ed 100644 --- a/pkg/middlewares/retry/retry.go +++ b/pkg/middlewares/retry/retry.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "maps" "math" "net" "net/http" @@ -239,10 +240,7 @@ func (r *responseWriterWithoutCloseNotify) WriteHeader(code int) { // to write headers to the backend : we are not going to perform any further retry. // So it is now safe to alter current response headers with headers collected during // the latest try before writing headers to client. - headers := r.responseWriter.Header() - for header, value := range r.headers { - headers[header] = value - } + maps.Copy(r.responseWriter.Header(), r.headers) r.responseWriter.WriteHeader(code) diff --git a/pkg/middlewares/tracing/entrypoint.go b/pkg/middlewares/tracing/entrypoint.go index 54f28cb86..743ae31ee 100644 --- a/pkg/middlewares/tracing/entrypoint.go +++ b/pkg/middlewares/tracing/entrypoint.go @@ -29,6 +29,7 @@ func NewEntryPoint(ctx context.Context, t *tracing.Tracing, entryPointName strin type entryPointMiddleware struct { *tracing.Tracing + entryPoint string next http.Handler } diff --git a/pkg/middlewares/tracing/entrypoint_test.go b/pkg/middlewares/tracing/entrypoint_test.go index 14ed69366..72426799a 100644 --- a/pkg/middlewares/tracing/entrypoint_test.go +++ b/pkg/middlewares/tracing/entrypoint_test.go @@ -13,7 +13,7 @@ import ( func TestEntryPointMiddleware(t *testing.T) { type expected struct { - Tags map[string]interface{} + Tags map[string]any OperationName string } @@ -29,10 +29,10 @@ func TestEntryPointMiddleware(t *testing.T) { entryPoint: "test", spanNameLimit: 0, tracing: &trackingBackenMock{ - tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, + tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]any)}}, }, expected: expected{ - Tags: map[string]interface{}{ + Tags: map[string]any{ "span.kind": ext.SpanKindRPCServerEnum, "http.method": http.MethodGet, "component": "", @@ -47,10 +47,10 @@ func TestEntryPointMiddleware(t *testing.T) { entryPoint: "test", spanNameLimit: 25, tracing: &trackingBackenMock{ - tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, + tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]any)}}, }, expected: expected{ - Tags: map[string]interface{}{ + Tags: map[string]any{ "span.kind": ext.SpanKindRPCServerEnum, "http.method": http.MethodGet, "component": "", diff --git a/pkg/middlewares/tracing/forwarder_test.go b/pkg/middlewares/tracing/forwarder_test.go index af9182e17..46a5716d9 100644 --- a/pkg/middlewares/tracing/forwarder_test.go +++ b/pkg/middlewares/tracing/forwarder_test.go @@ -13,7 +13,7 @@ import ( func TestNewForwarder(t *testing.T) { type expected struct { - Tags map[string]interface{} + Tags map[string]any OperationName string } @@ -29,12 +29,12 @@ func TestNewForwarder(t *testing.T) { desc: "Simple Forward Tracer without truncation and hashing", spanNameLimit: 101, tracing: &trackingBackenMock{ - tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, + tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]any)}}, }, service: "some-service.domain.tld", router: "some-service.domain.tld", expected: expected{ - Tags: map[string]interface{}{ + Tags: map[string]any{ "http.host": "www.test.com", "http.method": "GET", "http.url": "http://www.test.com/toto", @@ -49,12 +49,12 @@ func TestNewForwarder(t *testing.T) { desc: "Simple Forward Tracer with truncation and hashing", spanNameLimit: 101, tracing: &trackingBackenMock{ - tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, + tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]any)}}, }, service: "some-service-100.slug.namespace.environment.domain.tld", router: "some-service-100.slug.namespace.environment.domain.tld", expected: expected{ - Tags: map[string]interface{}{ + Tags: map[string]any{ "http.host": "www.test.com", "http.method": "GET", "http.url": "http://www.test.com/toto", @@ -69,12 +69,12 @@ func TestNewForwarder(t *testing.T) { desc: "Exactly 101 chars", spanNameLimit: 101, tracing: &trackingBackenMock{ - tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, + tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]any)}}, }, service: "some-service1.namespace.environment.domain.tld", router: "some-service1.namespace.environment.domain.tld", expected: expected{ - Tags: map[string]interface{}{ + Tags: map[string]any{ "http.host": "www.test.com", "http.method": "GET", "http.url": "http://www.test.com/toto", @@ -89,12 +89,12 @@ func TestNewForwarder(t *testing.T) { desc: "More than 101 chars", spanNameLimit: 101, tracing: &trackingBackenMock{ - tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, + tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]any)}}, }, service: "some-service1.frontend.namespace.environment.domain.tld", router: "some-service1.backend.namespace.environment.domain.tld", expected: expected{ - Tags: map[string]interface{}{ + Tags: map[string]any{ "http.host": "www.test.com", "http.method": "GET", "http.url": "http://www.test.com/toto", diff --git a/pkg/middlewares/tracing/mock_tracing_test.go b/pkg/middlewares/tracing/mock_tracing_test.go index 2d3c46891..daed99817 100644 --- a/pkg/middlewares/tracing/mock_tracing_test.go +++ b/pkg/middlewares/tracing/mock_tracing_test.go @@ -18,12 +18,12 @@ func (n MockTracer) StartSpan(operationName string, opts ...opentracing.StartSpa } // Inject belongs to the Tracer interface. -func (n MockTracer) Inject(sp opentracing.SpanContext, format, carrier interface{}) error { +func (n MockTracer) Inject(sp opentracing.SpanContext, format, carrier any) error { return nil } // Extract belongs to the Tracer interface. -func (n MockTracer) Extract(format, carrier interface{}) (opentracing.SpanContext, error) { +func (n MockTracer) Extract(format, carrier any) (opentracing.SpanContext, error) { return nil, opentracing.ErrSpanContextNotFound } @@ -35,29 +35,29 @@ func (n MockSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {} // MockSpan a span mock. type MockSpan struct { OpName string - Tags map[string]interface{} + Tags map[string]any } func (n *MockSpan) Context() opentracing.SpanContext { return MockSpanContext{} } func (n *MockSpan) SetBaggageItem(key, val string) opentracing.Span { - return &MockSpan{Tags: make(map[string]interface{})} + return &MockSpan{Tags: make(map[string]any)} } func (n *MockSpan) BaggageItem(key string) string { return "" } -func (n *MockSpan) SetTag(key string, value interface{}) opentracing.Span { +func (n *MockSpan) SetTag(key string, value any) opentracing.Span { n.Tags[key] = value return n } func (n *MockSpan) LogFields(fields ...log.Field) {} -func (n *MockSpan) LogKV(keyVals ...interface{}) {} +func (n *MockSpan) LogKV(keyVals ...any) {} func (n *MockSpan) Finish() {} func (n *MockSpan) FinishWithOptions(opts opentracing.FinishOptions) {} func (n *MockSpan) SetOperationName(operationName string) opentracing.Span { return n } func (n *MockSpan) Tracer() opentracing.Tracer { return MockTracer{} } func (n *MockSpan) LogEvent(event string) {} -func (n *MockSpan) LogEventWithPayload(event string, payload interface{}) {} +func (n *MockSpan) LogEventWithPayload(event string, payload any) {} func (n *MockSpan) Log(data opentracing.LogData) {} func (n *MockSpan) Reset() { - n.Tags = make(map[string]interface{}) + n.Tags = make(map[string]any) } type trackingBackenMock struct { diff --git a/pkg/middlewares/tracing/status_code.go b/pkg/middlewares/tracing/status_code.go index f0c57f81f..91a2a4e91 100644 --- a/pkg/middlewares/tracing/status_code.go +++ b/pkg/middlewares/tracing/status_code.go @@ -22,6 +22,7 @@ func newStatusCodeRecoder(rw http.ResponseWriter, status int) statusCodeRecoder type statusCodeWithoutCloseNotify struct { http.ResponseWriter + status int } diff --git a/pkg/muxer/http/mux.go b/pkg/muxer/http/mux.go index db398216e..0cd36a8ed 100644 --- a/pkg/muxer/http/mux.go +++ b/pkg/muxer/http/mux.go @@ -32,6 +32,7 @@ var httpFuncs = map[string]func(*mux.Route, ...string) error{ // Muxer handles routing with rules. type Muxer struct { *mux.Router + parser predicate.Parser } diff --git a/pkg/plugins/client.go b/pkg/plugins/client.go index 1cee55d3f..a2d08264d 100644 --- a/pkg/plugins/client.go +++ b/pkg/plugins/client.go @@ -245,35 +245,6 @@ func (c *Client) Unzip(pName, pVersion string) error { return c.unzipArchive(pName, pVersion) } -func (c *Client) unzipModule(pName, pVersion string) error { - src := c.buildArchivePath(pName, pVersion) - dest := filepath.Join(c.sources, filepath.FromSlash(pName)) - - return zip.Unzip(dest, module.Version{Path: pName, Version: pVersion}, src) -} - -func (c *Client) unzipArchive(pName, pVersion string) error { - zipPath := c.buildArchivePath(pName, pVersion) - - archive, err := zipa.OpenReader(zipPath) - if err != nil { - return err - } - - defer func() { _ = archive.Close() }() - - dest := filepath.Join(c.sources, filepath.FromSlash(pName)) - - for _, f := range archive.File { - err = unzipFile(f, dest) - if err != nil { - return fmt.Errorf("unable to unzip %s: %w", f.Name, err) - } - } - - return nil -} - func unzipFile(f *zipa.File, dest string) error { rc, err := f.Open() if err != nil { @@ -404,6 +375,35 @@ func (c *Client) ResetAll() error { return nil } +func (c *Client) unzipModule(pName, pVersion string) error { + src := c.buildArchivePath(pName, pVersion) + dest := filepath.Join(c.sources, filepath.FromSlash(pName)) + + return zip.Unzip(dest, module.Version{Path: pName, Version: pVersion}, src) +} + +func (c *Client) unzipArchive(pName, pVersion string) error { + zipPath := c.buildArchivePath(pName, pVersion) + + archive, err := zipa.OpenReader(zipPath) + if err != nil { + return err + } + + defer func() { _ = archive.Close() }() + + dest := filepath.Join(c.sources, filepath.FromSlash(pName)) + + for _, f := range archive.File { + err = unzipFile(f, dest) + if err != nil { + return fmt.Errorf("unable to unzip %s: %w", f.Name, err) + } + } + + return nil +} + func (c *Client) buildArchivePath(pName, pVersion string) string { return filepath.Join(c.archives, filepath.FromSlash(pName), pVersion+".zip") } diff --git a/pkg/plugins/middlewares.go b/pkg/plugins/middlewares.go index e46dbae34..0c5c549dd 100644 --- a/pkg/plugins/middlewares.go +++ b/pkg/plugins/middlewares.go @@ -13,7 +13,7 @@ import ( ) // Build builds a middleware plugin. -func (b Builder) Build(pName string, config map[string]interface{}, middlewareName string) (Constructor, error) { +func (b Builder) Build(pName string, config map[string]any, middlewareName string) (Constructor, error) { if b.middlewareBuilders == nil { return nil, fmt.Errorf("no plugin definition in the static configuration: %s", pName) } @@ -77,7 +77,7 @@ func (p middlewareBuilder) newHandler(ctx context.Context, next http.Handler, cf return handler, nil } -func (p middlewareBuilder) createConfig(config map[string]interface{}) (reflect.Value, error) { +func (p middlewareBuilder) createConfig(config map[string]any) (reflect.Value, error) { results := p.fnCreateConfig.Call(nil) if len(results) != 1 { return reflect.Value{}, fmt.Errorf("invalid number of return for the CreateConfig function: %d", len(results)) @@ -114,7 +114,7 @@ type Middleware struct { builder *middlewareBuilder } -func newMiddleware(builder *middlewareBuilder, config map[string]interface{}, middlewareName string) (*Middleware, error) { +func newMiddleware(builder *middlewareBuilder, config map[string]any, middlewareName string) (*Middleware, error) { vConfig, err := builder.createConfig(config) if err != nil { return nil, err diff --git a/pkg/plugins/providers.go b/pkg/plugins/providers.go index abec8d1b8..70132d062 100644 --- a/pkg/plugins/providers.go +++ b/pkg/plugins/providers.go @@ -24,7 +24,7 @@ type PP interface { } type _PP struct { - IValue interface{} + IValue any WInit func() error WProvide func(cfgChan chan<- json.Marshaler) error WStop func() error @@ -52,7 +52,7 @@ func ppSymbols() map[string]map[string]reflect.Value { } // BuildProvider builds a plugin's provider. -func (b Builder) BuildProvider(pName string, config map[string]interface{}) (provider.Provider, error) { +func (b Builder) BuildProvider(pName string, config map[string]any) (provider.Provider, error) { if b.providerBuilders == nil { return nil, fmt.Errorf("no plugin definition in the static configuration: %s", pName) } @@ -81,7 +81,7 @@ type Provider struct { pp PP } -func newProvider(builder providerBuilder, config map[string]interface{}, providerName string) (*Provider, error) { +func newProvider(builder providerBuilder, config map[string]any, providerName string) (*Provider, error) { basePkg := builder.BasePkg if basePkg == "" { basePkg = strings.ReplaceAll(path.Base(builder.Import), "-", "_") diff --git a/pkg/plugins/types.go b/pkg/plugins/types.go index 78ea13a34..4bdb92b6d 100644 --- a/pkg/plugins/types.go +++ b/pkg/plugins/types.go @@ -17,11 +17,11 @@ type LocalDescriptor struct { // Manifest The plugin manifest. type Manifest struct { - DisplayName string `yaml:"displayName"` - Type string `yaml:"type"` - Import string `yaml:"import"` - BasePkg string `yaml:"basePkg"` - Compatibility string `yaml:"compatibility"` - Summary string `yaml:"summary"` - TestData map[string]interface{} `yaml:"testData"` + DisplayName string `yaml:"displayName"` + Type string `yaml:"type"` + Import string `yaml:"import"` + BasePkg string `yaml:"basePkg"` + Compatibility string `yaml:"compatibility"` + Summary string `yaml:"summary"` + TestData map[string]any `yaml:"testData"` } diff --git a/pkg/provider/acme/local_store.go b/pkg/provider/acme/local_store.go index 9a75c704e..3b18fa3a7 100644 --- a/pkg/provider/acme/local_store.go +++ b/pkg/provider/acme/local_store.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "io" + "maps" "os" "sync" @@ -29,6 +30,52 @@ func NewLocalStore(filename string, routinesPool *safe.Pool) *LocalStore { return store } +// GetAccount returns ACME Account. +func (s *LocalStore) GetAccount(resolverName string) (*Account, error) { + storedData, err := s.get(resolverName) + if err != nil { + return nil, err + } + + return storedData.Account, nil +} + +// SaveAccount stores ACME Account. +func (s *LocalStore) SaveAccount(resolverName string, account *Account) error { + storedData, err := s.get(resolverName) + if err != nil { + return err + } + + storedData.Account = account + s.save(resolverName, storedData) + + return nil +} + +// GetCertificates returns ACME Certificates list. +func (s *LocalStore) GetCertificates(resolverName string) ([]*CertAndStore, error) { + storedData, err := s.get(resolverName) + if err != nil { + return nil, err + } + + return storedData.Certificates, nil +} + +// SaveCertificates stores ACME Certificates list. +func (s *LocalStore) SaveCertificates(resolverName string, certificates []*CertAndStore) error { + storedData, err := s.get(resolverName) + if err != nil { + return err + } + + storedData.Certificates = certificates + s.save(resolverName, storedData) + + return nil +} + func (s *LocalStore) save(resolverName string, storedData *StoredData) { s.lock.Lock() defer s.lock.Unlock() @@ -121,8 +168,7 @@ func (s *LocalStore) listenSaveAction(routinesPool *safe.Pool) { logger.Error(err) } - err = os.WriteFile(s.filename, data, 0o600) - if err != nil { + if err := os.WriteFile(s.filename, data, 0o600); err != nil { logger.Error(err) } } @@ -132,55 +178,5 @@ func (s *LocalStore) listenSaveAction(routinesPool *safe.Pool) { // unSafeCopyOfStoredData creates maps copy of storedData. Is not thread safe, you should use `s.lock`. func (s *LocalStore) unSafeCopyOfStoredData() map[string]*StoredData { - result := map[string]*StoredData{} - for k, v := range s.storedData { - result[k] = v - } - return result -} - -// GetAccount returns ACME Account. -func (s *LocalStore) GetAccount(resolverName string) (*Account, error) { - storedData, err := s.get(resolverName) - if err != nil { - return nil, err - } - - return storedData.Account, nil -} - -// SaveAccount stores ACME Account. -func (s *LocalStore) SaveAccount(resolverName string, account *Account) error { - storedData, err := s.get(resolverName) - if err != nil { - return err - } - - storedData.Account = account - s.save(resolverName, storedData) - - return nil -} - -// GetCertificates returns ACME Certificates list. -func (s *LocalStore) GetCertificates(resolverName string) ([]*CertAndStore, error) { - storedData, err := s.get(resolverName) - if err != nil { - return nil, err - } - - return storedData.Certificates, nil -} - -// SaveCertificates stores ACME Certificates list. -func (s *LocalStore) SaveCertificates(resolverName string, certificates []*CertAndStore) error { - storedData, err := s.get(resolverName) - if err != nil { - return err - } - - storedData.Certificates = certificates - s.save(resolverName, storedData) - - return nil + return maps.Clone(s.storedData) } diff --git a/pkg/provider/acme/local_store_unix.go b/pkg/provider/acme/local_store_unix.go index 163ecd7ae..60f85ce27 100644 --- a/pkg/provider/acme/local_store_unix.go +++ b/pkg/provider/acme/local_store_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows package acme diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go index 66d817993..1252bd1c8 100644 --- a/pkg/provider/acme/provider.go +++ b/pkg/provider/acme/provider.go @@ -58,6 +58,7 @@ func (a *Configuration) SetDefaults() { // CertAndStore allows mapping a TLS certificate to a TLS store. type CertAndStore struct { Certificate + Store string } @@ -93,6 +94,7 @@ type TLSChallenge struct{} // Provider holds configurations of the provider. type Provider struct { *Configuration + ResolverName string Store Store `json:"store,omitempty" toml:"store,omitempty" yaml:"store,omitempty"` @@ -955,7 +957,7 @@ func (p *Provider) certExists(validDomains []string) bool { func isDomainAlreadyChecked(domainToCheck string, existentDomains []string) bool { for _, certDomains := range existentDomains { - for _, certDomain := range strings.Split(certDomains, ",") { + for certDomain := range strings.SplitSeq(certDomains, ",") { if types.MatchDomain(domainToCheck, certDomain) { return true } diff --git a/pkg/provider/aggregator/aggregator.go b/pkg/provider/aggregator/aggregator.go index fca5f73ec..355504572 100644 --- a/pkg/provider/aggregator/aggregator.go +++ b/pkg/provider/aggregator/aggregator.go @@ -145,13 +145,6 @@ func NewProviderAggregator(conf static.Providers) *ProviderAggregator { return p } -func (p *ProviderAggregator) quietAddProvider(provider provider.Provider) { - err := p.AddProvider(provider) - if err != nil { - log.WithoutContext().Errorf("Error while initializing provider %T: %v", provider, err) - } -} - // AddProvider adds a provider in the providers map. func (p *ProviderAggregator) AddProvider(provider provider.Provider) error { err := provider.Init() @@ -197,6 +190,13 @@ func (p *ProviderAggregator) Provide(configurationChan chan<- dynamic.Message, p return nil } +func (p *ProviderAggregator) quietAddProvider(provider provider.Provider) { + err := p.AddProvider(provider) + if err != nil { + log.WithoutContext().Errorf("Error while initializing provider %T: %v", provider, err) + } +} + func (p *ProviderAggregator) launchProvider(configurationChan chan<- dynamic.Message, pool *safe.Pool, prd provider.Provider) { jsonConf, err := redactor.RemoveCredentials(prd) if err != nil { diff --git a/pkg/provider/configuration.go b/pkg/provider/configuration.go index 996ad47bb..4f55aa3c0 100644 --- a/pkg/provider/configuration.go +++ b/pkg/provider/configuration.go @@ -364,9 +364,7 @@ func MakeDefaultRuleTemplate(defaultRule string, funcMap template.FuncMap) (*tem defaultFuncMap := sprig.TxtFuncMap() defaultFuncMap["normalize"] = Normalize - for k, fn := range funcMap { - defaultFuncMap[k] = fn - } + maps.Copy(defaultFuncMap, funcMap) return template.New("defaultRule").Funcs(defaultFuncMap).Parse(defaultRule) } @@ -419,7 +417,7 @@ func BuildUDPRouterConfiguration(ctx context.Context, configuration *dynamic.UDP } // BuildRouterConfiguration builds a router configuration. -func BuildRouterConfiguration(ctx context.Context, configuration *dynamic.HTTPConfiguration, defaultRouterName string, defaultRuleTpl *template.Template, model interface{}) { +func BuildRouterConfiguration(ctx context.Context, configuration *dynamic.HTTPConfiguration, defaultRouterName string, defaultRuleTpl *template.Template, model any) { if len(configuration.Routers) == 0 { if len(configuration.Services) > 1 { log.FromContext(ctx).Info("Could not create a router for the container: too many services") diff --git a/pkg/provider/constraints/constraints_labels.go b/pkg/provider/constraints/constraints_labels.go index 6f194ba33..437a30f85 100644 --- a/pkg/provider/constraints/constraints_labels.go +++ b/pkg/provider/constraints/constraints_labels.go @@ -30,7 +30,7 @@ func MatchLabels(labels map[string]string, expr string) (bool, error) { NOT: notLabelFunc, OR: orLabelFunc, }, - Functions: map[string]interface{}{ + Functions: map[string]any{ "Label": labelFn, "LabelRegex": labelRegexFn, "MarathonConstraint": marathonFn, diff --git a/pkg/provider/constraints/constraints_tags.go b/pkg/provider/constraints/constraints_tags.go index b61f43c5d..ad8fa8fc9 100644 --- a/pkg/provider/constraints/constraints_tags.go +++ b/pkg/provider/constraints/constraints_tags.go @@ -25,7 +25,7 @@ func MatchTags(tags []string, expr string) (bool, error) { NOT: notTagFunc, OR: orTagFunc, }, - Functions: map[string]interface{}{ + Functions: map[string]any{ "Tag": tagFn, "TagRegex": tagRegexFn, }, diff --git a/pkg/provider/consulcatalog/consul_catalog.go b/pkg/provider/consulcatalog/consul_catalog.go index effd67cac..3ab0376e2 100644 --- a/pkg/provider/consulcatalog/consul_catalog.go +++ b/pkg/provider/consulcatalog/consul_catalog.go @@ -394,12 +394,12 @@ func (p *Provider) fetchService(ctx context.Context, name string, connectEnabled // watchServices watches for update events of the services list and statuses, // and transmits them to the caller through the p.watchServicesChan. func (p *Provider) watchServices(ctx context.Context) error { - servicesWatcher, err := watch.Parse(map[string]interface{}{"type": "services"}) + servicesWatcher, err := watch.Parse(map[string]any{"type": "services"}) if err != nil { return fmt.Errorf("failed to create services watcher plan: %w", err) } - servicesWatcher.HybridHandler = func(_ watch.BlockingParamVal, _ interface{}) { + servicesWatcher.HybridHandler = func(_ watch.BlockingParamVal, _ any) { select { case <-ctx.Done(): case p.watchServicesChan <- struct{}{}: @@ -408,12 +408,12 @@ func (p *Provider) watchServices(ctx context.Context) error { } } - checksWatcher, err := watch.Parse(map[string]interface{}{"type": "checks"}) + checksWatcher, err := watch.Parse(map[string]any{"type": "checks"}) if err != nil { return fmt.Errorf("failed to create checks watcher plan: %w", err) } - checksWatcher.HybridHandler = func(_ watch.BlockingParamVal, _ interface{}) { + checksWatcher.HybridHandler = func(_ watch.BlockingParamVal, _ any) { select { case <-ctx.Done(): case p.watchServicesChan <- struct{}{}: @@ -452,8 +452,8 @@ func (p *Provider) watchServices(ctx context.Context) error { } } -func rootsWatchHandler(ctx context.Context, dest chan<- []string) func(watch.BlockingParamVal, interface{}) { - return func(_ watch.BlockingParamVal, raw interface{}) { +func rootsWatchHandler(ctx context.Context, dest chan<- []string) func(watch.BlockingParamVal, any) { + return func(_ watch.BlockingParamVal, raw any) { if raw == nil { log.FromContext(ctx).Errorf("Root certificate watcher called with nil") return @@ -482,8 +482,8 @@ type keyPair struct { key string } -func leafWatcherHandler(ctx context.Context, dest chan<- keyPair) func(watch.BlockingParamVal, interface{}) { - return func(_ watch.BlockingParamVal, raw interface{}) { +func leafWatcherHandler(ctx context.Context, dest chan<- keyPair) func(watch.BlockingParamVal, any) { + return func(_ watch.BlockingParamVal, raw any) { if raw == nil { log.FromContext(ctx).Errorf("Leaf certificate watcher called with nil") return @@ -511,7 +511,7 @@ func leafWatcherHandler(ctx context.Context, dest chan<- keyPair) func(watch.Blo // certificate, and transmits them to the caller via p.certChan. func (p *Provider) watchConnectTLS(ctx context.Context) error { leafChan := make(chan keyPair) - leafWatcher, err := watch.Parse(map[string]interface{}{ + leafWatcher, err := watch.Parse(map[string]any{ "type": "connect_leaf", "service": p.ServiceName, }) @@ -521,7 +521,7 @@ func (p *Provider) watchConnectTLS(ctx context.Context) error { leafWatcher.HybridHandler = leafWatcherHandler(ctx, leafChan) rootsChan := make(chan []string) - rootsWatcher, err := watch.Parse(map[string]interface{}{ + rootsWatcher, err := watch.Parse(map[string]any{ "type": "connect_roots", }) if err != nil { diff --git a/pkg/provider/docker/docker.go b/pkg/provider/docker/docker.go index 937fbb863..194251107 100644 --- a/pkg/provider/docker/docker.go +++ b/pkg/provider/docker/docker.go @@ -104,79 +104,6 @@ type networkData struct { ID string } -func (p *Provider) createClient() (client.APIClient, error) { - opts, err := p.getClientOpts() - if err != nil { - return nil, err - } - - httpHeaders := map[string]string{ - "User-Agent": "Traefik " + version.Version, - } - opts = append(opts, - client.FromEnv, - client.WithAPIVersionNegotiation(), - client.WithHTTPHeaders(httpHeaders)) - - return client.NewClientWithOpts(opts...) -} - -func (p *Provider) getClientOpts() ([]client.Opt, error) { - helper, err := connhelper.GetConnectionHelper(p.Endpoint) - if err != nil { - return nil, err - } - - // SSH - if helper != nil { - // https://github.com/docker/cli/blob/ebca1413117a3fcb81c89d6be226dcec74e5289f/cli/context/docker/load.go#L112-L123 - - httpClient := &http.Client{ - Transport: &http.Transport{ - DialContext: helper.Dialer, - }, - } - - return []client.Opt{ - client.WithHTTPClient(httpClient), - client.WithTimeout(time.Duration(p.HTTPClientTimeout)), - client.WithHost(helper.Host), // To avoid 400 Bad Request: malformed Host header daemon error - client.WithDialContext(helper.Dialer), - }, nil - } - - opts := []client.Opt{ - client.WithHost(p.Endpoint), - client.WithTimeout(time.Duration(p.HTTPClientTimeout)), - } - - if p.TLS != nil { - ctx := log.With(context.Background(), log.Str(log.ProviderName, "docker")) - - conf, err := p.TLS.CreateTLSConfig(ctx) - if err != nil { - return nil, fmt.Errorf("unable to create client TLS configuration: %w", err) - } - - hostURL, err := client.ParseHostURL(p.Endpoint) - if err != nil { - return nil, err - } - - tr := &http.Transport{ - TLSClientConfig: conf, - } - - if err := sockets.ConfigureTransport(tr, hostURL.Scheme, hostURL.Host); err != nil { - return nil, err - } - - opts = append(opts, client.WithHTTPClient(&http.Client{Transport: tr, Timeout: time.Duration(p.HTTPClientTimeout)})) - } - - return opts, nil -} - // Provide allows the docker provider to provide configurations to traefik using the given configuration channel. func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { pool.GoCtx(func(routineCtx context.Context) { @@ -327,6 +254,79 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. return nil } +func (p *Provider) createClient() (client.APIClient, error) { + opts, err := p.getClientOpts() + if err != nil { + return nil, err + } + + httpHeaders := map[string]string{ + "User-Agent": "Traefik " + version.Version, + } + opts = append(opts, + client.FromEnv, + client.WithAPIVersionNegotiation(), + client.WithHTTPHeaders(httpHeaders)) + + return client.NewClientWithOpts(opts...) +} + +func (p *Provider) getClientOpts() ([]client.Opt, error) { + helper, err := connhelper.GetConnectionHelper(p.Endpoint) + if err != nil { + return nil, err + } + + // SSH + if helper != nil { + // https://github.com/docker/cli/blob/ebca1413117a3fcb81c89d6be226dcec74e5289f/cli/context/docker/load.go#L112-L123 + + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: helper.Dialer, + }, + } + + return []client.Opt{ + client.WithHTTPClient(httpClient), + client.WithTimeout(time.Duration(p.HTTPClientTimeout)), + client.WithHost(helper.Host), // To avoid 400 Bad Request: malformed Host header daemon error + client.WithDialContext(helper.Dialer), + }, nil + } + + opts := []client.Opt{ + client.WithHost(p.Endpoint), + client.WithTimeout(time.Duration(p.HTTPClientTimeout)), + } + + if p.TLS != nil { + ctx := log.With(context.Background(), log.Str(log.ProviderName, "docker")) + + conf, err := p.TLS.CreateTLSConfig(ctx) + if err != nil { + return nil, fmt.Errorf("unable to create client TLS configuration: %w", err) + } + + hostURL, err := client.ParseHostURL(p.Endpoint) + if err != nil { + return nil, err + } + + tr := &http.Transport{ + TLSClientConfig: conf, + } + + if err := sockets.ConfigureTransport(tr, hostURL.Scheme, hostURL.Host); err != nil { + return nil, err + } + + opts = append(opts, client.WithHTTPClient(&http.Client{Transport: tr, Timeout: time.Duration(p.HTTPClientTimeout)})) + } + + return opts, nil +} + func (p *Provider) listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) { containerList, err := dockerClient.ContainerList(ctx, dockercontainertypes.ListOptions{}) if err != nil { diff --git a/pkg/provider/docker/swarm_test.go b/pkg/provider/docker/swarm_test.go index df5371a9d..1136b249f 100644 --- a/pkg/provider/docker/swarm_test.go +++ b/pkg/provider/docker/swarm_test.go @@ -17,6 +17,7 @@ import ( type fakeTasksClient struct { dockerclient.APIClient + tasks []swarmtypes.Task container dockercontainertypes.InspectResponse err error @@ -105,6 +106,7 @@ func TestListTasks(t *testing.T) { type fakeServicesClient struct { dockerclient.APIClient + dockerVersion string networks []networktypes.Summary nodes []swarmtypes.Node diff --git a/pkg/provider/ecs/ecs.go b/pkg/provider/ecs/ecs.go index 3310236c9..357c0f273 100644 --- a/pkg/provider/ecs/ecs.go +++ b/pkg/provider/ecs/ecs.go @@ -101,43 +101,6 @@ func (p *Provider) Init() error { return nil } -func (p *Provider) createClient(ctx context.Context, logger log.Logger) (*awsClient, error) { - optFns := []func(*config.LoadOptions) error{ - config.WithLogger(logging.LoggerFunc(func(_ logging.Classification, format string, args ...interface{}) { - logger.Debugf(format, args...) - })), - } - if p.Region != "" { - optFns = append(optFns, config.WithRegion(p.Region)) - } else { - logger.Infoln("No region provided, will retrieve region from the EC2 Metadata service") - optFns = append(optFns, config.WithEC2IMDSRegion()) - } - - if p.AccessKeyID != "" && p.SecretAccessKey != "" { - // From https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/configure-gosdk.html#specify-credentials-programmatically: - // "If you explicitly provide credentials, as in this example, the SDK uses only those credentials." - // this makes sure that user-defined credentials always have the highest priority - staticCreds := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(p.AccessKeyID, p.SecretAccessKey, "")) - optFns = append(optFns, config.WithCredentialsProvider(staticCreds)) - - // If the access key and secret access key are not provided, config.LoadDefaultConfig - // will look for the credentials in the default credential chain. - // See https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/configure-gosdk.html#specifying-credentials. - } - - cfg, err := config.LoadDefaultConfig(ctx, optFns...) - if err != nil { - return nil, err - } - - return &awsClient{ - ecs.NewFromConfig(cfg), - ec2.NewFromConfig(cfg), - ssm.NewFromConfig(cfg), - }, nil -} - // Provide configuration to traefik from ECS. func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { pool.GoCtx(func(routineCtx context.Context) { @@ -184,6 +147,43 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. return nil } +func (p *Provider) createClient(ctx context.Context, logger log.Logger) (*awsClient, error) { + optFns := []func(*config.LoadOptions) error{ + config.WithLogger(logging.LoggerFunc(func(_ logging.Classification, format string, args ...any) { + logger.Debugf(format, args...) + })), + } + if p.Region != "" { + optFns = append(optFns, config.WithRegion(p.Region)) + } else { + logger.Infoln("No region provided, will retrieve region from the EC2 Metadata service") + optFns = append(optFns, config.WithEC2IMDSRegion()) + } + + if p.AccessKeyID != "" && p.SecretAccessKey != "" { + // From https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/configure-gosdk.html#specify-credentials-programmatically: + // "If you explicitly provide credentials, as in this example, the SDK uses only those credentials." + // this makes sure that user-defined credentials always have the highest priority + staticCreds := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(p.AccessKeyID, p.SecretAccessKey, "")) + optFns = append(optFns, config.WithCredentialsProvider(staticCreds)) + + // If the access key and secret access key are not provided, config.LoadDefaultConfig + // will look for the credentials in the default credential chain. + // See https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/configure-gosdk.html#specifying-credentials. + } + + cfg, err := config.LoadDefaultConfig(ctx, optFns...) + if err != nil { + return nil, err + } + + return &awsClient{ + ecs.NewFromConfig(cfg), + ec2.NewFromConfig(cfg), + ssm.NewFromConfig(cfg), + }, nil +} + func (p *Provider) loadConfiguration(ctx context.Context, client *awsClient, configurationChan chan<- dynamic.Message) error { instances, err := p.listInstances(ctx, client) if err != nil { diff --git a/pkg/provider/file/file.go b/pkg/provider/file/file.go index 8b9fbb40a..e3eb550ba 100644 --- a/pkg/provider/file/file.go +++ b/pkg/provider/file/file.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "maps" "os" "path" "path/filepath" @@ -106,6 +107,51 @@ func (p *Provider) BuildConfiguration() (*dynamic.Configuration, error) { return nil, errors.New("error using file configuration provider, neither filename or directory defined") } +// CreateConfiguration creates a provider configuration from content using templating. +func (p *Provider) CreateConfiguration(ctx context.Context, filename string, funcMap template.FuncMap, templateObjects any) (*dynamic.Configuration, error) { + tmplContent, err := readFile(filename) + if err != nil { + return nil, fmt.Errorf("error reading configuration file: %s - %w", filename, err) + } + + defaultFuncMap := sprig.TxtFuncMap() + defaultFuncMap["normalize"] = provider.Normalize + defaultFuncMap["split"] = strings.Split + maps.Copy(defaultFuncMap, funcMap) + + tmpl := template.New(p.Filename).Funcs(defaultFuncMap) + + _, err = tmpl.Parse(tmplContent) + if err != nil { + return nil, err + } + + var buffer bytes.Buffer + err = tmpl.Execute(&buffer, templateObjects) + if err != nil { + return nil, err + } + + renderedTemplate := buffer.String() + if p.DebugLogGeneratedTemplate { + logger := log.FromContext(ctx) + logger.Debugf("Template content: %s", tmplContent) + logger.Debugf("Rendering results: %s", renderedTemplate) + } + + return p.decodeConfiguration(filename, renderedTemplate) +} + +// DecodeConfiguration Decodes a *types.Configuration from a content. +func (p *Provider) DecodeConfiguration(filename string) (*dynamic.Configuration, error) { + content, err := readFile(filename) + if err != nil { + return nil, fmt.Errorf("error reading configuration file: %s - %w", filename, err) + } + + return p.decodeConfiguration(filename, content) +} + func (p *Provider) addWatcher(pool *safe.Pool, items []string, configurationChan chan<- dynamic.Message, callback func(chan<- dynamic.Message, fsnotify.Event)) error { watcher, err := fsnotify.NewWatcher() if err != nil { @@ -470,53 +516,6 @@ func (p *Provider) loadFileConfigFromDirectory(ctx context.Context, directory st return configuration, nil } -// CreateConfiguration creates a provider configuration from content using templating. -func (p *Provider) CreateConfiguration(ctx context.Context, filename string, funcMap template.FuncMap, templateObjects interface{}) (*dynamic.Configuration, error) { - tmplContent, err := readFile(filename) - if err != nil { - return nil, fmt.Errorf("error reading configuration file: %s - %w", filename, err) - } - - defaultFuncMap := sprig.TxtFuncMap() - defaultFuncMap["normalize"] = provider.Normalize - defaultFuncMap["split"] = strings.Split - for funcID, funcElement := range funcMap { - defaultFuncMap[funcID] = funcElement - } - - tmpl := template.New(p.Filename).Funcs(defaultFuncMap) - - _, err = tmpl.Parse(tmplContent) - if err != nil { - return nil, err - } - - var buffer bytes.Buffer - err = tmpl.Execute(&buffer, templateObjects) - if err != nil { - return nil, err - } - - renderedTemplate := buffer.String() - if p.DebugLogGeneratedTemplate { - logger := log.FromContext(ctx) - logger.Debugf("Template content: %s", tmplContent) - logger.Debugf("Rendering results: %s", renderedTemplate) - } - - return p.decodeConfiguration(filename, renderedTemplate) -} - -// DecodeConfiguration Decodes a *types.Configuration from a content. -func (p *Provider) DecodeConfiguration(filename string) (*dynamic.Configuration, error) { - content, err := readFile(filename) - if err != nil { - return nil, fmt.Errorf("error reading configuration file: %s - %w", filename, err) - } - - return p.decodeConfiguration(filename, content) -} - func (p *Provider) decodeConfiguration(filePath, content string) (*dynamic.Configuration, error) { configuration := &dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ diff --git a/pkg/provider/kubernetes/crd/client-containous.go b/pkg/provider/kubernetes/crd/client-containous.go index fc9610e0f..6636b6eaf 100644 --- a/pkg/provider/kubernetes/crd/client-containous.go +++ b/pkg/provider/kubernetes/crd/client-containous.go @@ -21,19 +21,19 @@ func (c *clientWrapper) appendContainousIngressRoutes(result []*traefikv1alpha1. for ns, factory := range c.factoriesCrd { ings, err := factory.TraefikContainous().V1alpha1().IngressRoutes().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list ingress routes in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list ingress routes in namespace %s: %v", ns, err) } for _, ing := range ings { key := objectKey(ing.ObjectMeta) if _, ok := listed[key]; ok { - log.Debugf("Ignoring traefik.containo.us/v1alpha1 ingress route (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) + log.WithoutContext().Debugf("Ignoring traefik.containo.us/v1alpha1 ingress route (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) continue } toVersion, err := traefikscheme.Scheme.ConvertToVersion(ing, GroupVersioner) if err != nil { - log.Errorf("Failed to convert ingress route in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to convert ingress route in namespace %s: %v", ns, err) continue } @@ -53,19 +53,19 @@ func (c *clientWrapper) appendContainousIngressRouteTCPs(result []*traefikv1alph for ns, factory := range c.factoriesCrd { ings, err := factory.TraefikContainous().V1alpha1().IngressRouteTCPs().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list tcp ingress routes in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list tcp ingress routes in namespace %s: %v", ns, err) } for _, ing := range ings { key := objectKey(ing.ObjectMeta) if _, ok := listed[key]; ok { - log.Debugf("Ignoring traefik.containo.us/v1alpha1 tcp ingress route (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) + log.WithoutContext().Debugf("Ignoring traefik.containo.us/v1alpha1 tcp ingress route (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) continue } toVersion, err := traefikscheme.Scheme.ConvertToVersion(ing, GroupVersioner) if err != nil { - log.Errorf("Failed to convert tcp ingress route in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to convert tcp ingress route in namespace %s: %v", ns, err) continue } @@ -85,19 +85,19 @@ func (c *clientWrapper) appendContainousIngressRouteUDPs(result []*traefikv1alph for ns, factory := range c.factoriesCrd { ings, err := factory.TraefikContainous().V1alpha1().IngressRouteUDPs().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list udp ingress routes in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list udp ingress routes in namespace %s: %v", ns, err) } for _, ing := range ings { key := objectKey(ing.ObjectMeta) if _, ok := listed[key]; ok { - log.Debugf("Ignoring traefik.containo.us/v1alpha1 udp ingress route (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) + log.WithoutContext().Debugf("Ignoring traefik.containo.us/v1alpha1 udp ingress route (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) continue } toVersion, err := traefikscheme.Scheme.ConvertToVersion(ing, GroupVersioner) if err != nil { - log.Errorf("Failed to convert udp ingress route in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to convert udp ingress route in namespace %s: %v", ns, err) continue } @@ -117,19 +117,19 @@ func (c *clientWrapper) appendContainousMiddlewares(result []*traefikv1alpha1.Mi for ns, factory := range c.factoriesCrd { middlewares, err := factory.TraefikContainous().V1alpha1().Middlewares().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list middlewares in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list middlewares in namespace %s: %v", ns, err) } for _, middleware := range middlewares { key := objectKey(middleware.ObjectMeta) if _, ok := listed[key]; ok { - log.Debugf("Ignoring traefik.containo.us/v1alpha1 middleware (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) + log.WithoutContext().Debugf("Ignoring traefik.containo.us/v1alpha1 middleware (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) continue } toVersion, err := traefikscheme.Scheme.ConvertToVersion(middleware, GroupVersioner) if err != nil { - log.Errorf("Failed to convert middleware in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to convert middleware in namespace %s: %v", ns, err) continue } @@ -149,19 +149,19 @@ func (c *clientWrapper) appendContainousMiddlewareTCPs(result []*traefikv1alpha1 for ns, factory := range c.factoriesCrd { middlewares, err := factory.TraefikContainous().V1alpha1().MiddlewareTCPs().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list tcp middlewares in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list tcp middlewares in namespace %s: %v", ns, err) } for _, middleware := range middlewares { key := objectKey(middleware.ObjectMeta) if _, ok := listed[key]; ok { - log.Debugf("Ignoring traefik.containo.us/v1alpha1 middleware (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) + log.WithoutContext().Debugf("Ignoring traefik.containo.us/v1alpha1 middleware (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) continue } toVersion, err := traefikscheme.Scheme.ConvertToVersion(middleware, GroupVersioner) if err != nil { - log.Errorf("Failed to convert tcp middleware in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to convert tcp middleware in namespace %s: %v", ns, err) continue } @@ -181,19 +181,19 @@ func (c *clientWrapper) appendContainousTraefikServices(result []*traefikv1alpha for ns, factory := range c.factoriesCrd { traefikServices, err := factory.TraefikContainous().V1alpha1().TraefikServices().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list Traefik services in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list Traefik services in namespace %s: %v", ns, err) } for _, traefikService := range traefikServices { key := objectKey(traefikService.ObjectMeta) if _, ok := listed[key]; ok { - log.Debugf("Ignoring traefik.containo.us/v1alpha1 Traefik service (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) + log.WithoutContext().Debugf("Ignoring traefik.containo.us/v1alpha1 Traefik service (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) continue } toVersion, err := traefikscheme.Scheme.ConvertToVersion(traefikService, GroupVersioner) if err != nil { - log.Errorf("Failed to convert Traefik service in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to convert Traefik service in namespace %s: %v", ns, err) continue } @@ -213,19 +213,19 @@ func (c *clientWrapper) appendContainousServersTransport(result []*traefikv1alph for ns, factory := range c.factoriesCrd { serversTransports, err := factory.TraefikContainous().V1alpha1().ServersTransports().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list servers transports in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list servers transports in namespace %s: %v", ns, err) } for _, serversTransport := range serversTransports { key := objectKey(serversTransport.ObjectMeta) if _, ok := listed[key]; ok { - log.Debugf("Ignoring traefik.containo.us/v1alpha1 servers transport (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) + log.WithoutContext().Debugf("Ignoring traefik.containo.us/v1alpha1 servers transport (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) continue } toVersion, err := traefikscheme.Scheme.ConvertToVersion(serversTransport, GroupVersioner) if err != nil { - log.Errorf("Failed to convert servers transport in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to convert servers transport in namespace %s: %v", ns, err) continue } @@ -245,19 +245,19 @@ func (c *clientWrapper) appendContainousTLSOptions(result []*traefikv1alpha1.TLS for ns, factory := range c.factoriesCrd { options, err := factory.TraefikContainous().V1alpha1().TLSOptions().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list tls options in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list tls options in namespace %s: %v", ns, err) } for _, option := range options { key := objectKey(option.ObjectMeta) if _, ok := listed[key]; ok { - log.Debugf("Ignoring traefik.containo.us/v1alpha1 tls option (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) + log.WithoutContext().Debugf("Ignoring traefik.containo.us/v1alpha1 tls option (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) continue } toVersion, err := traefikscheme.Scheme.ConvertToVersion(option, GroupVersioner) if err != nil { - log.Errorf("Failed to convert tls option in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to convert tls option in namespace %s: %v", ns, err) continue } @@ -277,19 +277,19 @@ func (c *clientWrapper) appendContainousTLSStores(result []*traefikv1alpha1.TLSS for ns, factory := range c.factoriesCrd { stores, err := factory.TraefikContainous().V1alpha1().TLSStores().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list tls stores in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list tls stores in namespace %s: %v", ns, err) } for _, store := range stores { key := objectKey(store.ObjectMeta) if _, ok := listed[key]; ok { - log.Debugf("Ignoring traefik.containo.us/v1alpha1 tls store (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) + log.WithoutContext().Debugf("Ignoring traefik.containo.us/v1alpha1 tls store (%s) already listed within traefik.io/v1alpha1 API GroupVersion", key) continue } toVersion, err := traefikscheme.Scheme.ConvertToVersion(store, GroupVersioner) if err != nil { - log.Errorf("Failed to convert tls store in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to convert tls store in namespace %s: %v", ns, err) continue } @@ -314,7 +314,7 @@ func (c *clientWrapper) getContainousTraefikService(namespace, name string) (*tr toVersion, err := traefikscheme.Scheme.ConvertToVersion(service, GroupVersioner) if err != nil { - log.Errorf("Failed to convert Traefik service in namespace %s: %v", namespace, err) + log.WithoutContext().Errorf("Failed to convert Traefik service in namespace %s: %v", namespace, err) } return toVersion.(*traefikv1alpha1.TraefikService), exist, err diff --git a/pkg/provider/kubernetes/crd/client.go b/pkg/provider/kubernetes/crd/client.go index 97047f138..b340740f7 100644 --- a/pkg/provider/kubernetes/crd/client.go +++ b/pkg/provider/kubernetes/crd/client.go @@ -31,7 +31,7 @@ const resyncPeriod = 10 * time.Minute // WatchAll starts the watch of the Provider resources and updates the stores. // The stores can then be accessed via the Get* functions. type Client interface { - WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) + WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan any, error) GetIngressRoutes() []*traefikv1alpha1.IngressRoute GetIngressRouteTCPs() []*traefikv1alpha1.IngressRouteTCP GetIngressRouteUDPs() []*traefikv1alpha1.IngressRouteUDP @@ -143,8 +143,8 @@ func newExternalClusterClient(endpoint, token, caFilePath string) (*clientWrappe } // WatchAll starts namespace-specific controllers for all relevant kinds. -func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) { - eventCh := make(chan interface{}, 1) +func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan any, error) { + eventCh := make(chan any, 1) eventHandler := &k8s.ResourceEventHandler{Ev: eventCh} if len(namespaces) == 0 { @@ -262,7 +262,7 @@ func (c *clientWrapper) GetIngressRoutes() []*traefikv1alpha1.IngressRoute { for ns, factory := range c.factoriesCrd { ings, err := factory.Traefik().V1alpha1().IngressRoutes().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list ingress routes in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list ingress routes in namespace %s: %v", ns, err) } result = append(result, ings...) } @@ -276,7 +276,7 @@ func (c *clientWrapper) GetIngressRouteTCPs() []*traefikv1alpha1.IngressRouteTCP for ns, factory := range c.factoriesCrd { ings, err := factory.Traefik().V1alpha1().IngressRouteTCPs().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list tcp ingress routes in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list tcp ingress routes in namespace %s: %v", ns, err) } result = append(result, ings...) } @@ -290,7 +290,7 @@ func (c *clientWrapper) GetIngressRouteUDPs() []*traefikv1alpha1.IngressRouteUDP for ns, factory := range c.factoriesCrd { ings, err := factory.Traefik().V1alpha1().IngressRouteUDPs().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list udp ingress routes in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list udp ingress routes in namespace %s: %v", ns, err) } result = append(result, ings...) } @@ -304,7 +304,7 @@ func (c *clientWrapper) GetMiddlewares() []*traefikv1alpha1.Middleware { for ns, factory := range c.factoriesCrd { middlewares, err := factory.Traefik().V1alpha1().Middlewares().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list middlewares in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list middlewares in namespace %s: %v", ns, err) } result = append(result, middlewares...) } @@ -318,7 +318,7 @@ func (c *clientWrapper) GetMiddlewareTCPs() []*traefikv1alpha1.MiddlewareTCP { for ns, factory := range c.factoriesCrd { middlewares, err := factory.Traefik().V1alpha1().MiddlewareTCPs().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list TCP middlewares in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list TCP middlewares in namespace %s: %v", ns, err) } result = append(result, middlewares...) } @@ -348,7 +348,7 @@ func (c *clientWrapper) GetTraefikServices() []*traefikv1alpha1.TraefikService { for ns, factory := range c.factoriesCrd { traefikServices, err := factory.Traefik().V1alpha1().TraefikServices().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list Traefik services in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list Traefik services in namespace %s: %v", ns, err) } result = append(result, traefikServices...) } @@ -363,7 +363,7 @@ func (c *clientWrapper) GetServersTransports() []*traefikv1alpha1.ServersTranspo for ns, factory := range c.factoriesCrd { serversTransports, err := factory.Traefik().V1alpha1().ServersTransports().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list servers transport in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list servers transport in namespace %s: %v", ns, err) } result = append(result, serversTransports...) } @@ -378,7 +378,7 @@ func (c *clientWrapper) GetTLSOptions() []*traefikv1alpha1.TLSOption { for ns, factory := range c.factoriesCrd { options, err := factory.Traefik().V1alpha1().TLSOptions().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list tls options in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list tls options in namespace %s: %v", ns, err) } result = append(result, options...) } @@ -393,7 +393,7 @@ func (c *clientWrapper) GetTLSStores() []*traefikv1alpha1.TLSStore { for ns, factory := range c.factoriesCrd { stores, err := factory.Traefik().V1alpha1().TLSStores().Lister().List(labels.Everything()) if err != nil { - log.Errorf("Failed to list tls stores in namespace %s: %v", ns, err) + log.WithoutContext().Errorf("Failed to list tls stores in namespace %s: %v", ns, err) } result = append(result, stores...) } diff --git a/pkg/provider/kubernetes/crd/client_mock_test.go b/pkg/provider/kubernetes/crd/client_mock_test.go index b80b9f0e1..5dc6e2161 100644 --- a/pkg/provider/kubernetes/crd/client_mock_test.go +++ b/pkg/provider/kubernetes/crd/client_mock_test.go @@ -40,7 +40,7 @@ type clientMock struct { traefikServices []*traefikv1alpha1.TraefikService serversTransport []*traefikv1alpha1.ServersTransport - watchChan chan interface{} + watchChan chan any } func newClientMock(paths ...string) clientMock { @@ -184,6 +184,6 @@ func (c clientMock) GetSecret(namespace, name string) (*corev1.Secret, bool, err return nil, false, nil } -func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) { +func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan any, error) { return c.watchChan, nil } diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 83f0b4846..79954288b 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -67,50 +67,6 @@ func (p *Provider) SetRouterTransform(routerTransform k8s.RouterTransform) { p.routerTransform = routerTransform } -func (p *Provider) applyRouterTransform(ctx context.Context, rt *dynamic.Router, ingress *traefikv1alpha1.IngressRoute) { - if p.routerTransform == nil { - return - } - - err := p.routerTransform.Apply(ctx, rt, ingress.Annotations) - if err != nil { - log.FromContext(ctx).WithError(err).Error("Apply router transform") - } -} - -func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { - _, err := labels.Parse(p.LabelSelector) - if err != nil { - return nil, fmt.Errorf("invalid label selector: %q", p.LabelSelector) - } - log.FromContext(ctx).Infof("label selector is: %q", p.LabelSelector) - - withEndpoint := "" - if p.Endpoint != "" { - withEndpoint = fmt.Sprintf(" with endpoint %s", p.Endpoint) - } - - var client *clientWrapper - switch { - case os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "": - log.FromContext(ctx).Infof("Creating in-cluster Provider client%s", withEndpoint) - client, err = newInClusterClient(p.Endpoint) - case os.Getenv("KUBECONFIG") != "": - log.FromContext(ctx).Infof("Creating cluster-external Provider client from KUBECONFIG %s", os.Getenv("KUBECONFIG")) - client, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG")) - default: - log.FromContext(ctx).Infof("Creating cluster-external Provider client%s", withEndpoint) - client, err = newExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath) - } - - if err != nil { - return nil, err - } - - client.labelSelector = p.LabelSelector - return client, nil -} - // Init the provider. func (p *Provider) Init() error { return nil @@ -201,6 +157,50 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. return nil } +func (p *Provider) applyRouterTransform(ctx context.Context, rt *dynamic.Router, ingress *traefikv1alpha1.IngressRoute) { + if p.routerTransform == nil { + return + } + + err := p.routerTransform.Apply(ctx, rt, ingress.Annotations) + if err != nil { + log.FromContext(ctx).WithError(err).Error("Apply router transform") + } +} + +func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { + _, err := labels.Parse(p.LabelSelector) + if err != nil { + return nil, fmt.Errorf("invalid label selector: %q", p.LabelSelector) + } + log.FromContext(ctx).Infof("label selector is: %q", p.LabelSelector) + + withEndpoint := "" + if p.Endpoint != "" { + withEndpoint = fmt.Sprintf(" with endpoint %s", p.Endpoint) + } + + var client *clientWrapper + switch { + case os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "": + log.FromContext(ctx).Infof("Creating in-cluster Provider client%s", withEndpoint) + client, err = newInClusterClient(p.Endpoint) + case os.Getenv("KUBECONFIG") != "": + log.FromContext(ctx).Infof("Creating cluster-external Provider client from KUBECONFIG %s", os.Getenv("KUBECONFIG")) + client, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG")) + default: + log.FromContext(ctx).Infof("Creating cluster-external Provider client%s", withEndpoint) + client, err = newExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath) + } + + if err != nil { + return nil, err + } + + client.labelSelector = p.LabelSelector + return client, nil +} + func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) *dynamic.Configuration { stores, tlsConfigs := buildTLSStores(ctx, client) if tlsConfigs == nil { @@ -488,7 +488,7 @@ func createPluginMiddleware(k8sClient Client, ns string, plugins map[string]apie return pcMap, nil } -func loadSecretKeys(k8sClient Client, ns string, i interface{}) (interface{}, error) { +func loadSecretKeys(k8sClient Client, ns string, i any) (any, error) { var err error switch iv := i.(type) { case string: @@ -498,14 +498,14 @@ func loadSecretKeys(k8sClient Client, ns string, i interface{}) (interface{}, er return getSecretValue(k8sClient, ns, iv) - case []interface{}: + case []any: for i := range iv { if iv[i], err = loadSecretKeys(k8sClient, ns, iv[i]); err != nil { return nil, err } } - case map[string]interface{}: + case map[string]any: for k := range iv { if iv[k], err = loadSecretKeys(k8sClient, ns, iv[k]); err != nil { return nil, err @@ -1149,12 +1149,12 @@ func getCABlocks(secret *corev1.Secret, namespace, secretName string) (string, e return "", fmt.Errorf("secret %s/%s contains neither tls.ca nor ca.crt", namespace, secretName) } -func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *safe.Pool, eventsChan <-chan interface{}) chan interface{} { +func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *safe.Pool, eventsChan <-chan any) chan any { if throttleDuration == 0 { return nil } // Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling) - eventsChanBuffered := make(chan interface{}, 1) + eventsChanBuffered := make(chan any, 1) // Run a goroutine that reads events from eventChan and does a non-blocking write to pendingEvent. // This guarantees that writing to eventChan will never block, diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index d00ca8a8e..f20503ee5 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -3411,7 +3411,7 @@ func TestLoadIngressRoutes(t *testing.T) { Middlewares: map[string]*dynamic.Middleware{ "default-test-secret": { Plugin: map[string]dynamic.PluginConf{ - "test-secret": map[string]interface{}{ + "test-secret": map[string]any{ "user": "admin", "secret": "this_is_the_secret", }, @@ -3442,10 +3442,10 @@ func TestLoadIngressRoutes(t *testing.T) { Middlewares: map[string]*dynamic.Middleware{ "default-test-secret": { Plugin: map[string]dynamic.PluginConf{ - "test-secret": map[string]interface{}{ - "secret_0": map[string]interface{}{ - "secret_1": map[string]interface{}{ - "secret_2": map[string]interface{}{ + "test-secret": map[string]any{ + "secret_0": map[string]any{ + "secret_1": map[string]any{ + "secret_2": map[string]any{ "user": "admin", "secret": "this_is_the_very_deep_secret", }, @@ -3479,8 +3479,8 @@ func TestLoadIngressRoutes(t *testing.T) { Middlewares: map[string]*dynamic.Middleware{ "default-test-secret": { Plugin: map[string]dynamic.PluginConf{ - "test-secret": map[string]interface{}{ - "secret": []interface{}{"secret_data1", "secret_data2"}, + "test-secret": map[string]any{ + "secret": []any{"secret_data1", "secret_data2"}, }, }, }, @@ -3509,13 +3509,13 @@ func TestLoadIngressRoutes(t *testing.T) { Middlewares: map[string]*dynamic.Middleware{ "default-test-secret": { Plugin: map[string]dynamic.PluginConf{ - "test-secret": map[string]interface{}{ - "users": []interface{}{ - map[string]interface{}{ + "test-secret": map[string]any{ + "users": []any{ + map[string]any{ "name": "admin", "secret": "admin_password", }, - map[string]interface{}{ + map[string]any{ "name": "user", "secret": "user_password", }, diff --git a/pkg/provider/kubernetes/crd/scheme.go b/pkg/provider/kubernetes/crd/scheme.go index 581a7983f..49a01d418 100644 --- a/pkg/provider/kubernetes/crd/scheme.go +++ b/pkg/provider/kubernetes/crd/scheme.go @@ -29,7 +29,7 @@ func init() { kschema.GroupKind{Group: containousv1alpha1.GroupName, Kind: containousv1alpha1.TraefikService{}.Kind}, ) - convert := map[interface{}]interface{}{} + convert := map[any]any{} convert[&containousv1alpha1.IngressRoute{}] = &traefikv1alpha1.IngressRoute{} convert[&containousv1alpha1.IngressRouteTCP{}] = &traefikv1alpha1.IngressRouteTCP{} convert[&containousv1alpha1.IngressRouteUDP{}] = &traefikv1alpha1.IngressRouteUDP{} @@ -41,7 +41,7 @@ func init() { convert[&containousv1alpha1.TraefikService{}] = &traefikv1alpha1.TraefikService{} for interfaceA, interfaceB := range convert { - err := traefikscheme.Scheme.AddConversionFunc(interfaceA, interfaceB, func(a, b interface{}, scope conversion.Scope) error { + err := traefikscheme.Scheme.AddConversionFunc(interfaceA, interfaceB, func(a, b any, scope conversion.Scope) error { unstruct, err := k8sruntime.DefaultUnstructuredConverter.ToUnstructured(a) if err != nil { return fmt.Errorf("failed to unstruct interface: %w", err) diff --git a/pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1/middlewaretcp.go b/pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1/middlewaretcp.go index 82c126687..0edf12ab2 100644 --- a/pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1/middlewaretcp.go +++ b/pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1/middlewaretcp.go @@ -27,8 +27,9 @@ type MiddlewareTCPSpec struct { InFlightConn *dynamic.TCPInFlightConn `json:"inFlightConn,omitempty"` // IPWhiteList defines the IPWhiteList middleware configuration. // This middleware accepts/refuses connections based on the client IP. - // Deprecated: please use IPAllowList instead. // More info: https://doc.traefik.io/traefik/v2.11/middlewares/tcp/ipwhitelist/ + // + // Deprecated: please use IPAllowList instead. IPWhiteList *dynamic.TCPIPWhiteList `json:"ipWhiteList,omitempty"` // IPAllowList defines the IPAllowList middleware configuration. // This middleware accepts/refuses connections based on the client IP. diff --git a/pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1/tlsoption.go index 74aba1a91..ab45c4665 100644 --- a/pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1/tlsoption.go +++ b/pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1/tlsoption.go @@ -43,6 +43,7 @@ type TLSOptionSpec struct { SniStrict bool `json:"sniStrict,omitempty"` // PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. // It is enabled automatically when minVersion or maxVersion is set. + // // Deprecated: https://github.com/golang/go/issues/45430 PreferServerCipherSuites bool `json:"preferServerCipherSuites,omitempty"` // ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go index 82c126687..0edf12ab2 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go @@ -27,8 +27,9 @@ type MiddlewareTCPSpec struct { InFlightConn *dynamic.TCPInFlightConn `json:"inFlightConn,omitempty"` // IPWhiteList defines the IPWhiteList middleware configuration. // This middleware accepts/refuses connections based on the client IP. - // Deprecated: please use IPAllowList instead. // More info: https://doc.traefik.io/traefik/v2.11/middlewares/tcp/ipwhitelist/ + // + // Deprecated: please use IPAllowList instead. IPWhiteList *dynamic.TCPIPWhiteList `json:"ipWhiteList,omitempty"` // IPAllowList defines the IPAllowList middleware configuration. // This middleware accepts/refuses connections based on the client IP. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go index 74aba1a91..ab45c4665 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go @@ -43,6 +43,7 @@ type TLSOptionSpec struct { SniStrict bool `json:"sniStrict,omitempty"` // PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. // It is enabled automatically when minVersion or maxVersion is set. + // // Deprecated: https://github.com/golang/go/issues/45430 PreferServerCipherSuites bool `json:"preferServerCipherSuites,omitempty"` // ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. diff --git a/pkg/provider/kubernetes/gateway/client.go b/pkg/provider/kubernetes/gateway/client.go index b69c6f35b..88223f1ba 100644 --- a/pkg/provider/kubernetes/gateway/client.go +++ b/pkg/provider/kubernetes/gateway/client.go @@ -25,14 +25,14 @@ import ( const resyncPeriod = 10 * time.Minute type resourceEventHandler struct { - ev chan<- interface{} + ev chan<- any } -func (reh *resourceEventHandler) OnAdd(obj interface{}, _ bool) { +func (reh *resourceEventHandler) OnAdd(obj any, _ bool) { eventHandlerFunc(reh.ev, obj) } -func (reh *resourceEventHandler) OnUpdate(oldObj, newObj interface{}) { +func (reh *resourceEventHandler) OnUpdate(oldObj, newObj any) { switch oldObj.(type) { case *gatev1alpha2.GatewayClass: // Skip update for gateway classes. We only manage addition or deletion for this cluster-wide resource. @@ -42,7 +42,7 @@ func (reh *resourceEventHandler) OnUpdate(oldObj, newObj interface{}) { } } -func (reh *resourceEventHandler) OnDelete(obj interface{}) { +func (reh *resourceEventHandler) OnDelete(obj any) { eventHandlerFunc(reh.ev, obj) } @@ -50,7 +50,7 @@ func (reh *resourceEventHandler) OnDelete(obj interface{}) { // WatchAll starts the watch of the Provider resources and updates the stores. // The stores can then be accessed via the Get* functions. type Client interface { - WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) + WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan any, error) GetGatewayClasses() ([]*gatev1alpha2.GatewayClass, error) UpdateGatewayStatus(gateway *gatev1alpha2.Gateway, gatewayStatus gatev1alpha2.GatewayStatus) error UpdateGatewayClassStatus(gatewayClass *gatev1alpha2.GatewayClass, condition metav1.Condition) error @@ -152,8 +152,8 @@ func newExternalClusterClient(endpoint, token, caFilePath string) (*clientWrappe } // WatchAll starts namespace-specific controllers for all relevant kinds. -func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) { - eventCh := make(chan interface{}, 1) +func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan any, error) { + eventCh := make(chan any, 1) eventHandler := &resourceEventHandler{ev: eventCh} if len(namespaces) == 0 { @@ -527,7 +527,7 @@ func (c *clientWrapper) lookupNamespace(ns string) string { // eventHandlerFunc will pass the obj on to the events channel or drop it. // This is so passing the events along won't block in the case of high volume. // The events are only used for signaling anyway so dropping a few is ok. -func eventHandlerFunc(events chan<- interface{}, obj interface{}) { +func eventHandlerFunc(events chan<- any, obj any) { select { case events <- obj: default: diff --git a/pkg/provider/kubernetes/gateway/client_mock_test.go b/pkg/provider/kubernetes/gateway/client_mock_test.go index aa8c2e567..a33dd6038 100644 --- a/pkg/provider/kubernetes/gateway/client_mock_test.go +++ b/pkg/provider/kubernetes/gateway/client_mock_test.go @@ -39,7 +39,7 @@ type clientMock struct { tcpRoutes []*gatev1alpha2.TCPRoute tlsRoutes []*gatev1alpha2.TLSRoute - watchChan chan interface{} + watchChan chan any } func newClientMock(paths ...string) clientMock { @@ -224,6 +224,6 @@ func (c clientMock) GetSecret(namespace, name string) (*corev1.Secret, bool, err return nil, false, nil } -func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) { +func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan any, error) { return c.watchChan, nil } diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index 449e743a0..a2c62a552 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "errors" "fmt" + "maps" "net" "os" "sort" @@ -62,60 +63,12 @@ func (p *Provider) SetRouterTransform(routerTransform k8s.RouterTransform) { p.routerTransform = routerTransform } -func (p *Provider) applyRouterTransform(ctx context.Context, rt *dynamic.Router, route *gatev1alpha2.HTTPRoute) { - if p.routerTransform == nil { - return - } - - err := p.routerTransform.Apply(ctx, rt, route.Annotations) - if err != nil { - log.FromContext(ctx).WithError(err).Error("Apply router transform") - } -} - // Entrypoint defines the available entry points. type Entrypoint struct { Address string HasHTTPTLSConf bool } -func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { - // Label selector validation - _, err := labels.Parse(p.LabelSelector) - if err != nil { - return nil, fmt.Errorf("invalid label selector: %q", p.LabelSelector) - } - - logger := log.FromContext(ctx) - logger.Infof("label selector is: %q", p.LabelSelector) - - withEndpoint := "" - if p.Endpoint != "" { - withEndpoint = fmt.Sprintf(" with endpoint %s", p.Endpoint) - } - - var client *clientWrapper - switch { - case os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "": - logger.Infof("Creating in-cluster Provider client%s", withEndpoint) - client, err = newInClusterClient(p.Endpoint) - case os.Getenv("KUBECONFIG") != "": - logger.Infof("Creating cluster-external Provider client from KUBECONFIG %s", os.Getenv("KUBECONFIG")) - client, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG")) - default: - logger.Infof("Creating cluster-external Provider client%s", withEndpoint) - client, err = newExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath) - } - - if err != nil { - return nil, err - } - - client.labelSelector = p.LabelSelector - - return client, nil -} - // Init the provider. func (p *Provider) Init() error { return nil @@ -195,6 +148,54 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. return nil } +func (p *Provider) applyRouterTransform(ctx context.Context, rt *dynamic.Router, route *gatev1alpha2.HTTPRoute) { + if p.routerTransform == nil { + return + } + + err := p.routerTransform.Apply(ctx, rt, route.Annotations) + if err != nil { + log.FromContext(ctx).WithError(err).Error("Apply router transform") + } +} + +func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { + // Label selector validation + _, err := labels.Parse(p.LabelSelector) + if err != nil { + return nil, fmt.Errorf("invalid label selector: %q", p.LabelSelector) + } + + logger := log.FromContext(ctx) + logger.Infof("label selector is: %q", p.LabelSelector) + + withEndpoint := "" + if p.Endpoint != "" { + withEndpoint = fmt.Sprintf(" with endpoint %s", p.Endpoint) + } + + var client *clientWrapper + switch { + case os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "": + logger.Infof("Creating in-cluster Provider client%s", withEndpoint) + client, err = newInClusterClient(p.Endpoint) + case os.Getenv("KUBECONFIG") != "": + logger.Infof("Creating cluster-external Provider client from KUBECONFIG %s", os.Getenv("KUBECONFIG")) + client, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG")) + default: + logger.Infof("Creating cluster-external Provider client%s", withEndpoint) + client, err = newExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath) + } + + if err != nil { + return nil, err + } + + client.labelSelector = p.LabelSelector + + return client, nil +} + // TODO Handle errors and update resources statuses (gatewayClass, gateway). func (p *Provider) loadConfigurationFromGateway(ctx context.Context, client Client) *dynamic.Configuration { logger := log.FromContext(ctx) @@ -795,9 +796,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li continue } - for svcName, svc := range subServices { - conf.HTTP.Services[svcName] = svc - } + maps.Copy(conf.HTTP.Services, subServices) serviceName := provider.Normalize(routerKey + "-wrr") conf.HTTP.Services[serviceName] = wrrService @@ -911,9 +910,7 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp continue } - for svcName, svc := range subServices { - conf.TCP.Services[svcName] = svc - } + maps.Copy(conf.TCP.Services, subServices) serviceName := fmt.Sprintf("%s-wrr-%d", routerKey, i) conf.TCP.Services[serviceName] = wrrService @@ -1058,9 +1055,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp continue } - for svcName, svc := range subServices { - conf.TCP.Services[svcName] = svc - } + maps.Copy(conf.TCP.Services, subServices) serviceName := fmt.Sprintf("%s-wrr-%d", routerKey, i) conf.TCP.Services[serviceName] = wrrService @@ -1686,12 +1681,12 @@ func getProtocol(portSpec corev1.ServicePort) string { return protocol } -func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *safe.Pool, eventsChan <-chan interface{}) chan interface{} { +func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *safe.Pool, eventsChan <-chan any) chan any { if throttleDuration == 0 { return nil } // Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling) - eventsChanBuffered := make(chan interface{}, 1) + eventsChanBuffered := make(chan any, 1) // Run a goroutine that reads events from eventChan and does a non-blocking write to pendingEvent. // This guarantees that writing to eventChan will never block, diff --git a/pkg/provider/kubernetes/ingress/client.go b/pkg/provider/kubernetes/ingress/client.go index a0893b771..a3e37394e 100644 --- a/pkg/provider/kubernetes/ingress/client.go +++ b/pkg/provider/kubernetes/ingress/client.go @@ -36,7 +36,7 @@ const ( // WatchAll starts the watch of the Provider resources and updates the stores. // The stores can then be accessed via the Get* functions. type Client interface { - WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) + WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan any, error) GetIngresses() []*netv1.Ingress GetIngressClasses() ([]*netv1.IngressClass, error) GetService(namespace, name string) (*corev1.Service, bool, error) @@ -132,7 +132,7 @@ func newClientImpl(clientset kclientset.Interface) *clientWrapper { } // WatchAll starts namespace-specific controllers for all relevant kinds. -func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) { +func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan any, error) { // Get and store the serverVersion for future use. serverVersionInfo, err := c.clientset.Discovery().ServerVersion() if err != nil { @@ -146,7 +146,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< c.serverVersion = serverVersion - eventCh := make(chan interface{}, 1) + eventCh := make(chan any, 1) eventHandler := &k8s.ResourceEventHandler{Ev: eventCh} if len(namespaces) == 0 { @@ -376,43 +376,6 @@ func (c *clientWrapper) UpdateIngressStatus(src *netv1.Ingress, ingStatus []netv return nil } -func (c *clientWrapper) updateIngressStatusOld(src *netv1.Ingress, ingStatus []netv1.IngressLoadBalancerIngress) error { - ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Networking().V1beta1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name) - if err != nil { - return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err) - } - - logger := log.WithoutContext().WithField("namespace", ing.Namespace).WithField("ingress", ing.Name) - - ingresses, err := convertSlice[netv1.IngressLoadBalancerIngress](ing.Status.LoadBalancer.Ingress) - if err != nil { - return err - } - - if isLoadBalancerIngressEquals(ingresses, ingStatus) { - logger.Debug("Skipping ingress status update") - return nil - } - - ingressesBeta1, err := convertSlice[netv1beta1.IngressLoadBalancerIngress](ingStatus) - if err != nil { - return err - } - - ingCopy := ing.DeepCopy() - ingCopy.Status = netv1beta1.IngressStatus{LoadBalancer: netv1beta1.IngressLoadBalancerStatus{Ingress: ingressesBeta1}} - - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) - defer cancel() - - _, err = c.clientset.NetworkingV1beta1().Ingresses(ingCopy.Namespace).UpdateStatus(ctx, ingCopy, metav1.UpdateOptions{}) - if err != nil { - return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err) - } - logger.Info("Updated ingress status") - return nil -} - // isLoadBalancerIngressEquals returns true if the given slices are equal, false otherwise. func isLoadBalancerIngressEquals(aSlice, bSlice []netv1.IngressLoadBalancerIngress) bool { if len(aSlice) != len(bSlice) { @@ -506,6 +469,48 @@ func (c *clientWrapper) GetIngressClasses() ([]*netv1.IngressClass, error) { return ics, nil } +// GetServerVersion returns the cluster server version, or an error. +func (c *clientWrapper) GetServerVersion() *version.Version { + return c.serverVersion +} + +func (c *clientWrapper) updateIngressStatusOld(src *netv1.Ingress, ingStatus []netv1.IngressLoadBalancerIngress) error { + ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Networking().V1beta1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name) + if err != nil { + return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err) + } + + logger := log.WithoutContext().WithField("namespace", ing.Namespace).WithField("ingress", ing.Name) + + ingresses, err := convertSlice[netv1.IngressLoadBalancerIngress](ing.Status.LoadBalancer.Ingress) + if err != nil { + return err + } + + if isLoadBalancerIngressEquals(ingresses, ingStatus) { + logger.Debug("Skipping ingress status update") + return nil + } + + ingressesBeta1, err := convertSlice[netv1beta1.IngressLoadBalancerIngress](ingStatus) + if err != nil { + return err + } + + ingCopy := ing.DeepCopy() + ingCopy.Status = netv1beta1.IngressStatus{LoadBalancer: netv1beta1.IngressLoadBalancerStatus{Ingress: ingressesBeta1}} + + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + + _, err = c.clientset.NetworkingV1beta1().Ingresses(ingCopy.Namespace).UpdateStatus(ctx, ingCopy, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err) + } + logger.Info("Updated ingress status") + return nil +} + // lookupNamespace returns the lookup namespace key for the given namespace. // When listening on all namespaces, it returns the client-go identifier ("") // for all-namespaces. Otherwise, it returns the given namespace. @@ -519,9 +524,14 @@ func (c *clientWrapper) lookupNamespace(ns string) string { return ns } -// GetServerVersion returns the cluster server version, or an error. -func (c *clientWrapper) GetServerVersion() *version.Version { - return c.serverVersion +// isWatchedNamespace checks to ensure that the namespace is being watched before we request +// it to ensure we don't panic by requesting an out-of-watch object. +func (c *clientWrapper) isWatchedNamespace(ns string) bool { + if c.isNamespaceAll { + return true + } + + return slices.Contains(c.watchedNamespaces, ns) } // translateNotFoundError will translate a "not found" error to a boolean return @@ -533,16 +543,6 @@ func translateNotFoundError(err error) (bool, error) { return err == nil, err } -// isWatchedNamespace checks to ensure that the namespace is being watched before we request -// it to ensure we don't panic by requesting an out-of-watch object. -func (c *clientWrapper) isWatchedNamespace(ns string) bool { - if c.isNamespaceAll { - return true - } - - return slices.Contains(c.watchedNamespaces, ns) -} - // IngressClass objects are supported since Kubernetes v1.18. // See https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class func supportsIngressClass(serverVersion *version.Version) bool { diff --git a/pkg/provider/kubernetes/ingress/client_mock_test.go b/pkg/provider/kubernetes/ingress/client_mock_test.go index 3ee622c8d..fa3151b05 100644 --- a/pkg/provider/kubernetes/ingress/client_mock_test.go +++ b/pkg/provider/kubernetes/ingress/client_mock_test.go @@ -27,7 +27,7 @@ type clientMock struct { apiEndpointsError error apiIngressStatusError error - watchChan chan interface{} + watchChan chan any } func newClientMock(serverVersion string, paths ...string) clientMock { @@ -128,7 +128,7 @@ func (c clientMock) GetIngressClasses() ([]*netv1.IngressClass, error) { return c.ingressClasses, nil } -func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) { +func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan any, error) { return c.watchChan, nil } diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 77184492b..8e3d2114d 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -58,17 +58,6 @@ func (p *Provider) SetRouterTransform(routerTransform k8s.RouterTransform) { p.routerTransform = routerTransform } -func (p *Provider) applyRouterTransform(ctx context.Context, rt *dynamic.Router, ingress *netv1.Ingress) { - if p.routerTransform == nil { - return - } - - err := p.routerTransform.Apply(ctx, rt, ingress.Annotations) - if err != nil { - log.FromContext(ctx).WithError(err).Error("Apply router transform") - } -} - // EndpointIngress holds the endpoint information for the Kubernetes provider. type EndpointIngress struct { IP string `description:"IP used for Kubernetes Ingress endpoints." json:"ip,omitempty" toml:"ip,omitempty" yaml:"ip,omitempty"` @@ -76,42 +65,6 @@ type EndpointIngress struct { PublishedService string `description:"Published Kubernetes Service to copy status from." json:"publishedService,omitempty" toml:"publishedService,omitempty" yaml:"publishedService,omitempty"` } -func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { - _, err := labels.Parse(p.LabelSelector) - if err != nil { - return nil, fmt.Errorf("invalid ingress label selector: %q", p.LabelSelector) - } - - logger := log.FromContext(ctx) - - logger.Infof("ingress label selector is: %q", p.LabelSelector) - - withEndpoint := "" - if p.Endpoint != "" { - withEndpoint = fmt.Sprintf(" with endpoint %v", p.Endpoint) - } - - var cl *clientWrapper - switch { - case os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "": - logger.Infof("Creating in-cluster Provider client%s", withEndpoint) - cl, err = newInClusterClient(p.Endpoint) - case os.Getenv("KUBECONFIG") != "": - logger.Infof("Creating cluster-external Provider client from KUBECONFIG %s", os.Getenv("KUBECONFIG")) - cl, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG")) - default: - logger.Infof("Creating cluster-external Provider client%s", withEndpoint) - cl, err = newExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath) - } - - if err != nil { - return nil, err - } - - cl.ingressLabelSelector = p.LabelSelector - return cl, nil -} - // Init the provider. func (p *Provider) Init() error { return nil @@ -199,6 +152,47 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. return nil } +func (p *Provider) applyRouterTransform(ctx context.Context, rt *dynamic.Router, ingress *netv1.Ingress) { + if p.routerTransform == nil { + return + } + + err := p.routerTransform.Apply(ctx, rt, ingress.Annotations) + if err != nil { + log.FromContext(ctx).WithError(err).Error("Apply router transform") + } +} + +func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { + _, err := labels.Parse(p.LabelSelector) + if err != nil { + return nil, fmt.Errorf("invalid ingress label selector: %q", p.LabelSelector) + } + + logger := log.FromContext(ctx) + + logger.Debugf("Creating in-cluster Provider client") + + var client *clientWrapper + + switch { + case os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "": + client, err = newInClusterClient(p.Endpoint) + case os.Getenv("KUBECONFIG") != "": + client, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG")) + default: + client, err = newExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath) + } + + if err != nil { + return nil, err + } + + client.ingressLabelSelector = p.LabelSelector + + return client, nil +} + func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Client) *dynamic.Configuration { conf := &dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ @@ -404,8 +398,8 @@ func (p *Provider) updateIngressStatus(ing *netv1.Ingress, k8sClient Client) err } if exists && service.Status.LoadBalancer.Ingress == nil { - // service exists, but has no Load Balancer status - log.Debugf("Skipping updating Ingress %s/%s due to service %s having no status set", ing.Namespace, ing.Name, p.IngressEndpoint.PublishedService) + // service exists but has no Load Balancer status + log.WithoutContext().Debugf("Skipping updating Ingress %s/%s due to service %s having no status set", ing.Namespace, ing.Name, p.IngressEndpoint.PublishedService) return nil } @@ -715,13 +709,13 @@ func loadRouter(rule netv1.IngressRule, pa netv1.HTTPIngressPath, rtConfig *Rout return rt } -func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *safe.Pool, eventsChan <-chan interface{}) chan interface{} { +func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *safe.Pool, eventsChan <-chan any) chan any { if throttleDuration == 0 { return nil } // Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling). - eventsChanBuffered := make(chan interface{}, 1) + eventsChanBuffered := make(chan any, 1) // Run a goroutine that reads events from eventChan and does a // non-blocking write to pendingEvent. This guarantees that writing to diff --git a/pkg/provider/kubernetes/k8s/event_handler.go b/pkg/provider/kubernetes/k8s/event_handler.go index ed78f7d57..be626d80e 100644 --- a/pkg/provider/kubernetes/k8s/event_handler.go +++ b/pkg/provider/kubernetes/k8s/event_handler.go @@ -7,38 +7,38 @@ import ( // ResourceEventHandler handles Add, Update or Delete Events for resources. type ResourceEventHandler struct { - Ev chan<- interface{} + Ev chan<- any } // OnAdd is called on Add Events. -func (reh *ResourceEventHandler) OnAdd(obj interface{}, _ bool) { +func (reh *ResourceEventHandler) OnAdd(obj any, _ bool) { eventHandlerFunc(reh.Ev, obj) } // OnUpdate is called on Update Events. // Ignores useless changes. -func (reh *ResourceEventHandler) OnUpdate(oldObj, newObj interface{}) { +func (reh *ResourceEventHandler) OnUpdate(oldObj, newObj any) { if objChanged(oldObj, newObj) { eventHandlerFunc(reh.Ev, newObj) } } // OnDelete is called on Delete Events. -func (reh *ResourceEventHandler) OnDelete(obj interface{}) { +func (reh *ResourceEventHandler) OnDelete(obj any) { eventHandlerFunc(reh.Ev, obj) } // eventHandlerFunc will pass the obj on to the events channel or drop it. // This is so passing the events along won't block in the case of high volume. // The events are only used for signaling anyway so dropping a few is ok. -func eventHandlerFunc(events chan<- interface{}, obj interface{}) { +func eventHandlerFunc(events chan<- any, obj any) { select { case events <- obj: default: } } -func objChanged(oldObj, newObj interface{}) bool { +func objChanged(oldObj, newObj any) bool { if oldObj == nil || newObj == nil { return true } diff --git a/pkg/provider/kubernetes/k8s/event_handler_test.go b/pkg/provider/kubernetes/k8s/event_handler_test.go index 323b4f550..4d88adb77 100644 --- a/pkg/provider/kubernetes/k8s/event_handler_test.go +++ b/pkg/provider/kubernetes/k8s/event_handler_test.go @@ -12,8 +12,8 @@ import ( func Test_detectChanges(t *testing.T) { tests := []struct { name string - oldObj interface{} - newObj interface{} + oldObj any + newObj any want bool }{ { diff --git a/pkg/provider/marathon/config.go b/pkg/provider/marathon/config.go index 4f9216e9b..24a4b4941 100644 --- a/pkg/provider/marathon/config.go +++ b/pkg/provider/marathon/config.go @@ -386,8 +386,7 @@ func processPorts(app marathon.Application, task marathon.Task, serverPort strin } } - if strings.HasPrefix(serverPort, "name:") { - name := strings.TrimPrefix(serverPort, "name:") + if name, ok := strings.CutPrefix(serverPort, "name:"); ok { port := retrieveNamedPort(app, name) if port == 0 { @@ -403,8 +402,7 @@ func processPorts(app marathon.Application, task marathon.Task, serverPort strin } portIndex := 0 - if strings.HasPrefix(serverPort, "index:") { - indexString := strings.TrimPrefix(serverPort, "index:") + if indexString, ok := strings.CutPrefix(serverPort, "index:"); ok { index, err := strconv.Atoi(indexString) if err != nil { return 0, err diff --git a/pkg/provider/marathon/marathon.go b/pkg/provider/marathon/marathon.go index 14c08f832..279a9d0d2 100644 --- a/pkg/provider/marathon/marathon.go +++ b/pkg/provider/marathon/marathon.go @@ -87,8 +87,8 @@ type Basic struct { // Init the provider. func (p *Provider) Init() error { fm := template.FuncMap{ - "strsToItfs": func(values []string) []interface{} { - var r []interface{} + "strsToItfs": func(values []string) []any { + var r []any for _, v := range values { r = append(r, v) } diff --git a/pkg/provider/marathon/readiness.go b/pkg/provider/marathon/readiness.go index df6559968..4e94c37e9 100644 --- a/pkg/provider/marathon/readiness.go +++ b/pkg/provider/marathon/readiness.go @@ -98,7 +98,7 @@ func (rc *readinessChecker) Do(task marathon.Task, app marathon.Application) boo // An unparseable start time should never occur; if it does, we assume the // problem should be surfaced as quickly as possible, which is easiest if // we shun the task from rotation. - log.Warnf("Failed to parse start-time %s of task %s from application %s: %s (assuming unready)", task.StartedAt, task.ID, app.ID, err) + log.WithoutContext().Warnf("Failed to parse start-time %s of task %s from application %s: %s (assuming unready)", task.StartedAt, task.ID, app.ID, err) return false } @@ -115,8 +115,8 @@ func (rc *readinessChecker) Do(task marathon.Task, app marathon.Application) boo return true } -func (rc *readinessChecker) tracef(format string, args ...interface{}) { +func (rc *readinessChecker) tracef(format string, args ...any) { if rc.traceLogging { - log.Debugf(readinessLogHeader+format, args...) + log.WithoutContext().Debugf(readinessLogHeader+format, args...) } } diff --git a/pkg/provider/rancher/rancher.go b/pkg/provider/rancher/rancher.go index 8de7ad862..797b18d77 100644 --- a/pkg/provider/rancher/rancher.go +++ b/pkg/provider/rancher/rancher.go @@ -82,17 +82,6 @@ func (p *Provider) Init() error { return nil } -func (p *Provider) createClient(ctx context.Context) (rancher.Client, error) { - metadataServiceURL := fmt.Sprintf("http://rancher-metadata.rancher.internal/%s", p.Prefix) - client, err := rancher.NewClientAndWait(metadataServiceURL) - if err != nil { - log.FromContext(ctx).Errorf("Failed to create Rancher metadata service client: %v", err) - return nil, err - } - - return client, nil -} - // Provide allows the rancher provider to provide configurations to traefik using the given configuration channel. func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { pool.GoCtx(func(routineCtx context.Context) { @@ -150,6 +139,17 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. return nil } +func (p *Provider) createClient(ctx context.Context) (rancher.Client, error) { + metadataServiceURL := fmt.Sprintf("http://rancher-metadata.rancher.internal/%s", p.Prefix) + client, err := rancher.NewClientAndWait(metadataServiceURL) + if err != nil { + log.FromContext(ctx).Errorf("Failed to create Rancher metadata service client: %v", err) + return nil, err + } + + return client, nil +} + func (p *Provider) intervalPoll(ctx context.Context, client rancher.Client, updateConfiguration func(string)) { ticker := time.NewTicker(time.Duration(p.RefreshSeconds) * time.Second) defer ticker.Stop() diff --git a/pkg/redactor/redactor.go b/pkg/redactor/redactor.go index ccdcc5b18..ed11a062f 100644 --- a/pkg/redactor/redactor.go +++ b/pkg/redactor/redactor.go @@ -20,11 +20,11 @@ const ( // Anonymize redacts the configuration fields that do not have an export=true struct tag. // It returns the resulting marshaled configuration. -func Anonymize(baseConfig interface{}) (string, error) { +func Anonymize(baseConfig any) (string, error) { return anonymize(baseConfig, false) } -func anonymize(baseConfig interface{}, indent bool) (string, error) { +func anonymize(baseConfig any, indent bool) (string, error) { conf, err := do(baseConfig, tagExport, true, indent) if err != nil { return "", err @@ -34,17 +34,17 @@ func anonymize(baseConfig interface{}, indent bool) (string, error) { // RemoveCredentials redacts the configuration fields that have a loggable=false struct tag. // It returns the resulting marshaled configuration. -func RemoveCredentials(baseConfig interface{}) (string, error) { +func RemoveCredentials(baseConfig any) (string, error) { return removeCredentials(baseConfig, false) } -func removeCredentials(baseConfig interface{}, indent bool) (string, error) { +func removeCredentials(baseConfig any, indent bool) (string, error) { return do(baseConfig, tagLoggable, false, indent) } // do marshals the given configuration, while redacting some of the fields // respectively to the given tag. -func do(baseConfig interface{}, tag string, redactByDefault, indent bool) (string, error) { +func do(baseConfig any, tag string, redactByDefault, indent bool) (string, error) { anomConfig, err := copystructure.Copy(baseConfig) if err != nil { return "", err @@ -70,7 +70,7 @@ func doOnJSON(input string) string { } func doOnStruct(field reflect.Value, tag string, redactByDefault bool) error { - if field.Type().AssignableTo(reflect.TypeOf(dynamic.PluginConf{})) { + if field.Type().AssignableTo(reflect.TypeFor[dynamic.PluginConf]()) { resetPlugin(field) return nil } @@ -164,7 +164,7 @@ func reset(field reflect.Value, name string) error { } case reflect.String: if field.String() != "" { - if field.Type().AssignableTo(reflect.TypeOf(tls.FileOrContent(""))) { + if field.Type().AssignableTo(reflect.TypeFor[tls.FileOrContent]()) { field.Set(reflect.ValueOf(tls.FileOrContent(maskShort))) } else { field.Set(reflect.ValueOf(maskShort)) @@ -211,7 +211,7 @@ func isExported(f reflect.StructField) bool { return true } -func marshal(anomConfig interface{}, indent bool) ([]byte, error) { +func marshal(anomConfig any, indent bool) ([]byte, error) { if indent { return json.MarshalIndent(anomConfig, "", " ") } diff --git a/pkg/rules/parser.go b/pkg/rules/parser.go index 2555c01b9..e3adc53d6 100644 --- a/pkg/rules/parser.go +++ b/pkg/rules/parser.go @@ -2,6 +2,7 @@ package rules import ( "fmt" + "slices" "strings" "github.com/vulcand/predicate" @@ -28,7 +29,7 @@ type Tree struct { // NewParser constructs a parser for the given matchers. func NewParser(matchers []string) (predicate.Parser, error) { - parserFuncs := make(map[string]interface{}) + parserFuncs := make(map[string]any) for _, matcherName := range matchers { fn := func(value ...string) TreeBuilder { @@ -104,10 +105,8 @@ func (tree *Tree) ParseMatchers(matchers []string) []string { case and, or: return append(tree.RuleLeft.ParseMatchers(matchers), tree.RuleRight.ParseMatchers(matchers)...) default: - for _, matcher := range matchers { - if tree.Matcher == matcher { - return lower(tree.Value) - } + if slices.Contains(matchers, tree.Matcher) { + return lower(tree.Value) } return nil diff --git a/pkg/safe/routine.go b/pkg/safe/routine.go index 71af2530d..e720cf30d 100644 --- a/pkg/safe/routine.go +++ b/pkg/safe/routine.go @@ -49,7 +49,7 @@ func Go(goroutine func()) { } // GoWithRecover starts a recoverable goroutine using given customRecover() function. -func GoWithRecover(goroutine func(), customRecover func(err interface{})) { +func GoWithRecover(goroutine func(), customRecover func(err any)) { go func() { defer func() { if err := recover(); err != nil { @@ -60,7 +60,7 @@ func GoWithRecover(goroutine func(), customRecover func(err interface{})) { }() } -func defaultRecoverGoroutine(err interface{}) { +func defaultRecoverGoroutine(err any) { logger := log.WithoutContext() logger.Errorf("Error in Go routine: %s", err) logger.Errorf("Stack: %s", debug.Stack()) diff --git a/pkg/safe/routine_test.go b/pkg/safe/routine_test.go index 3fa403e78..54c81bc1b 100644 --- a/pkg/safe/routine_test.go +++ b/pkg/safe/routine_test.go @@ -29,6 +29,7 @@ func TestNewPoolContext(t *testing.T) { type fakeRoutine struct { sync.Mutex + started bool startSig chan bool } diff --git a/pkg/safe/safe.go b/pkg/safe/safe.go index 8b44beb3f..822006cb8 100644 --- a/pkg/safe/safe.go +++ b/pkg/safe/safe.go @@ -6,24 +6,24 @@ import ( // Safe contains a thread-safe value. type Safe struct { - value interface{} + value any lock sync.RWMutex } // New create a new Safe instance given a value. -func New(value interface{}) *Safe { +func New(value any) *Safe { return &Safe{value: value, lock: sync.RWMutex{}} } // Get returns the value. -func (s *Safe) Get() interface{} { +func (s *Safe) Get() any { s.lock.RLock() defer s.lock.RUnlock() return s.value } // Set sets a new value. -func (s *Safe) Set(value interface{}) { +func (s *Safe) Set(value any) { s.lock.Lock() defer s.lock.Unlock() s.value = value diff --git a/pkg/server/middleware/plugins.go b/pkg/server/middleware/plugins.go index dfde88468..a58df8c9e 100644 --- a/pkg/server/middleware/plugins.go +++ b/pkg/server/middleware/plugins.go @@ -13,16 +13,16 @@ import ( // PluginsBuilder the plugin's builder interface. type PluginsBuilder interface { - Build(pName string, config map[string]interface{}, middlewareName string) (plugins.Constructor, error) + Build(pName string, config map[string]any, middlewareName string) (plugins.Constructor, error) } -func findPluginConfig(rawConfig map[string]dynamic.PluginConf) (string, map[string]interface{}, error) { +func findPluginConfig(rawConfig map[string]dynamic.PluginConf) (string, map[string]any, error) { if len(rawConfig) != 1 { return "", nil, errors.New("invalid configuration: no configuration or too many plugin definition") } var pluginType string - var rawPluginConfig map[string]interface{} + var rawPluginConfig map[string]any for pType, pConfig := range rawConfig { pluginType = pType diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index f01210bfa..5fc2aa2b2 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -64,14 +64,6 @@ func NewManager(conf *runtime.Configuration, } } -func (m *Manager) getHTTPRouters(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*runtime.RouterInfo { - if m.conf != nil { - return m.conf.GetRoutersByEntryPoints(ctx, entryPoints, tls) - } - - return make(map[string]map[string]*runtime.RouterInfo) -} - // BuildHandlers Builds handler for all entry points. func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, tls bool) map[string]http.Handler { entryPointHandlers := make(map[string]http.Handler) @@ -115,6 +107,14 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t return entryPointHandlers } +func (m *Manager) getHTTPRouters(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*runtime.RouterInfo { + if m.conf != nil { + return m.conf.GetRoutersByEntryPoints(ctx, entryPoints, tls) + } + + return make(map[string]map[string]*runtime.RouterInfo) +} + func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*runtime.RouterInfo) (http.Handler, error) { muxer, err := httpmuxer.NewMuxer() if err != nil { diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index add135bc5..e374c8167 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -1066,7 +1066,7 @@ func BenchmarkRouterServe(b *testing.B) { reqHost := requestdecorator.New(nil) b.ReportAllocs() - for range b.N { + for b.Loop() { reqHost.ServeHTTP(w, req, handlers["web"].ServeHTTP) } } @@ -1101,7 +1101,7 @@ func BenchmarkService(b *testing.B) { handler, _ := serviceManager.BuildHTTP(b.Context(), "foo-service") b.ReportAllocs() - for range b.N { + for b.Loop() { handler.ServeHTTP(w, req) } } diff --git a/pkg/server/router/tcp/manager.go b/pkg/server/router/tcp/manager.go index 45007fbd8..de4d94d2e 100644 --- a/pkg/server/router/tcp/manager.go +++ b/pkg/server/router/tcp/manager.go @@ -26,6 +26,16 @@ type middlewareBuilder interface { BuildChain(ctx context.Context, names []string) *tcp.Chain } +// Manager is a route/router manager. +type Manager struct { + serviceManager *tcpservice.Manager + middlewaresBuilder middlewareBuilder + httpHandlers map[string]http.Handler + httpsHandlers map[string]http.Handler + tlsManager *traefiktls.Manager + conf *runtime.Configuration +} + // NewManager Creates a new Manager. func NewManager(conf *runtime.Configuration, serviceManager *tcpservice.Manager, @@ -44,32 +54,6 @@ func NewManager(conf *runtime.Configuration, } } -// Manager is a route/router manager. -type Manager struct { - serviceManager *tcpservice.Manager - middlewaresBuilder middlewareBuilder - httpHandlers map[string]http.Handler - httpsHandlers map[string]http.Handler - tlsManager *traefiktls.Manager - conf *runtime.Configuration -} - -func (m *Manager) getTCPRouters(ctx context.Context, entryPoints []string) map[string]map[string]*runtime.TCPRouterInfo { - if m.conf != nil { - return m.conf.GetTCPRoutersByEntryPoints(ctx, entryPoints) - } - - return make(map[string]map[string]*runtime.TCPRouterInfo) -} - -func (m *Manager) getHTTPRouters(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*runtime.RouterInfo { - if m.conf != nil { - return m.conf.GetRoutersByEntryPoints(ctx, entryPoints, tls) - } - - return make(map[string]map[string]*runtime.RouterInfo) -} - // BuildHandlers builds the handlers for the given entrypoints. func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) map[string]*Router { entryPointsRouters := m.getTCPRouters(rootCtx, entryPoints) @@ -91,6 +75,22 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m return entryPointHandlers } +func (m *Manager) getTCPRouters(ctx context.Context, entryPoints []string) map[string]map[string]*runtime.TCPRouterInfo { + if m.conf != nil { + return m.conf.GetTCPRoutersByEntryPoints(ctx, entryPoints) + } + + return make(map[string]map[string]*runtime.TCPRouterInfo) +} + +func (m *Manager) getHTTPRouters(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*runtime.RouterInfo { + if m.conf != nil { + return m.conf.GetRoutersByEntryPoints(ctx, entryPoints, tls) + } + + return make(map[string]map[string]*runtime.RouterInfo) +} + type nameAndConfig struct { routerName string // just so we have it as additional information when logging TLSConfig *tls.Config diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index ae110c3c3..ad116cd0a 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -200,27 +200,6 @@ 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) { - tlsConn := tls.Server(conn, r.httpsTLSConfig) - defer tlsConn.Close() - - // This avoids stale connections when validating the ACME challenge, - // as we expect a validation request to complete in a short period of time. - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - if err := tlsConn.HandshakeContext(ctx); err != nil { - log.FromContext(ctx).WithError(err).Debug("Error during ACME-TLS/1 handshake") - } - }) -} - // AddRoute defines a handler for the given rule. func (r *Router) AddRoute(rule string, priority int, target tcp.Handler) error { return r.muxerTCP.AddRoute(rule, priority, target) @@ -320,17 +299,38 @@ func (r *Router) EnableACMETLSPassthrough() { r.acmeTLSPassthrough = true } +// 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) { + tlsConn := tls.Server(conn, r.httpsTLSConfig) + defer tlsConn.Close() + + // This avoids stale connections when validating the ACME challenge, + // as we expect a validation request to complete in a short period of time. + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + if err := tlsConn.HandshakeContext(ctx); err != nil { + log.FromContext(ctx).WithError(err).Debug("Error during ACME-TLS/1 handshake") + } + }) +} + // Conn is a connection proxy that handles Peeked bytes. type Conn struct { - // Peeked are the bytes that have been read from Conn for the purposes of route matching, - // but have not yet been consumed by Read calls. - // It set to nil by Read when fully consumed. - Peeked []byte - // Conn is the underlying connection. // It can be type asserted against *net.TCPConn or other types as needed. // It should not be read from directly unless Peeked is nil. tcp.WriteCloser + + // Peeked are the bytes that have been read from Conn for the purposes of route matching, + // but have not yet been consumed by Read calls. + // It set to nil by Read when fully consumed. + Peeked []byte } // Read reads bytes from the connection (using the buffer prior to actually reading). @@ -439,8 +439,9 @@ func getPeeked(br *bufio.Reader) string { // helloSniffConn is a net.Conn that reads from r, fails on Writes, // and crashes otherwise. type helloSniffConn struct { - r io.Reader net.Conn // nil; crash on any unexpected use + + r io.Reader } // Read reads from the underlying reader. diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go index ecf556b23..9783bd591 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -31,6 +31,7 @@ type checkRouter func(addr string, timeout time.Duration) error type httpForwarder struct { net.Listener + connChan chan net.Conn errChan chan error } diff --git a/pkg/server/router/udp/router.go b/pkg/server/router/udp/router.go index dedea33e9..38bca1aac 100644 --- a/pkg/server/router/udp/router.go +++ b/pkg/server/router/udp/router.go @@ -12,6 +12,12 @@ import ( "github.com/traefik/traefik/v2/pkg/udp" ) +// Manager is a route/router manager. +type Manager struct { + serviceManager *udpservice.Manager + conf *runtime.Configuration +} + // NewManager Creates a new Manager. func NewManager(conf *runtime.Configuration, serviceManager *udpservice.Manager, @@ -22,20 +28,6 @@ func NewManager(conf *runtime.Configuration, } } -// Manager is a route/router manager. -type Manager struct { - serviceManager *udpservice.Manager - conf *runtime.Configuration -} - -func (m *Manager) getUDPRouters(ctx context.Context, entryPoints []string) map[string]map[string]*runtime.UDPRouterInfo { - if m.conf != nil { - return m.conf.GetUDPRoutersByEntryPoints(ctx, entryPoints) - } - - return make(map[string]map[string]*runtime.UDPRouterInfo) -} - // BuildHandlers builds the handlers for the given entrypoints. func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) map[string]udp.Handler { entryPointsRouters := m.getUDPRouters(rootCtx, entryPoints) @@ -60,6 +52,14 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m return entryPointHandlers } +func (m *Manager) getUDPRouters(ctx context.Context, entryPoints []string) map[string]map[string]*runtime.UDPRouterInfo { + if m.conf != nil { + return m.conf.GetUDPRoutersByEntryPoints(ctx, entryPoints) + } + + return make(map[string]map[string]*runtime.UDPRouterInfo) +} + func (m *Manager) buildEntryPointHandlers(ctx context.Context, configs map[string]*runtime.UDPRouterInfo) []udp.Handler { var rtNames []string for routerName := range configs { diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 47d5c941c..b97caa20f 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -56,6 +56,7 @@ type connState struct { type httpForwarder struct { net.Listener + connChan chan net.Conn errChan chan error } @@ -369,6 +370,7 @@ func (e *TCPEntryPoint) SwitchRouter(rt *tcprouter.Router) { // connection type that was found to satisfy WriteCloser. type writeCloserWrapper struct { net.Conn + writeCloser tcp.WriteCloser } @@ -506,12 +508,6 @@ func (c *connectionTracker) RemoveConnection(conn net.Conn) { delete(c.conns, conn) } -func (c *connectionTracker) isEmpty() bool { - c.lock.RLock() - defer c.lock.RUnlock() - return len(c.conns) == 0 -} - // Shutdown wait for the connection closing. func (c *connectionTracker) Shutdown(ctx context.Context) error { ticker := time.NewTicker(500 * time.Millisecond) @@ -540,6 +536,12 @@ func (c *connectionTracker) Close() { } } +func (c *connectionTracker) isEmpty() bool { + c.lock.RLock() + defer c.lock.RUnlock() + return len(c.conns) == 0 +} + type stoppable interface { Shutdown(ctx context.Context) error Close() error @@ -678,8 +680,9 @@ func newTrackedConnection(conn tcp.WriteCloser, tracker *connectionTracker) *tra } type trackedConnection struct { - tracker *connectionTracker tcp.WriteCloser + + tracker *connectionTracker } func (t *trackedConnection) Close() error { diff --git a/pkg/server/server_entrypoint_tcp_http3.go b/pkg/server/server_entrypoint_tcp_http3.go index 324f0a93e..d8821267c 100644 --- a/pkg/server/server_entrypoint_tcp_http3.go +++ b/pkg/server/server_entrypoint_tcp_http3.go @@ -80,14 +80,14 @@ func (e *http3server) Switch(rt *tcprouter.Router) { e.getter = rt.GetTLSGetClientInfo() } +func (e *http3server) Shutdown(_ context.Context) error { + // TODO: use e.Server.CloseGracefully() when available. + return e.Server.Close() +} + func (e *http3server) getGetConfigForClient(info *tls.ClientHelloInfo) (*tls.Config, error) { e.lock.RLock() defer e.lock.RUnlock() return e.getter(info) } - -func (e *http3server) Shutdown(_ context.Context) error { - // TODO: use e.Server.CloseGracefully() when available. - return e.Server.Close() -} diff --git a/pkg/server/server_signals.go b/pkg/server/server_signals.go index d1ad8d51a..f48221726 100644 --- a/pkg/server/server_signals.go +++ b/pkg/server/server_signals.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows package server diff --git a/pkg/server/server_signals_windows.go b/pkg/server/server_signals_windows.go index 8bfd91fcf..e2566f020 100644 --- a/pkg/server/server_signals_windows.go +++ b/pkg/server/server_signals_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows package server diff --git a/pkg/server/service/bufferpool.go b/pkg/server/service/bufferpool.go index 948611502..e8ff48fc7 100644 --- a/pkg/server/service/bufferpool.go +++ b/pkg/server/service/bufferpool.go @@ -7,7 +7,7 @@ const bufferPoolSize = 32 * 1024 func newBufferPool() *bufferPool { return &bufferPool{ pool: sync.Pool{ - New: func() interface{} { + New: func() any { return make([]byte, bufferPoolSize) }, }, diff --git a/pkg/server/service/loadbalancer/failover/failover_test.go b/pkg/server/service/loadbalancer/failover/failover_test.go index 410557ee9..d32a40894 100644 --- a/pkg/server/service/loadbalancer/failover/failover_test.go +++ b/pkg/server/service/loadbalancer/failover/failover_test.go @@ -12,6 +12,7 @@ import ( type responseRecorder struct { *httptest.ResponseRecorder + save map[string]int sequence []string status []int diff --git a/pkg/server/service/loadbalancer/mirror/mirror.go b/pkg/server/service/loadbalancer/mirror/mirror.go index f0d12a952..630bf5dee 100644 --- a/pkg/server/service/loadbalancer/mirror/mirror.go +++ b/pkg/server/service/loadbalancer/mirror/mirror.go @@ -43,38 +43,15 @@ func New(handler http.Handler, pool *safe.Pool, maxBodySize int64, hc *dynamic.H } } -func (m *Mirroring) inc() uint64 { - m.lock.Lock() - defer m.lock.Unlock() - m.total++ - return m.total -} - type mirrorHandler struct { http.Handler + percent int lock sync.RWMutex count uint64 } -func (m *Mirroring) getActiveMirrors() []http.Handler { - total := m.inc() - - var mirrors []http.Handler - for _, handler := range m.mirrorHandlers { - handler.lock.Lock() - if handler.count*100 < total*uint64(handler.percent) { - handler.count++ - handler.lock.Unlock() - mirrors = append(mirrors, handler) - } else { - handler.lock.Unlock() - } - } - return mirrors -} - func (m *Mirroring) ServeHTTP(rw http.ResponseWriter, req *http.Request) { mirrors := m.getActiveMirrors() if len(mirrors) == 0 { @@ -163,6 +140,30 @@ func (m *Mirroring) RegisterStatusUpdater(fn func(up bool)) error { return nil } +func (m *Mirroring) inc() uint64 { + m.lock.Lock() + defer m.lock.Unlock() + m.total++ + return m.total +} + +func (m *Mirroring) getActiveMirrors() []http.Handler { + total := m.inc() + + var mirrors []http.Handler + for _, handler := range m.mirrorHandlers { + handler.lock.Lock() + if handler.count*100 < total*uint64(handler.percent) { + handler.count++ + handler.lock.Unlock() + mirrors = append(mirrors, handler) + } else { + handler.lock.Unlock() + } + } + return mirrors +} + type blackHoleResponseWriter struct{} func (b blackHoleResponseWriter) Flush() {} diff --git a/pkg/server/service/loadbalancer/wrr/wrr.go b/pkg/server/service/loadbalancer/wrr/wrr.go index ecca04b62..54d65a76f 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr.go +++ b/pkg/server/service/loadbalancer/wrr/wrr.go @@ -15,6 +15,7 @@ import ( type namedHandler struct { http.Handler + name string weight float64 deadline float64 @@ -82,7 +83,7 @@ func (b *Balancer) Swap(i, j int) { } // Push implements heap.Interface for pushing an item into the heap. -func (b *Balancer) Push(x interface{}) { +func (b *Balancer) Push(x any) { h, ok := x.(*namedHandler) if !ok { return @@ -93,7 +94,7 @@ func (b *Balancer) Push(x interface{}) { // Pop implements heap.Interface for popping an item from the heap. // It panics if b.Len() < 1. -func (b *Balancer) Pop() interface{} { +func (b *Balancer) Pop() any { h := b.handlers[len(b.handlers)-1] b.handlers = b.handlers[0 : len(b.handlers)-1] return h @@ -151,36 +152,6 @@ func (b *Balancer) RegisterStatusUpdater(fn func(up bool)) error { var errNoAvailableServer = errors.New("no available server") -func (b *Balancer) nextServer() (*namedHandler, error) { - b.handlersMu.Lock() - defer b.handlersMu.Unlock() - - if len(b.handlers) == 0 { - return nil, errors.New("no servers in the pool") - } - if len(b.status) == 0 { - return nil, errNoAvailableServer - } - - var handler *namedHandler - for { - // Pick handler with closest deadline. - handler = heap.Pop(b).(*namedHandler) - - // curDeadline should be handler's deadline so that new added entry would have a fair competition environment with the old ones. - b.curDeadline = handler.deadline - handler.deadline += 1 / handler.weight - - heap.Push(b, handler) - if _, ok := b.status[handler.name]; ok { - break - } - } - - log.WithoutContext().Debugf("Service selected by WRR: %s", handler.name) - return handler, nil -} - func (b *Balancer) ServeHTTP(w http.ResponseWriter, req *http.Request) { if b.stickyCookie != nil { cookie, err := req.Cookie(b.stickyCookie.name) @@ -247,6 +218,36 @@ func (b *Balancer) AddService(name string, handler http.Handler, weight *int) { b.handlersMu.Unlock() } +func (b *Balancer) nextServer() (*namedHandler, error) { + b.handlersMu.Lock() + defer b.handlersMu.Unlock() + + if len(b.handlers) == 0 { + return nil, errors.New("no servers in the pool") + } + if len(b.status) == 0 { + return nil, errNoAvailableServer + } + + var handler *namedHandler + for { + // Pick handler with closest deadline. + handler = heap.Pop(b).(*namedHandler) + + // curDeadline should be handler's deadline so that new added entry would have a fair competition environment with the old ones. + b.curDeadline = handler.deadline + handler.deadline += 1 / handler.weight + + heap.Push(b, handler) + if _, ok := b.status[handler.name]; ok { + break + } + } + + log.WithoutContext().Debugf("Service selected by WRR: %s", handler.name) + return handler, nil +} + func hash(input string) string { hasher := fnv.New64() // We purposely ignore the error because the implementation always returns nil. diff --git a/pkg/server/service/loadbalancer/wrr/wrr_test.go b/pkg/server/service/loadbalancer/wrr/wrr_test.go index 4a953128d..1ded0cb5f 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr_test.go +++ b/pkg/server/service/loadbalancer/wrr/wrr_test.go @@ -14,6 +14,7 @@ func pointer[T any](v T) *T { return &v } type responseRecorder struct { *httptest.ResponseRecorder + save map[string]int sequence []string status []int diff --git a/pkg/server/service/proxy.go b/pkg/server/service/proxy.go index ca5f4150f..85de41e05 100644 --- a/pkg/server/service/proxy.go +++ b/pkg/server/service/proxy.go @@ -121,7 +121,7 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar w.WriteHeader(statusCode) _, werr := w.Write([]byte(statusText(statusCode))) if werr != nil { - log.Debugf("Error while writing status code", werr) + log.WithoutContext().Debugf("Error while writing status code", werr) } }, } diff --git a/pkg/server/service/proxy_test.go b/pkg/server/service/proxy_test.go index cae812380..4d4d94d43 100644 --- a/pkg/server/service/proxy_test.go +++ b/pkg/server/service/proxy_test.go @@ -34,7 +34,7 @@ func BenchmarkProxy(b *testing.B) { handler, _ := buildProxy(pointer(false), nil, &staticTransport{res}, pool) b.ReportAllocs() - for range b.N { + for b.Loop() { handler.ServeHTTP(w, req) } } diff --git a/pkg/server/service/roundtripper.go b/pkg/server/service/roundtripper.go index fc5f9e88b..bcd21130a 100644 --- a/pkg/server/service/roundtripper.go +++ b/pkg/server/service/roundtripper.go @@ -29,6 +29,13 @@ func (t *h2cTransportWrapper) RoundTrip(req *http.Request) (*http.Response, erro return t.Transport.RoundTrip(req) } +// RoundTripperManager handles roundtripper for the reverse proxy. +type RoundTripperManager struct { + rtLock sync.RWMutex + roundTrippers map[string]http.RoundTripper + configs map[string]*dynamic.ServersTransport +} + // NewRoundTripperManager creates a new RoundTripperManager. func NewRoundTripperManager() *RoundTripperManager { return &RoundTripperManager{ @@ -37,13 +44,6 @@ func NewRoundTripperManager() *RoundTripperManager { } } -// RoundTripperManager handles roundtripper for the reverse proxy. -type RoundTripperManager struct { - rtLock sync.RWMutex - roundTrippers map[string]http.RoundTripper - configs map[string]*dynamic.ServersTransport -} - // Update updates the roundtrippers configurations. func (r *RoundTripperManager) Update(newConfigs map[string]*dynamic.ServersTransport) { r.rtLock.Lock() diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 0a13bb521..99c611bf9 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -49,20 +49,6 @@ type ServiceBuilder interface { BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) } -// NewManager creates a new Manager. -func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter, serviceBuilders ...ServiceBuilder) *Manager { - return &Manager{ - routinePool: routinePool, - metricsRegistry: metricsRegistry, - bufferPool: newBufferPool(), - roundTripperManager: roundTripperManager, - serviceBuilders: serviceBuilders, - balancers: make(map[string]healthcheck.Balancers), - configs: configs, - rand: rand.New(rand.NewSource(time.Now().UnixNano())), - } -} - // Manager The service manager. type Manager struct { routinePool *safe.Pool @@ -80,6 +66,20 @@ type Manager struct { rand *rand.Rand // For the initial shuffling of load-balancers. } +// NewManager creates a new Manager. +func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter, serviceBuilders ...ServiceBuilder) *Manager { + return &Manager{ + routinePool: routinePool, + metricsRegistry: metricsRegistry, + bufferPool: newBufferPool(), + roundTripperManager: roundTripperManager, + serviceBuilders: serviceBuilders, + balancers: make(map[string]healthcheck.Balancers), + configs: configs, + rand: rand.New(rand.NewSource(time.Now().UnixNano())), + } +} + // BuildHTTP Creates a http.Handler for a service configuration. func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) { ctx := log.With(rootCtx, log.Str(log.ServiceName, serviceName)) @@ -156,6 +156,29 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H return lb, nil } +// LaunchHealthCheck launches the health checks. +func (m *Manager) LaunchHealthCheck() { + backendConfigs := make(map[string]*healthcheck.BackendConfig) + + for serviceName, balancers := range m.balancers { + ctx := log.With(context.Background(), log.Str(log.ServiceName, serviceName)) + + service := m.configs[serviceName].LoadBalancer + + // Health Check + hcOpts := buildHealthCheckOptions(ctx, balancers, serviceName, service.HealthCheck) + if hcOpts == nil { + continue + } + hcOpts.Transport, _ = m.roundTripperManager.Get(service.ServersTransport) + log.FromContext(ctx).Debugf("Setting up healthcheck for service %s with %s", serviceName, *hcOpts) + + backendConfigs[serviceName] = healthcheck.NewBackendConfig(*hcOpts, serviceName) + } + + healthcheck.GetHealthCheck(m.metricsRegistry).SetBackendsConfiguration(context.Background(), backendConfigs) +} + func (m *Manager) getFailoverServiceHandler(ctx context.Context, serviceName string, config *dynamic.Failover) (http.Handler, error) { f := failover.New(config.HealthCheck) @@ -314,29 +337,6 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName return emptybackendhandler.New(balancer), nil } -// LaunchHealthCheck launches the health checks. -func (m *Manager) LaunchHealthCheck() { - backendConfigs := make(map[string]*healthcheck.BackendConfig) - - for serviceName, balancers := range m.balancers { - ctx := log.With(context.Background(), log.Str(log.ServiceName, serviceName)) - - service := m.configs[serviceName].LoadBalancer - - // Health Check - hcOpts := buildHealthCheckOptions(ctx, balancers, serviceName, service.HealthCheck) - if hcOpts == nil { - continue - } - hcOpts.Transport, _ = m.roundTripperManager.Get(service.ServersTransport) - log.FromContext(ctx).Debugf("Setting up healthcheck for service %s with %s", serviceName, *hcOpts) - - backendConfigs[serviceName] = healthcheck.NewBackendConfig(*hcOpts, serviceName) - } - - healthcheck.GetHealthCheck(m.metricsRegistry).SetBackendsConfiguration(context.Background(), backendConfigs) -} - func buildHealthCheckOptions(ctx context.Context, lb healthcheck.Balancer, backend string, hc *dynamic.ServerHealthCheck) *healthcheck.Options { if hc == nil { return nil diff --git a/pkg/tcp/proxy_unix.go b/pkg/tcp/proxy_unix.go index 727074bcc..b78361416 100644 --- a/pkg/tcp/proxy_unix.go +++ b/pkg/tcp/proxy_unix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows package tcp diff --git a/pkg/tcp/proxy_windows.go b/pkg/tcp/proxy_windows.go index 579d52063..8d9480135 100644 --- a/pkg/tcp/proxy_windows.go +++ b/pkg/tcp/proxy_windows.go @@ -1,5 +1,4 @@ //go:build windows -// +build windows package tcp diff --git a/pkg/tcp/wrr_load_balancer.go b/pkg/tcp/wrr_load_balancer.go index 69df3a254..798a70cf5 100644 --- a/pkg/tcp/wrr_load_balancer.go +++ b/pkg/tcp/wrr_load_balancer.go @@ -11,6 +11,7 @@ var errNoServersInPool = errors.New("no servers in the pool") type server struct { Handler + weight int } diff --git a/pkg/testhelpers/metrics.go b/pkg/testhelpers/metrics.go index bc41c2c3f..2267ce98c 100644 --- a/pkg/testhelpers/metrics.go +++ b/pkg/testhelpers/metrics.go @@ -46,12 +46,12 @@ type CollectingHealthCheckMetrics struct { Gauge *CollectingGauge } -// BackendServerUpGauge is there to satisfy the healthcheck.metricsRegistry interface. -func (m *CollectingHealthCheckMetrics) BackendServerUpGauge() metrics.Gauge { - return m.Gauge -} - // NewCollectingHealthCheckMetrics creates a new CollectingHealthCheckMetrics instance. func NewCollectingHealthCheckMetrics() *CollectingHealthCheckMetrics { return &CollectingHealthCheckMetrics{&CollectingGauge{}} } + +// BackendServerUpGauge is there to satisfy the healthcheck.metricsRegistry interface. +func (m *CollectingHealthCheckMetrics) BackendServerUpGauge() metrics.Gauge { + return m.Gauge +} diff --git a/pkg/tls/certificate.go b/pkg/tls/certificate.go index 02dd376a4..2dabe7dc1 100644 --- a/pkg/tls/certificate.go +++ b/pkg/tls/certificate.go @@ -83,8 +83,7 @@ func (c *Certificates) String() string { // Set's argument is a string to be parsed to set the flag. // It's a comma-separated list, so we split it. func (c *Certificates) Set(value string) error { - certificates := strings.Split(value, ";") - for _, certificate := range certificates { + for certificate := range strings.SplitSeq(value, ";") { files := strings.Split(certificate, ",") if len(files) != 2 { return fmt.Errorf("bad certificates format: %s", value) @@ -162,9 +161,9 @@ func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certifi } } if certExists { - log.Debugf("Skipping addition of certificate for domain(s) %q, to TLS Store %s, as it already exists for this store.", certKey, storeName) + log.WithoutContext().Debugf("Skipping addition of certificate for domain(s) %q, to TLS Store %s, as it already exists for this store.", certKey, storeName) } else { - log.Debugf("Adding certificate for domain(s) %s", certKey) + log.WithoutContext().Debugf("Adding certificate for domain(s) %s", certKey) certs[storeName][certKey] = &tlsCert } diff --git a/pkg/tls/certificate_store.go b/pkg/tls/certificate_store.go index 8a7d0e4ac..48198f100 100644 --- a/pkg/tls/certificate_store.go +++ b/pkg/tls/certificate_store.go @@ -31,32 +31,6 @@ func NewCertificateStore() *CertificateStore { } } -func (c *CertificateStore) getDefaultCertificateDomains() []string { - var allCerts []string - - if c.DefaultCertificate == nil { - return allCerts - } - - x509Cert, err := x509.ParseCertificate(c.DefaultCertificate.Certificate[0]) - if err != nil { - log.WithoutContext().Errorf("Could not parse default certificate: %v", err) - return allCerts - } - - if len(x509Cert.Subject.CommonName) > 0 { - allCerts = append(allCerts, x509Cert.Subject.CommonName) - } - - allCerts = append(allCerts, x509Cert.DNSNames...) - - for _, ipSan := range x509Cert.IPAddresses { - allCerts = append(allCerts, ipSan.String()) - } - - return allCerts -} - // GetAllDomains return a slice with all the certificate domain. func (c *CertificateStore) GetAllDomains() []string { allDomains := c.getDefaultCertificateDomains() @@ -93,7 +67,7 @@ func (c *CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo) matchedCerts := map[string]*tls.Certificate{} if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil { for domains, cert := range c.DynamicCerts.Get().(map[string]*tls.Certificate) { - for _, certDomain := range strings.Split(domains, ",") { + for certDomain := range strings.SplitSeq(domains, ",") { if matchDomain(serverName, certDomain) { matchedCerts[certDomain] = cert } @@ -138,7 +112,7 @@ func (c *CertificateStore) GetCertificate(domains []string) *tls.Certificate { } var matchedDomains []string - for _, certDomain := range strings.Split(certDomains, ",") { + for certDomain := range strings.SplitSeq(certDomains, ",") { for _, checkDomain := range domains { if certDomain == checkDomain { matchedDomains = append(matchedDomains, certDomain) @@ -163,6 +137,32 @@ func (c *CertificateStore) ResetCache() { } } +func (c *CertificateStore) getDefaultCertificateDomains() []string { + var allCerts []string + + if c.DefaultCertificate == nil { + return allCerts + } + + x509Cert, err := x509.ParseCertificate(c.DefaultCertificate.Certificate[0]) + if err != nil { + log.WithoutContext().Errorf("Could not parse default certificate: %v", err) + return allCerts + } + + if len(x509Cert.Subject.CommonName) > 0 { + allCerts = append(allCerts, x509Cert.Subject.CommonName) + } + + allCerts = append(allCerts, x509Cert.DNSNames...) + + for _, ipSan := range x509Cert.IPAddresses { + allCerts = append(allCerts, ipSan.String()) + } + + return allCerts +} + // matchDomain returns whether the server name matches the cert domain. // The server name, from TLS SNI, must not have trailing dots (https://datatracker.ietf.org/doc/html/rfc6066#section-3). // This is enforced by https://github.com/golang/go/blob/d3d7998756c33f69706488cade1cd2b9b10a4c7f/src/crypto/tls/handshake_messages.go#L423-L427. diff --git a/pkg/tls/tls.go b/pkg/tls/tls.go index 3bf366805..7e108233c 100644 --- a/pkg/tls/tls.go +++ b/pkg/tls/tls.go @@ -58,5 +58,6 @@ type GeneratedCert struct { // CertAndStores allows mapping a TLS certificate to a list of entry points. type CertAndStores struct { Certificate `yaml:",inline" export:"true"` - Stores []string `json:"stores,omitempty" toml:"stores,omitempty" yaml:"stores,omitempty" export:"true"` + + Stores []string `json:"stores,omitempty" toml:"stores,omitempty" yaml:"stores,omitempty" export:"true"` } diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go index 3208ad7d3..83d89879e 100644 --- a/pkg/tls/tlsmanager.go +++ b/pkg/tls/tlsmanager.go @@ -264,6 +264,14 @@ func (m *Manager) GetServerCertificates() []*x509.Certificate { return certificates } +// GetStore gets the certificate store of a given name. +func (m *Manager) GetStore(storeName string) *CertificateStore { + m.lock.RLock() + defer m.lock.RUnlock() + + return m.getStore(storeName) +} + // getStore returns the store found for storeName, or nil otherwise. func (m *Manager) getStore(storeName string) *CertificateStore { st, ok := m.stores[storeName] @@ -273,14 +281,6 @@ func (m *Manager) getStore(storeName string) *CertificateStore { return st } -// GetStore gets the certificate store of a given name. -func (m *Manager) GetStore(storeName string) *CertificateStore { - m.lock.RLock() - defer m.lock.RUnlock() - - return m.getStore(storeName) -} - func getDefaultCertificate(ctx context.Context, tlsStore Store, st *CertificateStore) (*tls.Certificate, error) { if tlsStore.DefaultCertificate != nil { cert, err := buildDefaultCertificate(tlsStore.DefaultCertificate) diff --git a/pkg/tracing/haystack/logger.go b/pkg/tracing/haystack/logger.go index 7635bd821..ff6aaa69b 100644 --- a/pkg/tracing/haystack/logger.go +++ b/pkg/tracing/haystack/logger.go @@ -9,16 +9,16 @@ type haystackLogger struct { } // Error prints the error message. -func (l haystackLogger) Error(format string, v ...interface{}) { +func (l haystackLogger) Error(format string, v ...any) { l.logger.Errorf(format, v...) } // Info prints the info message. -func (l haystackLogger) Info(format string, v ...interface{}) { +func (l haystackLogger) Info(format string, v ...any) { l.logger.Infof(format, v...) } // Debug prints the info message. -func (l haystackLogger) Debug(format string, v ...interface{}) { +func (l haystackLogger) Debug(format string, v ...any) { l.logger.Debugf(format, v...) } diff --git a/pkg/tracing/jaeger/logger.go b/pkg/tracing/jaeger/logger.go index 4aad3de77..6fa7e14fb 100644 --- a/pkg/tracing/jaeger/logger.go +++ b/pkg/tracing/jaeger/logger.go @@ -21,6 +21,6 @@ func (l *jaegerLogger) Error(msg string) { } // Infof logs a message at debug priority. -func (l *jaegerLogger) Infof(msg string, args ...interface{}) { +func (l *jaegerLogger) Infof(msg string, args ...any) { l.logger.Debugf(msg, args...) } diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go index f951fc1a3..24fc48d45 100644 --- a/pkg/tracing/tracing.go +++ b/pkg/tracing/tracing.go @@ -14,11 +14,10 @@ import ( type contextKey int -const ( - // SpanKindNoneEnum Span kind enum none. - SpanKindNoneEnum ext.SpanKindEnum = "none" - tracingKey contextKey = iota -) +// SpanKindNoneEnum Span kind enum none. +const SpanKindNoneEnum ext.SpanKindEnum = "none" + +const tracingKey contextKey = iota // WithTracing Adds Tracing into the context. func WithTracing(ctx context.Context, tracing *Tracing) context.Context { @@ -80,12 +79,12 @@ func (t *Tracing) StartSpanf(r *http.Request, spanKind ext.SpanKindEnum, opPrefi } // Inject delegates to opentracing.Tracer. -func (t *Tracing) Inject(sm opentracing.SpanContext, format, carrier interface{}) error { +func (t *Tracing) Inject(sm opentracing.SpanContext, format, carrier any) error { return t.tracer.Inject(sm, format, carrier) } // Extract delegates to opentracing.Tracer. -func (t *Tracing) Extract(format, carrier interface{}) (opentracing.SpanContext, error) { +func (t *Tracing) Extract(format, carrier any) (opentracing.SpanContext, error) { return t.tracer.Extract(format, carrier) } @@ -142,7 +141,7 @@ func InjectRequestHeaders(r *http.Request) { } // LogEventf logs an event to the span in the request context. -func LogEventf(r *http.Request, format string, args ...interface{}) { +func LogEventf(r *http.Request, format string, args ...any) { if span := GetSpan(r); span != nil { span.LogKV("event", fmt.Sprintf(format, args...)) } @@ -177,7 +176,7 @@ func SetError(r *http.Request) { } // SetErrorWithEvent flags the span associated with this request as in error and log an event. -func SetErrorWithEvent(r *http.Request, format string, args ...interface{}) { +func SetErrorWithEvent(r *http.Request, format string, args ...any) { SetError(r) LogEventf(r, format, args...) } diff --git a/pkg/udp/conn.go b/pkg/udp/conn.go index e3ad6caa9..a2412f832 100644 --- a/pkg/udp/conn.go +++ b/pkg/udp/conn.go @@ -77,19 +77,6 @@ func (l *Listener) Close() error { return l.Shutdown(0) } -// close should not be called more than once. -func (l *Listener) close() error { - l.mu.Lock() - defer l.mu.Unlock() - err := l.pConn.Close() - for k, v := range l.conns { - v.close() - delete(l.conns, k) - } - close(l.acceptCh) - return err -} - // Shutdown closes the listener. // It immediately stops accepting new sessions, // and it waits for all existing sessions to terminate, @@ -104,10 +91,7 @@ func (l *Listener) Shutdown(graceTimeout time.Duration) error { l.accepting = false l.mu.Unlock() - retryInterval := closeRetryInterval - if retryInterval > graceTimeout { - retryInterval = graceTimeout - } + retryInterval := min(closeRetryInterval, graceTimeout) start := time.Now() end := start.Add(graceTimeout) for !time.Now().After(end) { @@ -123,6 +107,19 @@ func (l *Listener) Shutdown(graceTimeout time.Duration) error { return l.close() } +// close should not be called more than once. +func (l *Listener) close() error { + l.mu.Lock() + defer l.mu.Unlock() + err := l.pConn.Close() + for k, v := range l.conns { + v.close() + delete(l.conns, k) + } + close(l.acceptCh) + return err +} + // readLoop receives all packets from all remotes. // If a packet comes from a remote that is already known to us (i.e. a "session"), // we find that session, and otherwise we create a new one. @@ -203,6 +200,44 @@ type Conn struct { doneCh chan struct{} } +// Read reads up to len(p) bytes into p from the connection. +// Each call corresponds to at most one datagram. +// If p is smaller than the datagram, the extra bytes will be discarded. +func (c *Conn) Read(p []byte) (int, error) { + select { + case c.readCh <- p: + n := <-c.sizeCh + c.muActivity.Lock() + c.lastActivity = time.Now() + c.muActivity.Unlock() + return n, nil + + case <-c.doneCh: + return 0, io.EOF + } +} + +// Write writes len(p) bytes from p to the underlying connection. +// Each call sends at most one datagram. +// It is an error to send a message larger than the system's max UDP datagram size. +func (c *Conn) Write(p []byte) (n int, err error) { + c.muActivity.Lock() + c.lastActivity = time.Now() + c.muActivity.Unlock() + + return c.listener.pConn.WriteTo(p, c.rAddr) +} + +// Close releases resources related to the Conn. +func (c *Conn) Close() error { + c.close() + + c.listener.mu.Lock() + defer c.listener.mu.Unlock() + delete(c.listener.conns, c.rAddr.String()) + return nil +} + // readLoop waits for data to come from the listener's readLoop. // It then waits for a Read operation to be ready to consume said data, // that is to say it waits on readCh to receive the slice of bytes that the Read operation wants to read onto. @@ -248,46 +283,8 @@ func (c *Conn) readLoop() { } } -// Read reads up to len(p) bytes into p from the connection. -// Each call corresponds to at most one datagram. -// If p is smaller than the datagram, the extra bytes will be discarded. -func (c *Conn) Read(p []byte) (int, error) { - select { - case c.readCh <- p: - n := <-c.sizeCh - c.muActivity.Lock() - c.lastActivity = time.Now() - c.muActivity.Unlock() - return n, nil - - case <-c.doneCh: - return 0, io.EOF - } -} - -// Write writes len(p) bytes from p to the underlying connection. -// Each call sends at most one datagram. -// It is an error to send a message larger than the system's max UDP datagram size. -func (c *Conn) Write(p []byte) (n int, err error) { - c.muActivity.Lock() - c.lastActivity = time.Now() - c.muActivity.Unlock() - - return c.listener.pConn.WriteTo(p, c.rAddr) -} - func (c *Conn) close() { c.doneOnce.Do(func() { close(c.doneCh) }) } - -// Close releases resources related to the Conn. -func (c *Conn) Close() error { - c.close() - - c.listener.mu.Lock() - defer c.listener.mu.Unlock() - delete(c.listener.conns, c.rAddr.String()) - return nil -} diff --git a/pkg/udp/wrr_load_balancer.go b/pkg/udp/wrr_load_balancer.go index f1d01308a..d2411ba0c 100644 --- a/pkg/udp/wrr_load_balancer.go +++ b/pkg/udp/wrr_load_balancer.go @@ -9,6 +9,7 @@ import ( type server struct { Handler + weight int } From 6cd85caa9996920c5d53f703362f24d83351b081 Mon Sep 17 00:00:00 2001 From: Sheddy Date: Fri, 16 Jan 2026 11:26:05 +0100 Subject: [PATCH 02/13] Improve Service Reference page --- .../http/load-balancing/service.md | 433 +++++++++++++----- 1 file changed, 327 insertions(+), 106 deletions(-) diff --git a/docs/content/reference/routing-configuration/http/load-balancing/service.md b/docs/content/reference/routing-configuration/http/load-balancing/service.md index 7b009e2e3..7039ac3db 100644 --- a/docs/content/reference/routing-configuration/http/load-balancing/service.md +++ b/docs/content/reference/routing-configuration/http/load-balancing/service.md @@ -4,14 +4,23 @@ description: "A service is in charge of connecting incoming requests to the Serv --- Traefik services define how to distribute incoming traffic across your backend servers. -Each service implements one of the load balancing strategies detailed on this page to ensure optimal traffic distribution and high availability. +This page covers two main concepts: + +- **Service Load Balancer**: Routes traffic to backend servers using various load balancing strategies +- **Advanced Service Types**: Compose multiple services together for weighted distribution, mirroring, or failover ## Service Load Balancer -The load balancers are able to load balance the requests between multiple instances of your programs. - +The `loadBalancer` service type routes incoming requests to a list of backend servers. Each service has a load-balancer, even if there is only one server to forward traffic to. +The load balancer supports multiple **strategies** for distributing traffic among servers: + +- `wrr` (Weighted Round Robin) - Default strategy, distributes requests evenly across servers in rotation +- `p2c` (Power of Two Choices) - Selects two random servers and routes to the one with fewer active connections +- `hrw` (Highest Random Weight) - Uses consistent hashing based on client IP for session affinity +- `leasttime` - Routes to the server with lowest response time combined with fewest active connections + ### Configuration Example ```yaml tab="Structured (YAML)" @@ -19,6 +28,7 @@ http: services: my-service: loadBalancer: + strategy: "wrr" servers: - url: "http://private-ip-server-1/" weight: 2 @@ -42,6 +52,7 @@ http: ```toml tab="Structured (TOML)" [http.services] [http.services.my-service.loadBalancer] + strategy = "wrr" [[http.services.my-service.loadBalancer.servers]] url = "http://private-ip-server-1/" @@ -66,6 +77,7 @@ http: ```yaml tab="Labels" labels: + - "traefik.http.services.my-service.loadBalancer.strategy=wrr" - "traefik.http.services.my-service.loadBalancer.servers[0].url=http://private-ip-server-1/" - "traefik.http.services.my-service.loadBalancer.servers[0].weight=2" - "traefik.http.services.my-service.loadBalancer.servers[0].preservePath=true" @@ -83,6 +95,7 @@ labels: ```json tab="Tags" { "Tags": [ + "traefik.http.services.my-service.loadBalancer.strategy=wrr", "traefik.http.services.my-service.loadBalancer.servers[0].url=http://private-ip-server-1/", "traefik.http.services.my-service.loadBalancer.servers[0].weight=2", "traefik.http.services.my-service.loadBalancer.servers[0].preservePath=true", @@ -104,6 +117,7 @@ labels: | Field | Description | Required | |------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| | `servers` | Represents individual backend instances for your service | Yes | +| `strategy` | Load balancing strategy for distributing traffic among servers. Valid values: `wrr` (default), `p2c`, `hrw`, `leasttime`. | No | | `sticky` | Defines a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response. | No | | `healthcheck` | Configures health check to remove unhealthy servers from the load balancing rotation. | No | | `passiveHealthcheck` | Configures the passive health check to remove unhealthy servers from the load balancing rotation. | No | @@ -124,7 +138,151 @@ Servers represent individual backend instances for your service. The [service lo | `weight` | Allows for weighted load balancing on the servers. | No | | `preservePath` | Allows to preserve the URL path. | No | -#### Health Check +### Load Balancing Strategies + +The `strategy` option on the load balancer determines how traffic is distributed among the backend servers. + +#### Weighted Round Robin (wrr) + +The default strategy. Distributes requests evenly across all servers in rotation, respecting server weights. +This strategy uses Earliest Deadline First (EDF) scheduling to provide weighted round-robin behavior. + +??? example "WRR Load Balancing -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" + + ```yaml tab="Structured (YAML)" + ## Routing configuration + http: + services: + my-service: + loadBalancer: + strategy: "wrr" + servers: + - url: "http://private-ip-server-1/" + weight: 3 + - url: "http://private-ip-server-2/" + weight: 1 + ``` + + ```toml tab="Structured (TOML)" + ## Routing configuration + [http.services] + [http.services.my-service.loadBalancer] + strategy = "wrr" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-1/" + weight = 3 + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-2/" + weight = 1 + ``` + +#### Power of Two Choices (p2c) + +Selects two servers at random and routes the request to the one with the fewest active connections. +This algorithm provides better load distribution when servers have varying response times. + +??? example "P2C Load Balancing -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" + + ```yaml tab="Structured (YAML)" + ## Routing configuration + http: + services: + my-service: + loadBalancer: + strategy: "p2c" + servers: + - url: "http://private-ip-server-1/" + - url: "http://private-ip-server-2/" + - url: "http://private-ip-server-3/" + ``` + + ```toml tab="Structured (TOML)" + ## Routing configuration + [http.services] + [http.services.my-service.loadBalancer] + strategy = "p2c" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-1/" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-2/" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-3/" + ``` + +#### Highest Random Weight (hrw) + +Uses consistent hashing (Rendezvous Hashing) based on the client's IP address to ensure requests from the same client are consistently routed to the same server. +This provides session affinity without requiring sticky cookies. + +The algorithm computes a score for each available backend using a hash of the client's source IP combined with the backend's identifier, and assigns the client to the backend with the highest score. + +??? example "HRW Load Balancing -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" + + ```yaml tab="Structured (YAML)" + ## Routing configuration + http: + services: + my-service: + loadBalancer: + strategy: "hrw" + servers: + - url: "http://private-ip-server-1/" + - url: "http://private-ip-server-2/" + - url: "http://private-ip-server-3/" + ``` + + ```toml tab="Structured (TOML)" + ## Routing configuration + [http.services] + [http.services.my-service.loadBalancer] + strategy = "hrw" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-1/" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-2/" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-3/" + ``` + +#### Least-Time + +Selects the server with the lowest average response time (Time To First Byte - TTFB), combined with the fewest active connections, weighted by server capacity. +This strategy is ideal for heterogeneous backend environments where servers have varying performance characteristics, different hardware capabilities, or varying network latency. + +The algorithm continuously measures each backend's response time and tracks active connection counts. +When routing a request, it calculates a score for each healthy server using the formula: `(avg_response_time × (1 + active_connections)) / weight`. +The server with the lowest score receives the request. +When multiple servers have identical scores, Weighted Round Robin (WRR) with Earliest Deadline First (EDF) scheduling is used as a tie-breaker. + +??? example "Least-Time Load Balancing -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" + + ```yaml tab="Structured (YAML)" + ## Routing configuration + http: + services: + my-service: + loadBalancer: + strategy: "leasttime" + servers: + - url: "http://private-ip-server-1/" + - url: "http://private-ip-server-2/" + - url: "http://private-ip-server-3/" + ``` + + ```toml tab="Structured (TOML)" + ## Routing configuration + [http.services] + [http.services.my-service.loadBalancer] + strategy = "leasttime" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-1/" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-2/" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-3/" + ``` + +### Health Check The `healthcheck` option configures health check to remove unhealthy servers from the load balancing rotation. Traefik will consider HTTP(s) servers healthy as long as they return a status code to the health check request (carried out every interval) between `2XX` and `3XX`, or matching the configured status. @@ -146,42 +304,41 @@ Below are the available options for the health check mechanism: | `timeout` | Defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. | 5s | No | | `headers` | Defines custom headers to be sent to the health check endpoint. | | No | | `followRedirects` | Defines whether redirects should be followed during the health check calls. | true | No | -| `hostname` | Defines the value of hostname in the Host header of the health check request. | "" | No | | `method` | Defines the HTTP method that will be used while connecting to the endpoint. | GET | No | | `status` | Defines the expected HTTP status code of the response to the health check request. | | No | -#### Sticky sessions +### Sticky Sessions When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response. On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set. -##### Stickiness on multiple levels +#### Stickiness on multiple levels When chaining or mixing load-balancers (e.g. a load-balancer of servers is one of the "children" of a load-balancer of services), for stickiness to work all the way, the option needs to be specified at all required levels. Which means the client needs to send a cookie with as many key/value pairs as there are sticky levels. -##### Stickiness & Unhealthy Servers +#### Stickiness & Unhealthy Servers If the server specified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server). -##### Cookie Name +#### Cookie Name The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`). -##### MaxAge +#### MaxAge By default, the affinity cookie will never expire as the `MaxAge` option is set to zero. This option indicates the number of seconds until the cookie expires. When set to a negative number, the cookie expires immediately. -##### Secure & HTTPOnly & SameSite flags +#### Secure & HTTPOnly & SameSite flags By default, the affinity cookie is created without those flags. One however can change that through configuration. `SameSite` can be `none`, `lax`, `strict` or empty. -##### Domain +#### Domain The Domain attribute of a cookie specifies the domain for which the cookie is valid. @@ -190,7 +347,7 @@ By setting the Domain attribute, the cookie can be shared across subdomains (for ??? example "Adding Stickiness -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" ```yaml tab="Structured (YAML)" - ## Dynamic configuration + ## Routing configuration http: services: my-service: @@ -200,7 +357,7 @@ By setting the Domain attribute, the cookie can be shared across subdomains (for ``` ```toml tab="Structured (TOML)" - ## Dynamic configuration + ## Routing configuration [http.services] [http.services.my-service] [http.services.my-service.loadBalancer.sticky.cookie] @@ -209,7 +366,7 @@ By setting the Domain attribute, the cookie can be shared across subdomains (for ??? example "Adding Stickiness with custom Options -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" ```yaml tab="Structured (YAML)" - ## Dynamic configuration + ## Routing configuration http: services: my-service: @@ -223,7 +380,7 @@ By setting the Domain attribute, the cookie can be shared across subdomains (for ``` ```toml tab="Structured (TOML)" - ## Dynamic configuration + ## Routing configuration [http.services] [http.services.my-service] [http.services.my-service.loadBalancer.sticky.cookie] @@ -237,7 +394,7 @@ By setting the Domain attribute, the cookie can be shared across subdomains (for ??? example "Setting Stickiness on all the required levels -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" ```yaml tab="Structured (YAML)" - ## Dynamic configuration + ## Routing configuration http: services: wrr1: @@ -271,7 +428,7 @@ By setting the Domain attribute, the cookie can be shared across subdomains (for ``` ```toml tab="Structured (TOML)" - ## Dynamic configuration + ## Routing configuration [http.services] [http.services.wrr1] [http.services.wrr1.weighted.sticky.cookie] @@ -308,7 +465,7 @@ To keep a session open with the same server, the client would then need to speci curl -b "lvl1=whoami1; lvl2=http://127.0.0.1:8081" http://localhost:8000 ``` -#### Passive Health Check +### Passive Health Check The `passiveHealthcheck` option configures passive health check to remove unhealthy servers from the load balancing rotation. @@ -326,18 +483,27 @@ Below are the available options for the passive health check mechanism: | `failureWindow` | Defines the time window during which the failed attempts must occur for the server to be marked as unhealthy. It also defines for how long the server will be considered unhealthy. | 10s | No | | `maxFailedAttempts` | Defines the number of consecutive failed attempts allowed within the failure window before marking the server as unhealthy. | 1 | No | -## Weighted Round Robin (WRR) +## Advanced Service Types -The WRR is able to load balance the requests between multiple services based on weights. +Advanced service types allow you to compose multiple services together for weighted distribution, consistent hashing, mirroring, or failover scenarios. +These are distinct from load balancing strategies - they operate at the **service level** rather than the **server level**. -This strategy is only available to load balance between services and not between servers. +!!! info "Key Difference" + + - **Load Balancing Strategies** (wrr, p2c, hrw, leasttime): Distribute traffic among **servers** within a single `loadBalancer` service + - **Advanced Service Types** (weighted, highestRandomWeight, mirroring, failover): Distribute or manage traffic among multiple **services** + +### Weighted Round robin + +The `weighted` service type load balances requests between multiple services based on weights. +This is different from the `wrr` strategy - it operates on services, not servers. !!! info "Supported Providers" - This strategy can be defined currently with the [File](../../../install-configuration/providers/others/file.md) or [IngressRoute](../../../install-configuration/providers/kubernetes/kubernetes-crd.md) providers. To load balance between servers based on weights, the Load Balancer service should be used instead. + This service type can be defined currently with the [File](../../../install-configuration/providers/others/file.md) provider or [IngressRoute](../../../routing-configuration/kubernetes/crd/http/ingressroute.md). ```yaml tab="Structured (YAML)" -## Dynamic configuration +## Routing configuration http: services: app: @@ -360,7 +526,7 @@ http: ``` ```toml tab="Structured (TOML)" -## Dynamic configuration +## Routing configuration [http.services] [http.services.app] [[http.services.app.weighted.services]] @@ -381,7 +547,7 @@ http: url = "http://private-ip-server-2/" ``` -### Health Check +#### Health Check HealthCheck enables automatic self-healthcheck for this service, i.e. whenever one of its children is reported as down, this service becomes aware of it, and takes it into account (i.e. it ignores the down child) when running the load-balancing algorithm. In addition, if the parent of this service also has HealthCheck enabled, this service reports to its parent any status change. @@ -392,7 +558,7 @@ HealthCheck enables automatic self-healthcheck for this service, i.e. whenever o HealthCheck on Weighted services can be defined currently only with the [File provider](../../../install-configuration/providers/others/file.md). ```yaml tab="Structured (YAML)" -## Dynamic configuration +## Routing configuration http: services: app: @@ -424,7 +590,7 @@ http: ``` ```toml tab="Structured (TOML)" -## Dynamic configuration +## Routing configuration [http.services] [http.services.app] [http.services.app.weighted.healthCheck] @@ -454,83 +620,138 @@ http: url = "http://private-ip-server-2/" ``` -## P2C +### Highest Random Weight -Power of two choices algorithm is a load balancing strategy that selects two servers at random and chooses the one with the least number of active requests. +The `highestRandomWeight` service type uses consistent hashing (Rendezvous Hashing) to load balance requests between multiple services. +This ensures that requests from the same client IP are consistently routed to the same service. -??? example "P2C Load Balancing -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" +This is different from the `hrw` strategy on a loadBalancer - it operates on **services**, not servers. - ```yaml tab="Structured (YAML)" - ## Dynamic configuration - http: - services: - my-service: - loadBalancer: - strategy: "p2c" - servers: - - url: "http://private-ip-server-1/" - - url: "http://private-ip-server-2/" - - url: "http://private-ip-server-3/" - ``` +!!! info "Supported Providers" - ```toml tab="Structured (TOML) " - ## Dynamic configuration - [http.services] - [http.services.my-service.loadBalancer] - strategy = "p2c" - [[http.services.my-service.loadBalancer.servers]] - url = "http://private-ip-server-1/" - [[http.services.my-service.loadBalancer.servers]] - url = "http://private-ip-server-2/" - [[http.services.my-service.loadBalancer.servers]] - url = "http://private-ip-server-3/" - ``` + This service type can be defined currently only with the [File](../../../install-configuration/providers/others/file.md) provider. -## Least-Time +```yaml tab="Structured (YAML)" +## Routing configuration +http: + services: + app: + highestRandomWeight: + services: + - name: appv1 + weight: 1 + - name: appv2 + weight: 1 -The Least-Time load balancing algorithm selects the server with the lowest average response time (Time To First Byte - TTFB), -combined with the fewest active connections, weighted by server capacity. -This strategy is ideal for heterogeneous backend environments where servers have varying performance characteristics, -different hardware capabilities, or varying network latency. + appv1: + loadBalancer: + servers: + - url: "http://private-ip-server-1/" -The algorithm continuously measures each backend's response time and tracks active connection counts. -When routing a request, -it calculates a score for each healthy server using the formula: `(avg_response_time × (1 + active_connections)) / weight`. -The server with the lowest score receives the request. -When multiple servers have identical scores, -Weighted Round Robin (WRR) with Earliest Deadline First (EDF) scheduling is used as a tie-breaker to ensure fair distribution. + appv2: + loadBalancer: + servers: + - url: "http://private-ip-server-2/" +``` -??? example "Basic Least-Time Load Balancing -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" +```toml tab="Structured (TOML)" +## Routing configuration +[http.services] + [http.services.app] + [[http.services.app.highestRandomWeight.services]] + name = "appv1" + weight = 1 + [[http.services.app.highestRandomWeight.services]] + name = "appv2" + weight = 1 - ```yaml tab="Structured (YAML)" - ## Dynamic configuration - http: - services: - my-service: - loadBalancer: - strategy: "leasttime" - servers: - - url: "http://private-ip-server-1/" - - url: "http://private-ip-server-2/" - - url: "http://private-ip-server-3/" - ``` + [http.services.appv1] + [http.services.appv1.loadBalancer] + [[http.services.appv1.loadBalancer.servers]] + url = "http://private-ip-server-1/" - ```toml tab="Structured (TOML)" - ## Dynamic configuration - [http.services] - [http.services.my-service.loadBalancer] - strategy = "leasttime" - [[http.services.my-service.loadBalancer.servers]] - url = "http://private-ip-server-1/" - [[http.services.my-service.loadBalancer.servers]] - url = "http://private-ip-server-2/" - [[http.services.my-service.loadBalancer.servers]] - url = "http://private-ip-server-3/" - ``` + [http.services.appv2] + [http.services.appv2.loadBalancer] + [[http.services.appv2.loadBalancer.servers]] + url = "http://private-ip-server-2/" +``` -## Mirroring +#### Health Check -The mirroring is able to mirror requests sent to a service to other services. Please note that by default the whole request is buffered in memory while it is being mirrored. See the `maxBodySize` option in the example below for how to modify this behaviour. You can also omit the request body by setting the `mirrorBody` option to false. +HealthCheck enables automatic self-healthcheck for this service, similar to the Weighted Round Robin service type. + +!!! note "Behavior" + + If HealthCheck is enabled for a given service and any of its descendants does not have it enabled, the creation of the service will fail. + + HealthCheck on Highest Random Weight services can be defined currently only with the [File provider](../../../install-configuration/providers/others/file.md). + +```yaml tab="Structured (YAML)" +## Routing configuration +http: + services: + app: + highestRandomWeight: + healthCheck: {} + services: + - name: appv1 + weight: 1 + - name: appv2 + weight: 1 + + appv1: + loadBalancer: + healthCheck: + path: /status + interval: 10s + timeout: 3s + servers: + - url: "http://private-ip-server-1/" + + appv2: + loadBalancer: + healthCheck: + path: /status + interval: 10s + timeout: 3s + servers: + - url: "http://private-ip-server-2/" +``` + +```toml tab="Structured (TOML)" +## Routing configuration +[http.services] + [http.services.app] + [http.services.app.highestRandomWeight.healthCheck] + [[http.services.app.highestRandomWeight.services]] + name = "appv1" + weight = 1 + [[http.services.app.highestRandomWeight.services]] + name = "appv2" + weight = 1 + + [http.services.appv1] + [http.services.appv1.loadBalancer] + [http.services.appv1.loadBalancer.healthCheck] + path = "/health" + interval = "10s" + timeout = "3s" + [[http.services.appv1.loadBalancer.servers]] + url = "http://private-ip-server-1/" + + [http.services.appv2] + [http.services.appv2.loadBalancer] + [http.services.appv2.loadBalancer.healthCheck] + path = "/health" + interval = "10s" + timeout = "3s" + [[http.services.appv2.loadBalancer.servers]] + url = "http://private-ip-server-2/" +``` + +### Mirroring + +The `mirroring` service type mirrors requests sent to a service to other services. Please note that by default the whole request is buffered in memory while it is being mirrored. See the `maxBodySize` option in the example below for how to modify this behaviour. You can also omit the request body by setting the `mirrorBody` option to false. !!! warning "Default behavior of `percent`" @@ -538,10 +759,10 @@ The mirroring is able to mirror requests sent to a service to other services. Pl !!! info "Supported Providers" - This strategy can be defined currently with the [File](../../../install-configuration/providers/others/file.md) or [IngressRoute](../../../install-configuration/providers/kubernetes/kubernetes-crd.md) providers. + This service type can be defined currently with the [File](../../../install-configuration/providers/others/file.md) provider or [IngressRoute](../../../routing-configuration/kubernetes/crd/http/ingressroute.md). ```yaml tab="Structured (YAML)" -## Dynamic configuration +## Routing configuration http: services: mirrored-api: @@ -572,7 +793,7 @@ http: ``` ```toml tab="Structured (TOML)" -## Dynamic configuration +## Routing configuration [http.services] [http.services.mirrored-api] [http.services.mirrored-api.mirroring] @@ -599,7 +820,7 @@ http: url = "http://private-ip-server-2/" ``` -### Health Check +#### Health Check HealthCheck enables automatic self-healthcheck for this service, i.e. if the main handler of the service becomes unreachable, the information is propagated upwards to its parent. @@ -610,7 +831,7 @@ HealthCheck enables automatic self-healthcheck for this service, i.e. if the mai HealthCheck on Mirroring services can be defined currently only with the [File provider](../../../install-configuration/providers/others/file.md). ```yaml tab="Structured (YAML)" -## Dynamic configuration +## Routing configuration http: services: mirrored-api: @@ -637,7 +858,7 @@ http: ``` ```toml tab="Structured (TOML)" -## Dynamic configuration +## Routing configuration [http.services] [http.services.mirrored-api] [http.services.mirrored-api.mirroring] @@ -658,7 +879,7 @@ http: [http.services.appv2] [http.services.appv2.loadBalancer] - [http.services.appv1.loadBalancer.healthCheck] + [http.services.appv2.loadBalancer.healthCheck] path = "/health" interval = "10s" timeout = "3s" @@ -666,17 +887,17 @@ http: url = "http://private-ip-server-2/" ``` -## Failover +### Failover -A failover service job is to forward all requests to a fallback service when the main service becomes unreachable. +The `failover` service type forwards all requests to a fallback service when the main service becomes unreachable. !!! info "Relation to HealthCheck" The failover service relies on the HealthCheck system to get notified when its main service becomes unreachable, which means HealthCheck needs to be enabled and functional on the main service. However, HealthCheck does not need to be enabled on the failover service itself for it to be functional. It is only required in order to propagate upwards the information when the failover itself becomes down (i.e. both its main and its fallback are down too). !!! info "Supported Provider" - This strategy can currently only be defined with the [File](../../../install-configuration/providers/others/file.md) provider. + This service type can currently only be defined with the [File](../../../install-configuration/providers/others/file.md) provider. -### HealthCheck +#### HealthCheck HealthCheck enables automatic self-healthcheck for this service, i.e. if the main and the fallback services become unreachable, the information is propagated upwards to its parent. @@ -687,7 +908,7 @@ HealthCheck enables automatic self-healthcheck for this service, i.e. if the mai HealthCheck on a Failover service can be defined currently only with the [File provider](../../../install-configuration/providers/others/file.md). ```yaml tab="Structured (YAML)" -## Dynamic configuration +## Routing configuration http: services: app: @@ -716,7 +937,7 @@ http: ``` ```toml tab="Structured (TOML)" -## Dynamic configuration +## Routing configuration [http.services] [http.services.app] [http.services.app.failover.healthCheck] From bb35197d5acfe97e37f68200a0933da9c4a463d3 Mon Sep 17 00:00:00 2001 From: Sheddy Date: Fri, 16 Jan 2026 14:48:06 +0100 Subject: [PATCH 03/13] Improve the structure of the routing reference pages --- .../other-providers/consul-catalog.md | 617 +++++------------- .../other-providers/docker.md | 547 +++------------- .../other-providers/ecs.md | 601 +++++------------ .../other-providers/kv.md | 94 +-- .../other-providers/nomad.md | 614 +++++------------ .../other-providers/swarm.md | 547 +++------------- 6 files changed, 727 insertions(+), 2293 deletions(-) diff --git a/docs/content/reference/routing-configuration/other-providers/consul-catalog.md b/docs/content/reference/routing-configuration/other-providers/consul-catalog.md index 7958a4c01..9a9a2dd8e 100644 --- a/docs/content/reference/routing-configuration/other-providers/consul-catalog.md +++ b/docs/content/reference/routing-configuration/other-providers/consul-catalog.md @@ -13,7 +13,90 @@ With Consul Catalog, Traefik can leverage tags attached to a service to generate We recommend to *not* use tags to store sensitive data (certificates, credentials, etc). Instead, we recommend to store sensitive data in a safer storage (secrets, file, etc). -## Routing Configuration +## Configuration Examples + +??? example "Configuring Consul Catalog & Deploying / Exposing one Service" + + Enabling the consul catalog provider + + ```yaml tab="Structured (YAML)" + providers: + consulCatalog: {} + ``` + + ```toml tab="Structured (TOML)" + [providers.consulCatalog] + ``` + + ```bash tab="CLI" + --providers.consulcatalog=true + ``` + + Attaching tags to services (when registering a service in Consul) + + ```bash + consul services register -name=my-service -tag="traefik.http.routers.my-service.rule=Host(`example.com`)" + ``` + + Or using a service definition file: + + ```json + { + "service": { + "name": "my-service", + "tags": [ + "traefik.http.routers.my-service.rule=Host(`example.com`)" + ] + } + } + ``` + +??? example "Specify a Custom Port for the Container" + + Forward requests for `http://example.com` to `http://:12345`: + + ```json + { + "service": { + "name": "my-service", + "tags": [ + "traefik.http.routers.my-service.rule=Host(`example.com`)", + "traefik.http.routers.my-service.service=my-service", + "traefik.http.services.my-service.loadbalancer.server.port=12345" + ] + } + } + ``` + + !!! important "Traefik Connecting to the Wrong Port: `HTTP/502 Gateway Error`" + By default, Traefik uses the first exposed port of a container. + + Setting the tag `traefik.http.services.xxx.loadbalancer.server.port` + overrides that behavior. + +??? example "Specifying more than one router and service per container" + + Forwarding requests to more than one port on a container requires referencing the service loadbalancer port definition using the service parameter on the router. + + In this example, requests are forwarded for `http://example-a.com` to `http://:8000` in addition to `http://example-b.com` forwarding to `http://:9000`: + + ```json + { + "service": { + "name": "my-service", + "tags": [ + "traefik.http.routers.www-router.rule=Host(`example-a.com`)", + "traefik.http.routers.www-router.service=www-service", + "traefik.http.services.www-service.loadbalancer.server.port=8000", + "traefik.http.routers.admin-router.rule=Host(`example-b.com`)", + "traefik.http.routers.admin-router.service=admin-service", + "traefik.http.services.admin-service.loadbalancer.server.port=9000" + ] + } + } + ``` + +## Configuration Options !!! info "tags" @@ -35,120 +118,24 @@ To update the configuration of the Router automatically attached to the service, For example, to change the rule, you could add the tag ```traefik.http.routers.my-service.rule=Host(`example.com`)```. -??? info "`traefik.http.routers..rule`" - - See [rule](../http/routing/rules-and-priority.md) for more information. - - ```yaml - traefik.http.routers.myrouter.rule=Host(`example.com`) - ``` +#### Configuration Options -??? info "`traefik.http.routers..ruleSyntax`" - - !!! warning - - RuleSyntax option is deprecated and will be removed in the next major version. - Please do not use this field and rewrite the router rules to use the v3 syntax. - - See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information. - - ```yaml - traefik.http.routers.myrouter.ruleSyntax=v3 - ``` - -??? info "`traefik.http.routers..priority`" - - See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. - - ```yaml - - "traefik.tcp.routers.mytcprouter.priority=42" - ``` - -??? info "`traefik.http.routers..entrypoints`" - - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - traefik.http.routers.myrouter.entrypoints=web,websecure - ``` - -??? info "`traefik.http.routers..middlewares`" - - See [middlewares overview](../http/middlewares/overview.md) for more information. - - ```yaml - traefik.http.routers.myrouter.middlewares=auth,prefix,cb - ``` - -??? info "`traefik.http.routers..service`" - - See [service](../http/load-balancing/service.md) for more information. - - ```yaml - traefik.http.routers.myrouter.service=myservice - ``` - -??? info "`traefik.http.routers..tls`" - - See [tls](../http/tls/overview.md) for more information. - - ```yaml - traefik.http.routers.myrouter.tls=true - ``` - -??? info "`traefik.http.routers..tls.certresolver`" - - See [certResolver](../../install-configuration/tls/certificate-resolvers/overview.md) for more information. - - ```yaml - traefik.http.routers.myrouter.tls.certresolver=myresolver - ``` - -??? info "`traefik.http.routers..tls.domains[n].main`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - ```yaml - traefik.http.routers.myrouter.tls.domains[0].main=example.org - ``` - -??? info "`traefik.http.routers..tls.domains[n].sans`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - ```yaml - traefik.http.routers.myrouter.tls.domains[0].sans=test.example.org,dev.example.org - ``` - -??? info "`traefik.http.routers..tls.options`" - - ```yaml - traefik.http.routers.myrouter.tls.options=foobar - ``` - -??? info "`traefik.http.routers..observability.accesslogs`" - - The accessLogs option controls whether the router will produce access-logs. - - ```yaml - "traefik.http.routers.myrouter.observability.accesslogs=true" - ``` - -??? info "`traefik.http.routers..observability.metrics`" - - The metrics option controls whether the router will produce metrics. - - ```yaml - "traefik.http.routers.myrouter.observability.metrics=true" - ``` - -??? info "`traefik.http.routers..observability.tracing`" - - The tracing option controls whether the router will produce traces. - - ```yaml - "traefik.http.routers.myrouter.observability.tracing=true" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.http.routers..rule` | See [rule](../http/routing/rules-and-priority.md#rules) for more information. | ```Host(`example.com`)``` | +| `traefik.http.routers..ruleSyntax` | See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information.
RuleSyntax option is deprecated and will be removed in the next major version.
Please do not use this field and rewrite the router rules to use the v3 syntax. | `v3` | +| `traefik.http.routers..priority` | See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. | `42` | +| `traefik.http.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `web,websecure` | +| `traefik.http.routers..middlewares` | See [middlewares overview](../http/middlewares/overview.md) for more information. | `auth,prefix,cb` | +| `traefik.http.routers..service` | See [service](../http/load-balancing/service.md) for more information. | `myservice` | +| `traefik.http.routers..tls` | See [tls](../http/tls/overview.md) for more information. | `true` | +| `traefik.http.routers..tls.certresolver` | See [certResolver](../../install-configuration/tls/certificate-resolvers/overview.md) for more information. | `myresolver` | +| `traefik.http.routers..tls.domains[n].main` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `example.org` | +| `traefik.http.routers..tls.domains[n].sans` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `test.example.org,dev.example.org` | +| `traefik.http.routers..tls.options` | | `foobar` | +| `traefik.http.routers..observability.accesslogs` | The accessLogs option controls whether the router will produce access-logs. | `true` | +| `traefik.http.routers..observability.metrics` | The metrics option controls whether the router will produce metrics. | `true` | +| `traefik.http.routers..observability.tracing` | The tracing option controls whether the router will produce traces. | `true` | ### Services @@ -158,181 +145,34 @@ add tags starting with `traefik.http.services.{name-of-your-choice}.`, followed For example, to change the `passHostHeader` behavior, you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.passhostheader=false`. -??? info "`traefik.http.services..loadbalancer.server.port`" - - Registers a port. - Useful when the service exposes multiples ports. - - ```yaml - traefik.http.services.myservice.loadbalancer.server.port=8080 - ``` +#### Configuration Options -??? info "`traefik.http.services..loadbalancer.server.scheme`" - - Overrides the default scheme. - - ```yaml - traefik.http.services.myservice.loadbalancer.server.scheme=http - ``` - -??? info "`traefik.http.services..loadbalancer.server.weight`" - - Overrides the default weight. - - ```yaml - traefik.http.services.myservice.loadbalancer.server.weight=42 - ``` - -??? info "`traefik.http.services..loadbalancer.serverstransport`" - - Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. - See [serverstransport](../http/load-balancing/serverstransport.md) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.serverstransport=foobar@file - ``` - -??? info "`traefik.http.services..loadbalancer.passhostheader`" - - ```yaml - traefik.http.services.myservice.loadbalancer.passhostheader=true - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.headers.`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.hostname`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.hostname=example.org - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.interval`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.interval=10 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.path`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.method`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.method=foobar - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.status`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.status=42 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.port`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.port=42 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.scheme`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.timeout`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.followredirects`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.followredirects=true - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie=true - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.httponly`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.name`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.path`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.path=/foobar - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.secure`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.samesite`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.maxage`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.maxage=42 - ``` - -??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" - - ```yaml - traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10 - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.http.services..loadbalancer.server.port` | Registers a port.
Useful when the service exposes multiples ports. | `8080` | +| `traefik.http.services..loadbalancer.server.scheme` | Overrides the default scheme. | `http` | +| `traefik.http.services..loadbalancer.server.weight` | Overrides the default weight. | `42` | +| `traefik.http.services..loadbalancer.serverstransport` | Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../http/load-balancing/serverstransport.md) for more information. | `foobar@file` | +| `traefik.http.services..loadbalancer.passhostheader` | | `true` | +| `traefik.http.services..loadbalancer.healthcheck.headers.` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `foobar` | +| `traefik.http.services..loadbalancer.healthcheck.hostname` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `example.org` | +| `traefik.http.services..loadbalancer.healthcheck.interval` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10` | +| `traefik.http.services..loadbalancer.healthcheck.unhealthyinterval` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10` | +| `traefik.http.services..loadbalancer.healthcheck.path` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `/foo` | +| `traefik.http.services..loadbalancer.healthcheck.method` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `foobar` | +| `traefik.http.services..loadbalancer.healthcheck.status` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `42` | +| `traefik.http.services..loadbalancer.healthcheck.port` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `42` | +| `traefik.http.services..loadbalancer.healthcheck.scheme` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `http` | +| `traefik.http.services..loadbalancer.healthcheck.timeout` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10` | +| `traefik.http.services..loadbalancer.healthcheck.followredirects` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.httponly` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.name` | | `foobar` | +| `traefik.http.services..loadbalancer.sticky.cookie.path` | | `/foobar` | +| `traefik.http.services..loadbalancer.sticky.cookie.secure` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.samesite` | | `none` | +| `traefik.http.services..loadbalancer.sticky.cookie.maxage` | | `42` | +| `traefik.http.services..loadbalancer.responseforwarding.flushinterval` | | `10` | ### Middleware @@ -375,124 +215,32 @@ You can declare TCP Routers, Middlewares and/or Services using tags. #### TCP Routers -??? info "`traefik.tcp.routers..entrypoints`" - - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2 - ``` +##### Configuration Options -??? info "`traefik.tcp.routers..rule`" - - See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.rule=HostSNI(`example.com`) - ``` - -??? info "`traefik.tcp.routers..ruleSyntax`" - - !!! warning - - RuleSyntax option is deprecated and will be removed in the next major version. - Please do not use this field and rewrite the router rules to use the v3 syntax. - - configure the rule syntax to be used for parsing the rule on a per-router basis. - - ```yaml - traefik.tcp.routers.mytcprouter.ruleSyntax=v3 - ``` - -??? info "`traefik.tcp.routers..priority`" - See [priority](../tcp/routing/rules-and-priority.md#priority-calculation) for more information. - ```yaml - - "traefik.tcp.routers.mytcprouter.priority=42" - ``` - -??? info "`traefik.tcp.routers..service`" - - See [service](../tcp/service.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.service=myservice - ``` - -??? info "`traefik.tcp.routers..tls`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls=true - ``` - -??? info "`traefik.tcp.routers..tls.certresolver`" - - See [certResolver](../tcp/tls.md#configuration-options) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver - ``` - -??? info "`traefik.tcp.routers..tls.domains[n].main`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.domains[0].main=example.org - ``` - -??? info "`traefik.tcp.routers..tls.domains[n].sans`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.example.org,dev.example.org - ``` - -??? info "`traefik.tcp.routers..tls.options`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.options=mysoptions - ``` - -??? info "`traefik.tcp.routers..tls.passthrough`" - - See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.passthrough=true - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.tcp.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1,ep2` | +| `traefik.tcp.routers..rule` | See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. | ```HostSNI(`example.com`)``` | +| `traefik.tcp.routers..ruleSyntax` | configure the rule syntax to be used for parsing the rule on a per-router basis.
RuleSyntax option is deprecated and will be removed in the next major version.
Please do not use this field and rewrite the router rules to use the v3 syntax. | `v3` | +| `traefik.tcp.routers..priority` | See [priority](../tcp/routing/rules-and-priority.md#priority-calculation) for more information. | `42` | +| `traefik.tcp.routers..service` | See [service](../tcp/service.md) for more information. | `myservice` | +| `traefik.tcp.routers..tls` | See [TLS](../tcp/tls.md) for more information. | `true` | +| `traefik.tcp.routers..tls.certresolver` | See [certResolver](../tcp/tls.md#configuration-options) for more information. | `myresolver` | +| `traefik.tcp.routers..tls.domains[n].main` | See [TLS](../tcp/tls.md) for more information. | `example.org` | +| `traefik.tcp.routers..tls.domains[n].sans` | See [TLS](../tcp/tls.md) for more information. | `test.example.org,dev.example.org` | +| `traefik.tcp.routers..tls.options` | See [TLS](../tcp/tls.md) for more information. | `mysoptions` | +| `traefik.tcp.routers..tls.passthrough` | See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. | `true` | #### TCP Services -??? info "`traefik.tcp.services..loadbalancer.server.port`" - - Registers a port of the application. - - ```yaml - traefik.tcp.services.mytcpservice.loadbalancer.server.port=423 - ``` +##### Configuration Options -??? info "`traefik.tcp.services..loadbalancer.server.tls`" - - Determines whether to use TLS when dialing with the backend. - - ```yaml - traefik.tcp.services.mytcpservice.loadbalancer.server.tls=true - ``` - -??? info "`traefik.tcp.services..loadbalancer.serverstransport`" - - Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. - See [serverstransport](../tcp/serverstransport.md) for more information. - - ```yaml - traefik.tcp.services.mytcpservice.loadbalancer.serverstransport=foobar@file - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.tcp.services..loadbalancer.server.port` | Registers a port of the application. | `423` | +| `traefik.tcp.services..loadbalancer.server.tls` | Determines whether to use TLS when dialing with the backend. | `true` | +| `traefik.tcp.services..loadbalancer.serverstransport` | Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../tcp/serverstransport.md) for more information. | `foobar@file` | #### TCP Middleware @@ -534,67 +282,28 @@ You can declare UDP Routers and/or Services using tags. #### UDP Routers -??? info "`traefik.udp.routers..entrypoints`" - - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - traefik.udp.routers.myudprouter.entrypoints=ep1,ep2 - ``` +##### Configuration Options -??? info "`traefik.udp.routers..service`" - - See [service](../udp/service.md) for more information. - - ```yaml - traefik.udp.routers.myudprouter.service=myservice - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.udp.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1,ep2` | +| `traefik.udp.routers..service` | See [service](../udp/service.md) for more information. | `myservice` | #### UDP Services -??? info "`traefik.udp.services..loadbalancer.server.port`" - - Registers a port of the application. - - ```yaml - traefik.udp.services.myudpservice.loadbalancer.server.port=423 - ``` +##### Configuration Options + +| Label | Description | Value | +|------|-------------|-------| +| `traefik.udp.services..loadbalancer.server.port` | Registers a port of the application. | `423` | ### Specific Provider Options -#### `traefik.enable` - -```yaml -traefik.enable=true -``` - -You can tell Traefik to consider (or not) the service by setting `traefik.enable` to true or false. - -This option overrides the value of `exposedByDefault`. - -#### `traefik.consulcatalog.connect` - -```yaml -traefik.consulcatalog.connect=true -``` - -You can tell Traefik to consider (or not) the service as a Connect capable one by setting `traefik.consulcatalog.connect` to true or false. - -This option overrides the value of `connectByDefault`. - -#### `traefik.consulcatalog.canary` - -```yaml -traefik.consulcatalog.canary=true -``` - -When ConsulCatalog, in the context of a Nomad orchestrator, -is a provider (of service registration) for Traefik, -one might have the need to distinguish within Traefik between a [Canary](https://learn.hashicorp.com/tutorials/nomad/job-blue-green-and-canary-deployments#deploy-with-canaries) instance of a service, or a production one. -For example if one does not want them to be part of the same load-balancer. - -Therefore, this option, which is meant to be provided as one of the values of the `canary_tags` field in the Nomad [service stanza](https://www.nomadproject.io/docs/job-specification/service#canary_tags), -allows Traefik to identify that the associated instance is a canary one. +| Label | Description | Value | +|------|-------------|-------| +| `traefik.enable` | You can tell Traefik to consider (or not) the service by setting `traefik.enable` to true or false.
This option overrides the value of `exposedByDefault`. | `true` | +| `traefik.consulcatalog.connect` | You can tell Traefik to consider (or not) the service as a Connect capable one by setting `traefik.consulcatalog.connect` to true or false.
This option overrides the value of `connectByDefault`. | `true` | +| `traefik.consulcatalog.canary` | When ConsulCatalog, in the context of a Nomad orchestrator, is a provider (of service registration) for Traefik, one might have the need to distinguish within Traefik between a [Canary](https://learn.hashicorp.com/tutorials/nomad/job-blue-green-and-canary-deployments#deploy-with-canaries) instance of a service, or a production one.
For example if one does not want them to be part of the same load-balancer.

Therefore, this option, which is meant to be provided as one of the values of the `canary_tags` field in the Nomad [service stanza](https://www.nomadproject.io/docs/job-specification/service#canary_tags), allows Traefik to identify that the associated instance is a canary one. | `true` | #### Port Lookup diff --git a/docs/content/reference/routing-configuration/other-providers/docker.md b/docs/content/reference/routing-configuration/other-providers/docker.md index 4cc633bd1..6b9953b00 100644 --- a/docs/content/reference/routing-configuration/other-providers/docker.md +++ b/docs/content/reference/routing-configuration/other-providers/docker.md @@ -19,12 +19,12 @@ With Docker, Traefik can leverage labels attached to a container to generate rou Enabling the docker provider - ```yaml tab="File (YAML)" + ```yaml tab="Structured (YAML)" providers: docker: {} ``` - ```toml tab="File (TOML)" + ```toml tab="Structured (TOML)" [providers.docker] ``` @@ -81,7 +81,7 @@ With Docker, Traefik can leverage labels attached to a container to generate rou - traefik.http.services.admin-service.loadbalancer.server.port=9000 ``` -## Routing Configuration +## Configuration Options !!! info "Labels" @@ -145,118 +145,24 @@ For example, to change the rule, you could add the label ```traefik.http.routers !!! warning "The character `@` is not authorized in the router name ``." -??? info "`traefik.http.routers..rule`" +#### Configuration Options - See [rule](../http/routing/rules-and-priority.md) for more information. - - ```yaml - "traefik.http.routers.myrouter.rule=Host(`example.com`)" - ``` - -??? info "`traefik.http.routers..ruleSyntax`" - - !!! warning - - RuleSyntax option is deprecated and will be removed in the next major version. - Please do not use this field and rewrite the router rules to use the v3 syntax. - - See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information. - - ```yaml - traefik.http.routers.myrouter.ruleSyntax=v3 - ``` - -??? info "`traefik.http.routers..entrypoints`" - - ```yaml - "traefik.http.routers.myrouter.entrypoints=ep1,ep2" - ``` - -??? info "`traefik.http.routers..middlewares`" - - See [middlewares overview](../http/middlewares/overview.md) for more information. - - ```yaml - "traefik.http.routers.myrouter.middlewares=auth,prefix,cb" - ``` - -??? info "`traefik.http.routers..service`" - - See [service](../http/load-balancing/service.md) for more information. - - ```yaml - "traefik.http.routers.myrouter.service=myservice" - ``` - -??? info "`traefik.http.routers..tls`" - - See [tls](../http/tls/overview.md) for more information. - - ```yaml - "traefik.http.routers.myrouter.tls=true" - ``` - -??? info "`traefik.http.routers..tls.certresolver`" - - See [certResolver](../../install-configuration/tls/certificate-resolvers/overview.md) for more information. - - ```yaml - "traefik.http.routers.myrouter.tls.certresolver=myresolver" - ``` - -??? info "`traefik.http.routers..tls.domains[n].main`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - ```yaml - "traefik.http.routers.myrouter.tls.domains[0].main=example.org" - ``` - -??? info "`traefik.http.routers..tls.domains[n].sans`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - ```yaml - "traefik.http.routers.myrouter.tls.domains[0].sans=test.example.org,dev.example.org" - ``` - -??? info "`traefik.http.routers..tls.options`" - - ```yaml - "traefik.http.routers.myrouter.tls.options=foobar" - ``` - -??? info "`traefik.http.routers..observability.accesslogs`" - - The accessLogs option controls whether the router will produce access-logs. - - ```yaml - "traefik.http.routers.myrouter.observability.accesslogs=true" - ``` - -??? info "`traefik.http.routers..observability.metrics`" - - The metrics option controls whether the router will produce metrics. - - ```yaml - "traefik.http.routers.myrouter.observability.metrics=true" - ``` - -??? info "`traefik.http.routers..observability.tracing`" - - The tracing option controls whether the router will produce traces. - - ```yaml - "traefik.http.routers.myrouter.observability.tracing=true" - ``` - -??? info "`traefik.http.routers..priority`" - - See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. - - ```yaml - "traefik.http.routers.myrouter.priority=42" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.http.routers..rule` | See [rule](../http/routing/rules-and-priority.md#rules) for more information. | ```Host(`example.com`)``` | +| `traefik.http.routers..ruleSyntax` | See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information.
RuleSyntax option is deprecated and will be removed in the next major version.
Please do not use this field and rewrite the router rules to use the v3 syntax. | `v3` | +| `traefik.http.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1,ep2` | +| `traefik.http.routers..middlewares` | See [middlewares overview](../http/middlewares/overview.md) for more information. | `auth,prefix,cb` | +| `traefik.http.routers..service` | See [service](../http/load-balancing/service.md) for more information. | `myservice` | +| `traefik.http.routers..tls` | See [tls](../http/tls/overview.md) for more information. | `true` | +| `traefik.http.routers..tls.certresolver` | See [certResolver](../../install-configuration/tls/certificate-resolvers/overview.md) for more information. | `myresolver` | +| `traefik.http.routers..tls.domains[n].main` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `example.org` | +| `traefik.http.routers..tls.domains[n].sans` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `test.example.org,dev.example.org` | +| `traefik.http.routers..tls.options` | | `foobar` | +| `traefik.http.routers..observability.accesslogs` | The accessLogs option controls whether the router will produce access-logs. | `true` | +| `traefik.http.routers..observability.metrics` | The metrics option controls whether the router will produce metrics. | `true` | +| `traefik.http.routers..observability.tracing` | The tracing option controls whether the router will produce traces. | `true` | +| `traefik.http.routers..priority` | See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. | `42` | ### Services @@ -268,182 +174,34 @@ you'd add the label `traefik.http.services..loadbalancer.pa !!! warning "The character `@` is not authorized in the service name ``." -??? info "`traefik.http.services..loadbalancer.server.port`" +#### Configuration Options - Registers a port. - Useful when the container exposes multiples ports. - - ```yaml - "traefik.http.services.myservice.loadbalancer.server.port=8080" - ``` - -??? info "`traefik.http.services..loadbalancer.server.scheme`" - - Overrides the default scheme. - - ```yaml - "traefik.http.services.myservice.loadbalancer.server.scheme=http" - ``` - -??? info "`traefik.http.services..loadbalancer.server.url`" - - Defines the service URL. - This option cannot be used in combination with `port` or `scheme` definition. - - ```yaml - traefik.http.services..loadbalancer.server.url=http://foobar:8080 - ``` - -??? info "`traefik.http.services..loadbalancer.serverstransport`" - - Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. - See [serverstransport](../http/load-balancing/serverstransport.md) for more information. - - ```yaml - "traefik.http.services.myservice.loadbalancer.serverstransport=foobar@file" - ``` - -??? info "`traefik.http.services..loadbalancer.passhostheader`" - - ```yaml - "traefik.http.services.myservice.loadbalancer.passhostheader=true" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.headers.`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.hostname`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.hostname=example.org" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.interval`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.interval=10s" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10s" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.path`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.method`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.method=foobar" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.status`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.status=42" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.port`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.port=42" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.scheme`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.timeout`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10s" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.followredirects`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.followredirects=true" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie`" - - ```yaml - "traefik.http.services.myservice.loadbalancer.sticky.cookie=true" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.httponly`" - - ```yaml - "traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.name`" - - ```yaml - "traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.path`" - - ```yaml - "traefik.http.services.myservice.loadbalancer.sticky.cookie.path=/foobar" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.secure`" - - ```yaml - "traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.samesite`" - - ```yaml - "traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.maxage`" - - ```yaml - "traefik.http.services.myservice.loadbalancer.sticky.cookie.maxage=42" - ``` - -??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" - - ```yaml - "traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.http.services..loadbalancer.server.port` | Registers a port.
Useful when the container exposes multiples ports. | `8080` | +| `traefik.http.services..loadbalancer.server.scheme` | Overrides the default scheme. | `http` | +| `traefik.http.services..loadbalancer.server.url` | Defines the service URL.
This option cannot be used in combination with `port` or `scheme` definition. | `http://foobar:8080` | +| `traefik.http.services..loadbalancer.serverstransport` | Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../http/load-balancing/serverstransport.md) for more information. | `foobar@file` | +| `traefik.http.services..loadbalancer.passhostheader` | | `true` | +| `traefik.http.services..loadbalancer.healthcheck.headers.` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `foobar` | +| `traefik.http.services..loadbalancer.healthcheck.hostname` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `example.org` | +| `traefik.http.services..loadbalancer.healthcheck.interval` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10s` | +| `traefik.http.services..loadbalancer.healthcheck.unhealthyinterval` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10s` | +| `traefik.http.services..loadbalancer.healthcheck.path` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `/foo` | +| `traefik.http.services..loadbalancer.healthcheck.method` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `foobar` | +| `traefik.http.services..loadbalancer.healthcheck.status` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `42` | +| `traefik.http.services..loadbalancer.healthcheck.port` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `42` | +| `traefik.http.services..loadbalancer.healthcheck.scheme` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `http` | +| `traefik.http.services..loadbalancer.healthcheck.timeout` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10s` | +| `traefik.http.services..loadbalancer.healthcheck.followredirects` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.httponly` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.name` | | `foobar` | +| `traefik.http.services..loadbalancer.sticky.cookie.path` | | `/foobar` | +| `traefik.http.services..loadbalancer.sticky.cookie.secure` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.samesite` | | `none` | +| `traefik.http.services..loadbalancer.sticky.cookie.maxage` | | `42` | +| `traefik.http.services..loadbalancer.responseforwarding.flushinterval` | | `10` | ### Middleware @@ -497,123 +255,31 @@ You can declare TCP Routers and/or Services using labels. #### TCP Routers -??? info "`traefik.tcp.routers..entrypoints`" +##### Configuration Options - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - "traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2" - ``` - -??? info "`traefik.tcp.routers..rule`" - - See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. - - ```yaml - "traefik.tcp.routers.mytcprouter.rule=HostSNI(`example.com`)" - ``` - -??? info "`traefik.tcp.routers..ruleSyntax`" - - !!! warning - - RuleSyntax option is deprecated and will be removed in the next major version. - Please do not use this field and rewrite the router rules to use the v3 syntax. - - configure the rule syntax to be used for parsing the rule on a per-router basis. - - ```yaml - traefik.tcp.routers.mytcprouter.ruleSyntax=v3 - ``` - -??? info "`traefik.tcp.routers..service`" - - See [service](../tcp/service.md) for more information. - - ```yaml - "traefik.tcp.routers.mytcprouter.service=myservice" - ``` - -??? info "`traefik.tcp.routers..tls`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - "traefik.tcp.routers.mytcprouter.tls=true" - ``` - -??? info "`traefik.tcp.routers..tls.certresolver`" - - See [certResolver](../tcp/tls.md#configuration-options) for more information. - - ```yaml - "traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver" - ``` - -??? info "`traefik.tcp.routers..tls.domains[n].main`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - "traefik.tcp.routers.mytcprouter.tls.domains[0].main=example.org" - ``` - -??? info "`traefik.tcp.routers..tls.domains[n].sans`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - "traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.example.org,dev.example.org" - ``` - -??? info "`traefik.tcp.routers..tls.options`" - - ```yaml - "traefik.tcp.routers.mytcprouter.tls.options=mysoptions" - ``` - -??? info "`traefik.tcp.routers..tls.passthrough`" - - See [TLS](../tcp/tls.md#opt-passthrough) for more information. - - ```yaml - "traefik.tcp.routers.mytcprouter.tls.passthrough=true" - ``` - -??? info "`traefik.tcp.routers..priority`" - - See [priority](../tcp/routing/rules-and-priority.md) for more information. - - ```yaml - "traefik.tcp.routers.mytcprouter.priority=42" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.tcp.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1,ep2` | +| `traefik.tcp.routers..rule` | See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. | ```HostSNI(`example.com`)``` | +| `traefik.tcp.routers..ruleSyntax` | configure the rule syntax to be used for parsing the rule on a per-router basis.
RuleSyntax option is deprecated and will be removed in the next major version.
Please do not use this field and rewrite the router rules to use the v3 syntax. | `v3` | +| `traefik.tcp.routers..service` | See [service](../tcp/service.md) for more information. | `myservice` | +| `traefik.tcp.routers..tls` | See [TLS](../tcp/tls.md) for more information. | `true` | +| `traefik.tcp.routers..tls.certresolver` | See [certResolver](../tcp/tls.md#configuration-options) for more information. | `myresolver` | +| `traefik.tcp.routers..tls.domains[n].main` | See [TLS](../tcp/tls.md) for more information. | `example.org` | +| `traefik.tcp.routers..tls.domains[n].sans` | See [TLS](../tcp/tls.md) for more information. | `test.example.org,dev.example.org` | +| `traefik.tcp.routers..tls.options` | | `mysoptions` | +| `traefik.tcp.routers..tls.passthrough` | See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. | `true` | +| `traefik.tcp.routers..priority` | See [priority](../tcp/routing/rules-and-priority.md#priority-calculation) for more information. | `42` | #### TCP Services -??? info "`traefik.tcp.services..loadbalancer.server.port`" +##### Configuration Options - Registers a port of the application. - - ```yaml - "traefik.tcp.services.mytcpservice.loadbalancer.server.port=423" - ``` - -??? info "`traefik.tcp.services..loadbalancer.server.tls`" - - Determines whether to use TLS when dialing with the backend. - - ```yaml - "traefik.tcp.services.mytcpservice.loadbalancer.server.tls=true" - ``` - -??? info "`traefik.tcp.services..loadbalancer.serverstransport`" - - Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. - See [serverstransport](../tcp/serverstransport.md) for more information. - - ```yaml - "traefik.tcp.services.mytcpservice.loadbalancer.serverstransport=foobar@file" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.tcp.services..loadbalancer.server.port` | Registers a port of the application. | `423` | +| `traefik.tcp.services..loadbalancer.server.tls` | Determines whether to use TLS when dialing with the backend. | `true` | +| `traefik.tcp.services..loadbalancer.serverstransport` | Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../tcp/serverstransport.md) for more information. | `foobar@file` | #### TCP Middleware @@ -659,76 +325,25 @@ You can declare UDP Routers and/or Services using labels. #### UDP Routers -??? info "`traefik.udp.routers..entrypoints`" +##### Configuration Options - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - "traefik.udp.routers.myudprouter.entrypoints=ep1,ep2" - ``` - -??? info "`traefik.udp.routers..service`" - - See [service](../udp/service.md) for more information. - - ```yaml - "traefik.udp.routers.myudprouter.service=myservice" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.udp.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1,ep2` | +| `traefik.udp.routers..service` | See [service](../udp/service.md) for more information. | `myservice` | #### UDP Services -??? info "`traefik.udp.services..loadbalancer.server.port`" +##### Configuration Options - Registers a port of the application. - - ```yaml - "traefik.udp.services.myudpservice.loadbalancer.server.port=423" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.udp.services..loadbalancer.server.port` | Registers a port of the application. | `423` | ### Specific Provider Options -#### `traefik.enable` - -```yaml -- "traefik.enable=true" -``` - -You can tell Traefik to consider (or not) the container by setting `traefik.enable` to true or false. - -This option overrides the value of `exposedByDefault`. - -#### `traefik.docker.allownonrunning` - -```yaml -- "traefik.docker.allownonrunning=true" -``` - -By default, Traefik only considers containers in "running" state. -This option controls whether containers that are not in "running" state (e.g., stopped, paused, exited) should still be visible to Traefik for service discovery. - -When this label is set to true, Traefik will: - -- Keep the router and service configuration even when the container is not running -- Create services with empty backend server lists -- Return 503 Service Unavailable for requests to stopped containers (instead of 404 Not Found) -- Execute the full middleware chain, allowing middlewares to intercept requests - -!!! warning "Configuration Collision" - - As the `traefik.docker.allownonrunning` enables the discovery of all containers exposing this option disregarding their state, - if multiple stopped containers expose the same router but their configurations diverge, then the routers will be dropped. - -#### `traefik.docker.network` - -```yaml -- "traefik.docker.network=mynetwork" -``` - -Overrides the default docker network to use for connections to the container. - -If a container is linked to several networks, be sure to set the proper network name (you can check this with `docker inspect `), -otherwise it will randomly pick one (depending on how docker is returning them). - -!!! warning - - When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`. +| Label | Description | Value | +|------|-------------|-------| +| `traefik.enable` | You can tell Traefik to consider (or not) the container by setting `traefik.enable` to true or false.
This option overrides the value of `exposedByDefault`. | `true` | +| `traefik.docker.allownonrunning` | By default, Traefik only considers containers in "running" state.
This option controls whether containers that are not in "running" state (e.g., stopped, paused, exited) should still be visible to Traefik for service discovery.

When this label is set to true, Traefik will:
- Keep the router and service configuration even when the container is not running
- Create services with empty backend server lists
- Return 503 Service Unavailable for requests to stopped containers (instead of 404 Not Found)
- Execute the full middleware chain, allowing middlewares to intercept requests

As the `traefik.docker.allownonrunning` enables the discovery of all containers exposing this option disregarding their state, if multiple stopped containers expose the same router but their configurations diverge, then the routers will be dropped. | `true` | +| `traefik.docker.network` | Overrides the default docker network to use for connections to the container.
If a container is linked to several networks, be sure to set the proper network name (you can check this with `docker inspect `), otherwise it will randomly pick one (depending on how docker is returning them).

When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`. | `mynetwork` | diff --git a/docs/content/reference/routing-configuration/other-providers/ecs.md b/docs/content/reference/routing-configuration/other-providers/ecs.md index 03b4f4d45..a69e43b6b 100644 --- a/docs/content/reference/routing-configuration/other-providers/ecs.md +++ b/docs/content/reference/routing-configuration/other-providers/ecs.md @@ -13,7 +13,96 @@ With ECS, Traefik can leverage labels attached to a container to generate routin We recommend to *not* use labels to store sensitive data (certificates, credentials, etc). Instead, we recommend to store sensitive data in a safer storage (secrets, file, etc). -## Routing Configurationred +## Configuration Examples + +??? example "Configuring ECS & Deploying / Exposing one Service" + + Enabling the ECS provider + + ```yaml tab="Structured (YAML)" + providers: + ecs: {} + ``` + + ```toml tab="Structured (TOML)" + [providers.ecs] + ``` + + ```bash tab="CLI" + --providers.ecs=true + ``` + + Attaching labels to containers (in your ECS task definition) + + ```json + { + "family": "my-service", + "containerDefinitions": [ + { + "name": "my-container", + "image": "my-image:latest", + "labels": { + "traefik.http.routers.my-container.rule": "Host(`example.com`)" + } + } + ] + } + ``` + +??? example "Specify a Custom Port for the Container" + + Forward requests for `http://example.com` to `http://:12345`: + + ```json + { + "family": "my-service", + "containerDefinitions": [ + { + "name": "my-container", + "image": "my-image:latest", + "labels": { + "traefik.http.routers.my-container.rule": "Host(`example.com`)", + "traefik.http.routers.my-container.service": "my-service", + "traefik.http.services.my-service.loadbalancer.server.port": "12345" + } + } + ] + } + ``` + + !!! important "Traefik Connecting to the Wrong Port: `HTTP/502 Gateway Error`" + By default, Traefik uses the first exposed port of a container. + + Setting the label `traefik.http.services.xxx.loadbalancer.server.port` + overrides that behavior. + +??? example "Specifying more than one router and service per container" + + Forwarding requests to more than one port on a container requires referencing the service loadbalancer port definition using the service parameter on the router. + + In this example, requests are forwarded for `http://example-a.com` to `http://:8000` in addition to `http://example-b.com` forwarding to `http://:9000`: + + ```json + { + "family": "my-service", + "containerDefinitions": [ + { + "name": "my-container", + "image": "my-image:latest", + "labels": { + "traefik.http.routers.www-router.rule": "Host(`example-a.com`)", + "traefik.http.routers.www-router.service": "www-service", + "traefik.http.services.www-service.loadbalancer.server.port": "8000", + "traefik.http.routers.admin-router.rule": "Host(`example-b.com`)", + "traefik.http.routers.admin-router.service": "admin-service", + "traefik.http.services.admin-service.loadbalancer.server.port": "9000" + } + } + ] + } + ``` + +## Configuration Options !!! info "labels" @@ -37,120 +126,24 @@ For example, to change the rule, you could add the label ```traefik.http.routers !!! warning "The character `@` is not authorized in the router name ``." -??? info "`traefik.http.routers..rule`" - - See [rule](../http/routing/rules-and-priority.md#rules) for more information. - - ```yaml - traefik.http.routers.myrouter.rule=Host(`example.com`) - ``` +#### Configuration Options -??? info "`traefik.http.routers..ruleSyntax`" - - !!! warning - - RuleSyntax option is deprecated and will be removed in the next major version. - Please do not use this field and rewrite the router rules to use the v3 syntax. - - See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information. - - ```yaml - traefik.http.routers.myrouter.ruleSyntax=v3 - ``` - -??? info "`traefik.http.routers..entrypoints`" - - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - traefik.http.routers.myrouter.entrypoints=web,websecure - ``` - -??? info "`traefik.http.routers..middlewares`" - - See [middlewares overview](../http/middlewares/overview.md) for more information. - - ```yaml - traefik.http.routers.myrouter.middlewares=auth,prefix,cb - ``` - -??? info "`traefik.http.routers..service`" - - See [service](../http/load-balancing/service.md) for more information. - - ```yaml - traefik.http.routers.myrouter.service=myservice - ``` - -??? info "`traefik.http.routers..tls`" - - See [tls](../http/tls/overview.md) for more information. - - ```yaml - traefik.http.routers.myrouter.tls=true - ``` - -??? info "`traefik.http.routers..tls.certresolver`" - - See [certResolver](../../install-configuration/tls/certificate-resolvers/overview.md) for more information. - - ```yaml - traefik.http.routers.myrouter.tls.certresolver=myresolver - ``` - -??? info "`traefik.http.routers..tls.domains[n].main`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - ```yaml - traefik.http.routers.myrouter.tls.domains[0].main=example.org - ``` - -??? info "`traefik.http.routers..tls.domains[n].sans`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - ```yaml - traefik.http.routers.myrouter.tls.domains[0].sans=test.example.org,dev.example.org - ``` - -??? info "`traefik.http.routers..tls.options`" - - ```yaml - traefik.http.routers.myrouter.tls.options=foobar - ``` - -??? info "`traefik.http.routers..observability.accesslogs`" - - The accessLogs option controls whether the router will produce access-logs. - - ```yaml - "traefik.http.routers.myrouter.observability.accesslogs=true" - ``` - -??? info "`traefik.http.routers..observability.metrics`" - - The metrics option controls whether the router will produce metrics. - - ```yaml - "traefik.http.routers.myrouter.observability.metrics=true" - ``` - -??? info "`traefik.http.routers..observability.tracing`" - - The tracing option controls whether the router will produce traces. - - ```yaml - "traefik.http.routers.myrouter.observability.tracing=true" - ``` - -??? info "`traefik.http.routers..priority`" - - See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. - - ```yaml - traefik.http.routers.myrouter.priority=42 - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.http.routers..rule` | See [rule](../http/routing/rules-and-priority.md#rules) for more information. | ```Host(`example.com`)``` | +| `traefik.http.routers..ruleSyntax` | See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information.
RuleSyntax option is deprecated and will be removed in the next major version.
Please do not use this field and rewrite the router rules to use the v3 syntax. | `v3` | +| `traefik.http.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `web,websecure` | +| `traefik.http.routers..middlewares` | See [middlewares overview](../http/middlewares/overview.md) for more information. | `auth,prefix,cb` | +| `traefik.http.routers..service` | See [service](../http/load-balancing/service.md) for more information. | `myservice` | +| `traefik.http.routers..tls` | See [tls](../http/tls/overview.md) for more information. | `true` | +| `traefik.http.routers..tls.certresolver` | See [certResolver](../../install-configuration/tls/certificate-resolvers/overview.md) for more information. | `myresolver` | +| `traefik.http.routers..tls.domains[n].main` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `example.org` | +| `traefik.http.routers..tls.domains[n].sans` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `test.example.org,dev.example.org` | +| `traefik.http.routers..tls.options` | | `foobar` | +| `traefik.http.routers..observability.accesslogs` | The accessLogs option controls whether the router will produce access-logs. | `true` | +| `traefik.http.routers..observability.metrics` | The metrics option controls whether the router will produce metrics. | `true` | +| `traefik.http.routers..observability.tracing` | The tracing option controls whether the router will produce traces. | `true` | +| `traefik.http.routers..priority` | See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. | `42` | ### Services @@ -162,175 +155,33 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa !!! warning "The character `@` is not authorized in the service name ``." -??? info "`traefik.http.services..loadbalancer.server.port`" - - Registers a port. - Useful when the service exposes multiples ports. - - ```yaml - traefik.http.services.myservice.loadbalancer.server.port=8080 - ``` +#### Configuration Options -??? info "`traefik.http.services..loadbalancer.server.scheme`" - - Overrides the default scheme. - - ```yaml - traefik.http.services.myservice.loadbalancer.server.scheme=http - ``` - -??? info "`traefik.http.services..loadbalancer.serverstransport`" - - Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. - See [serverstransport](../http/load-balancing/serverstransport.md) for more information. - - ```yaml - traefik.http.services..loadbalancer.serverstransport=foobar@file - ``` - -??? info "`traefik.http.services..loadbalancer.passhostheader`" - - ```yaml - traefik.http.services.myservice.loadbalancer.passhostheader=true - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.headers.`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.hostname`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.hostname=example.org - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.interval`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.interval=10 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.path`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.method`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.method=foobar - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.status`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.status=42 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.port`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.port=42 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.scheme`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.timeout`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.followredirects`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.followredirects=true - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie=true - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.httponly`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.name`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.path`" - - ```yaml - "traefik.http.services.myservice.loadbalancer.sticky.cookie.path=/foobar" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.secure`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.samesite`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.maxage`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.maxage=42 - ``` - -??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" - - `FlushInterval` specifies the flush interval to flush to the client while copying the response body. - - ```yaml - traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10 - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.http.services..loadbalancer.server.port` | Registers a port.
Useful when the service exposes multiples ports. | `8080` | +| `traefik.http.services..loadbalancer.server.scheme` | Overrides the default scheme. | `http` | +| `traefik.http.services..loadbalancer.serverstransport` | Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../http/load-balancing/serverstransport.md) for more information. | `foobar@file` | +| `traefik.http.services..loadbalancer.passhostheader` | | `true` | +| `traefik.http.services..loadbalancer.healthcheck.headers.` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `foobar` | +| `traefik.http.services..loadbalancer.healthcheck.hostname` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `example.org` | +| `traefik.http.services..loadbalancer.healthcheck.interval` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10` | +| `traefik.http.services..loadbalancer.healthcheck.unhealthyinterval` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10` | +| `traefik.http.services..loadbalancer.healthcheck.path` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `/foo` | +| `traefik.http.services..loadbalancer.healthcheck.method` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `foobar` | +| `traefik.http.services..loadbalancer.healthcheck.status` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `42` | +| `traefik.http.services..loadbalancer.healthcheck.port` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `42` | +| `traefik.http.services..loadbalancer.healthcheck.scheme` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `http` | +| `traefik.http.services..loadbalancer.healthcheck.timeout` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10` | +| `traefik.http.services..loadbalancer.healthcheck.followredirects` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.httponly` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.name` | | `foobar` | +| `traefik.http.services..loadbalancer.sticky.cookie.path` | | `/foobar` | +| `traefik.http.services..loadbalancer.sticky.cookie.secure` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.samesite` | | `none` | +| `traefik.http.services..loadbalancer.sticky.cookie.maxage` | | `42` | +| `traefik.http.services..loadbalancer.responseforwarding.flushinterval` | `FlushInterval` specifies the flush interval to flush to the client while copying the response body. | `10` | ### Middleware @@ -375,133 +226,32 @@ You can declare TCP Routers and/or Services using labels. #### TCP Routers -??? info "`traefik.tcp.routers..entrypoints`" - - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2 - ``` +##### Configuration Options -??? info "`traefik.tcp.routers..rule`" - - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.rule=HostSNI(`example.com`) - ``` - -??? info "`traefik.tcp.routers..ruleSyntax`" - - !!! warning - - RuleSyntax option is deprecated and will be removed in the next major version. - Please do not use this field and rewrite the router rules to use the v3 syntax. - - configure the rule syntax to be used for parsing the rule on a per-router basis. - - ```yaml - traefik.tcp.routers.mytcprouter.ruleSyntax=v3 - ``` - -??? info "`traefik.tcp.routers..service`" - - See [service](../tcp/service.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.service=myservice - ``` - -??? info "`traefik.tcp.routers..tls`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls=true - ``` - -??? info "`traefik.tcp.routers..tls.certresolver`" - - See [certResolver](../tcp/tls.md#configuration-options) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver - ``` - -??? info "`traefik.tcp.routers..tls.domains[n].main`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.domains[0].main=example.org - ``` - -??? info "`traefik.tcp.routers..tls.domains[n].sans`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.example.org,dev.example.org - ``` - -??? info "`traefik.tcp.routers..tls.options`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.options=mysoptions - ``` - -??? info "`traefik.tcp.routers..tls.passthrough`" - - See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.passthrough=true - ``` - -??? info "`traefik.tcp.routers..priority`" - - See [priority](../tcp/routing/rules-and-priority.md#priority-calculation) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.priority=42 - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.tcp.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1,ep2` | +| `traefik.tcp.routers..rule` | See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. | ```HostSNI(`example.com`)``` | +| `traefik.tcp.routers..ruleSyntax` | configure the rule syntax to be used for parsing the rule on a per-router basis.
RuleSyntax option is deprecated and will be removed in the next major version.
Please do not use this field and rewrite the router rules to use the v3 syntax. | `v3` | +| `traefik.tcp.routers..service` | See [service](../tcp/service.md) for more information. | `myservice` | +| `traefik.tcp.routers..tls` | See [TLS](../tcp/tls.md) for more information. | `true` | +| `traefik.tcp.routers..tls.certresolver` | See [certResolver](../tcp/tls.md#configuration-options) for more information. | `myresolver` | +| `traefik.tcp.routers..tls.domains[n].main` | See [TLS](../tcp/tls.md) for more information. | `example.org` | +| `traefik.tcp.routers..tls.domains[n].sans` | See [TLS](../tcp/tls.md) for more information. | `test.example.org,dev.example.org` | +| `traefik.tcp.routers..tls.options` | See [TLS](../tcp/tls.md) for more information. | `mysoptions` | +| `traefik.tcp.routers..tls.passthrough` | See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. | `true` | +| `traefik.tcp.routers..priority` | See [priority](../tcp/routing/rules-and-priority.md#priority-calculation) for more information. | `42` | #### TCP Services -??? info "`traefik.tcp.services..loadbalancer.server.port`" - - Registers a port of the application. - - ```yaml - traefik.tcp.services.mytcpservice.loadbalancer.server.port=423 - ``` +##### Configuration Options -??? info "`traefik.tcp.services..loadbalancer.server.tls`" - - Determines whether to use TLS when dialing with the backend. - - ```yaml - traefik.tcp.services.mytcpservice.loadbalancer.server.tls=true - ``` - -??? info "`traefik.http.services..loadbalancer.server.weight`" - - Overrides the default weight. - - ```yaml - traefik.http.services.myservice.loadbalancer.server.weight=42 - ``` - -??? info "`traefik.tcp.services..loadbalancer.serverstransport`" - - Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. - See [serverstransport](../tcp/serverstransport.md) for more information. - - ```yaml - traefik.tcp.services..loadbalancer.serverstransport=foobar@file - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.tcp.services..loadbalancer.server.port` | Registers a port of the application. | `423` | +| `traefik.tcp.services..loadbalancer.server.tls` | Determines whether to use TLS when dialing with the backend. | `true` | +| `traefik.tcp.services..loadbalancer.server.weight` | Overrides the default weight. | `42` | +| `traefik.tcp.services..loadbalancer.serverstransport` | Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../tcp/serverstransport.md) for more information. | `foobar@file` | ### UDP @@ -543,40 +293,25 @@ More information about available middlewares in the dedicated [middlewares secti #### UDP Routers -??? info "`traefik.udp.routers..entrypoints`" - - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - traefik.udp.routers.myudprouter.entrypoints=ep1,ep2 - ``` +##### Configuration Options -??? info "`traefik.udp.routers..service`" - - See [service](../udp/service.md) for more information. - - ```yaml - traefik.udp.routers.myudprouter.service=myservice - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.udp.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1,ep2` | +| `traefik.udp.routers..service` | See [service](../udp/service.md) for more information. | `myservice` | #### UDP Services -??? info "`traefik.udp.services..loadbalancer.server.port`" - - Registers a port of the application. - - ```yaml - traefik.udp.services.myudpservice.loadbalancer.server.port=423 - ``` +##### Configuration Options + +| Label | Description | Value | +|------|-------------|-------| +| `traefik.udp.services..loadbalancer.server.port` | Registers a port of the application. | `423` | ### Specific Provider Options -#### `traefik.enable` +#### Configuration Options -```yaml -traefik.enable=true -``` - -You can tell Traefik to consider (or not) the ECS service by setting `traefik.enable` to true or false. - -This option overrides the value of `exposedByDefault`. +| Label | Description | Value | +|------|-------------|-------| +| `traefik.enable` | You can tell Traefik to consider (or not) the ECS service by setting `traefik.enable` to true or false.
This option overrides the value of `exposedByDefault`. | `true` | diff --git a/docs/content/reference/routing-configuration/other-providers/kv.md b/docs/content/reference/routing-configuration/other-providers/kv.md index 8f11d50ba..6707ac2ba 100644 --- a/docs/content/reference/routing-configuration/other-providers/kv.md +++ b/docs/content/reference/routing-configuration/other-providers/kv.md @@ -5,6 +5,62 @@ description: "Read the technical documentation to learn the Traefik Routing Conf # Traefik & KV Stores +## Configuration Examples + +??? example "Configuring KV Store & Deploying / Exposing one Service" + + Enabling a KV store provider (example: Consul) + + ```yaml tab="Structured (YAML)" + providers: + consul: + endpoints: + - "127.0.0.1:8500" + ``` + + ```toml tab="Structured (TOML)" + [providers.consul] + endpoints = ["127.0.0.1:8500"] + ``` + + ```bash tab="CLI" + --providers.consul.endpoints=127.0.0.1:8500 + ``` + + Setting keys in the KV store (example: Consul) + + ```bash + consul kv put traefik/http/routers/my-router/rule "Host(`example.com`)" + consul kv put traefik/http/routers/my-router/service "my-service" + consul kv put traefik/http/services/my-service/loadbalancer/servers/0/url "http://127.0.0.1:8080" + ``` + +??? example "Specify a Custom Port for the Service" + + Forward requests for `http://example.com` to `http://127.0.0.1:12345`: + + ```bash + consul kv put traefik/http/routers/my-router/rule "Host(`example.com`)" + consul kv put traefik/http/routers/my-router/service "my-service" + consul kv put traefik/http/services/my-service/loadbalancer/servers/0/url "http://127.0.0.1:12345" + ``` + +??? example "Specifying more than one router and service" + + Forwarding requests to more than one service requires defining multiple routers and services. + + In this example, requests are forwarded for `http://example-a.com` to `http://127.0.0.1:8000` in addition to `http://example-b.com` forwarding to `http://127.0.0.1:9000`: + + ```bash + consul kv put traefik/http/routers/www-router/rule "Host(`example-a.com`)" + consul kv put traefik/http/routers/www-router/service "www-service" + consul kv put traefik/http/services/www-service/loadbalancer/servers/0/url "http://127.0.0.1:8000" + + consul kv put traefik/http/routers/admin-router/rule "Host(`example-b.com`)" + consul kv put traefik/http/routers/admin-router/service "admin-service" + consul kv put traefik/http/services/admin-service/loadbalancer/servers/0/url "http://127.0.0.1:9000" + ``` + ## Configuration Options !!! info "Keys" @@ -93,15 +149,6 @@ description: "Read the technical documentation to learn the Traefik Routing Conf If you declare multiple middleware with the same name but with different parameters, the middleware fails to be declared. -##### Configuration Example - -```bash -# Declaring a middleware -traefik/http/middlewares/myAddPrefix/addPrefix/prefix=/foobar -# Referencing a middleware -traefik/http/routers//middlewares/0=myAddPrefix -``` - #### ServerTransport ##### Configuration Options @@ -110,17 +157,6 @@ traefik/http/routers//middlewares/0=myAddPrefix |-----------------------------------------------------------------|-----------------------------------------------------------------|-----------------------------------------| | `traefik/http/serversTransports//st_option` | With `st_option` the ServerTransport option to set (ex `maxIdleConnsPerHost`).
More information about available options in the dedicated [ServerTransport section](../http/load-balancing/serverstransport.md). | ServerTransport Options | -##### Configuration Example - -```bash -# Declaring a ServerTransport -traefik/http/serversTransports/myServerTransport/maxIdleConnsPerHost=-1 -traefik/http/serversTransports/myServerTransport/certificates/0/certFile=mypath/cert.pem -traefik/http/serversTransports/myServerTransport/certificates/0/keyFile=mypath/key.pem -# Referencing a middleware -traefik/http/services/myService/serversTransports/0=myServerTransport -``` - ### TCP You can declare TCP Routers and/or Services using KV. @@ -170,15 +206,6 @@ More information about available middlewares in the dedicated [middlewares secti If you declare multiple middleware with the same name but with different parameters, the middleware fails to be declared. -##### Configuration Example - -```bash -# Declaring a middleware -traefik/tcp/middlewares/test-inflightconn/amount=10 -# Referencing a middleware -traefik/tcp/routers//middlewares/0=test-inflightconn -``` - #### ServerTransport ##### Configuration Options @@ -187,15 +214,6 @@ traefik/tcp/routers//middlewares/0=test-inflightconn |-----------------------------------------------------------------|-----------------------------------------------------------------|-----------------------------------------| | `traefik/tcp/serversTransports//st_option` | With `st_option` the ServerTransport option to set (ex `maxIdleConnsPerHost`).
More information about available options in the dedicated [ServerTransport section](../tcp/serverstransport.md). | ServerTransport Options | -##### Configuration Example - -```bash -# Declaring a ServerTransport -traefik/tcp/serversTransports/myServerTransport/maxIdleConnsPerHost=-1 -# Referencing a middleware -traefik/tcp/services/myService/serversTransports/0=myServerTransport -``` - ### UDP You can declare UDP Routers and/or Services using KV. diff --git a/docs/content/reference/routing-configuration/other-providers/nomad.md b/docs/content/reference/routing-configuration/other-providers/nomad.md index 18c962ed7..d4bf63b9e 100644 --- a/docs/content/reference/routing-configuration/other-providers/nomad.md +++ b/docs/content/reference/routing-configuration/other-providers/nomad.md @@ -13,7 +13,108 @@ With Nomad, Traefik can leverage tags attached to a service to generate routing We recommend to *not* use tags to store sensitive data (certificates, credentials, etc). Instead, we recommend to store sensitive data in a safer storage (secrets, file, etc). -## Routing Configuration +## Configuration Examples + +??? example "Configuring Nomad & Deploying / Exposing one Service" + + Enabling the nomad provider + + ```yaml tab="Structured (YAML)" + providers: + nomad: {} + ``` + + ```toml tab="Structured (TOML)" + [providers.nomad] + ``` + + ```bash tab="CLI" + --providers.nomad=true + ``` + + Attaching tags to services (in your Nomad job file) + + ```hcl + job "my-service" { + datacenters = ["dc1"] + + group "web" { + task "app" { + driver = "docker" + + service { + name = "my-service" + tags = [ + "traefik.http.routers.my-service.rule=Host(`example.com`)", + ] + } + } + } + } + ``` + +??? example "Specify a Custom Port for the Container" + + Forward requests for `http://example.com` to `http://:12345`: + + ```hcl + job "my-service" { + datacenters = ["dc1"] + + group "web" { + task "app" { + driver = "docker" + + service { + name = "my-service" + tags = [ + "traefik.http.routers.my-service.rule=Host(`example.com`)", + "traefik.http.routers.my-service.service=my-service", + "traefik.http.services.my-service.loadbalancer.server.port=12345", + ] + } + } + } + } + ``` + + !!! important "Traefik Connecting to the Wrong Port: `HTTP/502 Gateway Error`" + By default, Traefik uses the first exposed port of a container. + + Setting the tag `traefik.http.services.xxx.loadbalancer.server.port` + overrides that behavior. + +??? example "Specifying more than one router and service per container" + + Forwarding requests to more than one port on a container requires referencing the service loadbalancer port definition using the service parameter on the router. + + In this example, requests are forwarded for `http://example-a.com` to `http://:8000` in addition to `http://example-b.com` forwarding to `http://:9000`: + + ```hcl + job "my-service" { + datacenters = ["dc1"] + + group "web" { + task "app" { + driver = "docker" + + service { + name = "my-service" + tags = [ + "traefik.http.routers.www-router.rule=Host(`example-a.com`)", + "traefik.http.routers.www-router.service=www-service", + "traefik.http.services.www-service.loadbalancer.server.port=8000", + "traefik.http.routers.admin-router.rule=Host(`example-b.com`)", + "traefik.http.routers.admin-router.service=admin-service", + "traefik.http.services.admin-service.loadbalancer.server.port=9000", + ] + } + } + } + } + ``` + +## Configuration Options !!! info "Tags" @@ -35,120 +136,24 @@ To update the configuration of the Router automatically attached to the service, For example, to change the rule, you could add the tag ```traefik.http.routers.my-service.rule=Host(`example.com`)```. -??? info "`traefik.http.routers..rule`" +#### Configuration Options - See [rule](../http/routing/rules-and-priority.md) for more information. - - ```yaml - traefik.http.routers.myrouter.rule=Host(`example.com`) - ``` - -??? info "`traefik.http.routers..ruleSyntax`" - - !!! warning - - RuleSyntax option is deprecated and will be removed in the next major version. - Please do not use this field and rewrite the router rules to use the v3 syntax. - - See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information. - - ```yaml - traefik.http.routers.myrouter.ruleSyntax=v3 - ``` - -??? info "`traefik.http.routers..entrypoints`" - - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - traefik.http.routers.myrouter.entrypoints=web,websecure - ``` - -??? info "`traefik.http.routers..middlewares`" - - See [middlewares overview](../http/middlewares/overview.md) for more information. - - ```yaml - traefik.http.routers.myrouter.middlewares=auth,prefix,cb - ``` - -??? info "`traefik.http.routers..service`" - - See [service](../http/load-balancing/service.md) for more information. - - ```yaml - traefik.http.routers.myrouter.service=myservice - ``` - -??? info "`traefik.http.routers..tls`" - - See [tls](../http/tls/overview.md) for more information. - - ```yaml - traefik.http.routers.myrouter.tls=true - ``` - -??? info "`traefik.http.routers..tls.certresolver`" - - See [certResolver](../../install-configuration/tls/certificate-resolvers/overview.md) for more information. - - ```yaml - traefik.http.routers.myrouter.tls.certresolver=myresolver - ``` - -??? info "`traefik.http.routers..tls.domains[n].main`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - ```yaml - traefik.http.routers.myrouter.tls.domains[0].main=example.org - ``` - -??? info "`traefik.http.routers..tls.domains[n].sans`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - ```yaml - traefik.http.routers.myrouter.tls.domains[0].sans=test.example.org,dev.example.org - ``` - -??? info "`traefik.http.routers..tls.options`" - - ```yaml - traefik.http.routers.myrouter.tls.options=foobar - ``` - -??? info "`traefik.http.routers..priority`" - - See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. - - ```yaml - traefik.http.routers.myrouter.priority=42 - ``` - -??? info "`traefik.http.routers..observability.accesslogs`" - - The accessLogs option controls whether the router will produce access-logs. - - ```yaml - "traefik.http.routers.myrouter.observability.accesslogs=true" - ``` - -??? info "`traefik.http.routers..observability.metrics`" - - The metrics option controls whether the router will produce metrics. - - ```yaml - "traefik.http.routers.myrouter.observability.metrics=true" - ``` - -??? info "`traefik.http.routers..observability.tracing`" - - The tracing option controls whether the router will produce traces. - - ```yaml - "traefik.http.routers.myrouter.observability.tracing=true" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.http.routers..rule` | See [rule](../http/routing/rules-and-priority.md#rules) for more information. | ```Host(`example.com`)``` | +| `traefik.http.routers..ruleSyntax` | See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information.
RuleSyntax option is deprecated and will be removed in the next major version.
Please do not use this field and rewrite the router rules to use the v3 syntax. | `v3` | +| `traefik.http.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `web,websecure` | +| `traefik.http.routers..middlewares` | See [middlewares overview](../http/middlewares/overview.md) for more information. | `auth,prefix,cb` | +| `traefik.http.routers..service` | See [service](../http/load-balancing/service.md) for more information. | `myservice` | +| `traefik.http.routers..tls` | See [tls](../http/tls/overview.md) for more information. | `true` | +| `traefik.http.routers..tls.certresolver` | See [certResolver](../../install-configuration/tls/certificate-resolvers/overview.md) for more information. | `myresolver` | +| `traefik.http.routers..tls.domains[n].main` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `example.org` | +| `traefik.http.routers..tls.domains[n].sans` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `test.example.org,dev.example.org` | +| `traefik.http.routers..tls.options` | | `foobar` | +| `traefik.http.routers..priority` | See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. | `42` | +| `traefik.http.routers..observability.accesslogs` | The accessLogs option controls whether the router will produce access-logs. | `true` | +| `traefik.http.routers..observability.metrics` | The metrics option controls whether the router will produce metrics. | `true` | +| `traefik.http.routers..observability.tracing` | The tracing option controls whether the router will produce traces. | `true` | ### Services @@ -158,173 +163,33 @@ add tags starting with `traefik.http.services.{name-of-your-choice}.`, followed For example, to change the `passHostHeader` behavior, you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.passhostheader=false`. -??? info "`traefik.http.services..loadbalancer.server.port`" +#### Configuration Options - Registers a port. - Useful when the service exposes multiples ports. - - ```yaml - traefik.http.services.myservice.loadbalancer.server.port=8080 - ``` - -??? info "`traefik.http.services..loadbalancer.server.scheme`" - - Overrides the default scheme. - - ```yaml - traefik.http.services.myservice.loadbalancer.server.scheme=http - ``` - -??? info "`traefik.http.services..loadbalancer.server.weight`" - - Overrides the default weight. - - ```yaml - traefik.http.services.myservice.loadbalancer.server.weight=42 - ``` - -??? info "`traefik.http.services..loadbalancer.serverstransport`" - - Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. - See [serverstransport](../http/load-balancing/serverstransport.md) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.serverstransport=foobar@file - ``` - -??? info "`traefik.http.services..loadbalancer.passhostheader`" - - ```yaml - traefik.http.services.myservice.loadbalancer.passhostheader=true - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.headers.`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.hostname`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.hostname=example.org - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.interval`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.interval=10 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.path`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.status`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.status=42 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.port`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.port=42 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.scheme`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.timeout`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10 - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.followredirects`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - traefik.http.services.myservice.loadbalancer.healthcheck.followredirects=true - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie=true - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.httponly`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.name`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.path`" - - ```yaml - - "traefik.http.services.myservice.loadbalancer.sticky.cookie.path=/foobar" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.secure`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.samesite`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.maxage`" - - ```yaml - traefik.http.services.myservice.loadbalancer.sticky.cookie.maxage=42 - ``` - -??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" - - ```yaml - traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10 - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.http.services..loadbalancer.server.port` | Registers a port.
Useful when the service exposes multiples ports. | `8080` | +| `traefik.http.services..loadbalancer.server.scheme` | Overrides the default scheme. | `http` | +| `traefik.http.services..loadbalancer.server.weight` | Overrides the default weight. | `42` | +| `traefik.http.services..loadbalancer.serverstransport` | Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../http/load-balancing/serverstransport.md) for more information. | `foobar@file` | +| `traefik.http.services..loadbalancer.passhostheader` | | `true` | +| `traefik.http.services..loadbalancer.healthcheck.headers.` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `foobar` | +| `traefik.http.services..loadbalancer.healthcheck.hostname` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `example.org` | +| `traefik.http.services..loadbalancer.healthcheck.interval` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10` | +| `traefik.http.services..loadbalancer.healthcheck.unhealthyinterval` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10` | +| `traefik.http.services..loadbalancer.healthcheck.path` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `/foo` | +| `traefik.http.services..loadbalancer.healthcheck.status` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `42` | +| `traefik.http.services..loadbalancer.healthcheck.port` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `42` | +| `traefik.http.services..loadbalancer.healthcheck.scheme` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `http` | +| `traefik.http.services..loadbalancer.healthcheck.timeout` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10` | +| `traefik.http.services..loadbalancer.healthcheck.followredirects` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.httponly` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.name` | | `foobar` | +| `traefik.http.services..loadbalancer.sticky.cookie.path` | | `/foobar` | +| `traefik.http.services..loadbalancer.sticky.cookie.secure` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.samesite` | | `none` | +| `traefik.http.services..loadbalancer.sticky.cookie.maxage` | | `42` | +| `traefik.http.services..loadbalancer.responseforwarding.flushinterval` | | `10` | ### Middleware @@ -367,125 +232,31 @@ You can declare TCP Routers and/or Services using tags. #### TCP Routers -??? info "`traefik.tcp.routers..entrypoints`" +##### Configuration Options - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2 - ``` - -??? info "`traefik.tcp.routers..rule`" - - See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.rule=HostSNI(`example.com`) - ``` - -??? info "`traefik.tcp.routers..ruleSyntax`" - - !!! warning - - RuleSyntax option is deprecated and will be removed in the next major version. - Please do not use this field and rewrite the router rules to use the v3 syntax. - - configure the rule syntax to be used for parsing the rule on a per-router basis. - - ```yaml - traefik.tcp.routers.mytcprouter.ruleSyntax=v3 - ``` - -??? info "`traefik.tcp.routers..priority`" - - See [priority](../tcp/routing/rules-and-priority.md#priority-calculation) for more information. - - ```yaml - traefik.tcp.routers.myrouter.priority=42 - ``` - -??? info "`traefik.tcp.routers..service`" - - See [service](../tcp/service.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.service=myservice - ``` - -??? info "`traefik.tcp.routers..tls`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls=true - ``` - -??? info "`traefik.tcp.routers..tls.certresolver`" - - See [certResolver](../tcp/tls.md#configuration-options) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver - ``` - -??? info "`traefik.tcp.routers..tls.domains[n].main`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.domains[0].main=example.org - ``` - -??? info "`traefik.tcp.routers..tls.domains[n].sans`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.example.org,dev.example.org - ``` - -??? info "`traefik.tcp.routers..tls.options`" - - See [TLS](../tcp/tls.md#configuration-options) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.options=myoptions - ``` - -??? info "`traefik.tcp.routers..tls.passthrough`" - - See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. - - ```yaml - traefik.tcp.routers.mytcprouter.tls.passthrough=true - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.tcp.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1,ep2` | +| `traefik.tcp.routers..rule` | See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. | ```HostSNI(`example.com`)``` | +| `traefik.tcp.routers..ruleSyntax` | configure the rule syntax to be used for parsing the rule on a per-router basis.
RuleSyntax option is deprecated and will be removed in the next major version.
Please do not use this field and rewrite the router rules to use the v3 syntax. | `v3` | +| `traefik.tcp.routers..priority` | See [priority](../tcp/routing/rules-and-priority.md#priority-calculation) for more information. | `42` | +| `traefik.tcp.routers..service` | See [service](../tcp/service.md) for more information. | `myservice` | +| `traefik.tcp.routers..tls` | See [TLS](../tcp/tls.md) for more information. | `true` | +| `traefik.tcp.routers..tls.certresolver` | See [certResolver](../tcp/tls.md#configuration-options) for more information. | `myresolver` | +| `traefik.tcp.routers..tls.domains[n].main` | See [TLS](../tcp/tls.md) for more information. | `example.org` | +| `traefik.tcp.routers..tls.domains[n].sans` | See [TLS](../tcp/tls.md) for more information. | `test.example.org,dev.example.org` | +| `traefik.tcp.routers..tls.options` | See [TLS](../tcp/tls.md#configuration-options) for more information. | `myoptions` | +| `traefik.tcp.routers..tls.passthrough` | See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. | `true` | #### TCP Services -??? info "`traefik.tcp.services..loadbalancer.server.port`" +##### Configuration Options - Registers a port of the application. - - ```yaml - traefik.tcp.services.mytcpservice.loadbalancer.server.port=423 - ``` - -??? info "`traefik.tcp.services..loadbalancer.server.tls`" - - Determines whether to use TLS when dialing with the backend. - - ```yaml - traefik.tcp.services.mytcpservice.loadbalancer.server.tls=true - ``` - -??? info "`traefik.tcp.services..loadbalancer.serverstransport`" - - Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. - See [serverstransport](../tcp/serverstransport.md) for more information. - - ```yaml - traefik.tcp.services.myservice.loadbalancer.serverstransport=foobar@file - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.tcp.services..loadbalancer.server.port` | Registers a port of the application. | `423` | +| `traefik.tcp.services..loadbalancer.server.tls` | Determines whether to use TLS when dialing with the backend. | `true` | +| `traefik.tcp.services..loadbalancer.serverstransport` | Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../tcp/serverstransport.md) for more information. | `foobar@file` | #### TCP Middleware @@ -527,56 +298,27 @@ You can declare UDP Routers and/or Services using tags. #### UDP Routers -??? info "`traefik.udp.routers..entrypoints`" +##### Configuration Options - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - traefik.udp.routers.myudprouter.entrypoints=ep1,ep2 - ``` - -??? info "`traefik.udp.routers..service`" - - See [service](../udp/service.md) for more information. - - ```yaml - traefik.udp.routers.myudprouter.service=myservice - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.udp.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1,ep2` | +| `traefik.udp.routers..service` | See [service](../udp/service.md) for more information. | `myservice` | #### UDP Services -??? info "`traefik.udp.services..loadbalancer.server.port`" +##### Configuration Options - Registers a port of the application. - - ```yaml - traefik.udp.services.myudpservice.loadbalancer.server.port=423 - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.udp.services..loadbalancer.server.port` | Registers a port of the application. | `423` | ### Specific Provider Options -#### `traefik.enable` - -```yaml -traefik.enable=true -``` - -You can tell Traefik to consider (or not) the service by setting `traefik.enable` to true or false. - -This option overrides the value of `exposedByDefault`. - -#### `traefik.nomad.canary` - -```yaml -traefik.nomad.canary=true -``` - -When Nomad orchestrator is a provider (of service registration) for Traefik, -one might have the need to distinguish within Traefik between a [Canary](https://learn.hashicorp.com/tutorials/nomad/job-blue-green-and-canary-deployments#deploy-with-canaries) instance of a service, or a production one. -For example if one does not want them to be part of the same load-balancer. - -Therefore, this option, which is meant to be provided as one of the values of the `canary_tags` field in the Nomad [service stanza](https://www.nomadproject.io/docs/job-specification/service#canary_tags), -allows Traefik to identify that the associated instance is a canary one. +| Label | Description | Value | +|------|-------------|-------| +| `traefik.enable` | You can tell Traefik to consider (or not) the service by setting `traefik.enable` to true or false.
This option overrides the value of `exposedByDefault`. | `true` | +| `traefik.nomad.canary` | When Nomad orchestrator is a provider (of service registration) for Traefik, one might have the need to distinguish within Traefik between a [Canary](https://learn.hashicorp.com/tutorials/nomad/job-blue-green-and-canary-deployments#deploy-with-canaries) instance of a service, or a production one.
For example if one does not want them to be part of the same load-balancer.

Therefore, this option, which is meant to be provided as one of the values of the `canary_tags` field in the Nomad [service stanza](https://www.nomadproject.io/docs/job-specification/service#canary_tags), allows Traefik to identify that the associated instance is a canary one. | `true` | #### Port Lookup diff --git a/docs/content/reference/routing-configuration/other-providers/swarm.md b/docs/content/reference/routing-configuration/other-providers/swarm.md index 2885b1e48..a49e6c861 100644 --- a/docs/content/reference/routing-configuration/other-providers/swarm.md +++ b/docs/content/reference/routing-configuration/other-providers/swarm.md @@ -19,7 +19,7 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate Enabling the docker provider (Swarm Mode) - ```yaml tab="File (YAML)" + ```yaml tab="Structured (YAML)" providers: swarm: # swarm classic (1.12-) @@ -28,7 +28,7 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate endpoint: "tcp://127.0.0.1:2377" ``` - ```toml tab="File (TOML)" + ```toml tab="Structured (TOML)" [providers.swarm] # swarm classic (1.12-) # endpoint = "tcp://127.0.0.1:2375" @@ -104,7 +104,7 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate - traefik.http.services.admin-service.loadbalancer.server.port=9000 ``` -## Routing Configuration +## Configuration Options !!! info "Labels" @@ -156,120 +156,24 @@ For example, to change the rule, you could add the label ```traefik.http.routers !!! warning "The character `@` is not authorized in the router name ``." -??? info "`traefik.http.routers..rule`" +#### Configuration Options - See [rule](../http/routing/rules-and-priority.md) for more information. - - ```yaml - - "traefik.http.routers.myrouter.rule=Host(`example.com`)" - ``` - -??? info "`traefik.http.routers..ruleSyntax`" - - !!! warning - - RuleSyntax option is deprecated and will be removed in the next major version. - Please do not use this field and rewrite the router rules to use the v3 syntax. - - See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information. - - ```yaml - traefik.http.routers.myrouter.ruleSyntax=v3 - ``` - -??? info "`traefik.http.routers..entrypoints`" - - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - - "traefik.http.routers.myrouter.entrypoints=ep1,ep2" - ``` - -??? info "`traefik.http.routers..middlewares`" - - See [middlewares overview](../http/middlewares/overview.md) for more information. - - ```yaml - - "traefik.http.routers.myrouter.middlewares=auth,prefix,cb" - ``` - -??? info "`traefik.http.routers..service`" - - See [service](../http/load-balancing/service.md) for more information. - - ```yaml - - "traefik.http.routers.myrouter.service=myservice" - ``` - -??? info "`traefik.http.routers..tls`" - - See [tls](../http/tls/overview.md) for more information. - - ```yaml - - "traefik.http.routers.myrouter.tls=true" - ``` - -??? info "`traefik.http.routers..tls.certresolver`" - - See [certResolver](../../install-configuration/tls/certificate-resolvers/overview.md) for more information. - - ```yaml - - "traefik.http.routers.myrouter.tls.certresolver=myresolver" - ``` - -??? info "`traefik.http.routers..tls.domains[n].main`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - ```yaml - - "traefik.http.routers.myrouter.tls.domains[0].main=example.org" - ``` - -??? info "`traefik.http.routers..tls.domains[n].sans`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - ```yaml - - "traefik.http.routers.myrouter.tls.domains[0].sans=test.example.org,dev.example.org" - ``` - -??? info "`traefik.http.routers..tls.options`" - - ```yaml - - "traefik.http.routers.myrouter.tls.options=foobar" - ``` - -??? info "`traefik.http.routers..observability.accesslogs`" - - The accessLogs option controls whether the router will produce access-logs. - - ```yaml - "traefik.http.routers.myrouter.observability.accesslogs=true" - ``` - -??? info "`traefik.http.routers..observability.metrics`" - - The metrics option controls whether the router will produce metrics. - - ```yaml - "traefik.http.routers.myrouter.observability.metrics=true" - ``` - -??? info "`traefik.http.routers..observability.tracing`" - - The tracing option controls whether the router will produce traces. - - ```yaml - "traefik.http.routers.myrouter.observability.tracing=true" - ``` - -??? info "`traefik.http.routers..priority`" - - See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. - - ```yaml - - "traefik.http.routers.myrouter.priority=42" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.http.routers..rule` | See [rule](../http/routing/rules-and-priority.md#rules) for more information. | ```Host(`example.com`)``` | +| `traefik.http.routers..ruleSyntax` | See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information.
RuleSyntax option is deprecated and will be removed in the next major version.
Please do not use this field and rewrite the router rules to use the v3 syntax. | `v3` | +| `traefik.http.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1,ep2` | +| `traefik.http.routers..middlewares` | See [middlewares overview](../http/middlewares/overview.md) for more information. | `auth,prefix,cb` | +| `traefik.http.routers..service` | See [service](../http/load-balancing/service.md) for more information. | `myservice` | +| `traefik.http.routers..tls` | See [tls](../http/tls/overview.md) for more information. | `true` | +| `traefik.http.routers..tls.certresolver` | See [certResolver](../../install-configuration/tls/certificate-resolvers/overview.md) for more information. | `myresolver` | +| `traefik.http.routers..tls.domains[n].main` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `example.org` | +| `traefik.http.routers..tls.domains[n].sans` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `test.example.org,dev.example.org` | +| `traefik.http.routers..tls.options` | | `foobar` | +| `traefik.http.routers..observability.accesslogs` | The accessLogs option controls whether the router will produce access-logs. | `true` | +| `traefik.http.routers..observability.metrics` | The metrics option controls whether the router will produce metrics. | `true` | +| `traefik.http.routers..observability.tracing` | The tracing option controls whether the router will produce traces. | `true` | +| `traefik.http.routers..priority` | See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. | `42` | ### Services @@ -281,189 +185,34 @@ you'd add the label `traefik.http.services..loadbalancer.pa !!! warning "The character `@` is not authorized in the service name ``." -??? info "`traefik.http.services..loadbalancer.server.port`" +#### Configuration Options - Registers a port. - Useful when the container exposes multiples ports. - - Mandatory for Docker Swarm (see the section ["Port Detection with Docker Swarm"](../../install-configuration/providers/swarm.md#port-detection)). - {: #port } - - ```yaml - - "traefik.http.services.myservice.loadbalancer.server.port=8080" - ``` - -??? info "`traefik.http.services..loadbalancer.server.scheme`" - - Overrides the default scheme. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.server.scheme=http" - ``` - -??? info "`traefik.http.services..loadbalancer.server.url`" - - Defines the service URL. - This option cannot be used in combination with `port` or `scheme` definition. - - ```yaml - traefik.http.services..loadbalancer.server.url=http://foobar:8080 - ``` - -??? info "`traefik.http.services..loadbalancer.server.weight`" - - Overrides the default weight. - - ```yaml - traefik.http.services.myservice.loadbalancer.server.weight=42 - ``` - -??? info "`traefik.http.services..loadbalancer.serverstransport`" - - Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. - See [serverstransport](../http/load-balancing/serverstransport.md) for more information. - - ```yaml - - "traefik.http.services..loadbalancer.serverstransport=foobar@file" - ``` - -??? info "`traefik.http.services..loadbalancer.passhostheader`" - - ```yaml - - "traefik.http.services.myservice.loadbalancer.passhostheader=true" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.headers.`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.hostname`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.hostname=example.org" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.interval`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.interval=10s" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10s" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.path`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.method`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.method=foobar" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.status`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.status=42" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.port`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.port=42" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.scheme`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.timeout`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10s" - ``` - -??? info "`traefik.http.services..loadbalancer.healthcheck.followredirects`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.healthcheck.followredirects=true" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie`" - - ```yaml - - "traefik.http.services.myservice.loadbalancer.sticky.cookie=true" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.httponly`" - - ```yaml - - "traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.name`" - - ```yaml - - "traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.path`" - - ```yaml - - "traefik.http.services.myservice.loadbalancer.sticky.cookie.path=/foobar" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.secure`" - - ```yaml - - "traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true" - ``` - -??? info "`traefik.http.services..loadbalancer.sticky.cookie.samesite`" - - ```yaml - - "traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none" - ``` - -??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" - - See [response forwarding](../http/load-balancing/service.md#configuration-options) for more information. - - ```yaml - - "traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.http.services..loadbalancer.server.port` | Registers a port.
Useful when the container exposes multiples ports.
Mandatory for Docker Swarm (see the section ["Port Detection with Docker Swarm"](../../install-configuration/providers/swarm.md#port-detection)). | `8080` | +| `traefik.http.services..loadbalancer.server.scheme` | Overrides the default scheme. | `http` | +| `traefik.http.services..loadbalancer.server.url` | Defines the service URL.
This option cannot be used in combination with `port` or `scheme` definition. | `http://foobar:8080` | +| `traefik.http.services..loadbalancer.server.weight` | Overrides the default weight. | `42` | +| `traefik.http.services..loadbalancer.serverstransport` | Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../http/load-balancing/serverstransport.md) for more information. | `foobar@file` | +| `traefik.http.services..loadbalancer.passhostheader` | | `true` | +| `traefik.http.services..loadbalancer.healthcheck.headers.` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `foobar` | +| `traefik.http.services..loadbalancer.healthcheck.hostname` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `example.org` | +| `traefik.http.services..loadbalancer.healthcheck.interval` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10s` | +| `traefik.http.services..loadbalancer.healthcheck.unhealthyinterval` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10s` | +| `traefik.http.services..loadbalancer.healthcheck.path` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `/foo` | +| `traefik.http.services..loadbalancer.healthcheck.method` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `foobar` | +| `traefik.http.services..loadbalancer.healthcheck.status` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `42` | +| `traefik.http.services..loadbalancer.healthcheck.port` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `42` | +| `traefik.http.services..loadbalancer.healthcheck.scheme` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `http` | +| `traefik.http.services..loadbalancer.healthcheck.timeout` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10s` | +| `traefik.http.services..loadbalancer.healthcheck.followredirects` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.httponly` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.name` | | `foobar` | +| `traefik.http.services..loadbalancer.sticky.cookie.path` | | `/foobar` | +| `traefik.http.services..loadbalancer.sticky.cookie.secure` | | `true` | +| `traefik.http.services..loadbalancer.sticky.cookie.samesite` | | `none` | +| `traefik.http.services..loadbalancer.responseforwarding.flushinterval` | See [response forwarding](../http/load-balancing/service.md#configuration-options) for more information. | `10` | ### Middleware @@ -519,125 +268,31 @@ You can declare TCP Routers and/or Services using labels. #### TCP Routers -??? info "`traefik.tcp.routers..entrypoints`" +##### Configuration Options - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - - "traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2" - ``` - -??? info "`traefik.tcp.routers..rule`" - - See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. - - ```yaml - - "traefik.tcp.routers.mytcprouter.rule=HostSNI(`example.com`)" - ``` - -??? info "`traefik.tcp.routers..ruleSyntax`" - - !!! warning - - RuleSyntax option is deprecated and will be removed in the next major version. - Please do not use this field and rewrite the router rules to use the v3 syntax. - - configure the rule syntax to be used for parsing the rule on a per-router basis. - - ```yaml - traefik.tcp.routers.mytcprouter.ruleSyntax=v3 - ``` - -??? info "`traefik.tcp.routers..service`" - - See [service](../tcp/service.md) for more information. - - ```yaml - - "traefik.tcp.routers.mytcprouter.service=myservice" - ``` - -??? info "`traefik.tcp.routers..tls`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - - "traefik.tcp.routers.mytcprouter.tls=true" - ``` - -??? info "`traefik.tcp.routers..tls.certresolver`" - - See [certResolver](../tcp/tls.md#configuration-options) for more information. - - ```yaml - - "traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver" - ``` - -??? info "`traefik.tcp.routers..tls.domains[n].main`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - - "traefik.tcp.routers.mytcprouter.tls.domains[0].main=example.org" - ``` - -??? info "`traefik.tcp.routers..tls.domains[n].sans`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - - "traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.example.org,dev.example.org" - ``` - -??? info "`traefik.tcp.routers..tls.options`" - - See [TLS](../tcp/tls.md) for more information. - - ```yaml - - "traefik.tcp.routers.mytcprouter.tls.options=mysoptions" - ``` - -??? info "`traefik.tcp.routers..tls.passthrough`" - - See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. - - ```yaml - - "traefik.tcp.routers.mytcprouter.tls.passthrough=true" - ``` - -??? info "`traefik.tcp.routers..priority`" - - See [priority](../tcp/routing/rules-and-priority.md) for more information. - - ```yaml - - "traefik.tcp.routers.myrouter.priority=42" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.tcp.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1,ep2` | +| `traefik.tcp.routers..rule` | See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. | ```HostSNI(`example.com`)``` | +| `traefik.tcp.routers..ruleSyntax` | configure the rule syntax to be used for parsing the rule on a per-router basis.
RuleSyntax option is deprecated and will be removed in the next major version.
Please do not use this field and rewrite the router rules to use the v3 syntax. | `v3` | +| `traefik.tcp.routers..service` | See [service](../tcp/service.md) for more information. | `myservice` | +| `traefik.tcp.routers..tls` | See [TLS](../tcp/tls.md) for more information. | `true` | +| `traefik.tcp.routers..tls.certresolver` | See [certResolver](../tcp/tls.md#configuration-options) for more information. | `myresolver` | +| `traefik.tcp.routers..tls.domains[n].main` | See [TLS](../tcp/tls.md) for more information. | `example.org` | +| `traefik.tcp.routers..tls.domains[n].sans` | See [TLS](../tcp/tls.md) for more information. | `test.example.org,dev.example.org` | +| `traefik.tcp.routers..tls.options` | See [TLS](../tcp/tls.md) for more information. | `mysoptions` | +| `traefik.tcp.routers..tls.passthrough` | See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. | `true` | +| `traefik.tcp.routers..priority` | See [priority](../tcp/routing/rules-and-priority.md#priority-calculation) for more information. | `42` | #### TCP Services -??? info "`traefik.tcp.services..loadbalancer.server.port`" +##### Configuration Options - Registers a port of the application. - - ```yaml - - "traefik.tcp.services.mytcpservice.loadbalancer.server.port=423" - ``` - -??? info "`traefik.tcp.services..loadbalancer.server.tls`" - - Determines whether to use TLS when dialing with the backend. - - ```yaml - - "traefik.tcp.services.mytcpservice.loadbalancer.server.tls=true" - ``` - -??? info "`traefik.tcp.services..loadbalancer.serverstransport`" - - Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. - See [serverstransport](../tcp/serverstransport.md) for more information. - - ```yaml - - "traefik.tcp.services..loadbalancer.serverstransport=foobar@file" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.tcp.services..loadbalancer.server.port` | Registers a port of the application. | `423` | +| `traefik.tcp.services..loadbalancer.server.tls` | Determines whether to use TLS when dialing with the backend. | `true` | +| `traefik.tcp.services..loadbalancer.serverstransport` | Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../tcp/serverstransport.md) for more information. | `foobar@file` | #### TCP Middleware @@ -684,65 +339,25 @@ You can declare UDP Routers and/or Services using labels. #### UDP Routers -??? info "`traefik.udp.routers..entrypoints`" +##### Configuration Options - See [entry points](../../install-configuration/entrypoints.md) for more information. - - ```yaml - - "traefik.udp.routers.myudprouter.entrypoints=ep1,ep2" - ``` - -??? info "`traefik.udp.routers..service`" - - See [service](../udp/service.md) for more information. - - ```yaml - - "traefik.udp.routers.myudprouter.service=myservice" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.udp.routers..entrypoints` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1,ep2` | +| `traefik.udp.routers..service` | See [service](../udp/service.md) for more information. | `myservice` | #### UDP Services -??? info "`traefik.udp.services..loadbalancer.server.port`" +##### Configuration Options - Registers a port of the application. - - ```yaml - - "traefik.udp.services.myudpservice.loadbalancer.server.port=423" - ``` +| Label | Description | Value | +|------|-------------|-------| +| `traefik.udp.services..loadbalancer.server.port` | Registers a port of the application. | `423` | ### Specific Provider Options -#### `traefik.enable` - -```yaml -- "traefik.enable=true" -``` - -You can tell Traefik to consider (or not) the container by setting `traefik.enable` to true or false. - -This option overrides the value of `exposedByDefault`. - -#### `traefik.swarm.network` - -```yaml -- "traefik.swarm.network=mynetwork" -``` - -Overrides the default docker network to use for connections to the container. - -If a container is linked to several networks, be sure to set the proper network name (you can check this with `docker inspect `), -otherwise it will randomly pick one (depending on how docker is returning them). - -!!! warning - When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`. - -#### `traefik.swarm.lbswarm` - -```yaml -- "traefik.swarm.lbswarm=true" -``` - -Enables Swarm's inbuilt load balancer (only relevant in Swarm Mode). - -If you enable this option, Traefik will use the virtual IP provided by docker swarm instead of the containers IPs. -Which means that Traefik will not perform any kind of load balancing and will delegate this task to swarm. +| Label | Description | Value | +|------|-------------|-------| +| `traefik.enable` | You can tell Traefik to consider (or not) the container by setting `traefik.enable` to true or false.
This option overrides the value of `exposedByDefault`. | `true` | +| `traefik.swarm.network` | Overrides the default docker network to use for connections to the container.
If a container is linked to several networks, be sure to set the proper network name (you can check this with `docker inspect `), otherwise it will randomly pick one (depending on how docker is returning them).

When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`. | `mynetwork` | +| `traefik.swarm.lbswarm` | Enables Swarm's inbuilt load balancer (only relevant in Swarm Mode).
If you enable this option, Traefik will use the virtual IP provided by docker swarm instead of the containers IPs.
Which means that Traefik will not perform any kind of load balancing and will delegate this task to swarm. | `true` | From 63820e1d787b16d111bae4f757e7fed6f1d4580d Mon Sep 17 00:00:00 2001 From: dathbe Date: Tue, 20 Jan 2026 00:34:04 -0800 Subject: [PATCH 04/13] Remove extraneous dots in migration guide --- docs/content/migrate/v3.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/content/migrate/v3.md b/docs/content/migrate/v3.md index d947af7b7..c222024a9 100644 --- a/docs/content/migrate/v3.md +++ b/docs/content/migrate/v3.md @@ -568,12 +568,12 @@ Here is the list of the encoded characters that are rejected by default, along w | Encoded Character | Character | Config option to allow the encoded character | |-------------------|-------------------------|--------------------------------------------------------------------------------------| | `%2f` or `%2F` | `/` (slash) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedSlash` | -| `%5c` or `%5C` | `\` (backslash) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedBackSlash` | -| `%00` | `NULL` (null character) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedNullCharacter` | -| `%3b` or `%3B` | `;` (semicolon) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedSemicolon` | -| `%25` | `%` (percent) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedPercent` | -| `%3f` or `%3F` | `?` (question mark) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedQuestionMark` | -| `%23` | `#` (hash) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedHash` | +| `%5c` or `%5C` | `\` (backslash) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedBackSlash` | +| `%00` | `NULL` (null character) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedNullCharacter` | +| `%3b` or `%3B` | `;` (semicolon) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedSemicolon` | +| `%25` | `%` (percent) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedPercent` | +| `%3f` or `%3F` | `?` (question mark) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedQuestionMark` | +| `%23` | `#` (hash) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedHash` | Please check out the entrypoint [encodedCharacters option](../reference/install-configuration/entrypoints.md#opt-http-encodedCharacters) documentation for more details. @@ -590,12 +590,12 @@ Here is the list of the encoded characters that can be configured to `false` to | Encoded Character | Character | Config options | Default value | |-------------------|-------------------------|--------------------------------------------------------------------------------------|---------------| | `%2f` or `%2F` | `/` (slash) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedSlash` | `true` | -| `%5c` or `%5C` | `\` (backslash) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedBackSlash` | `true` | -| `%00` | `NULL` (null character) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedNullCharacter` | `true` | -| `%3b` or `%3B` | `;` (semicolon) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedSemicolon` | `true` | -| `%25` | `%` (percent) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedPercent` | `true` | -| `%3f` or `%3F` | `?` (question mark) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedQuestionMark` | `true` | -| `%23` | `#` (hash) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedHash` | `true` | +| `%5c` or `%5C` | `\` (backslash) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedBackSlash` | `true` | +| `%00` | `NULL` (null character) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedNullCharacter` | `true` | +| `%3b` or `%3B` | `;` (semicolon) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedSemicolon` | `true` | +| `%25` | `%` (percent) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedPercent` | `true` | +| `%3f` or `%3F` | `?` (question mark) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedQuestionMark` | `true` | +| `%23` | `#` (hash) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedHash` | `true` | Note: This check is not done against query parameters, but only against the request path as defined From 7cb25da31cfbf6e426e197fdc098d0dec72293b5 Mon Sep 17 00:00:00 2001 From: Romain Date: Tue, 20 Jan 2026 15:40:05 +0100 Subject: [PATCH 05/13] Remove extra dots in migration guide --- docs/content/migration/v2.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 325e2ac3b..586952361 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -726,12 +726,12 @@ Here is the list of the encoded characters that are rejected by default, along w | Encoded Character | Character | Config option to allow the encoded character | |-------------------|-------------------------|--------------------------------------------------------------------------------------| | `%2f` or `%2F` | `/` (slash) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedSlash` | -| `%5c` or `%5C` | `\` (backslash) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedBackSlash` | -| `%00` | `NULL` (null character) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedNullCharacter` | -| `%3b` or `%3B` | `;` (semicolon) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedSemicolon` | -| `%25` | `%` (percent) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedPercent` | -| `%3f` or `%3F` | `?` (question mark) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedQuestionMark` | -| `%23` | `#` (hash) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedHash` | +| `%5c` or `%5C` | `\` (backslash) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedBackSlash` | +| `%00` | `NULL` (null character) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedNullCharacter` | +| `%3b` or `%3B` | `;` (semicolon) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedSemicolon` | +| `%25` | `%` (percent) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedPercent` | +| `%3f` or `%3F` | `?` (question mark) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedQuestionMark` | +| `%23` | `#` (hash) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedHash` | Note: This check is not done against query parameters, but only against the request path as defined in [RFC3986 section-3](https://datatracker.ietf.org/doc/html/rfc3986#section-3). @@ -751,12 +751,12 @@ Here is the list of the encoded characters that can be configured to `false` to | Encoded Character | Character | Config options | Default value | |-------------------|-------------------------|--------------------------------------------------------------------------------------|---------------| | `%2f` or `%2F` | `/` (slash) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedSlash` | `true` | -| `%5c` or `%5C` | `\` (backslash) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedBackSlash` | `true` | -| `%00` | `NULL` (null character) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedNullCharacter` | `true` | -| `%3b` or `%3B` | `;` (semicolon) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedSemicolon` | `true` | -| `%25` | `%` (percent) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedPercent` | `true` | -| `%3f` or `%3F` | `?` (question mark) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedQuestionMark` | `true` | -| `%23` | `#` (hash) | `entryPoints..`
`.http.encodedCharacters`
`.allowEncodedHash` | `true` | +| `%5c` or `%5C` | `\` (backslash) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedBackSlash` | `true` | +| `%00` | `NULL` (null character) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedNullCharacter` | `true` | +| `%3b` or `%3B` | `;` (semicolon) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedSemicolon` | `true` | +| `%25` | `%` (percent) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedPercent` | `true` | +| `%3f` or `%3F` | `?` (question mark) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedQuestionMark` | `true` | +| `%23` | `#` (hash) | `entryPoints.`
`.http.encodedCharacters`
`.allowEncodedHash` | `true` | Note: This check is not done against query parameters, but only against the request path as defined From ef5d040fd6c6db636746e0aa250ca0ae64ebf806 Mon Sep 17 00:00:00 2001 From: Benjamin Schwartz Date: Wed, 21 Jan 2026 00:38:04 -0800 Subject: [PATCH 06/13] Alter TLS renewal period --- docs/content/https/acme.md | 16 ++++++++-------- pkg/provider/acme/provider.go | 4 ++-- pkg/provider/acme/provider_test.go | 18 +++++++++++++++--- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index aa3b8ecec..356bc8813 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -847,14 +847,14 @@ _Optional, Default=2160_ It defaults to `2160` (90 days) to follow Let's Encrypt certificates' duration. -| Certificate Duration | Renew Period | Renew Interval | -|----------------------|-------------------|-------------------------| -| >= 1 year | 4 months | 1 week | -| >= 90 days | 30 days | 1 day | -| >= 30 days | 10 days | 12 hours | -| >= 7 days | 1 day | 1 hour | -| >= 24 hours | 6 hours | 10 min | -| < 24 hours | 20 min | 1 min | +| Certificate Duration | Renew Period | Renew Interval | +|----------------------|--------------|----------------| +| >= 1 year | 4 months | 1 week | +| >= 90 days | 30 days | 1 day | +| >= 30 days | 10 days | 12 hours | +| >= 6 days | 2 days | 2 hours | +| >= 24 hours | 6 hours | 10 min | +| < 24 hours | 20 min | 1 min | !!! warning "Traefik cannot manage certificates with a duration lower than 1 hour." diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go index fb181dd54..c6800abb8 100644 --- a/pkg/provider/acme/provider.go +++ b/pkg/provider/acme/provider.go @@ -809,8 +809,8 @@ func getCertificateRenewDurations(certificatesDuration int) (time.Duration, time return 30 * 24 * time.Hour, 24 * time.Hour // 30 days, 1 day case certificatesDuration >= 30*24: // >= 30 days return 10 * 24 * time.Hour, 12 * time.Hour // 10 days, 12 hours - case certificatesDuration >= 7*24: // >= 7 days - return 24 * time.Hour, time.Hour // 1 days, 1 hour + case certificatesDuration >= 6*24: // >= 6 days + return 2 * 24 * time.Hour, 2 * time.Hour // 2 days, 2 hours case certificatesDuration >= 24: // >= 1 days return 6 * time.Hour, 10 * time.Minute // 6 hours, 10 minutes default: diff --git a/pkg/provider/acme/provider_test.go b/pkg/provider/acme/provider_test.go index 477164e3a..e7a16952f 100644 --- a/pkg/provider/acme/provider_test.go +++ b/pkg/provider/acme/provider_test.go @@ -612,6 +612,12 @@ func Test_getCertificateRenewDurations(t *testing.T) { expectRenewPeriod: time.Hour * 24 * 30, expectRenewInterval: time.Hour * 24, }, + { + desc: "45 Days certificates (Let's Encrypt 2028 standard): 10 days renew period, 12 hour renew interval", + certificatesDurations: 24 * 45, + expectRenewPeriod: time.Hour * 24 * 10, + expectRenewInterval: time.Hour * 12, + }, { desc: "30 Days certificates: 10 days renew period, 12 hour renew interval", certificatesDurations: 24 * 30, @@ -619,10 +625,16 @@ func Test_getCertificateRenewDurations(t *testing.T) { expectRenewInterval: time.Hour * 12, }, { - desc: "7 Days certificates: 1 days renew period, 1 hour renew interval", + desc: "7 Days certificates: 2 days renew period, 2 hour renew interval", certificatesDurations: 24 * 7, - expectRenewPeriod: time.Hour * 24, - expectRenewInterval: time.Hour, + expectRenewPeriod: time.Hour * 24 * 2, + expectRenewInterval: time.Hour * 2, + }, + { + desc: "160 hour certificate (Let's Encrypt 'shortlived' profile): 2 days renew period, 2 hour renew interval", + certificatesDurations: 160, + expectRenewPeriod: time.Hour * 24 * 2, + expectRenewInterval: time.Hour * 2, }, { desc: "24 Hours certificates: 6 hours renew period, 10 minutes renew interval", From 139b59def904eabe439123918e5367c0c7051b59 Mon Sep 17 00:00:00 2001 From: Julien Salleyron Date: Wed, 21 Jan 2026 10:26:04 +0100 Subject: [PATCH 07/13] Remove invalid private key in log --- pkg/provider/acme/account.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/provider/acme/account.go b/pkg/provider/acme/account.go index 434f58e3a..2c57fd9bd 100644 --- a/pkg/provider/acme/account.go +++ b/pkg/provider/acme/account.go @@ -58,7 +58,7 @@ func (a *Account) GetPrivateKey() crypto.PrivateKey { privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey) if err != nil { log.Error().Str(logs.ProviderName, "acme"). - Err(err).Msgf("Cannot unmarshal private key %+v", a.PrivateKey) + Err(err).Msg("Cannot unmarshal private key") return nil } From d675b163b326bf0554e747444e91e5f17ecc3941 Mon Sep 17 00:00:00 2001 From: Romain Date: Thu, 22 Jan 2026 09:54:04 +0100 Subject: [PATCH 08/13] Bump github.com/containerd/containerd to v1.7.29 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 52b707a34..a02414e28 100644 --- a/go.mod +++ b/go.mod @@ -168,7 +168,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect - github.com/containerd/containerd v1.7.23 // indirect + github.com/containerd/containerd v1.7.29 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect diff --git a/go.sum b/go.sum index 81d31eac5..4d8853765 100644 --- a/go.sum +++ b/go.sum @@ -338,8 +338,8 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ= -github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= +github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE= +github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= From 9ac5d3ac1ca8874be7b6b6332bbed430267c7ace Mon Sep 17 00:00:00 2001 From: "Gina A." <70909035+gndz07@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:10:07 +0100 Subject: [PATCH 09/13] Bump dependencies of documentation and webui --- docs/Makefile | 2 +- docs/requirements.txt | 4 +- webui/package.json | 7 +- webui/yarn.lock | 196 +++++++++++++++++++++++++++++++----------- 4 files changed, 152 insertions(+), 57 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 501cbdbad..9a2e41ff3 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -21,7 +21,7 @@ docs: docs-clean docs-image docs-lint docs-build docs-verify # Writer Mode: build and serve docs on http://localhost:8000 with livereload .PHONY: docs-serve docs-serve: docs-image - docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOCS_BUILD_IMAGE) mkdocs serve + docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOCS_BUILD_IMAGE) mkdocs serve -a 0.0.0.0:8000 ## Pull image for doc building .PHONY: docs-pull-images diff --git a/docs/requirements.txt b/docs/requirements.txt index 1eef672df..ba7f3a058 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -7,7 +7,7 @@ click==8.1.7 colorama==0.4.6 ghp-import==2.1.0 importlib_metadata==7.1.0 -Jinja2==3.1.3 +Jinja2==3.1.6 Markdown==3.3.6 MarkupSafe==2.1.5 mergedeep==1.3.4 @@ -20,4 +20,4 @@ PyYAML==6.0.1 pyyaml_env_tag==0.1 six==1.16.0 watchdog==4.0.0 -zipp==3.18.1 +zipp==3.19.1 diff --git a/webui/package.json b/webui/package.json index 9f125a879..0b5eb6624 100644 --- a/webui/package.json +++ b/webui/package.json @@ -19,7 +19,7 @@ }, "dependencies": { "@quasar/extras": "^1.16.12", - "axios": "^1.7.4", + "axios": "1.13.2", "chart.js": "^4.4.1", "core-js": "^3.35.1", "dot-prop": "^8.0.2", @@ -48,10 +48,11 @@ "eslint-plugin-promise": "^6.0.0", "eslint-plugin-vue": "^9.0.0", "postcss": "^8.4.14", - "vitest": "^1.6.0" + "vitest": "1.6.1" }, "resolutions": { - "cookie": "^0.7.0" + "cookie": "^0.7.0", + "nanoid": "3.3.11" }, "engines": { "node": "^24 || ^22", diff --git a/webui/yarn.lock b/webui/yarn.lock index d625882fb..ad0e8d0d5 100644 --- a/webui/yarn.lock +++ b/webui/yarn.lock @@ -1765,44 +1765,44 @@ resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz#72b8b705cfce36b00b59af196195146e356500c4" integrity sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A== -"@vitest/expect@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.6.0.tgz#0b3ba0914f738508464983f4d811bc122b51fb30" - integrity sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ== +"@vitest/expect@1.6.1": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.6.1.tgz#b90c213f587514a99ac0bf84f88cff9042b0f14d" + integrity sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog== dependencies: - "@vitest/spy" "1.6.0" - "@vitest/utils" "1.6.0" + "@vitest/spy" "1.6.1" + "@vitest/utils" "1.6.1" chai "^4.3.10" -"@vitest/runner@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.6.0.tgz#a6de49a96cb33b0e3ba0d9064a3e8d6ce2f08825" - integrity sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg== +"@vitest/runner@1.6.1": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.6.1.tgz#10f5857c3e376218d58c2bfacfea1161e27e117f" + integrity sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA== dependencies: - "@vitest/utils" "1.6.0" + "@vitest/utils" "1.6.1" p-limit "^5.0.0" pathe "^1.1.1" -"@vitest/snapshot@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.6.0.tgz#deb7e4498a5299c1198136f56e6e0f692e6af470" - integrity sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ== +"@vitest/snapshot@1.6.1": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.6.1.tgz#90414451a634bb36cd539ccb29ae0d048a8c0479" + integrity sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ== dependencies: magic-string "^0.30.5" pathe "^1.1.1" pretty-format "^29.7.0" -"@vitest/spy@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.6.0.tgz#362cbd42ccdb03f1613798fde99799649516906d" - integrity sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw== +"@vitest/spy@1.6.1": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.6.1.tgz#33376be38a5ed1ecd829eb986edaecc3e798c95d" + integrity sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw== dependencies: tinyspy "^2.2.0" -"@vitest/utils@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.6.0.tgz#5c5675ca7d6f546a7b4337de9ae882e6c57896a1" - integrity sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw== +"@vitest/utils@1.6.1": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.6.1.tgz#6d2f36cb6d866f2bbf59da854a324d6bf8040f17" + integrity sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g== dependencies: diff-sequences "^29.6.3" estree-walker "^3.0.3" @@ -2163,13 +2163,13 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -axios@^1.7.4: - version "1.7.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" - integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== +axios@1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.2.tgz#9ada120b7b5ab24509553ec3e40123521117f687" + integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA== dependencies: follow-redirects "^1.15.6" - form-data "^4.0.0" + form-data "^4.0.4" proxy-from-env "^1.1.0" b4a@^1.6.4: @@ -2376,6 +2376,14 @@ cac@^6.7.14: resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -2883,6 +2891,15 @@ dotenv@^16.4.4, dotenv@^16.4.5: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -2999,6 +3016,11 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" @@ -3011,6 +3033,13 @@ es-object-atoms@^1.0.0: dependencies: es-errors "^1.3.0" +es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + es-set-tostringtag@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" @@ -3020,6 +3049,16 @@ es-set-tostringtag@^2.0.3: has-tostringtag "^1.0.2" hasown "^2.0.1" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" @@ -3561,13 +3600,15 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== +form-data@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" + integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" mime-types "^2.1.12" forwarded@0.2.0: @@ -3650,6 +3691,30 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stream@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" @@ -3756,6 +3821,11 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -3807,6 +3877,11 @@ has-symbols@^1.0.2, has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" @@ -4384,6 +4459,11 @@ magic-string@^0.30.11, magic-string@^0.30.5: dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -4538,10 +4618,10 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== +nanoid@3.3.11, nanoid@^3.3.11, nanoid@^3.3.7: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== natural-compare@^1.4.0: version "1.4.0" @@ -4889,6 +4969,11 @@ picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -4940,7 +5025,16 @@ postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.14, postcss@^8.4.43, postcss@^8.4.47: +postcss@^8.4.14: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +postcss@^8.4.43, postcss@^8.4.47: version "8.4.47" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== @@ -6112,10 +6206,10 @@ vite-jsconfig-paths@^2.0.1: recrawl-sync "^2.0.3" tsconfig-paths "^3.9.0" -vite-node@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.6.0.tgz#2c7e61129bfecc759478fa592754fd9704aaba7f" - integrity sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw== +vite-node@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.6.1.tgz#fff3ef309296ea03ceaa6ca4bb660922f5416c57" + integrity sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA== dependencies: cac "^6.7.14" debug "^4.3.4" @@ -6143,16 +6237,16 @@ vite@^5.0.0, vite@^5.4.5: optionalDependencies: fsevents "~2.3.3" -vitest@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.6.0.tgz#9d5ad4752a3c451be919e412c597126cffb9892f" - integrity sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA== +vitest@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.6.1.tgz#b4a3097adf8f79ac18bc2e2e0024c534a7a78d2f" + integrity sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag== dependencies: - "@vitest/expect" "1.6.0" - "@vitest/runner" "1.6.0" - "@vitest/snapshot" "1.6.0" - "@vitest/spy" "1.6.0" - "@vitest/utils" "1.6.0" + "@vitest/expect" "1.6.1" + "@vitest/runner" "1.6.1" + "@vitest/snapshot" "1.6.1" + "@vitest/spy" "1.6.1" + "@vitest/utils" "1.6.1" acorn-walk "^8.3.2" chai "^4.3.10" debug "^4.3.4" @@ -6166,7 +6260,7 @@ vitest@^1.6.0: tinybench "^2.5.1" tinypool "^0.8.3" vite "^5.0.0" - vite-node "1.6.0" + vite-node "1.6.1" why-is-node-running "^2.2.2" vue-chartjs@^5.3.0: From 85cd5485b75a303baf17de20688ff368b6ab9748 Mon Sep 17 00:00:00 2001 From: Julien Salleyron Date: Mon, 26 Jan 2026 10:28:04 +0100 Subject: [PATCH 10/13] Avoid recursion with services --- pkg/metrics/prometheus_test.go | 24 +++--- pkg/server/configurationwatcher_test.go | 92 +++++++++++++++++------ pkg/server/middleware/middlewares.go | 22 +----- pkg/server/middleware/middlewares_test.go | 11 +-- pkg/server/recursion/recursion.go | 26 +++++++ pkg/server/routerfactory_test.go | 78 ++++++++++++++++--- pkg/server/service/service.go | 7 ++ pkg/testhelpers/config.go | 70 ++++++++++++++--- 8 files changed, 249 insertions(+), 81 deletions(-) create mode 100644 pkg/server/recursion/recursion.go diff --git a/pkg/metrics/prometheus_test.go b/pkg/metrics/prometheus_test.go index 953acfd62..39992e585 100644 --- a/pkg/metrics/prometheus_test.go +++ b/pkg/metrics/prometheus_test.go @@ -451,13 +451,13 @@ func TestPrometheusMetricRemoval(t *testing.T) { th.WithRouter("foo@providerName", th.WithServiceName("bar")), th.WithRouter("router2", th.WithServiceName("bar@providerName")), ), - th.WithLoadBalancerServices( - th.WithService("bar@providerName", th.WithServers( + th.WithServices( + th.WithService("bar@providerName", th.WithServiceServersLoadBalancer(th.WithServers( th.WithServer("http://localhost:9000"), th.WithServer("http://localhost:9999"), th.WithServer("http://localhost:9998"), - )), - th.WithService("service1", th.WithServers(th.WithServer("http://localhost:9000"))), + ))), + th.WithService("service1", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9000")))), ), ), } @@ -467,8 +467,8 @@ func TestPrometheusMetricRemoval(t *testing.T) { th.WithRouters( th.WithRouter("foo@providerName", th.WithServiceName("bar")), ), - th.WithLoadBalancerServices( - th.WithService("bar@providerName", th.WithServers(th.WithServer("http://localhost:9000"))), + th.WithServices( + th.WithService("bar@providerName", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9000")))), ), ), } @@ -538,8 +538,8 @@ func TestPrometheusMetricRemoveEndpointForRecoveredService(t *testing.T) { conf1 := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithLoadBalancerServices( - th.WithService("service1", th.WithServers(th.WithServer("http://localhost:9000"))), + th.WithServices( + th.WithService("service1", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9000")))), ), ), } @@ -550,8 +550,8 @@ func TestPrometheusMetricRemoveEndpointForRecoveredService(t *testing.T) { conf3 := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithLoadBalancerServices( - th.WithService("service1", th.WithServers(th.WithServer("http://localhost:9001"))), + th.WithServices( + th.WithService("service1", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9001")))), ), ), } @@ -577,8 +577,8 @@ func TestPrometheusRemovedMetricsReset(t *testing.T) { conf1 := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithLoadBalancerServices(th.WithService("service", - th.WithServers(th.WithServer("http://localhost:9000"))), + th.WithServices( + th.WithService("service", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9000")))), ), ), } diff --git a/pkg/server/configurationwatcher_test.go b/pkg/server/configurationwatcher_test.go index 64019c191..3e6c32fd7 100644 --- a/pkg/server/configurationwatcher_test.go +++ b/pkg/server/configurationwatcher_test.go @@ -86,7 +86,7 @@ func TestNewConfigurationWatcher(t *testing.T) { th.WithEntryPoints("e"), th.WithServiceName("scv"))), th.WithMiddlewares(), - th.WithLoadBalancerServices(), + th.WithServices(), ), TCP: &dynamic.TCPConfiguration{ Routers: map[string]*dynamic.TCPRouter{}, @@ -123,7 +123,9 @@ func TestWaitForRequiredProvider(t *testing.T) { config := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), } @@ -167,14 +169,18 @@ func TestIgnoreTransientConfiguration(t *testing.T) { config := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), } expectedConfig := dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar@mock")), + th.WithServices( + th.WithService("bar@mock", th.WithServiceServersLoadBalancer()), + ), th.WithMiddlewares(), ), TCP: &dynamic.TCPConfiguration{ @@ -197,7 +203,9 @@ func TestIgnoreTransientConfiguration(t *testing.T) { expectedConfig3 := dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar-config3@mock")), + th.WithServices( + th.WithService("bar-config3@mock", th.WithServiceServersLoadBalancer()), + ), th.WithMiddlewares(), ), TCP: &dynamic.TCPConfiguration{ @@ -220,14 +228,18 @@ func TestIgnoreTransientConfiguration(t *testing.T) { config2 := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("baz", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("toto")), + th.WithServices( + th.WithService("toto", th.WithServiceServersLoadBalancer()), + ), ), } config3 := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar-config3")), + th.WithServices( + th.WithService("bar-config3", th.WithServiceServersLoadBalancer()), + ), ), } watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{}, "") @@ -311,7 +323,9 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) { Configuration: &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i), th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), }, }) @@ -371,7 +385,9 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) { Configuration: &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), }, } @@ -403,14 +419,18 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), } transientConfiguration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bad")), + th.WithServices( + th.WithService("bad", th.WithServiceServersLoadBalancer()), + ), ), } @@ -442,7 +462,9 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar@mock")), + th.WithServices( + th.WithService("bar@mock", th.WithServiceServersLoadBalancer()), + ), th.WithMiddlewares(), ), TCP: &dynamic.TCPConfiguration{ @@ -471,14 +493,18 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), } transientConfiguration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bad")), + th.WithServices( + th.WithService("bad", th.WithServiceServersLoadBalancer()), + ), ), } @@ -531,7 +557,9 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar@mock")), + th.WithServices( + th.WithService("bar@mock", th.WithServiceServersLoadBalancer()), + ), th.WithMiddlewares(), ), TCP: &dynamic.TCPConfiguration{ @@ -570,7 +598,9 @@ func TestApplyConfigUnderStress(t *testing.T) { case watcher.allProvidersConfigs <- dynamic.Message{ProviderName: "mock", Configuration: &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i), th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), }}: } @@ -605,28 +635,36 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), } transientConfiguration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bad")), + th.WithServices( + th.WithService("bad", th.WithServiceServersLoadBalancer()), + ), ), } transientConfiguration2 := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("bad2", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bad2")), + th.WithServices( + th.WithService("bad2", th.WithServiceServersLoadBalancer()), + ), ), } finalConfiguration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("final", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("final")), + th.WithServices( + th.WithService("final", th.WithServiceServersLoadBalancer()), + ), ), } @@ -665,7 +703,9 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("final@mock", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("final@mock")), + th.WithServices( + th.WithService("final@mock", th.WithServiceServersLoadBalancer()), + ), th.WithMiddlewares(), ), TCP: &dynamic.TCPConfiguration{ @@ -696,7 +736,9 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), } @@ -729,9 +771,9 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { th.WithRouter("foo@mock", th.WithEntryPoints("ep")), th.WithRouter("foo@mock2", th.WithEntryPoints("ep")), ), - th.WithLoadBalancerServices( - th.WithService("bar@mock"), - th.WithService("bar@mock2"), + th.WithServices( + th.WithService("bar@mock", th.WithServiceServersLoadBalancer()), + th.WithService("bar@mock2", th.WithServiceServersLoadBalancer()), ), th.WithMiddlewares(), ), diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go index 05a4229d3..b9c72f6cd 100644 --- a/pkg/server/middleware/middlewares.go +++ b/pkg/server/middleware/middlewares.go @@ -6,8 +6,6 @@ import ( "fmt" "net/http" "reflect" - "slices" - "strings" "github.com/containous/alice" "github.com/traefik/traefik/v2/pkg/config/runtime" @@ -33,12 +31,7 @@ import ( "github.com/traefik/traefik/v2/pkg/middlewares/stripprefixregex" "github.com/traefik/traefik/v2/pkg/middlewares/tracing" "github.com/traefik/traefik/v2/pkg/server/provider" -) - -type middlewareStackType int - -const ( - middlewareStackKey middlewareStackType = iota + "github.com/traefik/traefik/v2/pkg/server/recursion" ) // Builder the middleware builder. @@ -70,7 +63,7 @@ func (b *Builder) BuildChain(ctx context.Context, middlewares []string) *alice.C } var err error - if constructorContext, err = checkRecursion(constructorContext, middlewareName); err != nil { + if constructorContext, err = recursion.CheckRecursion(constructorContext, "middleware", middlewareName); err != nil { b.configs[middlewareName].AddError(err, true) return nil, err } @@ -93,17 +86,6 @@ func (b *Builder) BuildChain(ctx context.Context, middlewares []string) *alice.C return &chain } -func checkRecursion(ctx context.Context, middlewareName string) (context.Context, error) { - currentStack, ok := ctx.Value(middlewareStackKey).([]string) - if !ok { - currentStack = []string{} - } - if slices.Contains(currentStack, middlewareName) { - return ctx, fmt.Errorf("could not instantiate middleware %s: recursion detected in %s", middlewareName, strings.Join(append(currentStack, middlewareName), "->")) - } - return context.WithValue(ctx, middlewareStackKey, append(currentStack, middlewareName)), nil -} - // it is the responsibility of the caller to make sure that b.configs[middlewareName].Middleware exists. func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (alice.Constructor, error) { config := b.configs[middlewareName] diff --git a/pkg/server/middleware/middlewares_test.go b/pkg/server/middleware/middlewares_test.go index ed4456b31..4f9d9c9ca 100644 --- a/pkg/server/middleware/middlewares_test.go +++ b/pkg/server/middleware/middlewares_test.go @@ -172,7 +172,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { }, }, }, - expectedError: errors.New("could not instantiate middleware m1: recursion detected in m1->m2->m3->m1"), + expectedError: errors.New("could not instantiate middleware m1: recursion detected in middleware:m1->middleware:m2->middleware:m3->middleware:m1"), }, { desc: "Detects recursion in Middleware chain", @@ -197,9 +197,10 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { }, }, }, - expectedError: errors.New("could not instantiate middleware m1@provider: recursion detected in m1@provider->m2@provider2->m3@provider->m1@provider"), + expectedError: errors.New("could not instantiate middleware m1@provider: recursion detected in middleware:m1@provider->middleware:m2@provider2->middleware:m3@provider->middleware:m1@provider"), }, { + desc: "Detects recursion in Middleware chain", buildChain: []string{"ok", "m0"}, configuration: map[string]*dynamic.Middleware{ "ok": { @@ -211,7 +212,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { }, }, }, - expectedError: errors.New("could not instantiate middleware m0: recursion detected in m0->m0"), + expectedError: errors.New("could not instantiate middleware m0: recursion detected in middleware:m0->middleware:m0"), }, { desc: "Detects MiddlewareChain that references a Chain that references a Chain with a missing middleware", @@ -238,7 +239,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { }, }, }, - expectedError: errors.New("could not instantiate middleware m2: recursion detected in m0->m1->m2->m3->m2"), + expectedError: errors.New("could not instantiate middleware m2: recursion detected in middleware:m0->middleware:m1->middleware:m2->middleware:m3->middleware:m2"), }, { desc: "--", @@ -250,7 +251,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { }, }, }, - expectedError: errors.New("could not instantiate middleware m0: recursion detected in m0->m0"), + expectedError: errors.New("could not instantiate middleware m0: recursion detected in middleware:m0->middleware:m0"), }, } diff --git a/pkg/server/recursion/recursion.go b/pkg/server/recursion/recursion.go new file mode 100644 index 000000000..40998a37c --- /dev/null +++ b/pkg/server/recursion/recursion.go @@ -0,0 +1,26 @@ +package recursion + +import ( + "context" + "fmt" + "slices" + "strings" +) + +type stackType int + +const ( + stackKey stackType = iota +) + +func CheckRecursion(ctx context.Context, itemType, itemName string) (context.Context, error) { + currentStack, ok := ctx.Value(stackKey).([]string) + if !ok { + currentStack = []string{} + } + name := itemType + ":" + itemName + if slices.Contains(currentStack, name) { + return ctx, fmt.Errorf("could not instantiate %s %s: recursion detected in %s", itemType, itemName, strings.Join(append(currentStack, name), "->")) + } + return context.WithValue(ctx, stackKey, append(currentStack, name)), nil +} diff --git a/pkg/server/routerfactory_test.go b/pkg/server/routerfactory_test.go index f70b62933..9dc5de135 100644 --- a/pkg/server/routerfactory_test.go +++ b/pkg/server/routerfactory_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/config/runtime" "github.com/traefik/traefik/v2/pkg/config/static" @@ -43,8 +44,8 @@ func TestReuseService(t *testing.T) { th.WithMiddlewares(th.WithMiddleware("basicauth", th.WithBasicAuth(&dynamic.BasicAuth{Users: []string{"foo:bar"}}), )), - th.WithLoadBalancerServices(th.WithService("bar", - th.WithServers(th.WithServer(testServer.URL))), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer(testServer.URL)))), ), ) @@ -91,8 +92,8 @@ func TestServerResponseEmptyBackend(t *testing.T) { th.WithServiceName("bar"), th.WithRule(routeRule)), ), - th.WithLoadBalancerServices(th.WithService("bar", - th.WithServers(th.WithServer(testServerURL))), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer(testServerURL)))), ), ) }, @@ -114,7 +115,9 @@ func TestServerResponseEmptyBackend(t *testing.T) { th.WithServiceName("bar"), th.WithRule(routeRule)), ), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ) }, expectedStatusCode: http.StatusServiceUnavailable, @@ -128,8 +131,8 @@ func TestServerResponseEmptyBackend(t *testing.T) { th.WithServiceName("bar"), th.WithRule(routeRule)), ), - th.WithLoadBalancerServices(th.WithService("bar", - th.WithSticky("test")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer(th.WithSticky("test"))), ), ) }, @@ -144,7 +147,9 @@ func TestServerResponseEmptyBackend(t *testing.T) { th.WithServiceName("bar"), th.WithRule(routeRule)), ), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ) }, expectedStatusCode: http.StatusServiceUnavailable, @@ -158,8 +163,8 @@ func TestServerResponseEmptyBackend(t *testing.T) { th.WithServiceName("bar"), th.WithRule(routeRule)), ), - th.WithLoadBalancerServices(th.WithService("bar", - th.WithSticky("test")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer(th.WithSticky("test"))), ), ) }, @@ -241,3 +246,56 @@ func TestInternalServices(t *testing.T) { assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code") } + +func TestRecursionService(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + defer testServer.Close() + + staticConfig := static.Configuration{ + EntryPoints: map[string]*static.EntryPoint{ + "web": {}, + }, + } + + dynamicConfigs := th.BuildConfiguration( + th.WithRouters( + th.WithRouter("foo@provider1", + th.WithEntryPoints("web"), + th.WithServiceName("bar"), + th.WithRule("Path(`/ok`)")), + ), + th.WithMiddlewares(th.WithMiddleware("customerror", + th.WithErrorPage(&dynamic.ErrorPage{Service: "bar"}), + )), + th.WithServices( + th.WithService("bar@provider1", th.WithServiceWRR(th.WithWRRServices(th.WithWRRService("foo")))), + th.WithService("foo@provider1", th.WithServiceWRR(th.WithWRRServices(th.WithWRRService("bar")))), + ), + ) + + roundTripperManager := service.NewRoundTripperManager() + roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) + managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil) + tlsManager := tls.NewManager() + + voidRegistry := metrics.NewVoidRegistry() + + factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(voidRegistry, nil, nil), nil, voidRegistry) + + rtConf := runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs}) + entryPointsHandlers, _ := factory.CreateRouters(rtConf) + + // Test that the /ok path returns a status 404. + responseRecorderOk := &httptest.ResponseRecorder{} + requestOk := httptest.NewRequest(http.MethodGet, testServer.URL+"/ok", nil) + entryPointsHandlers["web"].GetHTTPHandler().ServeHTTP(responseRecorderOk, requestOk) + + assert.Equal(t, http.StatusNotFound, responseRecorderOk.Result().StatusCode, "status code") + + require.NotNil(t, rtConf.Routers["foo@provider1"]) + assert.Contains(t, rtConf.Routers["foo@provider1"].Err, "could not instantiate service bar@provider1: recursion detected in service:bar@provider1->service:foo@provider1->service:bar@provider1") + require.NotNil(t, rtConf.Services["bar@provider1"]) + assert.Contains(t, rtConf.Services["bar@provider1"].Err, "could not instantiate service bar@provider1: recursion detected in service:bar@provider1->service:foo@provider1->service:bar@provider1") +} diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 99c611bf9..033736e2e 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -25,6 +25,7 @@ import ( "github.com/traefik/traefik/v2/pkg/safe" "github.com/traefik/traefik/v2/pkg/server/cookie" "github.com/traefik/traefik/v2/pkg/server/provider" + "github.com/traefik/traefik/v2/pkg/server/recursion" "github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/failover" "github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror" "github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/wrr" @@ -116,6 +117,12 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H return nil, err } + var errRecursion error + if ctx, errRecursion = recursion.CheckRecursion(ctx, "service", serviceName); errRecursion != nil { + conf.AddError(errRecursion, true) + return nil, errRecursion + } + var lb http.Handler switch { diff --git a/pkg/testhelpers/config.go b/pkg/testhelpers/config.go index ab810e4f0..303934f41 100644 --- a/pkg/testhelpers/config.go +++ b/pkg/testhelpers/config.go @@ -53,30 +53,75 @@ func WithServiceName(serviceName string) func(*dynamic.Router) { } } -// WithLoadBalancerServices is a helper to create a configuration. -func WithLoadBalancerServices(opts ...func(service *dynamic.ServersLoadBalancer) string) func(*dynamic.HTTPConfiguration) { +// WithServices is a helper to create a configuration. +func WithServices(opts ...func(service *dynamic.Service) string) func(*dynamic.HTTPConfiguration) { return func(c *dynamic.HTTPConfiguration) { c.Services = make(map[string]*dynamic.Service) for _, opt := range opts { - b := &dynamic.ServersLoadBalancer{} + b := &dynamic.Service{} name := opt(b) - c.Services[name] = &dynamic.Service{ - LoadBalancer: b, - } + c.Services[name] = b } } } // WithService is a helper to create a configuration. -func WithService(name string, opts ...func(*dynamic.ServersLoadBalancer)) func(*dynamic.ServersLoadBalancer) string { - return func(r *dynamic.ServersLoadBalancer) string { +func WithService(name string, opts ...func(*dynamic.Service)) func(*dynamic.Service) string { + return func(s *dynamic.Service) string { for _, opt := range opts { - opt(r) + opt(s) } + return name } } +func WithServiceServersLoadBalancer(opts ...func(*dynamic.ServersLoadBalancer)) func(*dynamic.Service) { + return func(s *dynamic.Service) { + b := &dynamic.ServersLoadBalancer{} + b.SetDefaults() + + for _, opt := range opts { + opt(b) + } + + s.LoadBalancer = b + } +} + +func WithServiceWRR(opts ...func(*dynamic.WeightedRoundRobin)) func(*dynamic.Service) { + return func(s *dynamic.Service) { + b := &dynamic.WeightedRoundRobin{} + + for _, opt := range opts { + opt(b) + } + + s.Weighted = b + } +} + +// WithWRRServices is a helper to create a configuration. +func WithWRRServices(opts ...func(*dynamic.WRRService)) func(*dynamic.WeightedRoundRobin) { + return func(b *dynamic.WeightedRoundRobin) { + for _, opt := range opts { + service := dynamic.WRRService{} + opt(&service) + b.Services = append(b.Services, service) + } + } +} + +// WithWRRService is a helper to create a configuration. +func WithWRRService(name string, opts ...func(*dynamic.WRRService)) func(*dynamic.WRRService) { + return func(s *dynamic.WRRService) { + for _, opt := range opts { + opt(s) + } + s.Name = name + } +} + // WithMiddlewares is a helper to create a configuration. func WithMiddlewares(opts ...func(*dynamic.Middleware) string) func(*dynamic.HTTPConfiguration) { return func(c *dynamic.HTTPConfiguration) { @@ -106,6 +151,13 @@ func WithBasicAuth(auth *dynamic.BasicAuth) func(*dynamic.Middleware) { } } +// WithErrorPage is a helper to create a configuration. +func WithErrorPage(errorPage *dynamic.ErrorPage) func(*dynamic.Middleware) { + return func(r *dynamic.Middleware) { + r.Errors = errorPage + } +} + // WithEntryPoints is a helper to create a configuration. func WithEntryPoints(eps ...string) func(*dynamic.Router) { return func(f *dynamic.Router) { From 91381590d37f7f98e163c7dc4a1dc4eb9a012a1c Mon Sep 17 00:00:00 2001 From: Sheddy Date: Mon, 26 Jan 2026 14:36:04 +0100 Subject: [PATCH 11/13] Split Expose User Guides & Add Multi-Layer Routing Section --- .../expose/{docker.md => docker/advanced.md} | 444 +++++------ docs/content/expose/docker/basic.md | 250 ++++++ .../{kubernetes.md => kubernetes/advanced.md} | 752 +++++++----------- docs/content/expose/kubernetes/basic.md | 438 ++++++++++ docs/content/expose/overview.md | 6 +- .../expose/{swarm.md => swarm/advanced.md} | 388 ++++----- docs/content/expose/swarm/basic.md | 191 +++++ docs/mkdocs.yml | 28 +- 8 files changed, 1573 insertions(+), 924 deletions(-) rename docs/content/expose/{docker.md => docker/advanced.md} (54%) create mode 100644 docs/content/expose/docker/basic.md rename docs/content/expose/{kubernetes.md => kubernetes/advanced.md} (59%) create mode 100644 docs/content/expose/kubernetes/basic.md rename docs/content/expose/{swarm.md => swarm/advanced.md} (51%) create mode 100644 docs/content/expose/swarm/basic.md diff --git a/docs/content/expose/docker.md b/docs/content/expose/docker/advanced.md similarity index 54% rename from docs/content/expose/docker.md rename to docs/content/expose/docker/advanced.md index 0e6de8cc6..088daa10f 100644 --- a/docs/content/expose/docker.md +++ b/docs/content/expose/docker/advanced.md @@ -1,252 +1,29 @@ -# Exposing Services with Traefik on Docker +# Exposing Services with Traefik on Docker - Advanced -This guide will help you expose your services securely through Traefik Proxy using Docker. We'll cover routing HTTP and HTTPS traffic, implementing TLS, adding middlewares, Let's Encrypt integration, and sticky sessions. +This guide builds on the concepts and setup from the [Basic Guide](basic.md). Make sure you've completed the basic guide and have a working Traefik setup with Docker before proceeding. + +In this advanced guide, you'll learn how to enhance your Traefik deployment with: + +- **Middlewares** for security headers and access control +- **Let's Encrypt** for automated certificate management +- **Sticky sessions** for stateful applications +- **Multi-layer routing** for hierarchical routing with a complex authentication based routing example ## Prerequisites +- Completed the [Basic Guide](basic.md) - Docker and Docker Compose installed -- Basic understanding of Docker concepts -- Traefik deployed using the Traefik Docker Setup guide - -## Expose Your First HTTP Service - -Let's expose a simple HTTP service using the [whoami](https://hub.docker.com/r/traefik/whoami) application. This will demonstrate basic routing to a backend service. - -First, create a `docker-compose.yml` file: - -```yaml -services: - traefik: - image: "traefik:v3.4" - container_name: "traefik" - restart: unless-stopped - security_opt: - - no-new-privileges:true - networks: - - proxy - command: - - "--providers.docker=true" - - "--providers.docker.exposedbydefault=false" - - "--providers.docker.network=proxy" - - "--entryPoints.web.address=:80" - ports: - - "80:80" - - "8080:8080" - volumes: - - "/var/run/docker.sock:/var/run/docker.sock:ro" - - whoami: - image: "traefik/whoami" - restart: unless-stopped - networks: - - proxy - labels: - - "traefik.enable=true" - - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)" - - "traefik.http.routers.whoami.entrypoints=web" - -networks: - proxy: - name: proxy -``` - -Save this as `docker-compose.yml` and start the services: - -```bash -docker compose up -d -``` - -### Verify Your Service - -Your service is now available at http://whoami.docker.localhost/. Test that it works: - -```bash -curl -H "Host: whoami.docker.localhost" http://localhost/ -``` - -You should see output similar to: - -```bash -Hostname: whoami -IP: 127.0.0.1 -IP: ::1 -IP: 172.18.0.3 -IP: fe80::215:5dff:fe00:c9e -RemoteAddr: 172.18.0.2:55108 -GET / HTTP/1.1 -Host: whoami.docker.localhost -User-Agent: curl/7.68.0 -Accept: */* -Accept-Encoding: gzip -X-Forwarded-For: 172.18.0.1 -X-Forwarded-Host: whoami.docker.localhost -X-Forwarded-Port: 80 -X-Forwarded-Proto: http -X-Forwarded-Server: 5789f594e7d5 -X-Real-Ip: 172.18.0.1 -``` - -This confirms that Traefik is successfully routing requests to your whoami application. - -## Add Routing Rules - -Now we'll enhance our routing by directing traffic to different services based on [URL paths](../reference/routing-configuration/http/routing/rules-and-priority.md#path-pathprefix-and-pathregexp). This is useful for API versioning, frontend/backend separation, or organizing microservices. - -Update your `docker-compose.yml` to add another service: - -```yaml -# ... - -# New service - whoami-api: - image: "traefik/whoami" - networks: - - proxy - container_name: "whoami-api" - environment: - - WHOAMI_NAME=API Service - labels: - - "traefik.enable=true" - # Path-based routing - - "traefik.http.routers.whoami-api.rule=Host(`whoami.docker.localhost`) && PathPrefix(`/api`)" - - "traefik.http.routers.whoami-api.entrypoints=web" -``` - -Apply the changes: - -```bash -docker compose up -d -``` - -### Test the Path-Based Routing - -Verify that different paths route to different services: - -```bash -# Root path should go to the main whoami service -curl -H "Host: whoami.docker.localhost" http://localhost/ - -# /api path should go to the whoami-api service -curl -H "Host: whoami.docker.localhost" http://localhost/api -``` - -For the `/api` requests, you should see the response showing "API Service" in the environment variables section, confirming that your path-based routing is working correctly. - -## Enable TLS - -Let's secure our service with HTTPS by adding TLS. We'll start with a self-signed certificate for local development. - -### Create a Self-Signed Certificate - -Generate a self-signed certificate: - -```bash -mkdir -p certs -openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ - -keyout certs/local.key -out certs/local.crt \ - -subj "/CN=*.docker.localhost" -``` - -Create a directory for dynamic configuration and add a TLS configuration file: - -```bash -mkdir -p dynamic -cat > dynamic/tls.yml << EOF -tls: - certificates: - - certFile: /certs/local.crt - keyFile: /certs/local.key -EOF -``` - -Update your `docker-compose.yml` file with the following changes: - -```yaml -services: - traefik: - image: "traefik:v3.4" - container_name: "traefik" - restart: unless-stopped - security_opt: - - no-new-privileges:true - networks: - - proxy - command: - - "--api.insecure=false" - - "--api.dashboard=true" - - "--providers.docker=true" - - "--providers.docker.exposedbydefault=false" - - "--providers.docker.network=proxy" - - "--providers.file.directory=/etc/traefik/dynamic" - - "--entryPoints.web.address=:80" - - "--entryPoints.websecure.address=:443" - - "--entryPoints.websecure.http.tls=true" - ports: - - "80:80" - - "443:443" - - "8080:8080" - volumes: - - "/var/run/docker.sock:/var/run/docker.sock:ro" - # Add the following volumes - - "./certs:/certs:ro" - - "./dynamic:/etc/traefik/dynamic:ro" - labels: - - "traefik.enable=true" - - "traefik.http.routers.dashboard.rule=Host(`dashboard.docker.localhost`)" - - "traefik.http.routers.dashboard.entrypoints=websecure" - - "traefik.http.routers.dashboard.service=api@internal" - # Add the following label - - "traefik.http.routers.dashboard.tls=true" - - whoami: - image: "traefik/whoami" - restart: unless-stopped - networks: - - proxy - labels: - - "traefik.enable=true" - - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)" - - "traefik.http.routers.whoami.entrypoints=websecure" - # Add the following label - - "traefik.http.routers.whoami.tls=true" - - whoami-api: - image: "traefik/whoami" - container_name: "whoami-api" - restart: unless-stopped - networks: - - proxy - environment: - - WHOAMI_NAME=API Service - labels: - - "traefik.enable=true" - - "traefik.http.routers.whoami-api.rule=Host(`whoami.docker.localhost`) && PathPrefix(`/api`)" - - "traefik.http.routers.whoami-api.entrypoints=websecure" - # Add the following label - - "traefik.http.routers.whoami-api.tls=true" - -networks: - proxy: - name: proxy -``` - -Apply the changes: - -```bash -docker compose up -d -``` - -Your browser can access https://whoami.docker.localhost/ for the service. You'll need to accept the security warning for the self-signed certificate. +- Working Traefik setup from the basic guide ## Add Middlewares -Middlewares allow you to modify requests or responses as they pass through Traefik. Let's add two useful middlewares: [Headers](../reference/routing-configuration/http/middlewares/headers.md) for security and [IP allowlisting](../reference/routing-configuration/http/middlewares/ipallowlist.md) for access control. +Middlewares allow you to modify requests or responses as they pass through Traefik. Let's add two useful middlewares: [Headers](../../reference/routing-configuration/http/middlewares/headers.md) for security and [IP allowlisting](../../reference/routing-configuration/http/middlewares/ipallowlist.md) for access control. Add the following labels to your whoami service in `docker-compose.yml`: ```yaml labels: - + # Secure Headers Middleware - "traefik.http.middlewares.secure-headers.headers.frameDeny=true" - "traefik.http.middlewares.secure-headers.headers.sslRedirect=true" @@ -255,10 +32,10 @@ labels: - "traefik.http.middlewares.secure-headers.headers.stsIncludeSubdomains=true" - "traefik.http.middlewares.secure-headers.headers.stsPreload=true" - "traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000" - + # IP Allowlist Middleware - "traefik.http.middlewares.ip-allowlist.ipallowlist.sourceRange=127.0.0.1/32,192.168.0.0/16,10.0.0.0/8" - + # Apply middlewares to whoami router - "traefik.http.routers.whoami.middlewares=secure-headers,ip-allowlist" ``` @@ -288,7 +65,7 @@ curl -k -I -H "Host: whoami.docker.localhost" https://localhost/ In the response headers, you should see security headers set by the middleware: -- `X-Frame-Options: DENY` +- `X-Frame-Options: DENY` - `X-Content-Type-Options: nosniff` - `X-XSS-Protection: 1; mode=block` - `Strict-Transport-Security` with the appropriate settings @@ -438,28 +215,191 @@ You should see different `Hostname` values in these responses, as each request i !!! important "Browser Testing" When testing in browsers, you need to use the same browser session to maintain the cookie. The cookie is set with `httpOnly` and `secure` flags for security, so it will only be sent over HTTPS connections and won't be accessible via JavaScript. -For more advanced configuration options, see the [reference documentation](../reference/routing-configuration/http/load-balancing/service.md). +For more advanced configuration options, see the [reference documentation](../../reference/routing-configuration/http/load-balancing/service.md). + +## Multi-Layer Routing + +Multi-layer routing enables hierarchical relationships between routers, where parent routers can process requests through middleware before child routers make final routing decisions. This is particularly useful for authentication-based routing or staged middleware application. + +!!! info "Provider Requirement" + Multi-layer routing requires the File provider, as Docker labels do not support the `parentRefs` field. However, you can use **both Docker and File providers together** - Docker labels for service discovery and File configuration for multi-layer routing. + +### Setup Multi-Layer Routing with Docker + +To use multi-layer routing with Docker, you need to enable the File provider alongside the Docker provider. + +Update your Traefik service in `docker-compose.yml`: + +```yaml +services: + traefik: + image: "traefik:latest" + container_name: "traefik" + restart: unless-stopped + security_opt: + - no-new-privileges:true + networks: + - proxy + command: + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--providers.docker.network=proxy" + - "--providers.file.directory=/etc/traefik/dynamic" # Enable File provider + - "--entryPoints.web.address=:80" + - "--entryPoints.websecure.address=:443" + - "--entryPoints.websecure.http.tls=true" + ports: + - "80:80" + - "443:443" + - "8080:8080" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - "./dynamic:/etc/traefik/dynamic:ro" # Mount directory for dynamic config +``` + +### Authentication-Based Routing Example + +Let's create a multi-layer routing setup where a parent router authenticates requests, and child routers direct traffic based on user roles. + +First, keep your Docker services defined with labels as usual: + +```yaml +# In docker-compose.yml +services: + # ... traefik service from above ... + + # Mock authentication service that adds X-User-Role header + auth-service: + image: "traefik/whoami" + networks: + - proxy + environment: + - WHOAMI_NAME=Auth Service + labels: + - "traefik.enable=true" + - "traefik.http.services.auth-service.loadbalancer.server.port=80" + + # Admin backend service + admin-backend: + image: "traefik/whoami" + networks: + - proxy + environment: + - WHOAMI_NAME=Admin Backend + labels: + - "traefik.enable=true" + - "traefik.http.services.admin-backend.loadbalancer.server.port=80" + + # User backend service + user-backend: + image: "traefik/whoami" + networks: + - proxy + environment: + - WHOAMI_NAME=User Backend + labels: + - "traefik.enable=true" + - "traefik.http.services.user-backend.loadbalancer.server.port=80" +``` + +Now create the multi-layer routing configuration in a file. Create `dynamic/mlr.yml`: + +```yaml +http: + routers: + # Parent router with authentication middleware + api-parent: + rule: "Host(`api.docker.localhost`) && PathPrefix(`/api`)" + middlewares: + - auth-middleware + entryPoints: + - websecure + # Note: No service and no TLS config - this is a parent router + + # Child router for admin users + api-admin: + rule: "HeadersRegexp(`X-Auth-User`, `admin`)" + service: admin-backend@docker # Reference Docker service + parentRefs: + - api-parent@file # Explicit reference to parent in file provider + + # Child router for regular users + api-user: + rule: "HeadersRegexp(`X-Auth-User`, `user`)" + service: user-backend@docker # Reference Docker service + parentRefs: + - api-parent@file # Explicit reference to parent in file provider + + middlewares: + auth-middleware: + basicAuth: + users: + - "admin:$apr1$DmXR3Add$wfdbGw6RWIhFb0ffXMM4d0" + - "user:$apr1$GJtcIY1o$mSLdsWYeXpPHVsxGDqadI." + headerField: X-Auth-User +``` + +!!! note "Generating Password Hashes" + The password hashes above are generated using `htpasswd`. To create your own user credentials: + + ```bash + # Using htpasswd (Apache utils) + htpasswd -nb admin yourpassword + ``` + +!!! important "Cross-Provider References" + Notice the `@docker` suffix on service names and the `@file` suffix in `parentRefs`. When using the File provider to orchestrate multi-layer routing with Docker services: + + - Use `service-name@docker` to reference Docker services + - Use `parent-name@file` in `parentRefs` to reference the parent router in the File provider + + The `@provider` suffix tells Traefik which provider namespace to look in for the resource. + +Apply the changes: + +```bash +docker compose up -d +``` + +### Test Multi-Layer Routing + +Test the routing behavior: + +```bash +# Request goes through parent router → auth middleware → admin child router +curl -k -u admin:test -H "Host: api.docker.localhost" https://localhost/api +``` + +You should see the response from the admin-backend service when authenticating as `admin`. Try with `user:test` credentials to reach the user-backend service instead. + +### How It Works + +1. **Request arrives** at `api.docker.localhost/api` +2. **Parent router** (`api-parent`) matches based on host and path +3. **BasicAuth middleware** authenticates the user and sets the `X-Auth-User` header with the username +4. **Child router** (`api-admin` or `api-user`) matches based on the header value +5. **Request forwarded** to the appropriate Docker service + +For more details about multi-layer routing, see the [Multi-Layer Routing documentation](../../reference/routing-configuration/http/routing/multi-layer-routing.md). ## Conclusion -In this guide, you've learned how to: +In this advanced guide, you've learned how to: -- Expose HTTP services through Traefik in Docker -- Set up path-based routing to direct traffic to different backend services -- Secure your services with TLS using self-signed certificates - Add security with middlewares like secure headers and IP allow listing - Automate certificate management with Let's Encrypt - Implement sticky sessions for stateful applications +- Setup multi-layer routing for authentication-based routing -These fundamental capabilities provide a solid foundation for exposing any application through Traefik Proxy in Docker. Each of these can be further customized to meet your specific requirements. +These advanced capabilities allow you to build production-ready Traefik deployments with Docker. Each of these can be further customized to meet your specific requirements. ### Next Steps -Now that you understand the basics of exposing services with Traefik Proxy, you might want to explore: +Now that you've mastered both basic and advanced Traefik features with Docker, you might want to explore: -- [Advanced routing options](../reference/routing-configuration/http/routing/rules-and-priority.md) like query parameter matching, header-based routing, and more -- [Additional middlewares](../reference/routing-configuration/http/middlewares/overview.md) for authentication, rate limiting, and request modifications -- [Observability features](../reference/install-configuration/observability/metrics.md) for monitoring and debugging your Traefik deployment -- [TCP services](../reference/routing-configuration/tcp/service.md) for exposing TCP services -- [UDP services](../reference/routing-configuration/udp/service.md) for exposing UDP services -- [Docker provider documentation](../reference/install-configuration/providers/docker.md) for more details about the Docker integration +- [Advanced routing options](../../reference/routing-configuration/http/routing/rules-and-priority.md) like query parameter matching, header-based routing, and more +- [Additional middlewares](../../reference/routing-configuration/http/middlewares/overview.md) for authentication, rate limiting, and request modifications +- [Observability features](../../reference/install-configuration/observability/metrics.md) for monitoring and debugging your Traefik deployment +- [TCP services](../../reference/routing-configuration/tcp/service.md) for exposing TCP services +- [UDP services](../../reference/routing-configuration/udp/service.md) for exposing UDP services +- [Docker provider documentation](../../reference/install-configuration/providers/docker.md) for more details about the Docker integration diff --git a/docs/content/expose/docker/basic.md b/docs/content/expose/docker/basic.md new file mode 100644 index 000000000..4db874c62 --- /dev/null +++ b/docs/content/expose/docker/basic.md @@ -0,0 +1,250 @@ +# Exposing Services with Traefik on Docker - Basic + +This guide will help you get started with exposing your services through Traefik Proxy using Docker. You'll learn the fundamentals of routing HTTP traffic, setting up path-based routing, and securing your services with TLS. + +## Prerequisites + +- Docker and Docker Compose installed +- Basic understanding of Docker concepts +- Traefik deployed using the [Traefik Docker Setup guide](../../setup/docker.md) + +## Expose Your First HTTP Service + +Let's expose a simple HTTP service using the [whoami](https://hub.docker.com/r/traefik/whoami) application. This will demonstrate basic routing to a backend service. + +First, create a `docker-compose.yml` file: + +```yaml +services: + traefik: + image: "traefik:v3.4" + container_name: "traefik" + restart: unless-stopped + security_opt: + - no-new-privileges:true + networks: + - proxy + command: + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--providers.docker.network=proxy" + - "--entryPoints.web.address=:80" + ports: + - "80:80" + - "8080:8080" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + + whoami: + image: "traefik/whoami" + restart: unless-stopped + networks: + - proxy + labels: + - "traefik.enable=true" + - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)" + - "traefik.http.routers.whoami.entrypoints=web" + +networks: + proxy: + name: proxy +``` + +Save this as `docker-compose.yml` and start the services: + +```bash +docker compose up -d +``` + +### Verify Your Service + +Your service is now available at http://whoami.docker.localhost/. Test that it works: + +```bash +curl -H "Host: whoami.docker.localhost" http://localhost/ +``` + +You should see output similar to: + +```bash +Hostname: whoami +IP: 127.0.0.1 +IP: ::1 +IP: 172.18.0.3 +IP: fe80::215:5dff:fe00:c9e +RemoteAddr: 172.18.0.2:55108 +GET / HTTP/1.1 +Host: whoami.docker.localhost +User-Agent: curl/7.68.0 +Accept: */* +Accept-Encoding: gzip +X-Forwarded-For: 172.18.0.1 +X-Forwarded-Host: whoami.docker.localhost +X-Forwarded-Port: 80 +X-Forwarded-Proto: http +X-Forwarded-Server: 5789f594e7d5 +X-Real-Ip: 172.18.0.1 +``` + +This confirms that Traefik is successfully routing requests to your whoami application. + +## Add Routing Rules + +Now we'll enhance our routing by directing traffic to different services based on [URL paths](../../reference/routing-configuration/http/routing/rules-and-priority.md#path-pathprefix-and-pathregexp). This is useful for API versioning, frontend/backend separation, or organizing microservices. + +Update your `docker-compose.yml` to add another service: + +```yaml +# ... + +# New service + whoami-api: + image: "traefik/whoami" + networks: + - proxy + container_name: "whoami-api" + environment: + - WHOAMI_NAME=API Service + labels: + - "traefik.enable=true" + # Path-based routing + - "traefik.http.routers.whoami-api.rule=Host(`whoami.docker.localhost`) && PathPrefix(`/api`)" + - "traefik.http.routers.whoami-api.entrypoints=web" +``` + +Apply the changes: + +```bash +docker compose up -d +``` + +### Test the Path-Based Routing + +Verify that different paths route to different services: + +```bash +# Root path should go to the main whoami service +curl -H "Host: whoami.docker.localhost" http://localhost/ + +# /api path should go to the whoami-api service +curl -H "Host: whoami.docker.localhost" http://localhost/api +``` + +For the `/api` requests, you should see the response showing "API Service" in the environment variables section, confirming that your path-based routing is working correctly. + +## Enable TLS + +Let's secure our service with HTTPS by adding TLS. We'll start with a self-signed certificate for local development. + +### Create a Self-Signed Certificate + +Generate a self-signed certificate: + +```bash +mkdir -p certs +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout certs/local.key -out certs/local.crt \ + -subj "/CN=*.docker.localhost" +``` + +Create a directory for dynamic configuration and add a TLS configuration file: + +```bash +mkdir -p dynamic +cat > dynamic/tls.yml << EOF +tls: + certificates: + - certFile: /certs/local.crt + keyFile: /certs/local.key +EOF +``` + +Update your `docker-compose.yml` file with the following changes: + +```yaml +services: + traefik: + image: "traefik:v3.4" + container_name: "traefik" + restart: unless-stopped + security_opt: + - no-new-privileges:true + networks: + - proxy + command: + - "--api.insecure=false" + - "--api.dashboard=true" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--providers.docker.network=proxy" + - "--providers.file.directory=/etc/traefik/dynamic" + - "--entryPoints.web.address=:80" + - "--entryPoints.websecure.address=:443" + - "--entryPoints.websecure.http.tls=true" + ports: + - "80:80" + - "443:443" + - "8080:8080" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + # Add the following volumes + - "./certs:/certs:ro" + - "./dynamic:/etc/traefik/dynamic:ro" + labels: + - "traefik.enable=true" + - "traefik.http.routers.dashboard.rule=Host(`dashboard.docker.localhost`)" + - "traefik.http.routers.dashboard.entrypoints=websecure" + - "traefik.http.routers.dashboard.service=api@internal" + # Add the following label + - "traefik.http.routers.dashboard.tls=true" + + whoami: + image: "traefik/whoami" + restart: unless-stopped + networks: + - proxy + labels: + - "traefik.enable=true" + - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)" + - "traefik.http.routers.whoami.entrypoints=websecure" + # Add the following label + - "traefik.http.routers.whoami.tls=true" + + whoami-api: + image: "traefik/whoami" + container_name: "whoami-api" + restart: unless-stopped + networks: + - proxy + environment: + - WHOAMI_NAME=API Service + labels: + - "traefik.enable=true" + - "traefik.http.routers.whoami-api.rule=Host(`whoami.docker.localhost`) && PathPrefix(`/api`)" + - "traefik.http.routers.whoami-api.entrypoints=websecure" + # Add the following label + - "traefik.http.routers.whoami-api.tls=true" + +networks: + proxy: + name: proxy +``` + +Apply the changes: + +```bash +docker compose up -d +``` + +Your browser can access https://whoami.docker.localhost/ for the service. You'll need to accept the security warning for the self-signed certificate. + +## Next Steps + +Now that you've mastered the basics of exposing services with Traefik on Docker, you're ready to explore more advanced features like middlewares, Let's Encrypt certificates, sticky sessions, and multi-layer routing. + +Continue to the [Advanced Guide](advanced.md) to learn about: + +- Adding middlewares for security and access control +- Generating certificates with Let's Encrypt +- Configuring sticky sessions for stateful applications +- Setting up multi-layer routing for authentication-based routing diff --git a/docs/content/expose/kubernetes.md b/docs/content/expose/kubernetes/advanced.md similarity index 59% rename from docs/content/expose/kubernetes.md rename to docs/content/expose/kubernetes/advanced.md index 7abac99e6..64f5e78b7 100644 --- a/docs/content/expose/kubernetes.md +++ b/docs/content/expose/kubernetes/advanced.md @@ -1,432 +1,25 @@ -# Exposing Services with Traefik on Kubernetes +# Exposing Services with Traefik on Kubernetes - Advanced -This guide will help you expose your services securely through Traefik Proxy on Kubernetes. We'll cover routing HTTP and HTTPS traffic, implementing TLS, adding security middleware, and configuring sticky sessions. For routing, this guide gives you two options: +This guide builds on the concepts and setup from the [Basic Guide](basic.md). Make sure you've completed the basic guide and have a working Traefik setup with Kubernetes before proceeding. -- [Gateway API](../reference/routing-configuration/kubernetes/gateway-api.md) -- [IngressRoute](../reference/routing-configuration/kubernetes/crd/http/ingressroute.md) +In this advanced guide, you'll learn how to enhance your Traefik deployment with: -Feel free to choose the one that fits your needs best. +- **Middlewares** for security headers and access control +- **Let's Encrypt** for automated certificate management (IngressRoute) +- **cert-manager** for automated certificate management (Gateway API) +- **Sticky sessions** for stateful applications +- **Multi-layer routing** for hierarchical routing with complex authentication scenarios (IngressRoute only) ## Prerequisites +- Completed the [Basic Guide](basic.md) - A Kubernetes cluster with Traefik Proxy installed - `kubectl` configured to interact with your cluster -- Traefik deployed using the Traefik Kubernetes Setup guide - -## Expose Your First HTTP Service - -Let's expose a simple HTTP service using the [whoami](https://github.com/traefik/whoami) application. This will demonstrate basic routing to a backend service. - -First, create the deployment and service: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: whoami - namespace: default -spec: - replicas: 2 - selector: - matchLabels: - app: whoami - template: - metadata: - labels: - app: whoami - spec: - containers: - - name: whoami - image: traefik/whoami - ports: - - containerPort: 80 ---- -apiVersion: v1 -kind: Service -metadata: - name: whoami - namespace: default -spec: - selector: - app: whoami - ports: - - port: 80 -``` - -Save this as `whoami.yaml` and apply it: - -```bash -kubectl apply -f whoami.yaml -``` - -Now, let's create routes using either Gateway API or IngressRoute. - -### Using Gateway API - -```yaml -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: whoami - namespace: default -spec: - parentRefs: - - name: traefik-gateway # This Gateway is automatically created by Traefik - hostnames: - - "whoami.docker.localhost" - rules: - - matches: - - path: - type: PathPrefix - value: / - backendRefs: - - name: whoami - port: 80 -``` - -Save this as `whoami-route.yaml` and apply it: - -```bash -kubectl apply -f whoami-route.yaml -``` - -### Using IngressRoute - -```yaml -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: whoami - namespace: default -spec: - entryPoints: - - web - routes: - - match: Host(`whoami.docker.localhost`) - kind: Rule - services: - - name: whoami - port: 80 -``` - -Save this as `whoami-ingressroute.yaml` and apply it: - -```bash -kubectl apply -f whoami-ingressroute.yaml -``` - -### Verify Your Service - -Your service is now available at http://whoami.docker.localhost/. Test that it works: - -```bash -curl -H "Host: whoami.docker.localhost" http://localhost/ -``` - -!!! info - Make sure to remove the `ports.web.redirections` block from the `values.yaml` file if you followed the Kubernetes Setup Guide to install Traefik otherwise you will be redirected to the HTTPS entrypoint: - - ```yaml - redirections: - entryPoint: - to: websecure - ``` - -You should see output similar to: - -```bash -Hostname: whoami-6d5d964cb-8pv4k -IP: 127.0.0.1 -IP: ::1 -IP: 10.42.0.18 -IP: fe80::d4c0:3bff:fe20:b0a3 -RemoteAddr: 10.42.0.17:39872 -GET / HTTP/1.1 -Host: whoami.docker.localhost -User-Agent: curl/7.68.0 -Accept: */* -Accept-Encoding: gzip -X-Forwarded-For: 10.42.0.1 -X-Forwarded-Host: whoami.docker.localhost -X-Forwarded-Port: 80 -X-Forwarded-Proto: http -X-Forwarded-Server: traefik-76cbd5b89c-rx5xn -X-Real-Ip: 10.42.0.1 -``` - -This confirms that Traefik is successfully routing requests to your whoami application. - -## Add Routing Rules - -Now we'll enhance our routing by directing traffic to different services based on URL paths. This is useful for API versioning, frontend/backend separation, or organizing microservices. - -First, deploy a second service to represent an API: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: whoami-api - namespace: default -spec: - replicas: 1 - selector: - matchLabels: - app: whoami-api - template: - metadata: - labels: - app: whoami-api - spec: - containers: - - name: whoami - image: traefik/whoami - env: - - name: WHOAMI_NAME - value: "API Service" - ports: - - containerPort: 80 ---- -apiVersion: v1 -kind: Service -metadata: - name: whoami-api - namespace: default -spec: - selector: - app: whoami-api - ports: - - port: 80 -``` - -Save this as `whoami-api.yaml` and apply it: - -```bash -kubectl apply -f whoami-api.yaml -``` - -Now set up path-based routing: - -### Gateway API with Path Rules - -Update your existing `HTTPRoute` to include path-based routing: - -```yaml -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: whoami - namespace: default -spec: - parentRefs: - - name: traefik-gateway - hostnames: - - "whoami.docker.localhost" - rules: - - matches: - - path: - type: PathPrefix - value: /api - backendRefs: - - name: whoami-api - port: 80 - - matches: - - path: - type: PathPrefix - value: / - backendRefs: - - name: whoami - port: 80 -``` - -Update the file `whoami-route.yaml` and apply it: - -```bash -kubectl apply -f whoami-route.yaml -``` - -### IngressRoute with Path Rules - -Update your existing IngressRoute to include path-based routing: - -```yaml -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: whoami - namespace: default -spec: - entryPoints: - - web - routes: - - match: Host(`whoami.docker.localhost`) && Path(`/api`) - kind: Rule - services: - - name: whoami-api - port: 80 - - match: Host(`whoami.docker.localhost`) - kind: Rule - services: - - name: whoami - port: 80 -``` - -Save this as `whoami-ingressroute.yaml` and apply it: - -```bash -kubectl apply -f whoami-ingressroute.yaml -``` - -### Test the Path-Based Routing - -Verify that different paths route to different services: - -```bash -# Root path should go to the main whoami service -curl -H "Host: whoami.docker.localhost" http://localhost/ - -# /api path should go to the whoami-api service -curl -H "Host: whoami.docker.localhost" http://localhost/api -``` - -For the `/api` requests, you should see the response showing "API Service" in the environment variables section, confirming that your path-based routing is working correctly: - -```bash -{"hostname":"whoami-api-67d97b4868-dvvll","ip":["127.0.0.1","::1","10.42.0.9","fe80::10aa:37ff:fe74:31f2"],"headers":{"Accept":["*/*"],"Accept-Encoding":["gzip"],"User-Agent":["curl/8.7.1"],"X-Forwarded-For":["10.42.0.1"],"X-Forwarded-Host":["whoami.docker.localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["traefik-669c479df8-vkj22"],"X-Real-Ip":["10.42.0.1"]},"url":"/api","host":"whoami.docker.localhost","method":"GET","name":"API Service","remoteAddr":"10.42.0.13:36592"} -``` - -## Enable TLS - -Let's secure our service with HTTPS by adding TLS. We'll start with a self-signed certificate for local development. - -### Create a Self-Signed Certificate - -Generate a self-signed certificate: - -```bash -openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ - -keyout tls.key -out tls.crt \ - -subj "/CN=whoami.docker.localhost" -``` - -Create a TLS secret in Kubernetes: - -```bash -kubectl create secret tls whoami-tls --cert=tls.crt --key=tls.key -``` - -!!! important "Prerequisite for Gateway API with TLS" - Before using the Gateway API with TLS, you must define the `websecure` listener in your Traefik installation. This is typically done in your Helm values. - - Example configuration in `values.yaml`: - ```yaml - gateway: - listeners: - web: - port: 80 - protocol: HTTP - namespacePolicy: - from: All - websecure: - port: 443 - protocol: HTTPS - namespacePolicy: - from: All - mode: Terminate - certificateRefs: - - kind: Secret - name: local-selfsigned-tls - group: "" - ``` - - See the Traefik Kubernetes Setup Guide for complete installation details. - -### Gateway API with TLS - -Update your existing `HTTPRoute` to use the secured gateway listener: - -```yaml -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: whoami - namespace: default -spec: - parentRefs: - - name: traefik-gateway - sectionName: websecure # The HTTPS listener - hostnames: - - "whoami.docker.localhost" - rules: - - matches: - - path: - type: PathPrefix - value: /api - backendRefs: - - name: whoami-api - port: 80 - - matches: - - path: - type: PathPrefix - value: / - backendRefs: - - name: whoami - port: 80 -``` - -Update the file `whoami-route.yaml` and apply it: - -```bash -kubectl apply -f whoami-route.yaml -``` - -### IngressRoute with TLS - -Update your existing IngressRoute to use TLS: - -```yaml -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: whoami - namespace: default -spec: - entryPoints: - - websecure # Changed from 'web' to 'websecure' - routes: - - match: Host(`whoami.docker.localhost`) && Path(`/api`) - kind: Rule - services: - - name: whoami-api - port: 80 - - match: Host(`whoami.docker.localhost`) - kind: Rule - services: - - name: whoami - port: 80 - tls: - secretName: whoami-tls # Added TLS configuration -``` - -Update the file `whoami-ingressroute.yaml` and apply it: - -```bash -kubectl apply -f whoami-ingressroute.yaml -``` - -### Verify HTTPS Access - -Now you can access your service securely. Since we're using a self-signed certificate, you'll need to skip certificate verification: - -```bash -curl -k -H "Host: whoami.docker.localhost" https://localhost/ -``` - -Your browser can also access https://whoami.docker.localhost/ (you'll need to accept the security warning for the self-signed certificate). +- Working Traefik setup from the basic guide ## Add Middlewares -Middlewares allow you to modify requests or responses as they pass through Traefik. Let's add two useful middlewares: [Headers](../reference/routing-configuration/http/middlewares/headers.md) for security and [IP allowlisting](../reference/routing-configuration/http/middlewares/ipallowlist.md) for access control. +Middlewares allow you to modify requests or responses as they pass through Traefik. Let's add two useful middlewares: [Headers](../../reference/routing-configuration/http/middlewares/headers.md) for security and [IP allowlisting](../../reference/routing-configuration/http/middlewares/ipallowlist.md) for access control. ### Create Middlewares @@ -469,37 +62,6 @@ kubectl apply -f middlewares.yaml In Gateway API, you can apply middlewares using the `ExtensionRef` filter type. This is the preferred and standard way to use Traefik middlewares with Gateway API, as it integrates directly with the HTTPRoute specification. -First, make sure you have the same middlewares defined: - -```yaml -apiVersion: traefik.io/v1alpha1 -kind: Middleware -metadata: - name: secure-headers - namespace: default -spec: - headers: - frameDeny: true - sslRedirect: true - browserXssFilter: true - contentTypeNosniff: true - stsIncludeSubdomains: true - stsPreload: true - stsSeconds: 31536000 ---- -apiVersion: traefik.io/v1alpha1 -kind: Middleware -metadata: - name: ip-allowlist - namespace: default -spec: - ipAllowList: - sourceRange: - - 127.0.0.1/32 - - 10.0.0.0/8 # Typical cluster network range - - 192.168.0.0/16 # Common local network range -``` - Now, update your `HTTPRoute` to reference these middlewares using the `ExtensionRef` filter: ```yaml @@ -525,7 +87,7 @@ spec: group: traefik.io kind: Middleware name: secure-headers - - type: ExtensionRef + - type: ExtensionRef extensionRef: # IP AllowList Middleware Definition group: traefik.io kind: Middleware @@ -543,7 +105,7 @@ spec: group: traefik.io kind: Middleware name: secure-headers - - type: ExtensionRef + - type: ExtensionRef extensionRef: # IP AllowList Middleware Definition group: traefik.io kind: Middleware @@ -854,7 +416,7 @@ spec: group: traefik.io kind: Middleware name: secure-headers - - type: ExtensionRef + - type: ExtensionRef extensionRef: # IP AllowList Middleware Definition group: traefik.io kind: Middleware @@ -876,7 +438,7 @@ spec: group: traefik.io kind: Middleware name: secure-headers - - type: ExtensionRef + - type: ExtensionRef extensionRef: # IP AllowList Middleware Definition group: traefik.io kind: Middleware @@ -986,29 +548,283 @@ You should see different `Hostname` values in these responses, as each request i !!! important "Browser Testing" When testing in browsers, you need to use the same browser session to maintain the cookie. The cookie is set with `httpOnly` and `secure` flags for security, so it will only be sent over HTTPS connections and won't be accessible via JavaScript. -For more advanced configuration options, see the [reference documentation](../reference/routing-configuration/http/load-balancing/service.md). +For more advanced configuration options, see the [reference documentation](../../reference/routing-configuration/http/load-balancing/service.md). + +## Setup Multi-Layer Routing + +Multi-layer routing enables hierarchical relationships between routers, where parent routers can process requests through middleware before child routers make final routing decisions. This is particularly useful for authentication-based routing or staged middleware application. + +!!! info "IngressRoute Support" + Multi-layer routing is **natively supported** by Kubernetes IngressRoute (CRD) using the `spec.parentRefs` field. This feature is not available when using standard Kubernetes Ingress or Gateway API resources. + +### Authentication-Based Routing Example + +Let's create a multi-layer routing setup where a parent IngressRoute authenticates requests, and child IngressRoutes direct traffic based on user roles. + +!!! important "Parent Router Requirements" + Parent routers in multi-layer routing must not have a service defined. The child routers will handle the service selection based on their matching rules. Make sure all child IngressRoutes reference the parent correctly using `parentRefs`. + +First, deploy your backend services: + +```yaml +# whoami-backends.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: admin-backend + namespace: default +spec: + replicas: 2 + selector: + matchLabels: + app: admin-backend + template: + metadata: + labels: + app: admin-backend + spec: + containers: + - name: whoami + image: traefik/whoami + env: + - name: WHOAMI_NAME + value: "Admin Backend" + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: admin-backend + namespace: default +spec: + selector: + app: admin-backend + ports: + - port: 80 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: user-backend + namespace: default +spec: + replicas: 2 + selector: + matchLabels: + app: user-backend + template: + metadata: + labels: + app: user-backend + spec: + containers: + - name: whoami + image: traefik/whoami + env: + - name: WHOAMI_NAME + value: "User Backend" + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: user-backend + namespace: default +spec: + selector: + app: user-backend + ports: + - port: 80 +``` + +Apply the backend services: + +```bash +kubectl apply -f whoami-backends.yaml +``` + +Now create the middleware and IngressRoutes for multi-layer routing: + +```yaml +# mlr-ingressroute.yaml +apiVersion: v1 +kind: Secret +metadata: + name: auth-secret + namespace: default +type: Opaque +stringData: + users: | + admin:$apr1$DmXR3Add$wfdbGw6RWIhFb0ffXMM4d0 + user:$apr1$GJtcIY1o$mSLdsWYeXpPHVsxGDqadI. +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: auth-middleware + namespace: default +spec: + basicAuth: + secret: auth-secret + headerField: X-Auth-User +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: api-parent + namespace: default +spec: + entryPoints: + - websecure + routes: + - match: Host(`api.docker.localhost`) && PathPrefix(`/api`) + kind: Rule + middlewares: + - name: auth-middleware + # Note: No services and no TLS config - this is a parent IngressRoute +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: api-admin + namespace: default +spec: + parentRefs: + - name: api-parent + namespace: default # Optional, defaults to same namespace + routes: + - match: HeadersRegexp(`X-Auth-User`, `admin`) + kind: Rule + services: + - name: admin-backend + port: 80 +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: api-user + namespace: default +spec: + parentRefs: + - name: api-parent + namespace: default # Optional, defaults to same namespace + routes: + - match: HeadersRegexp(`X-Auth-User`, `user`) + kind: Rule + services: + - name: user-backend + port: 80 +``` + +!!! note "Generating Password Hashes" + The password hashes above are generated using `htpasswd`. To create your own user credentials: + + ```bash + # Using htpasswd (Apache utils) + htpasswd -nb admin yourpassword + ``` + +Apply the multi-layer routing configuration: + +```bash +kubectl apply -f mlr-ingressroute.yaml +``` + +### Test Multi-Layer Routing + +Test the routing behavior: + +```bash +# Request goes through parent router → auth middleware → admin child router +curl -k -u admin:test -H "Host: api.docker.localhost" https://localhost/api +``` + +You should see the response from the admin-backend service when authenticating as `admin`. Try with `user:test` credentials to reach the user-backend service instead. + +### How It Works + +1. **Request arrives** at `api.docker.localhost/api` +2. **Parent IngressRoute** (`api-parent`) matches based on host and path +3. **BasicAuth middleware** authenticates the user and sets the `X-Auth-User` header with the username +4. **Child IngressRoute** (`api-admin` or `api-user`) matches based on the header value +5. **Request forwarded** to the appropriate Kubernetes service + +### Cross-Namespace Parent References + +You can reference parent IngressRoutes in different namespaces by specifying the `namespace` field: + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: api-child + namespace: app-namespace +spec: + parentRefs: + - name: api-parent + namespace: shared-namespace # Parent in different namespace + routes: + - match: Path(`/child`) + kind: Rule + services: + - name: child-service + port: 80 +``` + +!!! important "Cross-Namespace Requirement" + To use cross-namespace parent references, you must enable the `allowCrossNamespace` option in your Traefik Helm values: + + ```yaml + providers: + kubernetesCRD: + allowCrossNamespace: true + ``` + +### Multiple Parent References + +Child IngressRoutes can reference multiple parent IngressRoutes: + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: api-child + namespace: default +spec: + parentRefs: + - name: parent-one + - name: parent-two + routes: + - match: Path(`/api`) + kind: Rule + services: + - name: child-service + port: 80 +``` + +For more details about multi-layer routing, see the [Multi-Layer Routing documentation](../../reference/routing-configuration/http/routing/multi-layer-routing.md). ## Conclusion -In this guide, you've learned how to: +In this advanced guide, you've learned how to: -- Expose HTTP services through Traefik in Kubernetes using both Gateway API and IngressRoute -- Set up path-based routing to direct traffic to different backend services -- Secure your services with TLS using self-signed certificates - Add security with middlewares like secure headers and IP allow listing -- Automate certificate management with Let's Encrypt +- Automate certificate management with Let's Encrypt (IngressRoute) and cert-manager (Gateway API) - Implement sticky sessions for stateful applications +- Setup multi-layer routing for authentication-based routing (IngressRoute only) -These fundamental capabilities provide a solid foundation for exposing any application through Traefik Proxy in Kubernetes. Each of these can be further customized to meet your specific requirements. +These advanced capabilities allow you to build production-ready Traefik deployments with Kubernetes. Each of these can be further customized to meet your specific requirements. ### Next Steps -Now that you understand the basics of exposing services with Traefik Proxy, you might want to explore: +Now that you've mastered both basic and advanced Traefik features with Kubernetes, you might want to explore: -- [Advanced routing options](../reference/routing-configuration/http/routing/rules-and-priority.md) like query parameter matching, header-based routing, and more -- [Additional middlewares](../reference/routing-configuration/http/middlewares/overview.md) for authentication, rate limiting, and request modifications -- [Observability features](../reference/install-configuration/observability/metrics.md) for monitoring and debugging your Traefik deployment -- [TCP services](../reference/routing-configuration/tcp/service.md) for exposing TCP services -- [UDP services](../reference/routing-configuration/udp/service.md) for exposing UDP services -- [Kubernetes Provider documentation](../reference/install-configuration/providers/kubernetes/kubernetes-crd.md) for more details about the Kubernetes integration. -- [Gateway API provider documentation](../reference/install-configuration/providers/kubernetes/kubernetes-gateway.md) for more details about the Gateway API integration. +- [Advanced routing options](../../reference/routing-configuration/http/routing/rules-and-priority.md) like query parameter matching, header-based routing, and more +- [Additional middlewares](../../reference/routing-configuration/http/middlewares/overview.md) for authentication, rate limiting, and request modifications +- [Observability features](../../reference/install-configuration/observability/metrics.md) for monitoring and debugging your Traefik deployment +- [TCP services](../../reference/routing-configuration/tcp/service.md) for exposing TCP services +- [UDP services](../../reference/routing-configuration/udp/service.md) for exposing UDP services +- [Kubernetes Provider documentation](../../reference/install-configuration/providers/kubernetes/kubernetes-crd.md) for more details about the Kubernetes integration +- [Gateway API provider documentation](../../reference/install-configuration/providers/kubernetes/kubernetes-gateway.md) for more details about the Gateway API integration diff --git a/docs/content/expose/kubernetes/basic.md b/docs/content/expose/kubernetes/basic.md new file mode 100644 index 000000000..86db8a6da --- /dev/null +++ b/docs/content/expose/kubernetes/basic.md @@ -0,0 +1,438 @@ +# Exposing Services with Traefik on Kubernetes - Basic + +This guide will help you get started with exposing your services through Traefik Proxy on Kubernetes. You'll learn the fundamentals of routing HTTP traffic, setting up path-based routing, and securing your services with TLS. + +For routing, this guide gives you two options: + +- [Gateway API](../../reference/routing-configuration/kubernetes/gateway-api.md) +- [IngressRoute](../../reference/routing-configuration/kubernetes/crd/http/ingressroute.md) + +Feel free to choose the one that fits your needs best. + +## Prerequisites + +- A Kubernetes cluster with Traefik Proxy installed +- `kubectl` configured to interact with your cluster +- Traefik deployed using the [Traefik Kubernetes Setup guide](../../setup/kubernetes.md) + +## Expose Your First HTTP Service + +Let's expose a simple HTTP service using the [whoami](https://github.com/traefik/whoami) application. This will demonstrate basic routing to a backend service. + +First, create the deployment and service: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: whoami + namespace: default +spec: + replicas: 2 + selector: + matchLabels: + app: whoami + template: + metadata: + labels: + app: whoami + spec: + containers: + - name: whoami + image: traefik/whoami + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami + namespace: default +spec: + selector: + app: whoami + ports: + - port: 80 +``` + +Save this as `whoami.yaml` and apply it: + +```bash +kubectl apply -f whoami.yaml +``` + +Now, let's create routes using either Gateway API or IngressRoute. + +### Using Gateway API + +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: whoami + namespace: default +spec: + parentRefs: + - name: traefik-gateway # This Gateway is automatically created by Traefik + hostnames: + - "whoami.docker.localhost" + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: whoami + port: 80 +``` + +Save this as `whoami-route.yaml` and apply it: + +```bash +kubectl apply -f whoami-route.yaml +``` + +### Using IngressRoute + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: whoami + namespace: default +spec: + entryPoints: + - web + routes: + - match: Host(`whoami.docker.localhost`) + kind: Rule + services: + - name: whoami + port: 80 +``` + +Save this as `whoami-ingressroute.yaml` and apply it: + +```bash +kubectl apply -f whoami-ingressroute.yaml +``` + +### Verify Your Service + +Your service is now available at http://whoami.docker.localhost/. Test that it works: + +```bash +curl -H "Host: whoami.docker.localhost" http://localhost/ +``` + +!!! info + Make sure to remove the `ports.web.redirections` block from the `values.yaml` file if you followed the Kubernetes Setup Guide to install Traefik otherwise you will be redirected to the HTTPS entrypoint: + + ```yaml + redirections: + entryPoint: + to: websecure + ``` + +You should see output similar to: + +```bash +Hostname: whoami-6d5d964cb-8pv4k +IP: 127.0.0.1 +IP: ::1 +IP: 10.42.0.18 +IP: fe80::d4c0:3bff:fe20:b0a3 +RemoteAddr: 10.42.0.17:39872 +GET / HTTP/1.1 +Host: whoami.docker.localhost +User-Agent: curl/7.68.0 +Accept: */* +Accept-Encoding: gzip +X-Forwarded-For: 10.42.0.1 +X-Forwarded-Host: whoami.docker.localhost +X-Forwarded-Port: 80 +X-Forwarded-Proto: http +X-Forwarded-Server: traefik-76cbd5b89c-rx5xn +X-Real-Ip: 10.42.0.1 +``` + +This confirms that Traefik is successfully routing requests to your whoami application. + +## Add Routing Rules + +Now we'll enhance our routing by directing traffic to different services based on URL paths. This is useful for API versioning, frontend/backend separation, or organizing microservices. + +First, deploy a second service to represent an API: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: whoami-api + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: whoami-api + template: + metadata: + labels: + app: whoami-api + spec: + containers: + - name: whoami + image: traefik/whoami + env: + - name: WHOAMI_NAME + value: "API Service" + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami-api + namespace: default +spec: + selector: + app: whoami-api + ports: + - port: 80 +``` + +Save this as `whoami-api.yaml` and apply it: + +```bash +kubectl apply -f whoami-api.yaml +``` + +Now set up path-based routing: + +### Gateway API with Path Rules + +Update your existing `HTTPRoute` to include path-based routing: + +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: whoami + namespace: default +spec: + parentRefs: + - name: traefik-gateway + hostnames: + - "whoami.docker.localhost" + rules: + - matches: + - path: + type: PathPrefix + value: /api + backendRefs: + - name: whoami-api + port: 80 + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: whoami + port: 80 +``` + +Update the file `whoami-route.yaml` and apply it: + +```bash +kubectl apply -f whoami-route.yaml +``` + +### IngressRoute with Path Rules + +Update your existing IngressRoute to include path-based routing: + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: whoami + namespace: default +spec: + entryPoints: + - web + routes: + - match: Host(`whoami.docker.localhost`) && Path(`/api`) + kind: Rule + services: + - name: whoami-api + port: 80 + - match: Host(`whoami.docker.localhost`) + kind: Rule + services: + - name: whoami + port: 80 +``` + +Save this as `whoami-ingressroute.yaml` and apply it: + +```bash +kubectl apply -f whoami-ingressroute.yaml +``` + +### Test the Path-Based Routing + +Verify that different paths route to different services: + +```bash +# Root path should go to the main whoami service +curl -H "Host: whoami.docker.localhost" http://localhost/ + +# /api path should go to the whoami-api service +curl -H "Host: whoami.docker.localhost" http://localhost/api +``` + +For the `/api` requests, you should see the response showing "API Service" in the environment variables section, confirming that your path-based routing is working correctly: + +```bash +{"hostname":"whoami-api-67d97b4868-dvvll","ip":["127.0.0.1","::1","10.42.0.9","fe80::10aa:37ff:fe74:31f2"],"headers":{"Accept":["*/*"],"Accept-Encoding":["gzip"],"User-Agent":["curl/8.7.1"],"X-Forwarded-For":["10.42.0.1"],"X-Forwarded-Host":["whoami.docker.localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["traefik-669c479df8-vkj22"],"X-Real-Ip":["10.42.0.1"]},"url":"/api","host":"whoami.docker.localhost","method":"GET","name":"API Service","remoteAddr":"10.42.0.13:36592"} +``` + +## Enable TLS + +Let's secure our service with HTTPS by adding TLS. We'll start with a self-signed certificate for local development. + +### Create a Self-Signed Certificate + +Generate a self-signed certificate: + +```bash +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout tls.key -out tls.crt \ + -subj "/CN=whoami.docker.localhost" +``` + +Create a TLS secret in Kubernetes: + +```bash +kubectl create secret tls whoami-tls --cert=tls.crt --key=tls.key +``` + +!!! important "Prerequisite for Gateway API with TLS" + Before using the Gateway API with TLS, you must define the `websecure` listener in your Traefik installation. This is typically done in your Helm values. + + Example configuration in `values.yaml`: + ```yaml + gateway: + listeners: + web: + port: 80 + protocol: HTTP + namespacePolicy: + from: All + websecure: + port: 443 + protocol: HTTPS + namespacePolicy: + from: All + mode: Terminate + certificateRefs: + - kind: Secret + name: local-selfsigned-tls + group: "" + ``` + + See the Traefik Kubernetes Setup Guide for complete installation details. + +### Gateway API with TLS + +Update your existing `HTTPRoute` to use the secured gateway listener: + +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: whoami + namespace: default +spec: + parentRefs: + - name: traefik-gateway + sectionName: websecure # The HTTPS listener + hostnames: + - "whoami.docker.localhost" + rules: + - matches: + - path: + type: PathPrefix + value: /api + backendRefs: + - name: whoami-api + port: 80 + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: whoami + port: 80 +``` + +Update the file `whoami-route.yaml` and apply it: + +```bash +kubectl apply -f whoami-route.yaml +``` + +### IngressRoute with TLS + +Update your existing IngressRoute to use TLS: + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: whoami + namespace: default +spec: + entryPoints: + - websecure # Changed from 'web' to 'websecure' + routes: + - match: Host(`whoami.docker.localhost`) && Path(`/api`) + kind: Rule + services: + - name: whoami-api + port: 80 + - match: Host(`whoami.docker.localhost`) + kind: Rule + services: + - name: whoami + port: 80 + tls: + secretName: whoami-tls # Added TLS configuration +``` + +Update the file `whoami-ingressroute.yaml` and apply it: + +```bash +kubectl apply -f whoami-ingressroute.yaml +``` + +### Verify HTTPS Access + +Now you can access your service securely. Since we're using a self-signed certificate, you'll need to skip certificate verification: + +```bash +curl -k -H "Host: whoami.docker.localhost" https://localhost/ +``` + +Your browser can also access https://whoami.docker.localhost/ (you'll need to accept the security warning for the self-signed certificate). + +## Next Steps + +Now that you've mastered the basics of exposing services with Traefik on Kubernetes, you're ready to explore more advanced features like middlewares, Let's Encrypt certificates, sticky sessions, and multi-layer routing. + +Continue to the [Advanced Guide](advanced.md) to learn about: + +- Adding middlewares for security and access control +- Generating certificates with Let's Encrypt (IngressRoute) or cert-manager (Gateway API) +- Configuring sticky sessions for stateful applications +- Setting up multi-layer routing for authentication-based routing with IngressRoute diff --git a/docs/content/expose/overview.md b/docs/content/expose/overview.md index 641203a65..513018a70 100644 --- a/docs/content/expose/overview.md +++ b/docs/content/expose/overview.md @@ -17,6 +17,6 @@ Following these guides, you'll learn how to: For detailed steps tailored to your environment, follow the guide for your platform: -- [Kubernetes](./kubernetes.md) -- [Docker](./docker.md) -- [Docker Swarm](./swarm.md) +- [Kubernetes](./kubernetes/basic.md) +- [Docker](./docker/basic.md) +- [Docker Swarm](./swarm/basic.md) diff --git a/docs/content/expose/swarm.md b/docs/content/expose/swarm/advanced.md similarity index 51% rename from docs/content/expose/swarm.md rename to docs/content/expose/swarm/advanced.md index 67a01663a..efe93df3d 100644 --- a/docs/content/expose/swarm.md +++ b/docs/content/expose/swarm/advanced.md @@ -1,186 +1,23 @@ -# Exposing Services with Traefik on Docker Swarm +# Exposing Services with Traefik on Docker Swarm - Advanced -This guide will help you expose your services securely through Traefik Proxy using Docker Swarm. We'll cover routing HTTP and HTTPS traffic, implementing TLS, adding middlewares, Let's Encrypt integration, and sticky sessions. +This guide builds on the concepts and setup from the [Basic Guide](basic.md). Make sure you've completed the basic guide and have a working Traefik setup with Docker Swarm before proceeding. + +In this advanced guide, you'll learn how to enhance your Traefik deployment with: + +- **Middlewares** for security headers and access control +- **Let's Encrypt** for automated certificate management +- **Sticky sessions** for stateful applications +- **Multi-layer routing** for complex authentication scenarios ## Prerequisites +- Completed the [Basic Guide](basic.md) - Docker Swarm cluster initialized -- Basic understanding of Docker Swarm concepts -- Traefik deployed using the Traefik Docker Swarm Setup guide - -## Expose Your First HTTP Service - -Let's expose a simple HTTP service using the [whoami](https://hub.docker.com/r/traefik/whoami) application. This will demonstrate basic routing to a backend service. - -First, update your existing `docker-compose.yml` file if you haven't already: - -```yaml -services: - whoami: - image: traefik/whoami - networks: - - traefik_proxy - deploy: - replicas: 3 - labels: - - "traefik.enable=true" - - "traefik.http.routers.whoami.rule=Host(`whoami.swarm.localhost`)" - - "traefik.http.routers.whoami.entrypoints=web,websecure" -``` - -Save this as `docker-compose.yml` and deploy the stack: - -```bash -docker stack deploy -c docker-compose.yml traefik -``` - -### Verify Your Service - -Your service is now available at http://whoami.swarm.localhost/. Test that it works: - -```bash -curl -H "Host: whoami.swarm.localhost" http://localhost/ -``` - -You should see output similar to: - -```bash -Hostname: whoami.1.7c8f7tr56q3p949rscxrkp80e -IP: 127.0.0.1 -IP: ::1 -IP: 10.0.1.8 -IP: fe80::215:5dff:fe00:c9e -RemoteAddr: 10.0.1.2:45098 -GET / HTTP/1.1 -Host: whoami.swarm.localhost -User-Agent: curl/7.68.0 -Accept: */* -Accept-Encoding: gzip -X-Forwarded-For: 10.0.1.1 -X-Forwarded-Host: whoami.swarm.localhost -X-Forwarded-Port: 80 -X-Forwarded-Proto: http -X-Forwarded-Server: 5789f594e7d5 -X-Real-Ip: 10.0.1.1 -``` - -This confirms that Traefik is successfully routing requests to your whoami application. - -## Add Routing Rules - -Now we'll enhance our routing by directing traffic to different services based on [URL paths](../reference/routing-configuration/http/routing/rules-and-priority.md#path-pathprefix-and-pathregexp). This is useful for API versioning, frontend/backend separation, or organizing microservices. - -Update your `docker-compose.yml` to add another service: - -```yaml -# ... - -# New service - whoami-api: - image: traefik/whoami - networks: - - traefik_proxy - environment: - - WHOAMI_NAME=API Service - deploy: - replicas: 2 - labels: - - "traefik.enable=true" - # Path-based routing - - "traefik.http.routers.whoami-api.rule=Host(`whoami.swarm.localhost`) && PathPrefix(`/api`)" - - "traefik.http.routers.whoami-api.entrypoints=web,websecure" - - "traefik.http.routers.whoami-api.service=whoami-api-svc" - - "traefik.http.services.whoami-api-svc.loadbalancer.server.port=80" - -# ... -``` - -Apply the changes: - -```bash -docker stack deploy -c docker-compose.yml traefik -``` - -### Test the Path-Based Routing - -Verify that different paths route to different services: - -```bash -# Root path should go to the main whoami service -curl -H "Host: whoami.swarm.localhost" http://localhost/ - -# /api path should go to the whoami-api service -curl -H "Host: whoami.swarm.localhost" http://localhost/api -``` - -For the `/api` requests, you should see the response showing "API Service" in the environment variables section, confirming that your path-based routing is working correctly. - -## Enable TLS - -Let's secure our service with HTTPS by adding TLS. We'll start with a self-signed certificate for local development. - -### Create a Self-Signed Certificate - -Generate a self-signed certificate and dynamic config file to tell Traefik where the cert lives: - -```bash -mkdir -p certs - -# key + cert (valid for one year) -openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ - -keyout certs/local.key -out certs/local.crt \ - -subj "/CN=*.swarm.localhost" - -# dynamic config that tells Traefik where the cert lives -cat > certs/tls.yml <<'EOF' -tls: - certificates: - - certFile: /certificates/local.crt - keyFile: /certificates/local.key -EOF -``` - -Create a Docker config for the certificate files: - -```bash -docker config create swarm-cert.crt certs/local.crt -docker config create swarm-cert.key certs/local.key -docker config create swarm-tls.yml certs/tls.yml -``` - -Update your `docker-compose.yml` file with the following changes: - -```yaml -# Add to the Traefik command section: -command: - # ... existing commands ... - - "--entryPoints.websecure.address=:443" - - "--entryPoints.websecure.http.tls=true" - - "--providers.file.directory=/etc/traefik/dynamic" -``` - -```yaml -# Add to the root of your docker-compose.yml file: -configs: - swarm-cert.crt: - file: ./certs/local.crt - swarm-cert.key: - file: ./certs/local.key - swarm-tls.yml: - file: ./certs/tls.yml -``` - -Deploy the stack: - -```bash -docker stack deploy -c docker-compose.yml traefik -``` - -Your browser can access https://whoami.swarm.localhost/ for the service. You'll need to accept the security warning for the self-signed certificate. +- Working Traefik setup from the basic guide ## Add Middlewares -Middlewares allow you to modify requests or responses as they pass through Traefik. Let's add two useful middlewares: [Headers](../reference/routing-configuration/http/middlewares/headers.md) for security and [IP allowlisting](../reference/routing-configuration/http/middlewares/ipallowlist.md) for access control. +Middlewares allow you to modify requests or responses as they pass through Traefik. Let's add two useful middlewares: [Headers](../../reference/routing-configuration/http/middlewares/headers.md) for security and [IP allowlisting](../../reference/routing-configuration/http/middlewares/ipallowlist.md) for access control. Add the following labels to your whoami service deployment section in `docker-compose.yml`: @@ -189,7 +26,7 @@ deploy: # ... existing configuration ... labels: # ... existing labels ... - + # Secure Headers Middleware - "traefik.http.middlewares.secure-headers.headers.frameDeny=true" - "traefik.http.middlewares.secure-headers.headers.sslRedirect=true" @@ -198,10 +35,10 @@ deploy: - "traefik.http.middlewares.secure-headers.headers.stsIncludeSubdomains=true" - "traefik.http.middlewares.secure-headers.headers.stsPreload=true" - "traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000" - + # IP Allowlist Middleware - "traefik.http.middlewares.ip-allowlist.ipallowlist.sourceRange=127.0.0.1/32,192.168.0.0/16,10.0.0.0/8" - + # Apply the middlewares - "traefik.http.routers.whoami.middlewares=secure-headers,ip-allowlist" ``` @@ -332,7 +169,7 @@ deploy: # ... existing configuration ... labels: # ... existing labels ... - + # Sticky Sessions Configuration - "traefik.http.services.whoami.loadbalancer.sticky.cookie=true" - "traefik.http.services.whoami.loadbalancer.sticky.cookie.name=sticky_cookie" @@ -374,28 +211,195 @@ You should see different `Hostname` values in these responses, as each request i !!! important "Browser Testing" When testing in browsers, you need to use the same browser session to maintain the cookie. The cookie is set with `httpOnly` and `secure` flags for security, so it will only be sent over HTTPS connections and won't be accessible via JavaScript. -For more advanced configuration options, see the [reference documentation](../reference/routing-configuration/http/load-balancing/service.md). +For more advanced configuration options, see the [reference documentation](../../reference/routing-configuration/http/load-balancing/service.md). + +## Multi-Layer Routing + +Multi-layer routing enables hierarchical relationships between routers, where parent routers can process requests through middleware before child routers make final routing decisions. This is particularly useful for authentication-based routing or staged middleware application. + +!!! info "Provider Requirement" + Multi-layer routing requires the File provider, as Docker Swarm labels do not support the `parentRefs` field. However, you can use **both Docker Swarm and File providers together** - Swarm labels for service discovery and File configuration for multi-layer routing. + +### Setup Multi-Layer Routing with Docker Swarm + +To use multi-layer routing with Docker Swarm, you need to enable the File provider alongside the Docker provider. + +Update your Traefik service in `docker-compose.yml`: + +```yaml +services: + traefik: + image: traefik:v3.4 + command: + - "--api.dashboard=true" + - "--providers.docker.swarmMode=true" + - "--providers.docker.exposedbydefault=false" + - "--providers.docker.network=traefik_proxy" + - "--providers.file.directory=/etc/traefik/dynamic" # Enable File provider + - "--entrypoints.web.address=:80" + - "--entrypoints.websecure.address=:443" + - "--entrypoints.websecure.http.tls=true" + ports: + - "80:80" + - "443:443" + - "8080:8080" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + configs: + - source: mlr-config + target: /etc/traefik/dynamic/mlr.yml + networks: + - traefik_proxy + deploy: + placement: + constraints: + - node.role == manager + +configs: + mlr-config: + file: ./dynamic/mlr.yml + +networks: + traefik_proxy: + external: true +``` + +### Authentication-Based Routing Example + +Let's create a multi-layer routing setup where a parent router authenticates requests, and child routers direct traffic based on user roles. + +First, keep your Docker Swarm services defined with labels as usual: + +```yaml +# In docker-compose.yml +services: + # ... traefik service from above ... + + # Admin backend service + admin-backend: + image: traefik/whoami + networks: + - traefik_proxy + environment: + - WHOAMI_NAME=Admin Backend + deploy: + replicas: 2 + labels: + - "traefik.enable=true" + - "traefik.http.services.admin-backend.loadbalancer.server.port=80" + + # User backend service + user-backend: + image: traefik/whoami + networks: + - traefik_proxy + environment: + - WHOAMI_NAME=User Backend + deploy: + replicas: 2 + labels: + - "traefik.enable=true" + - "traefik.http.services.user-backend.loadbalancer.server.port=80" +``` + +Now create the multi-layer routing configuration in a file. Create `dynamic/mlr.yml`: + +```yaml +http: + routers: + # Parent router with authentication middleware + api-parent: + rule: "Host(`api.swarm.localhost`) && PathPrefix(`/api`)" + middlewares: + - auth-middleware + entryPoints: + - websecure + # Note: No service and no TLS config - this is a parent router + + # Child router for admin users + api-admin: + rule: "HeadersRegexp(`X-Auth-User`, `admin`)" + service: admin-backend@swarm # Reference Swarm service + parentRefs: + - api-parent@file # Explicit reference to parent in file provider + + # Child router for regular users + api-user: + rule: "HeadersRegexp(`X-Auth-User`, `user`)" + service: user-backend@swarm # Reference Swarm service + parentRefs: + - api-parent@file # Explicit reference to parent in file provider + + middlewares: + auth-middleware: + basicAuth: + users: + - "admin:$apr1$DmXR3Add$wfdbGw6RWIhFb0ffXMM4d0" + - "user:$apr1$GJtcIY1o$mSLdsWYeXpPHVsxGDqadI." + headerField: X-Auth-User +``` + +!!! note "Generating Password Hashes" + The password hashes above are generated using `htpasswd`. To create your own user credentials: + + ```bash + # Using htpasswd (Apache utils) + htpasswd -nb admin yourpassword + ``` + +!!! important "Cross-Provider References" + Notice the `@swarm` suffix on service names and the `@file` suffix in `parentRefs`. When using the File provider to orchestrate multi-layer routing with Swarm services: + + - Use `service-name@swarm` to reference Swarm services + - Use `parent-name@file` in `parentRefs` to reference the parent router in the File provider + + The `@provider` suffix tells Traefik which provider namespace to look in for the resource. + +Deploy the stack: + +```bash +docker stack deploy -c docker-compose.yml traefik +``` + +### Test Multi-Layer Routing + +Test the routing behavior: + +```bash +# Request goes through parent router → auth middleware → admin child router +curl -k -u admin:test -H "Host: api.swarm.localhost" https://localhost/api +``` + +You should see the response from the admin-backend service when authenticating as `admin`. Try with `user:test` credentials to reach the user-backend service instead. + +### How It Works + +1. **Request arrives** at `api.swarm.localhost/api` +2. **Parent router** (`api-parent`) matches based on host and path +3. **BasicAuth middleware** authenticates the user and sets the `X-Auth-User` header with the username +4. **Child router** (`api-admin` or `api-user`) matches based on the header value +5. **Request forwarded** to the appropriate Swarm service + +For more details about multi-layer routing, see the [Multi-Layer Routing documentation](../../reference/routing-configuration/http/routing/multi-layer-routing.md). ## Conclusion -In this guide, you've learned how to: +In this advanced guide, you've learned how to: -- Expose HTTP services through Traefik in Docker Swarm -- Set up path-based routing to direct traffic to different backend services -- Secure your services with TLS using self-signed certificates - Add security with middlewares like secure headers and IP allow listing - Automate certificate management with Let's Encrypt - Implement sticky sessions for stateful applications +- Setup multi-layer routing for authentication-based routing -These fundamental capabilities provide a solid foundation for exposing any application through Traefik Proxy in Docker Swarm. Each of these can be further customized to meet your specific requirements. +These advanced capabilities allow you to build production-ready Traefik deployments with Docker Swarm. Each of these can be further customized to meet your specific requirements. ### Next Steps -Now that you understand the basics of exposing services with Traefik Proxy, you might want to explore: +Now that you've mastered both basic and advanced Traefik features with Docker Swarm, you might want to explore: -- [Advanced routing options](../reference/routing-configuration/http/routing/rules-and-priority.md) like query parameter matching, header-based routing, and more -- [Additional middlewares](../reference/routing-configuration/http/middlewares/overview.md) for authentication, rate limiting, and request modifications -- [Observability features](../reference/install-configuration/observability/metrics.md) for monitoring and debugging your Traefik deployment -- [TCP services](../reference/routing-configuration/tcp/service.md) for exposing TCP services -- [UDP services](../reference/routing-configuration/udp/service.md) for exposing UDP services -- [Docker provider documentation](../reference/install-configuration/providers/docker.md) for more details about the Docker integration +- [Advanced routing options](../../reference/routing-configuration/http/routing/rules-and-priority.md) like query parameter matching, header-based routing, and more +- [Additional middlewares](../../reference/routing-configuration/http/middlewares/overview.md) for authentication, rate limiting, and request modifications +- [Observability features](../../reference/install-configuration/observability/metrics.md) for monitoring and debugging your Traefik deployment +- [TCP services](../../reference/routing-configuration/tcp/service.md) for exposing TCP services +- [UDP services](../../reference/routing-configuration/udp/service.md) for exposing UDP services +- [Docker provider documentation](../../reference/install-configuration/providers/docker.md) for more details about the Docker integration diff --git a/docs/content/expose/swarm/basic.md b/docs/content/expose/swarm/basic.md new file mode 100644 index 000000000..19282a90e --- /dev/null +++ b/docs/content/expose/swarm/basic.md @@ -0,0 +1,191 @@ +# Exposing Services with Traefik on Docker Swarm - Basic + +This guide will help you get started with exposing your services through Traefik Proxy using Docker Swarm. You'll learn the fundamentals of routing HTTP traffic, setting up path-based routing, and securing your services with TLS. + +## Prerequisites + +- Docker Swarm cluster initialized +- Basic understanding of Docker Swarm concepts +- Traefik deployed using the [Traefik Docker Swarm Setup guide](../../setup/swarm.md) + + +## Expose Your First HTTP Service + +Let's expose a simple HTTP service using the [whoami](https://hub.docker.com/r/traefik/whoami) application. This will demonstrate basic routing to a backend service. + +First, update your existing `docker-compose.yml` file if you haven't already: + +```yaml +services: + whoami: + image: traefik/whoami + networks: + - traefik_proxy + deploy: + replicas: 3 + labels: + - "traefik.enable=true" + - "traefik.http.routers.whoami.rule=Host(`whoami.swarm.localhost`)" + - "traefik.http.routers.whoami.entrypoints=web,websecure" +``` + +Save this as `docker-compose.yml` and deploy the stack: + +```bash +docker stack deploy -c docker-compose.yml traefik +``` + +### Verify Your Service + +Your service is now available at http://whoami.swarm.localhost/. Test that it works: + +```bash +curl -H "Host: whoami.swarm.localhost" http://localhost/ +``` + +You should see output similar to: + +```bash +Hostname: whoami.1.7c8f7tr56q3p949rscxrkp80e +IP: 127.0.0.1 +IP: ::1 +IP: 10.0.1.8 +IP: fe80::215:5dff:fe00:c9e +RemoteAddr: 10.0.1.2:45098 +GET / HTTP/1.1 +Host: whoami.swarm.localhost +User-Agent: curl/7.68.0 +Accept: */* +Accept-Encoding: gzip +X-Forwarded-For: 10.0.1.1 +X-Forwarded-Host: whoami.swarm.localhost +X-Forwarded-Port: 80 +X-Forwarded-Proto: http +X-Forwarded-Server: 5789f594e7d5 +X-Real-Ip: 10.0.1.1 +``` + +This confirms that Traefik is successfully routing requests to your whoami application. + +## Add Routing Rules + +Now we'll enhance our routing by directing traffic to different services based on [URL paths](../../reference/routing-configuration/http/routing/rules-and-priority.md#path-pathprefix-and-pathregexp). This is useful for API versioning, frontend/backend separation, or organizing microservices. + +Update your `docker-compose.yml` to add another service: + +```yaml +# ... + +# New service + whoami-api: + image: traefik/whoami + networks: + - traefik_proxy + environment: + - WHOAMI_NAME=API Service + deploy: + replicas: 2 + labels: + - "traefik.enable=true" + # Path-based routing + - "traefik.http.routers.whoami-api.rule=Host(`whoami.swarm.localhost`) && PathPrefix(`/api`)" + - "traefik.http.routers.whoami-api.entrypoints=web,websecure" + - "traefik.http.routers.whoami-api.service=whoami-api-svc" + - "traefik.http.services.whoami-api-svc.loadbalancer.server.port=80" + +# ... +``` + +Apply the changes: + +```bash +docker stack deploy -c docker-compose.yml traefik +``` + +### Test the Path-Based Routing + +Verify that different paths route to different services: + +```bash +# Root path should go to the main whoami service +curl -H "Host: whoami.swarm.localhost" http://localhost/ + +# /api path should go to the whoami-api service +curl -H "Host: whoami.swarm.localhost" http://localhost/api +``` + +For the `/api` requests, you should see the response showing "API Service" in the environment variables section, confirming that your path-based routing is working correctly. + +## Enable TLS + +Let's secure our service with HTTPS by adding TLS. We'll start with a self-signed certificate for local development. + +### Create a Self-Signed Certificate + +Generate a self-signed certificate and dynamic config file to tell Traefik where the cert lives: + +```bash +mkdir -p certs + +# key + cert (valid for one year) +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout certs/local.key -out certs/local.crt \ + -subj "/CN=*.swarm.localhost" + +# dynamic config that tells Traefik where the cert lives +cat > certs/tls.yml <<'EOF' +tls: + certificates: + - certFile: /certificates/local.crt + keyFile: /certificates/local.key +EOF +``` + +Create a Docker config for the certificate files: + +```bash +docker config create swarm-cert.crt certs/local.crt +docker config create swarm-cert.key certs/local.key +docker config create swarm-tls.yml certs/tls.yml +``` + +Update your `docker-compose.yml` file with the following changes: + +```yaml +# Add to the Traefik command section: +command: + # ... existing commands ... + - "--entryPoints.websecure.address=:443" + - "--entryPoints.websecure.http.tls=true" + - "--providers.file.directory=/etc/traefik/dynamic" +``` + +```yaml +# Add to the root of your docker-compose.yml file: +configs: + swarm-cert.crt: + file: ./certs/local.crt + swarm-cert.key: + file: ./certs/local.key + swarm-tls.yml: + file: ./certs/tls.yml +``` + +Deploy the stack: + +```bash +docker stack deploy -c docker-compose.yml traefik +``` + +Your browser can access https://whoami.swarm.localhost/ for the service. You'll need to accept the security warning for the self-signed certificate. + +## Next Steps + +Now that you've mastered the basics of exposing services with Traefik on Docker Swarm, you're ready to explore more advanced features like middlewares, Let's Encrypt certificates, sticky sessions, and multi-layer routing. + +Continue to the [Advanced Guide](advanced.md) to learn about: + +- Adding middlewares for security and access control +- Generating certificates with Let's Encrypt +- Configuring sticky sessions for stateful applications +- Setting up multi-layer routing for authentication-based routing diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 77e5ce866..c6e27e6d6 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -136,12 +136,16 @@ plugins: 'middlewares/tcp/ipwhitelist.md': 'reference/routing-configuration/tcp/middlewares/ipallowlist.md' 'middlewares/tcp/ipallowlist.md': 'reference/routing-configuration/tcp/middlewares/ipallowlist.md' ## User Guides - 'user-guides/crd-acme/index.md': 'expose/kubernetes.md' - 'user-guides/cert-manager.md': 'expose/kubernetes.md' - 'user-guides/docker-compose/basic-example/index.md': 'expose/docker.md' - 'user-guides/docker-compose/acme-tls/index.md': 'expose/docker.md' - 'user-guides/docker-compose/acme-http/index.md': 'expose/docker.md' - 'user-guides/docker-compose/acme-dns/index.md': 'expose/docker.md' + 'user-guides/crd-acme/index.md': 'expose/kubernetes/basic.md' + 'user-guides/cert-manager.md': 'expose/kubernetes/advanced.md' + 'user-guides/docker-compose/basic-example/index.md': 'expose/docker/basic.md' + 'user-guides/docker-compose/acme-tls/index.md': 'expose/docker/advanced.md' + 'user-guides/docker-compose/acme-http/index.md': 'expose/docker/advanced.md' + 'user-guides/docker-compose/acme-dns/index.md': 'expose/docker/advanced.md' + ## Expose pages (redirect old URLs to new structure) + 'expose/kubernetes.md': 'expose/kubernetes/basic.md' + 'expose/docker.md': 'expose/docker/basic.md' + 'expose/swarm.md': 'expose/swarm/basic.md' # References # Static Configuration 'reference/static-configuration/overview.md': 'reference/install-configuration/configuration-options.md' @@ -201,9 +205,15 @@ nav: - 'Swarm': 'setup/swarm.md' - 'Expose': - 'Overview': 'expose/overview.md' - - 'Kubernetes': 'expose/kubernetes.md' - - 'Docker': 'expose/docker.md' - - 'Swarm': 'expose/swarm.md' + - 'Kubernetes': + - 'Basic': 'expose/kubernetes/basic.md' + - 'Advanced': 'expose/kubernetes/advanced.md' + - 'Docker': + - 'Basic': 'expose/docker/basic.md' + - 'Advanced': 'expose/docker/advanced.md' + - 'Swarm': + - 'Basic': 'expose/swarm/basic.md' + - 'Advanced': 'expose/swarm/advanced.md' - 'Secure': - 'Secure Access with JWT Traefik Hub API Gateway': 'secure/secure-api-access-with-jwt.md' - 'Secure Access with OIDC Traefik Hub API Gateway': 'secure/secure-api-access-with-oidc.md' From d4d61dbd8070088b096024d861ae918805e4f6e9 Mon Sep 17 00:00:00 2001 From: Emile Vauge <6207234+emilevauge@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:04:05 +0100 Subject: [PATCH 12/13] Add @gndz07 as a current maintainer --- docs/content/contributing/maintainers.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/content/contributing/maintainers.md b/docs/content/contributing/maintainers.md index a7c3bbe50..a86671181 100644 --- a/docs/content/contributing/maintainers.md +++ b/docs/content/contributing/maintainers.md @@ -23,6 +23,7 @@ description: "Traefik Proxy is an open source software with a thriving community * Simon Delicata [@sdelicata](https://github.com/sdelicata) * Baptiste Mayelle [@youkoulayley](https://github.com/youkoulayley) * Jesper Noordsij [@jnoordsij](https://github.com/jnoordsij) +* Gina Adzani [@gndz07](https://github.com/gndz07) ## Past Maintainers From 7e5ee8988e6158d962e87fccdce4ce51be65a561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20BUISSON?= Date: Mon, 26 Jan 2026 15:14:05 +0100 Subject: [PATCH 13/13] chore: pin GitHub Actions to SHA hashes for supply chain security --- .github/workflows/build.yaml | 6 +++--- .github/workflows/check_doc.yaml | 8 ++++---- .github/workflows/codeql.yml | 10 +++++----- .github/workflows/documentation.yaml | 4 ++-- .github/workflows/experimental.yaml | 12 ++++++------ .github/workflows/release.yaml | 16 ++++++++-------- .github/workflows/template-webui.yaml | 6 +++--- .github/workflows/test-integration.yaml | 18 +++++++++--------- .github/workflows/test-unit.yaml | 12 ++++++------ .github/workflows/validate.yaml | 14 +++++++------- 10 files changed, 53 insertions(+), 53 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a6e2e15e3..1e02a3c0d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -51,12 +51,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 env: ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }} with: @@ -64,7 +64,7 @@ jobs: check-latest: true - name: Artifact webui - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: webui.tar.gz diff --git a/.github/workflows/check_doc.yaml b/.github/workflows/check_doc.yaml index 5fea9809c..994566896 100644 --- a/.github/workflows/check_doc.yaml +++ b/.github/workflows/check_doc.yaml @@ -16,7 +16,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 @@ -28,7 +28,7 @@ jobs: run: ./docs/scripts/lint.sh docs - name: Setup python - uses: actions/setup-python@v6 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: '3.12' cache: 'pip' @@ -41,7 +41,7 @@ jobs: mkdocs build --strict - name: Setup ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@e69dcf3ded5967f30d7ef595704928d91cdae930 # v1.285.0 with: ruby-version: '3.4' @@ -54,7 +54,7 @@ jobs: # Comes from https://github.com/gjtorikian/html-proofer?tab=readme-ov-file#caching-with-continuous-integration - name: Cache HTMLProofer - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: tmp/.htmlproofer key: ${{ runner.os }}-htmlproofer diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 30ab2221b..e93a0c545 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -28,17 +28,17 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: setup go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 if: ${{ matrix.language == 'go' }} with: go-version-file: 'go.mod' # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -52,7 +52,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -65,6 +65,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index 53d5579bc..b5d804c70 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -20,12 +20,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Login to DockerHub - uses: docker/login-action@v3 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/experimental.yaml b/.github/workflows/experimental.yaml index a56928fff..3c66efa58 100644 --- a/.github/workflows/experimental.yaml +++ b/.github/workflows/experimental.yaml @@ -23,12 +23,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 env: ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }} with: @@ -42,19 +42,19 @@ jobs: run: echo ${GITHUB_REF##*/} - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Artifact webui - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: webui.tar.gz diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d5e869670..0c2e4e933 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -30,12 +30,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 env: # Ensure cache consistency on Linux, see https://github.com/actions/setup-go/pull/383 ImageOS: ${{ matrix.os }} @@ -44,7 +44,7 @@ jobs: check-latest: true - name: Artifact webui - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: webui.tar.gz @@ -63,7 +63,7 @@ jobs: echo "GORELEASER_CONFIG_FILE_PATH=$GORELEASER_CONFIG_FILE_PATH" >> $GITHUB_ENV - name: Build with goreleaser - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 with: distribution: goreleaser # 'latest', 'nightly', or a semver @@ -71,7 +71,7 @@ jobs: args: release --clean --timeout="90m" --config "${{ env.GORELEASER_CONFIG_FILE_PATH }}" - name: Artifact binaries - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: ${{ matrix.os }}-binaries path: | @@ -89,12 +89,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Artifact webui - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: webui.tar.gz @@ -111,7 +111,7 @@ jobs: echo "${TRAEFIKER_RSA}" | base64 --decode > ~/.ssh/traefiker_rsa - name: Download All Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: path: dist/ pattern: "*-binaries" diff --git a/.github/workflows/template-webui.yaml b/.github/workflows/template-webui.yaml index 71d8a09e8..0a8cd41ca 100644 --- a/.github/workflows/template-webui.yaml +++ b/.github/workflows/template-webui.yaml @@ -10,12 +10,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Setup node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version-file: webui/.nvmrc cache: yarn @@ -38,7 +38,7 @@ jobs: tar czvf webui.tar.gz ./webui/static/ - name: Artifact webui - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: webui.tar.gz path: webui.tar.gz diff --git a/.github/workflows/test-integration.yaml b/.github/workflows/test-integration.yaml index 3d05f81d4..bbbca2a9a 100644 --- a/.github/workflows/test-integration.yaml +++ b/.github/workflows/test-integration.yaml @@ -20,12 +20,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true @@ -37,14 +37,14 @@ jobs: run: make binary-linux-amd64 - name: Save go cache build - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cache/go-build key: ${{ runner.os }}-go-build-cache-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }} - name: Artifact traefik binary - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: traefik path: ./dist/linux/amd64/traefik @@ -62,12 +62,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true @@ -76,7 +76,7 @@ jobs: run: touch webui/static/index.html - name: Download traefik binary - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: traefik path: ./dist/linux/amd64/ @@ -85,7 +85,7 @@ jobs: run: chmod +x ./dist/linux/amd64/traefik - name: Restore go cache build - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cache/go-build @@ -93,7 +93,7 @@ jobs: - name: Generate go test Slice id: test_split - uses: hashicorp-forge/go-test-split-action@v2.0.0 + uses: hashicorp-forge/go-test-split-action@720a77701f0d1b2973126510492a6aad11ed0d5a # v2.0.0 with: packages: ./integration total: ${{ matrix.parallel }} diff --git a/.github/workflows/test-unit.yaml b/.github/workflows/test-unit.yaml index 942c4ca2e..63ab624dd 100644 --- a/.github/workflows/test-unit.yaml +++ b/.github/workflows/test-unit.yaml @@ -21,12 +21,12 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true @@ -47,12 +47,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true @@ -69,12 +69,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version-file: webui/.nvmrc cache: 'yarn' diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 317653e13..c9a60768c 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -17,18 +17,18 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true - name: golangci-lint - uses: golangci/golangci-lint-action@v7 + uses: golangci/golangci-lint-action@9fae48acfc02a90574d7c304a1758ef9895495fa # v7.0.1 with: version: "${{ env.GOLANGCI_LINT_VERSION }}" @@ -37,12 +37,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true @@ -61,12 +61,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true