Merge current v2.11 into v3.6

This commit is contained in:
mmatur
2026-01-15 11:26:40 +01:00
parent 27b27e9b1f
commit 3315a9fbec
195 changed files with 1748 additions and 1852 deletions
+1 -2
View File
@@ -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]
+6 -5
View File
@@ -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"))
}
+42 -41
View File
@@ -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",
@@ -837,3 +797,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)
}
+11 -10
View File
@@ -25,6 +25,7 @@ import (
// Consul test suites.
type ConsulSuite struct {
BaseSuite
kvClient store.Store
consulURL string
}
@@ -162,16 +163,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
@@ -220,3 +211,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()
}
+3 -3
View File
@@ -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"])
+1
View File
@@ -21,6 +21,7 @@ const traefikTestOTLPLogFile = "traefik_otlp.log"
// DualLoggingSuite tests that both OTLP and stdout logging can work together.
type DualLoggingSuite struct {
BaseSuite
otlpLogs []string
collector *httptest.Server
}
+1
View File
@@ -14,6 +14,7 @@ import (
// ErrorPagesSuite test suites.
type ErrorPagesSuite struct {
BaseSuite
ErrorPageIP string
BackendIP string
}
+1
View File
@@ -23,6 +23,7 @@ import (
// etcd test suites.
type EtcdSuite struct {
BaseSuite
kvClient store.Store
etcdAddr string
}
+10 -1
View File
@@ -374,6 +374,7 @@ spec:
description: |-
Syntax defines the router's rule syntax.
More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/rules-and-priority/#rulesyntax
Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax.
type: string
required:
@@ -585,6 +586,7 @@ spec:
description: |-
ProxyProtocol defines the PROXY protocol configuration.
More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/service/#proxy-protocol
Deprecated: ProxyProtocol will not be supported in future APIVersions, please use ServersTransport to configure ProxyProtocol instead.
properties:
version:
@@ -607,6 +609,7 @@ spec:
hence fully terminating the connection.
It is a duration in milliseconds, defaulting to 100.
A negative value means an infinite deadline (i.e. the reading capability is never closed).
Deprecated: TerminationDelay will not be supported in future APIVersions, please use ServersTransport to configure the TerminationDelay instead.
type: integer
tls:
@@ -627,6 +630,7 @@ spec:
description: |-
Syntax defines the router's rule syntax.
More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/rules-and-priority/#rulesyntax
Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax.
enum:
- v3
@@ -1061,6 +1065,7 @@ spec:
description: |-
AutoDetect specifies whether to let the `Content-Type` header, if it has not been set by the backend,
be automatically set to a value derived from the contents of the response.
Deprecated: AutoDetect option is deprecated, Content-Type middleware is only meant to be used to enable the content-type detection, please remove any usage of this option.
type: boolean
type: object
@@ -2213,8 +2218,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/v3.6/reference/routing-configuration/tcp/middlewares/ipwhitelist/
Deprecated: please use IPAllowList instead.
properties:
sourceRange:
description: SourceRange defines the allowed IPs (or ranges of
@@ -2371,6 +2377,7 @@ spec:
rootCAsSecrets:
description: |-
RootCAsSecrets defines a list of CA secret used to validate self-signed certificate.
Deprecated: RootCAsSecrets is deprecated, please use the RootCAs option instead.
items:
type: string
@@ -2525,6 +2532,7 @@ spec:
rootCAsSecrets:
description: |-
RootCAsSecrets defines a list of CA secret used to validate self-signed certificate.
Deprecated: RootCAsSecrets is deprecated, please use the RootCAs option instead.
items:
type: string
@@ -2660,6 +2668,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:
+1
View File
@@ -19,6 +19,7 @@ import (
// HealthCheck test suites.
type HealthCheckSuite struct {
BaseSuite
whoami1IP string
whoami2IP string
whoami3IP string
+3 -3
View File
@@ -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
+34 -34
View File
@@ -877,40 +877,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: types.FileOrContent("fixtures/https/" + certFileName + ".cert"),
KeyFile: types.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))
@@ -1177,6 +1143,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: types.FileOrContent("fixtures/https/" + certFileName + ".cert"),
KeyFile: types.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{}{})
+37 -36
View File
@@ -71,45 +71,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(s.T()) {
_, err := os.Stat(tailscaleSecretFilePath)
@@ -409,7 +376,7 @@ func (s *BaseSuite) displayTraefikLog(output *bytes.Buffer) {
if output == nil || output.Len() == 0 {
log.Info().Msg("No Traefik logs.")
} else {
for _, line := range strings.Split(output.String(), "\n") {
for line := range strings.SplitSeq(output.String(), "\n") {
log.Info().Msg(line)
}
}
@@ -425,7 +392,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)
@@ -513,3 +480,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)
}
}
}
-1
View File
@@ -1,5 +1,4 @@
//go:build !windows
// +build !windows
package integration
+6 -3
View File
@@ -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
}
+1
View File
@@ -13,6 +13,7 @@ import (
type RateLimitSuite struct {
BaseSuite
ServerIP string
RedisEndpoint string
}
+31 -30
View File
@@ -28,6 +28,7 @@ import (
// Redis test suites.
type RedisSentinelSuite struct {
BaseSuite
kvClient store.Store
redisEndpoints []string
}
@@ -75,36 +76,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, `","`),
@@ -201,3 +172,33 @@ func (s *RedisSentinelSuite) TestSentinelConfiguration() {
log.Info().Msg(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)
}
}
+1
View File
@@ -24,6 +24,7 @@ import (
// Redis test suites.
type RedisSuite struct {
BaseSuite
kvClient store.Store
redisEndpoints []string
}
+1
View File
@@ -18,6 +18,7 @@ import (
type RestSuite struct {
BaseSuite
whoamiAddr string
}
+1
View File
@@ -15,6 +15,7 @@ import (
type RetrySuite struct {
BaseSuite
whoamiIP string
}
+1
View File
@@ -15,6 +15,7 @@ import (
// TCPHealthCheckSuite test suite for TCP health checks.
type TCPHealthCheckSuite struct {
BaseSuite
whoamitcp1IP string
whoamitcp2IP string
}
+1
View File
@@ -21,6 +21,7 @@ import (
type TracingSuite struct {
BaseSuite
whoamiIP string
whoamiPort int
tempoIP string
+2 -4
View File
@@ -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)
+1
View File
@@ -24,6 +24,7 @@ import (
// Zk test suites.
type ZookeeperSuite struct {
BaseSuite
kvClient store.Store
zookeeperAddr string
}