Merge branch 'v1.5' into master

This commit is contained in:
Fernandez Ludovic 2017-12-04 13:35:02 +01:00
commit dc74f76a03
21 changed files with 400 additions and 139 deletions

View file

@ -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{}},
}},
}}

View file

@ -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),

View file

@ -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
```

View file

@ -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: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `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: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `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.<br>When deploying to production, be sure to set this to false. |
### On Service
Services labels can be used for overriding default behaviour

View file

@ -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: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `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: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `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: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `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. |

View file

@ -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

4
glide.lock generated
View file

@ -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

View file

@ -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)
}

View file

@ -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"

View file

@ -0,0 +1,4 @@
whoami1:
image: emilevauge/whoami
whoami2:
image: emilevauge/whoami

View file

@ -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)
}
}
}

View file

@ -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)
}

View file

@ -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)
}
})
}
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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)}

View file

@ -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}}

View file

@ -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
}

View file

@ -1,4 +1,4 @@
package provider
package types
import (
"testing"

View file

@ -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
}