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 }