From 51343bc15fd54a68c520746657801d26376e14a7 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 14 Jan 2026 17:26:08 +0100 Subject: [PATCH 1/6] 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 7cb25da31cfbf6e426e197fdc098d0dec72293b5 Mon Sep 17 00:00:00 2001 From: Romain Date: Tue, 20 Jan 2026 15:40:05 +0100 Subject: [PATCH 2/6] 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 d675b163b326bf0554e747444e91e5f17ecc3941 Mon Sep 17 00:00:00 2001 From: Romain Date: Thu, 22 Jan 2026 09:54:04 +0100 Subject: [PATCH 3/6] 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 4/6] 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 5/6] 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 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 6/6] 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