From 95dd17e0200e5a861e4519675d2f37acfc7b95eb Mon Sep 17 00:00:00 2001 From: Taylor Yelverton Date: Thu, 9 Jan 2025 10:20:06 -0600 Subject: [PATCH] Allow configuring server URLs with label providers --- .../dynamic-configuration/docker-labels.yml | 2 + .../routing/providers/consul-catalog.md | 9 + docs/content/routing/providers/docker.md | 9 + docs/content/routing/providers/ecs.md | 9 + docs/content/routing/providers/nomad.md | 9 + docs/content/routing/providers/swarm.md | 9 + integration/consul_catalog_test.go | 10 - pkg/config/dynamic/http_config.go | 16 +- pkg/config/label/label_test.go | 32 ++- pkg/provider/consulcatalog/config.go | 15 +- pkg/provider/consulcatalog/config_test.go | 188 +++++++++++++++ pkg/provider/docker/config.go | 17 +- pkg/provider/docker/config_test.go | 220 ++++++++++++++++++ pkg/provider/ecs/config.go | 17 +- pkg/provider/ecs/config_test.go | 200 ++++++++++++++++ pkg/provider/kv/kv_test.go | 6 +- pkg/provider/nomad/config.go | 16 +- pkg/provider/nomad/config_test.go | 188 +++++++++++++++ 18 files changed, 924 insertions(+), 48 deletions(-) diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index 71e113f88..593829571 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -204,7 +204,9 @@ - "traefik.http.services.service02.loadbalancer.sticky.cookie.samesite=foobar" - "traefik.http.services.service02.loadbalancer.sticky.cookie.secure=true" - "traefik.http.services.service02.loadbalancer.server.port=foobar" +- "traefik.http.services.service02.loadbalancer.server.preservepath=true" - "traefik.http.services.service02.loadbalancer.server.scheme=foobar" +- "traefik.http.services.service02.loadbalancer.server.url=foobar" - "traefik.http.services.service02.loadbalancer.server.weight=42" - "traefik.tcp.middlewares.tcpmiddleware01.ipallowlist.sourcerange=foobar, foobar" - "traefik.tcp.middlewares.tcpmiddleware02.ipwhitelist.sourcerange=foobar, foobar" diff --git a/docs/content/routing/providers/consul-catalog.md b/docs/content/routing/providers/consul-catalog.md index b3c918892..17b20c935 100644 --- a/docs/content/routing/providers/consul-catalog.md +++ b/docs/content/routing/providers/consul-catalog.md @@ -168,6 +168,15 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass traefik.http.services.myservice.loadbalancer.server.scheme=http ``` +??? info "`traefik.http.services..loadbalancer.server.url`" + + Defines the service URL. + This option cannot be used in combination with `port` or `scheme` definition. + + ```yaml + traefik.http.services.myservice.loadbalancer.server.url=http://foobar:8080 + ``` + ??? info "`traefik.http.services..loadbalancer.serverstransport`" Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md index 43d785010..2ed74a796 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -283,6 +283,15 @@ you'd add the label `traefik.http.services..loadbalancer.pa - "traefik.http.services.myservice.loadbalancer.server.scheme=http" ``` +??? info "`traefik.http.services..loadbalancer.server.url`" + + Defines the service URL. + This option cannot be used in combination with `port` or `scheme` definition. + + ```yaml + - "traefik.http.services.myservice.loadbalancer.server.url=http://foobar:8080" + ``` + ??? info "`traefik.http.services..loadbalancer.serverstransport`" Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. diff --git a/docs/content/routing/providers/ecs.md b/docs/content/routing/providers/ecs.md index 35ba6e09c..b87f4f75e 100644 --- a/docs/content/routing/providers/ecs.md +++ b/docs/content/routing/providers/ecs.md @@ -170,6 +170,15 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa traefik.http.services.myservice.loadbalancer.server.scheme=http ``` +??? info "`traefik.http.services..loadbalancer.server.url`" + + Defines the service URL. + This option cannot be used in combination with `port` or `scheme` definition. + + ```yaml + traefik.http.services.myservice.loadbalancer.server.url=http://foobar:8080 + ``` + ??? info "`traefik.http.services..loadbalancer.serverstransport`" Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. diff --git a/docs/content/routing/providers/nomad.md b/docs/content/routing/providers/nomad.md index 476091201..8a9dcd4b7 100644 --- a/docs/content/routing/providers/nomad.md +++ b/docs/content/routing/providers/nomad.md @@ -168,6 +168,15 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass traefik.http.services.myservice.loadbalancer.server.scheme=http ``` +??? info "`traefik.http.services..loadbalancer.server.url`" + + Defines the service URL. + This option cannot be used in combination with `port` or `scheme` definition. + + ```yaml + traefik.http.services.myservice.loadbalancer.server.url=http://foobar:8080 + ``` + ??? info "`traefik.http.services..loadbalancer.serverstransport`" Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. diff --git a/docs/content/routing/providers/swarm.md b/docs/content/routing/providers/swarm.md index ebe339b7a..7224af183 100644 --- a/docs/content/routing/providers/swarm.md +++ b/docs/content/routing/providers/swarm.md @@ -297,6 +297,15 @@ you'd add the label `traefik.http.services..loadbalancer.pa - "traefik.http.services.myservice.loadbalancer.server.scheme=http" ``` +??? info "`traefik.http.services..loadbalancer.server.url`" + + Defines the service URL. + This option cannot be used in combination with `port` or `scheme` definition. + + ```yaml + - "traefik.http.services.myservice.loadbalancer.server.url=http://foobar:8080" + ``` + ??? info "`traefik.http.services..loadbalancer.serverstransport`" Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. diff --git a/integration/consul_catalog_test.go b/integration/consul_catalog_test.go index 95f0ab713..65bb89a5e 100644 --- a/integration/consul_catalog_test.go +++ b/integration/consul_catalog_test.go @@ -170,8 +170,6 @@ func (s *ConsulCatalogSuite) TestByLabels() { Tags: []string{ "traefik.enable=true", "traefik.http.routers.router1.rule=Path(`/whoami`)", - "traefik.http.routers.router1.service=service1", - "traefik.http.services.service1.loadBalancer.server.url=http://" + containerIP, }, Port: 80, Address: containerIP, @@ -576,8 +574,6 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck() { tags := []string{ "traefik.enable=true", "traefik.http.routers.router1.rule=Path(`/whoami`)", - "traefik.http.routers.router1.service=service1", - "traefik.http.services.service1.loadBalancer.server.url=http://" + whoamiIP, } reg1 := &api.AgentServiceRegistration{ @@ -658,8 +654,6 @@ func (s *ConsulCatalogSuite) TestConsulConnect() { "traefik.enable=true", "traefik.consulcatalog.connect=true", "traefik.http.routers.router1.rule=Path(`/`)", - "traefik.http.routers.router1.service=service1", - "traefik.http.services.service1.loadBalancer.server.url=https://" + connectIP, }, Connect: &api.AgentServiceConnect{ Native: true, @@ -718,8 +712,6 @@ func (s *ConsulCatalogSuite) TestConsulConnect_ByDefault() { Tags: []string{ "traefik.enable=true", "traefik.http.routers.router1.rule=Path(`/`)", - "traefik.http.routers.router1.service=service1", - "traefik.http.services.service1.loadBalancer.server.url=https://" + connectIP, }, Connect: &api.AgentServiceConnect{ Native: true, @@ -800,8 +792,6 @@ func (s *ConsulCatalogSuite) TestConsulConnect_NotAware() { "traefik.enable=true", "traefik.consulcatalog.connect=true", "traefik.http.routers.router1.rule=Path(`/`)", - "traefik.http.routers.router1.service=service1", - "traefik.http.services.service1.loadBalancer.server.url=https://" + connectIP, }, Connect: &api.AgentServiceConnect{ Native: true, diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index 7b7e200af..78fafefe9 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -270,17 +270,13 @@ func (r *ResponseForwarding) SetDefaults() { // Server holds the server configuration. type Server struct { - URL string `json:"url,omitempty" toml:"url,omitempty" yaml:"url,omitempty" label:"-"` - Weight *int `json:"weight,omitempty" toml:"weight,omitempty" yaml:"weight,omitempty" label:"weight" export:"true"` - PreservePath bool `json:"preservePath,omitempty" toml:"preservePath,omitempty" yaml:"preservePath,omitempty" label:"-" export:"true"` + URL string `json:"url,omitempty" toml:"url,omitempty" yaml:"url,omitempty"` + Weight *int `json:"weight,omitempty" toml:"weight,omitempty" yaml:"weight,omitempty" export:"true"` + PreservePath bool `json:"preservePath,omitempty" toml:"preservePath,omitempty" yaml:"preservePath,omitempty" export:"true"` Fenced bool `json:"fenced,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-"` - Scheme string `json:"-" toml:"-" yaml:"-" file:"-"` - Port string `json:"-" toml:"-" yaml:"-" file:"-"` -} - -// SetDefaults Default values for a Server. -func (s *Server) SetDefaults() { - s.Scheme = "http" + // Scheme can only be defined with label Providers. + Scheme string `json:"-" toml:"-" yaml:"-" file:"-" kv:"-"` + Port string `json:"-" toml:"-" yaml:"-" file:"-" kv:"-"` } // +k8s:deepcopy-gen=true diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go index 963fa6c87..f798e7ff3 100644 --- a/pkg/config/label/label_test.go +++ b/pkg/config/label/label_test.go @@ -171,6 +171,8 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.http.services.Service0.loadbalancer.healthcheck.followredirects": "true", "traefik.http.services.Service0.loadbalancer.passhostheader": "true", "traefik.http.services.Service0.loadbalancer.responseforwarding.flushinterval": "1s", + "traefik.http.services.Service0.loadbalancer.server.url": "foobar", + "traefik.http.services.Service0.loadbalancer.server.preservepath": "true", "traefik.http.services.Service0.loadbalancer.server.scheme": "foobar", "traefik.http.services.Service0.loadbalancer.server.port": "8080", "traefik.http.services.Service0.loadbalancer.sticky.cookie.name": "foobar", @@ -191,6 +193,8 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.http.services.Service1.loadbalancer.healthcheck.followredirects": "true", "traefik.http.services.Service1.loadbalancer.passhostheader": "true", "traefik.http.services.Service1.loadbalancer.responseforwarding.flushinterval": "1s", + "traefik.http.services.Service1.loadbalancer.server.url": "foobar", + "traefik.http.services.Service1.loadbalancer.server.preservepath": "true", "traefik.http.services.Service1.loadbalancer.server.scheme": "foobar", "traefik.http.services.Service1.loadbalancer.server.port": "8080", "traefik.http.services.Service1.loadbalancer.sticky": "false", @@ -683,8 +687,10 @@ func TestDecodeConfiguration(t *testing.T) { }, Servers: []dynamic.Server{ { - Scheme: "foobar", - Port: "8080", + URL: "foobar", + PreservePath: true, + Scheme: "foobar", + Port: "8080", }, }, HealthCheck: &dynamic.ServerHealthCheck{ @@ -714,8 +720,10 @@ func TestDecodeConfiguration(t *testing.T) { LoadBalancer: &dynamic.ServersLoadBalancer{ Servers: []dynamic.Server{ { - Scheme: "foobar", - Port: "8080", + URL: "foobar", + PreservePath: true, + Scheme: "foobar", + Port: "8080", }, }, HealthCheck: &dynamic.ServerHealthCheck{ @@ -1218,8 +1226,10 @@ func TestEncodeConfiguration(t *testing.T) { }, Servers: []dynamic.Server{ { - Scheme: "foobar", - Port: "8080", + URL: "foobar", + PreservePath: true, + Scheme: "foobar", + Port: "8080", }, }, HealthCheck: &dynamic.ServerHealthCheck{ @@ -1247,8 +1257,10 @@ func TestEncodeConfiguration(t *testing.T) { LoadBalancer: &dynamic.ServersLoadBalancer{ Servers: []dynamic.Server{ { - Scheme: "foobar", - Port: "8080", + URL: "foobar", + PreservePath: true, + Scheme: "foobar", + Port: "8080", }, }, HealthCheck: &dynamic.ServerHealthCheck{ @@ -1454,6 +1466,8 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Timeout": "1000000000", "traefik.HTTP.Services.Service0.LoadBalancer.PassHostHeader": "true", "traefik.HTTP.Services.Service0.LoadBalancer.ResponseForwarding.FlushInterval": "1000000000", + "traefik.HTTP.Services.Service0.LoadBalancer.server.URL": "foobar", + "traefik.HTTP.Services.Service0.LoadBalancer.server.PreservePath": "true", "traefik.HTTP.Services.Service0.LoadBalancer.server.Port": "8080", "traefik.HTTP.Services.Service0.LoadBalancer.server.Scheme": "foobar", "traefik.HTTP.Services.Service0.LoadBalancer.Sticky.Cookie.Name": "foobar", @@ -1474,6 +1488,8 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Timeout": "1000000000", "traefik.HTTP.Services.Service1.LoadBalancer.PassHostHeader": "true", "traefik.HTTP.Services.Service1.LoadBalancer.ResponseForwarding.FlushInterval": "1000000000", + "traefik.HTTP.Services.Service1.LoadBalancer.server.URL": "foobar", + "traefik.HTTP.Services.Service1.LoadBalancer.server.PreservePath": "true", "traefik.HTTP.Services.Service1.LoadBalancer.server.Port": "8080", "traefik.HTTP.Services.Service1.LoadBalancer.server.Scheme": "foobar", "traefik.HTTP.Services.Service1.LoadBalancer.ServersTransport": "foobar", diff --git a/pkg/provider/consulcatalog/config.go b/pkg/provider/consulcatalog/config.go index 98c320013..513b85298 100644 --- a/pkg/provider/consulcatalog/config.go +++ b/pkg/provider/consulcatalog/config.go @@ -272,16 +272,20 @@ func (p *Provider) addServer(item itemData, loadBalancer *dynamic.ServersLoadBal } if len(loadBalancer.Servers) == 0 { - server := dynamic.Server{} - server.SetDefaults() - - loadBalancer.Servers = []dynamic.Server{server} + loadBalancer.Servers = []dynamic.Server{{}} } if item.Address == "" { return errors.New("address is missing") } + if loadBalancer.Servers[0].URL != "" { + if loadBalancer.Servers[0].Scheme != "" || loadBalancer.Servers[0].Port != "" { + return errors.New("defining scheme or port is not allowed when URL is defined") + } + return nil + } + port := loadBalancer.Servers[0].Port loadBalancer.Servers[0].Port = "" @@ -295,6 +299,9 @@ func (p *Provider) addServer(item itemData, loadBalancer *dynamic.ServersLoadBal scheme := loadBalancer.Servers[0].Scheme loadBalancer.Servers[0].Scheme = "" + if scheme == "" { + scheme = "http" + } if item.ExtraConf.ConsulCatalog.Connect { loadBalancer.ServersTransport = itemServersTransportKey(item) diff --git a/pkg/provider/consulcatalog/config_test.go b/pkg/provider/consulcatalog/config_test.go index 1d17d4699..89e40db43 100644 --- a/pkg/provider/consulcatalog/config_test.go +++ b/pkg/provider/consulcatalog/config_test.go @@ -1923,6 +1923,194 @@ func Test_buildConfiguration(t *testing.T) { }, }, }, + { + desc: "one container with label url", + items: []itemData{ + { + ID: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.http.services.Service1.LoadBalancer.server.url": "http://1.2.3.4:5678", + }, + Address: "127.0.0.1", + Port: "80", + Status: api.HealthPassing, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://1.2.3.4:5678", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "one container with label url and preserve path", + items: []itemData{ + { + ID: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.http.services.Service1.LoadBalancer.server.url": "http://1.2.3.4:5678", + "traefik.http.services.Service1.LoadBalancer.server.preservepath": "true", + }, + Address: "127.0.0.1", + Port: "80", + Status: api.HealthPassing, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://1.2.3.4:5678", + PreservePath: true, + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "one container with label url and port", + items: []itemData{ + { + ID: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.http.services.Service1.LoadBalancer.server.url": "http://1.2.3.4:5678", + "traefik.http.services.Service1.LoadBalancer.server.port": "1234", + }, + Address: "127.0.0.1", + Port: "80", + Status: api.HealthPassing, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "one container with label url and scheme", + items: []itemData{ + { + ID: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.http.services.Service1.LoadBalancer.server.url": "http://1.2.3.4:5678", + "traefik.http.services.Service1.LoadBalancer.server.scheme": "https", + }, + Address: "127.0.0.1", + Port: "80", + Status: api.HealthPassing, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, { desc: "one container with label port on two services", items: []itemData{ diff --git a/pkg/provider/docker/config.go b/pkg/provider/docker/config.go index 4d819862a..760aba1b2 100644 --- a/pkg/provider/docker/config.go +++ b/pkg/provider/docker/config.go @@ -262,10 +262,14 @@ func (p *DynConfBuilder) addServer(ctx context.Context, container dockerData, lo } if len(loadBalancer.Servers) == 0 { - server := dynamic.Server{} - server.SetDefaults() + loadBalancer.Servers = []dynamic.Server{{}} + } - loadBalancer.Servers = []dynamic.Server{server} + if loadBalancer.Servers[0].URL != "" { + if loadBalancer.Servers[0].Scheme != "" || loadBalancer.Servers[0].Port != "" { + return errors.New("defining scheme or port is not allowed when URL is defined") + } + return nil } serverPort := loadBalancer.Servers[0].Port @@ -280,8 +284,13 @@ func (p *DynConfBuilder) addServer(ctx context.Context, container dockerData, lo return errors.New("port is missing") } - loadBalancer.Servers[0].URL = fmt.Sprintf("%s://%s", loadBalancer.Servers[0].Scheme, net.JoinHostPort(ip, port)) + scheme := loadBalancer.Servers[0].Scheme loadBalancer.Servers[0].Scheme = "" + if scheme == "" { + scheme = "http" + } + + loadBalancer.Servers[0].URL = fmt.Sprintf("%s://%s", scheme, net.JoinHostPort(ip, port)) return nil } diff --git a/pkg/provider/docker/config_test.go b/pkg/provider/docker/config_test.go index 2a6f8ed32..718568c55 100644 --- a/pkg/provider/docker/config_test.go +++ b/pkg/provider/docker/config_test.go @@ -2362,6 +2362,226 @@ func TestDynConfBuilder_build(t *testing.T) { }, }, }, + { + desc: "one container with label url", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.http.services.Service1.LoadBalancer.server.url": "http://1.2.3.4:5678", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("4567/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://1.2.3.4:5678", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "one container with label url and preserve path", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.http.services.Service1.LoadBalancer.server.url": "http://1.2.3.4:5678", + "traefik.http.services.Service1.LoadBalancer.server.preservepath": "true", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("4567/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://1.2.3.4:5678", + PreservePath: true, + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "one container with label url and port", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.http.services.Service1.LoadBalancer.server.url": "http://1.2.3.4:5678", + "traefik.http.services.Service1.LoadBalancer.server.port": "1234", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("4567/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "one container with label url and scheme", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.http.services.Service1.LoadBalancer.server.url": "http://1.2.3.4:5678", + "traefik.http.services.Service1.LoadBalancer.server.scheme": "https", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("4567/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, { desc: "one container without port", containers: []dockerData{ diff --git a/pkg/provider/ecs/config.go b/pkg/provider/ecs/config.go index 028a3a1d7..2762d653c 100644 --- a/pkg/provider/ecs/config.go +++ b/pkg/provider/ecs/config.go @@ -252,10 +252,14 @@ func (p *Provider) addServer(instance ecsInstance, loadBalancer *dynamic.Servers } if len(loadBalancer.Servers) == 0 { - server := dynamic.Server{} - server.SetDefaults() + loadBalancer.Servers = []dynamic.Server{{}} + } - loadBalancer.Servers = []dynamic.Server{server} + if loadBalancer.Servers[0].URL != "" { + if loadBalancer.Servers[0].Scheme != "" || loadBalancer.Servers[0].Port != "" { + return errors.New("defining scheme or port is not allowed when URL is defined") + } + return nil } serverPort := loadBalancer.Servers[0].Port @@ -270,8 +274,13 @@ func (p *Provider) addServer(instance ecsInstance, loadBalancer *dynamic.Servers return errors.New("port is missing") } - loadBalancer.Servers[0].URL = fmt.Sprintf("%s://%s", loadBalancer.Servers[0].Scheme, net.JoinHostPort(ip, port)) + scheme := loadBalancer.Servers[0].Scheme loadBalancer.Servers[0].Scheme = "" + if scheme == "" { + scheme = "http" + } + + loadBalancer.Servers[0].URL = fmt.Sprintf("%s://%s", scheme, net.JoinHostPort(ip, port)) return nil } diff --git a/pkg/provider/ecs/config_test.go b/pkg/provider/ecs/config_test.go index ab0589d7b..8c2a735ee 100644 --- a/pkg/provider/ecs/config_test.go +++ b/pkg/provider/ecs/config_test.go @@ -2057,6 +2057,206 @@ func Test_buildConfiguration(t *testing.T) { }, }, }, + { + desc: "one container with label url", + containers: []ecsInstance{ + instance( + name("Test"), + labels(map[string]string{ + "traefik.http.services.Service1.LoadBalancer.server.url": "http://1.2.3.4:5678", + }), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 8080, "tcp"), + ), + ), + ), + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://1.2.3.4:5678", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "one container with label url and preserve path", + containers: []ecsInstance{ + instance( + name("Test"), + labels(map[string]string{ + "traefik.http.services.Service1.LoadBalancer.server.url": "http://1.2.3.4:5678", + "traefik.http.services.Service1.LoadBalancer.server.preservepath": "true", + }), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 8080, "tcp"), + ), + ), + ), + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://1.2.3.4:5678", + PreservePath: true, + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "one container with label url and port", + containers: []ecsInstance{ + instance( + name("Test"), + labels(map[string]string{ + "traefik.http.services.Service1.LoadBalancer.server.url": "http://1.2.3.4:5678", + "traefik.http.services.Service1.LoadBalancer.server.port": "1234", + }), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 8080, "tcp"), + ), + ), + ), + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "one container with label url and scheme", + containers: []ecsInstance{ + instance( + name("Test"), + labels(map[string]string{ + "traefik.http.services.Service1.LoadBalancer.server.url": "http://1.2.3.4:5678", + "traefik.http.services.Service1.LoadBalancer.server.scheme": "https", + }), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 8080, "tcp"), + ), + ), + ), + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, { desc: "one container with label port not exposed by container", containers: []ecsInstance{ diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go index 492bf5936..ad87e3ef2 100644 --- a/pkg/provider/kv/kv_test.go +++ b/pkg/provider/kv/kv_test.go @@ -654,12 +654,10 @@ func Test_buildConfiguration(t *testing.T) { }, Servers: []dynamic.Server{ { - URL: "foobar", - Scheme: "http", + URL: "foobar", }, { - URL: "foobar", - Scheme: "http", + URL: "foobar", }, }, HealthCheck: &dynamic.ServerHealthCheck{ diff --git a/pkg/provider/nomad/config.go b/pkg/provider/nomad/config.go index 83022f31d..4a2192ef0 100644 --- a/pkg/provider/nomad/config.go +++ b/pkg/provider/nomad/config.go @@ -241,16 +241,20 @@ func (p *Provider) addServer(i item, lb *dynamic.ServersLoadBalancer) error { } if len(lb.Servers) == 0 { - server := dynamic.Server{} - server.SetDefaults() - - lb.Servers = []dynamic.Server{server} + lb.Servers = []dynamic.Server{{}} } if i.Address == "" { return errors.New("address is missing") } + if lb.Servers[0].URL != "" { + if lb.Servers[0].Scheme != "" || lb.Servers[0].Port != "" { + return errors.New("defining scheme or port is not allowed when URL is defined") + } + return nil + } + port := lb.Servers[0].Port lb.Servers[0].Port = "" @@ -264,6 +268,10 @@ func (p *Provider) addServer(i item, lb *dynamic.ServersLoadBalancer) error { scheme := lb.Servers[0].Scheme lb.Servers[0].Scheme = "" + if scheme == "" { + scheme = "http" + } + lb.Servers[0].URL = fmt.Sprintf("%s://%s", scheme, net.JoinHostPort(i.Address, port)) return nil diff --git a/pkg/provider/nomad/config_test.go b/pkg/provider/nomad/config_test.go index 963b2ddae..fb6bb0458 100644 --- a/pkg/provider/nomad/config_test.go +++ b/pkg/provider/nomad/config_test.go @@ -1551,6 +1551,194 @@ func Test_buildConfig(t *testing.T) { }, }, }, + { + desc: "one service with label url", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.LoadBalancer.server.url = http://1.2.3.4:5678", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://1.2.3.4:5678", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "one service with label url and preserve path", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.LoadBalancer.server.url = http://1.2.3.4:5678", + "traefik.http.services.Service1.LoadBalancer.server.preservepath = true", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://1.2.3.4:5678", + PreservePath: true, + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "one service with label url and port", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.LoadBalancer.server.url = http://1.2.3.4:5678", + "traefik.http.services.Service1.LoadBalancer.server.port = 1234", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "one service with label url and scheme", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.LoadBalancer.server.url = http://1.2.3.4:5678", + "traefik.http.services.Service1.LoadBalancer.server.scheme = https", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, { desc: "one service with label port on two services", items: []item{