From 51343bc15fd54a68c520746657801d26376e14a7 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 14 Jan 2026 17:26:08 +0100 Subject: [PATCH] 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 }