diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go
index cdcd6448a..d0bac7c6c 100644
--- a/autogen/gentemplates/gen.go
+++ b/autogen/gentemplates/gen.go
@@ -213,7 +213,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
SSLTemporaryRedirect = {{getSSLTemporaryRedirectHeaders $container}}
{{end}}
{{if hasSSLHostHeaders $container}}
- SSLHost = {{getSSLHostHeaders $container}}
+ SSLHost = "{{getSSLHostHeaders $container}}"
{{end}}
{{if hasSTSSecondsHeaders $container}}
STSSeconds = {{getSTSSecondsHeaders $container}}
@@ -231,7 +231,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
FrameDeny = {{getFrameDenyHeaders $container}}
{{end}}
{{if hasCustomFrameOptionsValueHeaders $container}}
- CustomFrameOptionsValue = {{getCustomFrameOptionsValueHeaders $container}}
+ CustomFrameOptionsValue = "{{getCustomFrameOptionsValueHeaders $container}}"
{{end}}
{{if hasContentTypeNosniffHeaders $container}}
ContentTypeNosniff = {{getContentTypeNosniffHeaders $container}}
@@ -240,13 +240,13 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
BrowserXSSFilter = {{getBrowserXSSFilterHeaders $container}}
{{end}}
{{if hasContentSecurityPolicyHeaders $container}}
- ContentSecurityPolicy = {{getContentSecurityPolicyHeaders $container}}
+ ContentSecurityPolicy = "{{getContentSecurityPolicyHeaders $container}}"
{{end}}
{{if hasPublicKeyHeaders $container}}
- PublicKey = {{getPublicKeyHeaders $container}}
+ PublicKey = "{{getPublicKeyHeaders $container}}"
{{end}}
{{if hasReferrerPolicyHeaders $container}}
- ReferrerPolicy = {{getReferrerPolicyHeaders $container}}
+ ReferrerPolicy = "{{getReferrerPolicyHeaders $container}}"
{{end}}
{{if hasIsDevelopmentHeaders $container}}
IsDevelopment = {{getIsDevelopmentHeaders $container}}
@@ -884,17 +884,17 @@ type bintree struct {
}
var _bintree = &bintree{nil, map[string]*bintree{
- "templates": &bintree{nil, map[string]*bintree{
- "consul_catalog.tmpl": &bintree{templatesConsul_catalogTmpl, map[string]*bintree{}},
- "docker.tmpl": &bintree{templatesDockerTmpl, map[string]*bintree{}},
- "ecs.tmpl": &bintree{templatesEcsTmpl, map[string]*bintree{}},
- "eureka.tmpl": &bintree{templatesEurekaTmpl, map[string]*bintree{}},
- "kubernetes.tmpl": &bintree{templatesKubernetesTmpl, map[string]*bintree{}},
- "kv.tmpl": &bintree{templatesKvTmpl, map[string]*bintree{}},
- "marathon.tmpl": &bintree{templatesMarathonTmpl, map[string]*bintree{}},
- "mesos.tmpl": &bintree{templatesMesosTmpl, map[string]*bintree{}},
- "notFound.tmpl": &bintree{templatesNotfoundTmpl, map[string]*bintree{}},
- "rancher.tmpl": &bintree{templatesRancherTmpl, map[string]*bintree{}},
+ "templates": {nil, map[string]*bintree{
+ "consul_catalog.tmpl": {templatesConsul_catalogTmpl, map[string]*bintree{}},
+ "docker.tmpl": {templatesDockerTmpl, map[string]*bintree{}},
+ "ecs.tmpl": {templatesEcsTmpl, map[string]*bintree{}},
+ "eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}},
+ "kubernetes.tmpl": {templatesKubernetesTmpl, map[string]*bintree{}},
+ "kv.tmpl": {templatesKvTmpl, map[string]*bintree{}},
+ "marathon.tmpl": {templatesMarathonTmpl, map[string]*bintree{}},
+ "mesos.tmpl": {templatesMesosTmpl, map[string]*bintree{}},
+ "notFound.tmpl": {templatesNotfoundTmpl, map[string]*bintree{}},
+ "rancher.tmpl": {templatesRancherTmpl, map[string]*bintree{}},
}},
}}
diff --git a/cmd/traefik/configuration.go b/cmd/traefik/configuration.go
index 1b041d582..692a956c4 100644
--- a/cmd/traefik/configuration.go
+++ b/cmd/traefik/configuration.go
@@ -279,7 +279,7 @@ func NewTraefikConfiguration() *TraefikConfiguration {
LogLevel: "ERROR",
EntryPoints: map[string]*configuration.EntryPoint{},
Constraints: types.Constraints{},
- DefaultEntryPoints: []string{},
+ DefaultEntryPoints: []string{"http"},
ProvidersThrottleDuration: flaeg.Duration(2 * time.Second),
MaxIdleConnsPerHost: 200,
IdleTimeout: flaeg.Duration(0),
diff --git a/docs/configuration/backends/consul.md b/docs/configuration/backends/consul.md
index ba1fb1866..25d809906 100644
--- a/docs/configuration/backends/consul.md
+++ b/docs/configuration/backends/consul.md
@@ -62,7 +62,6 @@ To enable constraints see [backend-specific constraints section](/configuration/
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
-
## Consul Catalog backend
Træfik can be configured to use service discovery catalog of Consul as a backend configuration.
@@ -134,3 +133,19 @@ Additional settings can be defined using Consul Catalog tags.
| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) |
+
+### Examples
+
+If you want that Træfik uses Consul tags correctly you need to defined them like that:
+```json
+traefik.enable=true
+traefik.tags=api
+traefik.tags=external
+```
+
+If the prefix defined in Træfik configuration is `bla`, tags need to be defined like that:
+```json
+bla.enable=true
+bla.tags=api
+bla.tags=external
+```
\ No newline at end of file
diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md
index 20200d28a..2dfd9fdd7 100644
--- a/docs/configuration/backends/docker.md
+++ b/docs/configuration/backends/docker.md
@@ -1,3 +1,4 @@
+
# Docker Backend
Træfik can be configured to use Docker as a backend configuration.
@@ -174,28 +175,29 @@ Labels can be used on containers to override default behaviour.
#### Security Headers
-| Label | Description |
-|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` |
-|`traefik.frontend.headers.customrequestheaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: `HEADER:value,HEADER2:value2` |
-| `traefik.frontend.headers.customresponseheaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: `HEADER:value,HEADER2:value2` |
-|`traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` |
-| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
-| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
-| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
-| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as X-Forwarded-For:https). Format: `HEADER:value,HEADER2:value2` |
-| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
-| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the IncludeSubdomains section of the STS header. |
-| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
-| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
-| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
-| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
-| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
-| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
-| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
-| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
-| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
-| `traefik.frontend.headers.isDevelopment=false` | This will cause the AllowedHosts, SSLRedirect, and STSSeconds/STSIncludeSubdomains options to be ignored during development. When deploying to production, be sure to set this to false. |
+| Label | Description |
+|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` |
+| `traefik.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: HEADER:value||HEADER2:value2
|
+| `traefik.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: HEADER:value||HEADER2:value2
|
+| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` |
+| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
+| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
+| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
+| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`). Format: HEADER:value||HEADER2:value2
|
+| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
+| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
+| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
+| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
+| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
+| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
+| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
+| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
+| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
+| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
+| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
+| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.
When deploying to production, be sure to set this to false. |
+
### On Service
Services labels can be used for overriding default behaviour
diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md
index b2d6d27dd..069054602 100644
--- a/docs/configuration/backends/kubernetes.md
+++ b/docs/configuration/backends/kubernetes.md
@@ -139,13 +139,13 @@ The following security annotations can be applied to the ingress object to add s
| Annotation | Description |
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ingress.kubernetes.io/allowed-hosts:EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` |
-| `ingress.kubernetes.io/custom-request-headers:EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: `HEADER:value,HEADER2:value2` |
-| `ingress.kubernetes.io/custom-response-headers:EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: `HEADER:value,HEADER2:value2` |
+| `ingress.kubernetes.io/custom-request-headers:EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: HEADER:value||HEADER2:value2
|
+| `ingress.kubernetes.io/custom-response-headers:EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: HEADER:value||HEADER2:value2
|
| `ingress.kubernetes.io/proxy-headers:EXPR ` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` |
| `ingress.kubernetes.io/ssl-redirect:true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
| `ingress.kubernetes.io/ssl-temporary-redirect:true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
| `ingress.kubernetes.io/ssl-host:HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
-| `ingress.kubernetes.io/ssl-proxy-headers:EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`). Format: `HEADER:value,HEADER2:value2` |
+| `ingress.kubernetes.io/ssl-proxy-headers:EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`). Format: HEADER:value||HEADER2:value2
|
| `ingress.kubernetes.io/hsts-max-age:315360000` | Sets the max-age of the HSTS header. |
| `ngress.kubernetes.io/hsts-include-subdomains:true` | Adds the IncludeSubdomains section of the STS header. |
| `ingress.kubernetes.io/hsts-preload:true` | Adds the preload flag to the HSTS header. |
diff --git a/generate.go b/generate.go
index 8de57bf3b..5d3c0e2c4 100644
--- a/generate.go
+++ b/generate.go
@@ -2,7 +2,7 @@
//go:generate rm -vf autogen/genstatic/gen.go
//go:generate mkdir -p static
//go:generate go-bindata -pkg gentemplates -nometadata -nocompress -o autogen/gentemplates/gen.go ./templates/...
-//go:generate gofmt -w autogen/gentemplates/gen.go
+//go:generate gofmt -s -w autogen/gentemplates/gen.go
//go:generate go-bindata -pkg genstatic -nocompress -o autogen/genstatic/gen.go ./static/...
package main
diff --git a/glide.lock b/glide.lock
index ce1283933..b34e5ef07 100644
--- a/glide.lock
+++ b/glide.lock
@@ -1,5 +1,5 @@
hash: 8c5908b11f5078edd9ed93e2710ebb3a29b7e02d1259fddd679f8c46540becc9
-updated: 2017-11-29T12:05:49.613148632+01:00
+updated: 2017-11-30T10:34:41.246378337+01:00
imports:
- name: cloud.google.com/go
version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c
@@ -88,7 +88,7 @@ imports:
- name: github.com/codegangsta/cli
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
- name: github.com/containous/flaeg
- version: b5d2dc5878df07c2d74413348186982e7b865871
+ version: 60c87a513a955ca7225e1b1c772581cea8420cb4
- name: github.com/containous/mux
version: 06ccd3e75091eb659b1d720cda0e16bc7057954c
- name: github.com/containous/staert
diff --git a/integration/basic_test.go b/integration/basic_test.go
index 2d78343af..cc3eacd57 100644
--- a/integration/basic_test.go
+++ b/integration/basic_test.go
@@ -189,6 +189,41 @@ func (s *SimpleSuite) TestApiOnSameEntryPoint(c *check.C) {
c.Assert(err, checker.IsNil)
}
+func (s *SimpleSuite) TestStatsWithMultipleEntryPoint(c *check.C) {
+ s.createComposeProject(c, "stats")
+ s.composeProject.Start(c)
+
+ whoami1 := "http://" + s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress + ":80"
+ whoami2 := "http://" + s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress + ":80"
+
+ file := s.adaptFile(c, "fixtures/simple_stats.toml", struct {
+ Server1 string
+ Server2 string
+ }{whoami1, whoami2})
+ cmd, output := s.traefikCmd(withConfigFile(file))
+ defer output(c)
+
+ err := cmd.Start()
+ c.Assert(err, checker.IsNil)
+ defer cmd.Process.Kill()
+
+ err = try.GetRequest("http://127.0.0.1:8080/api", 1*time.Second, try.StatusCodeIs(http.StatusOK))
+ c.Assert(err, checker.IsNil)
+
+ err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("PathPrefix"))
+ c.Assert(err, checker.IsNil)
+
+ err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
+ c.Assert(err, checker.IsNil)
+
+ err = try.GetRequest("http://127.0.0.1:8080/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
+ c.Assert(err, checker.IsNil)
+
+ err = try.GetRequest("http://127.0.0.1:8080/health", 1*time.Second, try.BodyContains(`"total_status_code_count":{"200":2}`))
+ c.Assert(err, checker.IsNil)
+
+}
+
func (s *SimpleSuite) TestNoAuthOnPing(c *check.C) {
s.createComposeProject(c, "base")
s.composeProject.Start(c)
@@ -263,3 +298,41 @@ func (s *SimpleSuite) TestWebCompatibilityWithPath(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
}
+
+func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) {
+
+ s.createComposeProject(c, "base")
+ s.composeProject.Start(c)
+
+ cmd, output := s.traefikCmd("--entryPoints=Name:http Address::8000", "--debug", "--docker", "--api")
+ defer output(c)
+
+ err := cmd.Start()
+ c.Assert(err, checker.IsNil)
+ defer cmd.Process.Kill()
+
+ err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("PathPrefix"))
+ c.Assert(err, checker.IsNil)
+
+ err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
+ c.Assert(err, checker.IsNil)
+}
+
+func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) {
+
+ s.createComposeProject(c, "base")
+ s.composeProject.Start(c)
+
+ cmd, output := s.traefikCmd("--defaultEntryPoints=https,http", "--entryPoints=Name:http Address::8000", "--debug", "--docker", "--api")
+ defer output(c)
+
+ err := cmd.Start()
+ c.Assert(err, checker.IsNil)
+ defer cmd.Process.Kill()
+
+ err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("PathPrefix"))
+ c.Assert(err, checker.IsNil)
+
+ err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
+ c.Assert(err, checker.IsNil)
+}
diff --git a/integration/fixtures/simple_stats.toml b/integration/fixtures/simple_stats.toml
new file mode 100644
index 000000000..b5e38511b
--- /dev/null
+++ b/integration/fixtures/simple_stats.toml
@@ -0,0 +1,30 @@
+debug=true
+
+[entryPoints]
+ [entryPoints.http]
+ address = ":8000"
+
+[api]
+[file]
+ [backends]
+ [backends.backend1]
+ [backends.backend1.servers.server1]
+ url = "{{ .Server1 }}"
+ [backends.backend2]
+ [backends.backend2.servers.server1]
+ url = "{{ .Server2 }}"
+
+ [frontends]
+ [frontends.frontend1]
+ entrypoints=["http"]
+
+ backend = "backend1"
+ [frontends.frontend1.routes.test_1]
+ rule = "PathPrefix:/whoami"
+
+ [frontends.frontend2]
+ backend = "backend2"
+ entrypoints=["traefik"]
+
+ [frontends.frontend2.routes.test_1]
+ rule = "PathPrefix:/whoami"
diff --git a/integration/resources/compose/stats.yml b/integration/resources/compose/stats.yml
new file mode 100644
index 000000000..0256f9622
--- /dev/null
+++ b/integration/resources/compose/stats.yml
@@ -0,0 +1,4 @@
+whoami1:
+ image: emilevauge/whoami
+whoami2:
+ image: emilevauge/whoami
\ No newline at end of file
diff --git a/provider/docker/docker.go b/provider/docker/docker.go
index ad90b39f5..721953425 100644
--- a/provider/docker/docker.go
+++ b/provider/docker/docker.go
@@ -86,7 +86,7 @@ type networkData struct {
ID string
}
-func (p Provider) createClient() (client.APIClient, error) {
+func (p *Provider) createClient() (client.APIClient, error) {
var httpClient *http.Client
if p.TLS != nil {
@@ -121,7 +121,6 @@ func (p Provider) createClient() (client.APIClient, error) {
}
return client.NewClient(p.Endpoint, apiVersion, httpClient, httpHeaders)
-
}
// Provide allows the docker provider to provide configurations to traefik
@@ -293,10 +292,10 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con
"getServiceRedirect": getFuncServiceStringLabel(types.SuffixFrontendRedirect, defaultFrontendRedirect),
"getWhitelistSourceRange": getFuncSliceStringLabel(types.LabelTraefikFrontendWhitelistSourceRange),
- "hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeader),
- "getRequestHeaders": getFuncMapLabel(types.LabelFrontendRequestHeader),
- "hasResponseHeaders": hasLabel(types.LabelFrontendResponseHeader),
- "getResponseHeaders": getFuncMapLabel(types.LabelFrontendResponseHeader),
+ "hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeaders),
+ "getRequestHeaders": getFuncMapLabel(types.LabelFrontendRequestHeaders),
+ "hasResponseHeaders": hasLabel(types.LabelFrontendResponseHeaders),
+ "getResponseHeaders": getFuncMapLabel(types.LabelFrontendResponseHeaders),
"hasAllowedHostsHeaders": hasLabel(types.LabelFrontendAllowedHosts),
"getAllowedHostsHeaders": getFuncSliceStringLabel(types.LabelFrontendAllowedHosts),
"hasHostsProxyHeaders": hasLabel(types.LabelFrontendHostsProxyHeaders),
@@ -750,7 +749,7 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD
networkMap := make(map[string]*dockertypes.NetworkResource)
if err != nil {
- log.Debug("Failed to network inspect on client for docker, error: %s", err)
+ log.Debugf("Failed to network inspect on client for docker, error: %s", err)
return []dockerData{}, err
}
for _, network := range networkList {
@@ -763,16 +762,18 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD
for _, service := range serviceList {
dockerData := parseService(service, networkMap)
- useSwarmLB, _ := strconv.ParseBool(getIsBackendLBSwarm(dockerData))
- isGlobalSvc := service.Spec.Mode.Global != nil
+ if len(dockerData.NetworkSettings.Networks) > 0 {
+ useSwarmLB, _ := strconv.ParseBool(getIsBackendLBSwarm(dockerData))
- if useSwarmLB {
- dockerDataList = append(dockerDataList, dockerData)
- } else {
- dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dockerData, networkMap, isGlobalSvc)
+ if useSwarmLB {
+ dockerDataList = append(dockerDataList, dockerData)
+ } else {
+ isGlobalSvc := service.Spec.Mode.Global != nil
+ dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dockerData, networkMap, isGlobalSvc)
- for _, dockerDataTask := range dockerDataListTasks {
- dockerDataList = append(dockerDataList, dockerDataTask)
+ for _, dockerDataTask := range dockerDataListTasks {
+ dockerDataList = append(dockerDataList, dockerDataTask)
+ }
}
}
}
@@ -788,10 +789,9 @@ func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes
}
if service.Spec.EndpointSpec != nil {
- switch service.Spec.EndpointSpec.Mode {
- case swarmtypes.ResolutionModeDNSRR:
- log.Debug("Ignored endpoint-mode not supported, service name: %s", dockerData.Name)
- case swarmtypes.ResolutionModeVIP:
+ if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR {
+ log.Warnf("Ignored endpoint-mode not supported, service name: %s", service.Spec.Annotations.Name)
+ } else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP {
dockerData.NetworkSettings.Networks = make(map[string]*networkData)
for _, virtualIP := range service.Endpoint.VirtualIPs {
networkService := networkMap[virtualIP.NetworkID]
@@ -804,7 +804,7 @@ func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes
}
dockerData.NetworkSettings.Networks[network.Name] = network
} else {
- log.Debug("Network not found, id: %s", virtualIP.NetworkID)
+ log.Debugf("Network not found, id: %s", virtualIP.NetworkID)
}
}
}
diff --git a/provider/docker/labels.go b/provider/docker/labels.go
index 070d3b55d..ad12a271b 100644
--- a/provider/docker/labels.go
+++ b/provider/docker/labels.go
@@ -2,12 +2,11 @@ package docker
import (
"fmt"
- "math"
+ "net/http"
"strconv"
"strings"
"github.com/containous/traefik/log"
- "github.com/containous/traefik/provider"
"github.com/containous/traefik/types"
)
@@ -26,13 +25,12 @@ type labelServiceProperties map[string]map[string]string
func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 {
return func(container dockerData) int64 {
- if label, err := getLabel(container, labelName); err == nil {
- i, errConv := strconv.ParseInt(label, 10, 64)
- if errConv != nil {
- log.Errorf("Unable to parse traefik.backend.maxconn.amount %s", label)
- return math.MaxInt64
+ if rawValue, err := getLabel(container, labelName); err == nil {
+ value, errConv := strconv.ParseInt(rawValue, 10, 64)
+ if errConv == nil {
+ return value
}
- return i
+ log.Errorf("Unable to parse %q: %q", labelName, rawValue)
}
return defaultValue
}
@@ -45,21 +43,30 @@ func getFuncMapLabel(labelName string) func(container dockerData) map[string]str
}
func parseMapLabel(container dockerData, labelName string) map[string]string {
- customHeaders := make(map[string]string)
- if label, err := getLabel(container, labelName); err == nil {
- for _, headers := range strings.Split(label, ",") {
- pair := strings.Split(headers, ":")
+ if parts, err := getLabel(container, labelName); err == nil {
+ if len(parts) == 0 {
+ log.Errorf("Could not load %q", labelName)
+ return nil
+ }
+
+ values := make(map[string]string)
+ for _, headers := range strings.Split(parts, "||") {
+ pair := strings.SplitN(headers, ":", 2)
if len(pair) != 2 {
- log.Warnf("Could not load header %q: %v, skipping...", labelName, pair)
+ log.Warnf("Could not load %q: %v, skipping...", labelName, pair)
} else {
- customHeaders[pair[0]] = pair[1]
+ values[http.CanonicalHeaderKey(strings.TrimSpace(pair[0]))] = strings.TrimSpace(pair[1])
}
}
+
+ if len(values) == 0 {
+ log.Errorf("Could not load %q", labelName)
+ return nil
+ }
+ return values
}
- if len(customHeaders) == 0 {
- log.Errorf("Could not load %q", labelName)
- }
- return customHeaders
+
+ return nil
}
func getFuncStringLabel(label string, defaultValue string) func(container dockerData) string {
@@ -96,7 +103,7 @@ func getSliceStringLabel(container dockerData, labelName string) []string {
var value []string
if label, err := getLabel(container, labelName); err == nil {
- value = provider.SplitAndTrimString(label)
+ value = types.SplitAndTrimString(label)
}
if len(value) == 0 {
@@ -173,10 +180,8 @@ func hasLabel(label string) func(container dockerData) bool {
}
func getLabel(container dockerData, label string) (string, error) {
- for key, value := range container.Labels {
- if key == label {
- return value, nil
- }
+ if value, ok := container.Labels[label]; ok {
+ return value, nil
}
return "", fmt.Errorf("label not found: %s", label)
}
diff --git a/provider/docker/swarm_test.go b/provider/docker/swarm_test.go
index 374e5a6ee..32f95cf74 100644
--- a/provider/docker/swarm_test.go
+++ b/provider/docker/swarm_test.go
@@ -5,6 +5,7 @@ import (
"strconv"
"strings"
"testing"
+ "time"
"github.com/containous/traefik/types"
"github.com/davecgh/go-spew/spew"
@@ -12,6 +13,7 @@ import (
dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
dockerclient "github.com/docker/docker/client"
+ "github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
@@ -765,3 +767,119 @@ func TestListTasks(t *testing.T) {
})
}
}
+
+type fakeServicesClient struct {
+ dockerclient.APIClient
+ dockerVersion string
+ networks []dockertypes.NetworkResource
+ services []swarm.Service
+ err error
+}
+
+func (c *fakeServicesClient) ServiceList(ctx context.Context, options dockertypes.ServiceListOptions) ([]swarm.Service, error) {
+ return c.services, c.err
+}
+
+func (c *fakeServicesClient) ServerVersion(ctx context.Context) (dockertypes.Version, error) {
+ return dockertypes.Version{APIVersion: c.dockerVersion}, c.err
+}
+
+func (c *fakeServicesClient) NetworkList(ctx context.Context, options dockertypes.NetworkListOptions) ([]dockertypes.NetworkResource, error) {
+ return c.networks, c.err
+}
+
+func TestListServices(t *testing.T) {
+ testCases := []struct {
+ desc string
+ services []swarm.Service
+ dockerVersion string
+ networks []dockertypes.NetworkResource
+ expectedServices []string
+ }{
+ {
+ desc: "Should return no service due to no networks defined",
+ services: []swarm.Service{
+ swarmService(
+ serviceName("service1"),
+ serviceLabels(map[string]string{
+ labelDockerNetwork: "barnet",
+ labelBackendLoadBalancerSwarm: "true",
+ }),
+ withEndpointSpec(modeVIP),
+ withEndpoint(
+ virtualIP("1", "10.11.12.13/24"),
+ virtualIP("2", "10.11.12.99/24"),
+ )),
+ swarmService(
+ serviceName("service2"),
+ serviceLabels(map[string]string{
+ labelDockerNetwork: "barnet",
+ }),
+ withEndpointSpec(modeDNSSR)),
+ },
+ dockerVersion: "1.30",
+ networks: []dockertypes.NetworkResource{},
+ expectedServices: []string{},
+ },
+ {
+ desc: "Should return only service1",
+ services: []swarm.Service{
+ swarmService(
+ serviceName("service1"),
+ serviceLabels(map[string]string{
+ labelDockerNetwork: "barnet",
+ labelBackendLoadBalancerSwarm: "true",
+ }),
+ withEndpointSpec(modeVIP),
+ withEndpoint(
+ virtualIP("yk6l57rfwizjzxxzftn4amaot", "10.11.12.13/24"),
+ virtualIP("2", "10.11.12.99/24"),
+ )),
+ swarmService(
+ serviceName("service2"),
+ serviceLabels(map[string]string{
+ labelDockerNetwork: "barnet",
+ }),
+ withEndpointSpec(modeDNSSR)),
+ },
+ dockerVersion: "1.30",
+ networks: []dockertypes.NetworkResource{
+ {
+ Name: "network_name",
+ ID: "yk6l57rfwizjzxxzftn4amaot",
+ Created: time.Now(),
+ Scope: "swarm",
+ Driver: "overlay",
+ EnableIPv6: false,
+ Internal: true,
+ Ingress: false,
+ ConfigOnly: false,
+ Options: map[string]string{
+ "com.docker.network.driver.overlay.vxlanid_list": "4098",
+ "com.docker.network.enable_ipv6": "false",
+ },
+ Labels: map[string]string{
+ "com.docker.stack.namespace": "test",
+ },
+ },
+ },
+ expectedServices: []string{
+ "service1",
+ },
+ },
+ }
+
+ for caseID, test := range testCases {
+ test := test
+ t.Run(strconv.Itoa(caseID), func(t *testing.T) {
+ t.Parallel()
+ dockerClient := &fakeServicesClient{services: test.services, dockerVersion: test.dockerVersion, networks: test.networks}
+ serviceDockerData, _ := listServices(context.Background(), dockerClient)
+
+ assert.Equal(t, len(test.expectedServices), len(serviceDockerData))
+ for i, serviceName := range test.expectedServices {
+ assert.Equal(t, serviceName, serviceDockerData[i].Name)
+ }
+ })
+ }
+}
diff --git a/provider/kubernetes/annotations_parser.go b/provider/kubernetes/annotations_parser.go
index 361a87f04..813146e6f 100644
--- a/provider/kubernetes/annotations_parser.go
+++ b/provider/kubernetes/annotations_parser.go
@@ -1,10 +1,10 @@
package kubernetes
import (
+ "net/http"
"strings"
"github.com/containous/traefik/log"
- "github.com/containous/traefik/provider"
"github.com/containous/traefik/types"
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
)
@@ -20,7 +20,7 @@ func getBoolAnnotation(meta *v1beta1.Ingress, name string, defaultValue bool) bo
case annotationStringValue == "true":
annotationValue = true
default:
- log.Warnf("Unknown value '%s' for %s, falling back to %s", name, types.LabelFrontendPassTLSCert, defaultValue)
+ log.Warnf("Unknown value %q for %q, falling back to %v", annotationStringValue, name, defaultValue)
}
return annotationValue
}
@@ -33,7 +33,7 @@ func getStringAnnotation(meta *v1beta1.Ingress, name string) string {
func getSliceAnnotation(meta *v1beta1.Ingress, name string) []string {
var value []string
if annotation, ok := meta.Annotations[name]; ok && annotation != "" {
- value = provider.SplitAndTrimString(annotation)
+ value = types.SplitAndTrimString(annotation)
}
if len(value) == 0 {
log.Debugf("Could not load %v annotation, skipping...", name)
@@ -42,21 +42,30 @@ func getSliceAnnotation(meta *v1beta1.Ingress, name string) []string {
return value
}
-func getMapAnnotation(meta *v1beta1.Ingress, name string) map[string]string {
- value := make(map[string]string)
- if annotation := meta.Annotations[name]; annotation != "" {
- for _, v := range strings.Split(annotation, ",") {
- pair := strings.Split(v, ":")
+func getMapAnnotation(meta *v1beta1.Ingress, annotName string) map[string]string {
+ if values, ok := meta.Annotations[annotName]; ok {
+
+ if len(values) == 0 {
+ log.Errorf("Missing value for annotation %q", annotName)
+ return nil
+ }
+
+ mapValue := make(map[string]string)
+ for _, parts := range strings.Split(values, "||") {
+ pair := strings.SplitN(parts, ":", 2)
if len(pair) != 2 {
- log.Debugf("Could not load annotation (%v) with value: %v, skipping...", name, pair)
+ log.Warnf("Could not load %q: %v, skipping...", annotName, pair)
} else {
- value[pair[0]] = pair[1]
+ mapValue[http.CanonicalHeaderKey(strings.TrimSpace(pair[0]))] = strings.TrimSpace(pair[1])
}
}
+
+ if len(mapValue) == 0 {
+ log.Errorf("Could not load %q, skipping...", annotName)
+ return nil
+ }
+ return mapValue
}
- if len(value) == 0 {
- log.Debugf("Could not load %v annotation, skipping...", name)
- return nil
- }
- return value
+
+ return nil
}
diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go
index 5284a2220..804510d73 100644
--- a/provider/kubernetes/kubernetes.go
+++ b/provider/kubernetes/kubernetes.go
@@ -73,7 +73,7 @@ type Provider struct {
lastConfiguration safe.Safe
}
-func (p Provider) newK8sClient() (Client, error) {
+func (p *Provider) newK8sClient() (Client, error) {
withEndpoint := ""
if p.Endpoint != "" {
withEndpoint = fmt.Sprintf(" with endpoint %v", p.Endpoint)
@@ -356,7 +356,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
return &templateObjects, nil
}
-func (p Provider) loadConfig(templateObjects types.Configuration) *types.Configuration {
+func (p *Provider) loadConfig(templateObjects types.Configuration) *types.Configuration {
var FuncMap = template.FuncMap{}
configuration, err := p.GetConfiguration("templates/kubernetes.tmpl", FuncMap, templateObjects)
if err != nil {
diff --git a/provider/string_util.go b/provider/string_util.go
deleted file mode 100644
index f3877e293..000000000
--- a/provider/string_util.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package provider
-
-import "strings"
-
-// SplitAndTrimString splits separatedString at the comma character and trims each
-// piece, filtering out empty pieces. Returns the list of pieces or nil if the input
-// did not contain a non-empty piece.
-func SplitAndTrimString(base string) []string {
- var trimmedStrings []string
-
- for _, s := range strings.Split(base, ",") {
- s = strings.TrimSpace(s)
- if len(s) > 0 {
- trimmedStrings = append(trimmedStrings, s)
- }
- }
-
- return trimmedStrings
-}
diff --git a/server/server.go b/server/server.go
index 70e2f72cc..e2255131a 100644
--- a/server/server.go
+++ b/server/server.go
@@ -289,10 +289,14 @@ func (s *Server) setupServerEntryPoint(newServerEntryPointName string, newServer
serverMiddlewares = append(serverMiddlewares, middlewares.NewMetricsWrapper(s.metricsRegistry, newServerEntryPointName))
}
if s.globalConfiguration.API != nil {
- s.globalConfiguration.API.Stats = thoas_stats.New()
+ if s.globalConfiguration.API.Stats == nil {
+ s.globalConfiguration.API.Stats = thoas_stats.New()
+ }
serverMiddlewares = append(serverMiddlewares, s.globalConfiguration.API.Stats)
if s.globalConfiguration.API.Statistics != nil {
- s.globalConfiguration.API.StatsRecorder = middlewares.NewStatsRecorder(s.globalConfiguration.API.Statistics.RecentErrors)
+ if s.globalConfiguration.API.StatsRecorder == nil {
+ s.globalConfiguration.API.StatsRecorder = middlewares.NewStatsRecorder(s.globalConfiguration.API.Statistics.RecentErrors)
+ }
serverMiddlewares = append(serverMiddlewares, s.globalConfiguration.API.StatsRecorder)
}
@@ -906,13 +910,17 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
}
-
+ var failedEntrypoints int
for _, entryPointName := range frontend.EntryPoints {
log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName)
if _, ok := serverEntryPoints[entryPointName]; !ok {
log.Errorf("Undefined entrypoint '%s' for frontend %s", entryPointName, frontendName)
- log.Errorf("Skipping frontend %s...", frontendName)
- continue frontend
+ failedEntrypoints++
+ if failedEntrypoints == len(frontend.EntryPoints) {
+ log.Errorf("Skipping frontend %s...", frontendName)
+ continue frontend
+ }
+ continue
}
newServerRoute := &serverRoute{route: serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)}
diff --git a/templates/docker.tmpl b/templates/docker.tmpl
index b135ae47b..d4a7c9a84 100644
--- a/templates/docker.tmpl
+++ b/templates/docker.tmpl
@@ -88,7 +88,7 @@
SSLTemporaryRedirect = {{getSSLTemporaryRedirectHeaders $container}}
{{end}}
{{if hasSSLHostHeaders $container}}
- SSLHost = {{getSSLHostHeaders $container}}
+ SSLHost = "{{getSSLHostHeaders $container}}"
{{end}}
{{if hasSTSSecondsHeaders $container}}
STSSeconds = {{getSTSSecondsHeaders $container}}
@@ -106,7 +106,7 @@
FrameDeny = {{getFrameDenyHeaders $container}}
{{end}}
{{if hasCustomFrameOptionsValueHeaders $container}}
- CustomFrameOptionsValue = {{getCustomFrameOptionsValueHeaders $container}}
+ CustomFrameOptionsValue = "{{getCustomFrameOptionsValueHeaders $container}}"
{{end}}
{{if hasContentTypeNosniffHeaders $container}}
ContentTypeNosniff = {{getContentTypeNosniffHeaders $container}}
@@ -115,13 +115,13 @@
BrowserXSSFilter = {{getBrowserXSSFilterHeaders $container}}
{{end}}
{{if hasContentSecurityPolicyHeaders $container}}
- ContentSecurityPolicy = {{getContentSecurityPolicyHeaders $container}}
+ ContentSecurityPolicy = "{{getContentSecurityPolicyHeaders $container}}"
{{end}}
{{if hasPublicKeyHeaders $container}}
- PublicKey = {{getPublicKeyHeaders $container}}
+ PublicKey = "{{getPublicKeyHeaders $container}}"
{{end}}
{{if hasReferrerPolicyHeaders $container}}
- ReferrerPolicy = {{getReferrerPolicyHeaders $container}}
+ ReferrerPolicy = "{{getReferrerPolicyHeaders $container}}"
{{end}}
{{if hasIsDevelopmentHeaders $container}}
IsDevelopment = {{getIsDevelopmentHeaders $container}}
diff --git a/types/common_label.go b/types/common_label.go
index 52240ce17..af543b573 100644
--- a/types/common_label.go
+++ b/types/common_label.go
@@ -32,8 +32,8 @@ const (
LabelFrontendRedirect = LabelPrefix + SuffixFrontendRedirect
LabelTraefikFrontendValue = LabelPrefix + "frontend.value"
LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange"
- LabelFrontendRequestHeader = LabelPrefix + "frontend.headers.customrequestheaders"
- LabelFrontendResponseHeader = LabelPrefix + "frontend.headers.customresponseheaders"
+ LabelFrontendRequestHeaders = LabelPrefix + "frontend.headers.customRequestHeaders"
+ LabelFrontendResponseHeaders = LabelPrefix + "frontend.headers.customResponseHeaders"
LabelFrontendAllowedHosts = LabelPrefix + "frontend.headers.allowedHosts"
LabelFrontendHostsProxyHeaders = LabelPrefix + "frontend.headers.hostsProxyHeaders"
LabelFrontendSSLRedirect = LabelPrefix + "frontend.headers.SSLRedirect"
@@ -75,3 +75,19 @@ func ServiceLabel(key, serviceName string) string {
}
return key
}
+
+// SplitAndTrimString splits separatedString at the comma character and trims each
+// piece, filtering out empty pieces. Returns the list of pieces or nil if the input
+// did not contain a non-empty piece.
+func SplitAndTrimString(base string) []string {
+ var trimmedStrings []string
+
+ for _, s := range strings.Split(base, ",") {
+ s = strings.TrimSpace(s)
+ if len(s) > 0 {
+ trimmedStrings = append(trimmedStrings, s)
+ }
+ }
+
+ return trimmedStrings
+}
diff --git a/provider/string_util_test.go b/types/common_label_test.go
similarity index 98%
rename from provider/string_util_test.go
rename to types/common_label_test.go
index 5b8c6854d..b97be2085 100644
--- a/provider/string_util_test.go
+++ b/types/common_label_test.go
@@ -1,4 +1,4 @@
-package provider
+package types
import (
"testing"
diff --git a/vendor/github.com/containous/flaeg/flaeg.go b/vendor/github.com/containous/flaeg/flaeg.go
index 10aac07fe..fb4512537 100644
--- a/vendor/github.com/containous/flaeg/flaeg.go
+++ b/vendor/github.com/containous/flaeg/flaeg.go
@@ -343,7 +343,7 @@ func fillStructRecursive(objValue reflect.Value, defaultPointerValmap map[string
contains := false
for flag := range valmap {
// TODO replace by regexp
- if strings.Contains(flag, name+".") {
+ if strings.HasPrefix(flag, name+".") {
contains = true
break
}