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}} SSLTemporaryRedirect = {{getSSLTemporaryRedirectHeaders $container}}
{{end}} {{end}}
{{if hasSSLHostHeaders $container}} {{if hasSSLHostHeaders $container}}
SSLHost = {{getSSLHostHeaders $container}} SSLHost = "{{getSSLHostHeaders $container}}"
{{end}} {{end}}
{{if hasSTSSecondsHeaders $container}} {{if hasSTSSecondsHeaders $container}}
STSSeconds = {{getSTSSecondsHeaders $container}} STSSeconds = {{getSTSSecondsHeaders $container}}
@ -231,7 +231,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
FrameDeny = {{getFrameDenyHeaders $container}} FrameDeny = {{getFrameDenyHeaders $container}}
{{end}} {{end}}
{{if hasCustomFrameOptionsValueHeaders $container}} {{if hasCustomFrameOptionsValueHeaders $container}}
CustomFrameOptionsValue = {{getCustomFrameOptionsValueHeaders $container}} CustomFrameOptionsValue = "{{getCustomFrameOptionsValueHeaders $container}}"
{{end}} {{end}}
{{if hasContentTypeNosniffHeaders $container}} {{if hasContentTypeNosniffHeaders $container}}
ContentTypeNosniff = {{getContentTypeNosniffHeaders $container}} ContentTypeNosniff = {{getContentTypeNosniffHeaders $container}}
@ -240,13 +240,13 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
BrowserXSSFilter = {{getBrowserXSSFilterHeaders $container}} BrowserXSSFilter = {{getBrowserXSSFilterHeaders $container}}
{{end}} {{end}}
{{if hasContentSecurityPolicyHeaders $container}} {{if hasContentSecurityPolicyHeaders $container}}
ContentSecurityPolicy = {{getContentSecurityPolicyHeaders $container}} ContentSecurityPolicy = "{{getContentSecurityPolicyHeaders $container}}"
{{end}} {{end}}
{{if hasPublicKeyHeaders $container}} {{if hasPublicKeyHeaders $container}}
PublicKey = {{getPublicKeyHeaders $container}} PublicKey = "{{getPublicKeyHeaders $container}}"
{{end}} {{end}}
{{if hasReferrerPolicyHeaders $container}} {{if hasReferrerPolicyHeaders $container}}
ReferrerPolicy = {{getReferrerPolicyHeaders $container}} ReferrerPolicy = "{{getReferrerPolicyHeaders $container}}"
{{end}} {{end}}
{{if hasIsDevelopmentHeaders $container}} {{if hasIsDevelopmentHeaders $container}}
IsDevelopment = {{getIsDevelopmentHeaders $container}} IsDevelopment = {{getIsDevelopmentHeaders $container}}
@ -884,17 +884,17 @@ type bintree struct {
} }
var _bintree = &bintree{nil, map[string]*bintree{ var _bintree = &bintree{nil, map[string]*bintree{
"templates": &bintree{nil, map[string]*bintree{ "templates": {nil, map[string]*bintree{
"consul_catalog.tmpl": &bintree{templatesConsul_catalogTmpl, map[string]*bintree{}}, "consul_catalog.tmpl": {templatesConsul_catalogTmpl, map[string]*bintree{}},
"docker.tmpl": &bintree{templatesDockerTmpl, map[string]*bintree{}}, "docker.tmpl": {templatesDockerTmpl, map[string]*bintree{}},
"ecs.tmpl": &bintree{templatesEcsTmpl, map[string]*bintree{}}, "ecs.tmpl": {templatesEcsTmpl, map[string]*bintree{}},
"eureka.tmpl": &bintree{templatesEurekaTmpl, map[string]*bintree{}}, "eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}},
"kubernetes.tmpl": &bintree{templatesKubernetesTmpl, map[string]*bintree{}}, "kubernetes.tmpl": {templatesKubernetesTmpl, map[string]*bintree{}},
"kv.tmpl": &bintree{templatesKvTmpl, map[string]*bintree{}}, "kv.tmpl": {templatesKvTmpl, map[string]*bintree{}},
"marathon.tmpl": &bintree{templatesMarathonTmpl, map[string]*bintree{}}, "marathon.tmpl": {templatesMarathonTmpl, map[string]*bintree{}},
"mesos.tmpl": &bintree{templatesMesosTmpl, map[string]*bintree{}}, "mesos.tmpl": {templatesMesosTmpl, map[string]*bintree{}},
"notFound.tmpl": &bintree{templatesNotfoundTmpl, map[string]*bintree{}}, "notFound.tmpl": {templatesNotfoundTmpl, map[string]*bintree{}},
"rancher.tmpl": &bintree{templatesRancherTmpl, map[string]*bintree{}}, "rancher.tmpl": {templatesRancherTmpl, map[string]*bintree{}},
}}, }},
}} }}

View file

@ -279,7 +279,7 @@ func NewTraefikConfiguration() *TraefikConfiguration {
LogLevel: "ERROR", LogLevel: "ERROR",
EntryPoints: map[string]*configuration.EntryPoint{}, EntryPoints: map[string]*configuration.EntryPoint{},
Constraints: types.Constraints{}, Constraints: types.Constraints{},
DefaultEntryPoints: []string{}, DefaultEntryPoints: []string{"http"},
ProvidersThrottleDuration: flaeg.Duration(2 * time.Second), ProvidersThrottleDuration: flaeg.Duration(2 * time.Second),
MaxIdleConnsPerHost: 200, MaxIdleConnsPerHost: 200,
IdleTimeout: flaeg.Duration(0), 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. 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 ## Consul Catalog backend
Træfik can be configured to use service discovery catalog of Consul as a backend configuration. 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=true` | enable backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for 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) | | `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 # Docker Backend
Træfik can be configured to use Docker as a backend configuration. 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 #### Security Headers
| Label | Description | | Label | Description |
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` | | `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.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: `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: <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.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.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.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.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.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.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.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.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.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.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.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.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.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.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. | | `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy 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. | | `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 ### On Service
Services labels can be used for overriding default behaviour 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 | | 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/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-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: `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: <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/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-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-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-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. | | `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. | | `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. | | `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 rm -vf autogen/genstatic/gen.go
//go:generate mkdir -p static //go:generate mkdir -p static
//go:generate go-bindata -pkg gentemplates -nometadata -nocompress -o autogen/gentemplates/gen.go ./templates/... //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/... //go:generate go-bindata -pkg genstatic -nocompress -o autogen/genstatic/gen.go ./static/...
package main package main

4
glide.lock generated
View file

@ -1,5 +1,5 @@
hash: 8c5908b11f5078edd9ed93e2710ebb3a29b7e02d1259fddd679f8c46540becc9 hash: 8c5908b11f5078edd9ed93e2710ebb3a29b7e02d1259fddd679f8c46540becc9
updated: 2017-11-29T12:05:49.613148632+01:00 updated: 2017-11-30T10:34:41.246378337+01:00
imports: imports:
- name: cloud.google.com/go - name: cloud.google.com/go
version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c
@ -88,7 +88,7 @@ imports:
- name: github.com/codegangsta/cli - name: github.com/codegangsta/cli
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50 version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
- name: github.com/containous/flaeg - name: github.com/containous/flaeg
version: b5d2dc5878df07c2d74413348186982e7b865871 version: 60c87a513a955ca7225e1b1c772581cea8420cb4
- name: github.com/containous/mux - name: github.com/containous/mux
version: 06ccd3e75091eb659b1d720cda0e16bc7057954c version: 06ccd3e75091eb659b1d720cda0e16bc7057954c
- name: github.com/containous/staert - name: github.com/containous/staert

View file

@ -189,6 +189,41 @@ func (s *SimpleSuite) TestApiOnSameEntryPoint(c *check.C) {
c.Assert(err, checker.IsNil) 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) { func (s *SimpleSuite) TestNoAuthOnPing(c *check.C) {
s.createComposeProject(c, "base") s.createComposeProject(c, "base")
s.composeProject.Start(c) 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)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) 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 ID string
} }
func (p Provider) createClient() (client.APIClient, error) { func (p *Provider) createClient() (client.APIClient, error) {
var httpClient *http.Client var httpClient *http.Client
if p.TLS != nil { if p.TLS != nil {
@ -121,7 +121,6 @@ func (p Provider) createClient() (client.APIClient, error) {
} }
return client.NewClient(p.Endpoint, apiVersion, httpClient, httpHeaders) return client.NewClient(p.Endpoint, apiVersion, httpClient, httpHeaders)
} }
// Provide allows the docker provider to provide configurations to traefik // 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), "getServiceRedirect": getFuncServiceStringLabel(types.SuffixFrontendRedirect, defaultFrontendRedirect),
"getWhitelistSourceRange": getFuncSliceStringLabel(types.LabelTraefikFrontendWhitelistSourceRange), "getWhitelistSourceRange": getFuncSliceStringLabel(types.LabelTraefikFrontendWhitelistSourceRange),
"hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeader), "hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeaders),
"getRequestHeaders": getFuncMapLabel(types.LabelFrontendRequestHeader), "getRequestHeaders": getFuncMapLabel(types.LabelFrontendRequestHeaders),
"hasResponseHeaders": hasLabel(types.LabelFrontendResponseHeader), "hasResponseHeaders": hasLabel(types.LabelFrontendResponseHeaders),
"getResponseHeaders": getFuncMapLabel(types.LabelFrontendResponseHeader), "getResponseHeaders": getFuncMapLabel(types.LabelFrontendResponseHeaders),
"hasAllowedHostsHeaders": hasLabel(types.LabelFrontendAllowedHosts), "hasAllowedHostsHeaders": hasLabel(types.LabelFrontendAllowedHosts),
"getAllowedHostsHeaders": getFuncSliceStringLabel(types.LabelFrontendAllowedHosts), "getAllowedHostsHeaders": getFuncSliceStringLabel(types.LabelFrontendAllowedHosts),
"hasHostsProxyHeaders": hasLabel(types.LabelFrontendHostsProxyHeaders), "hasHostsProxyHeaders": hasLabel(types.LabelFrontendHostsProxyHeaders),
@ -750,7 +749,7 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD
networkMap := make(map[string]*dockertypes.NetworkResource) networkMap := make(map[string]*dockertypes.NetworkResource)
if err != nil { 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 return []dockerData{}, err
} }
for _, network := range networkList { for _, network := range networkList {
@ -763,16 +762,18 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD
for _, service := range serviceList { for _, service := range serviceList {
dockerData := parseService(service, networkMap) dockerData := parseService(service, networkMap)
useSwarmLB, _ := strconv.ParseBool(getIsBackendLBSwarm(dockerData)) if len(dockerData.NetworkSettings.Networks) > 0 {
isGlobalSvc := service.Spec.Mode.Global != nil useSwarmLB, _ := strconv.ParseBool(getIsBackendLBSwarm(dockerData))
if useSwarmLB { if useSwarmLB {
dockerDataList = append(dockerDataList, dockerData) dockerDataList = append(dockerDataList, dockerData)
} else { } else {
dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dockerData, networkMap, isGlobalSvc) isGlobalSvc := service.Spec.Mode.Global != nil
dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dockerData, networkMap, isGlobalSvc)
for _, dockerDataTask := range dockerDataListTasks { for _, dockerDataTask := range dockerDataListTasks {
dockerDataList = append(dockerDataList, dockerDataTask) dockerDataList = append(dockerDataList, dockerDataTask)
}
} }
} }
} }
@ -788,10 +789,9 @@ func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes
} }
if service.Spec.EndpointSpec != nil { if service.Spec.EndpointSpec != nil {
switch service.Spec.EndpointSpec.Mode { if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR {
case swarmtypes.ResolutionModeDNSRR: log.Warnf("Ignored endpoint-mode not supported, service name: %s", service.Spec.Annotations.Name)
log.Debug("Ignored endpoint-mode not supported, service name: %s", dockerData.Name) } else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP {
case swarmtypes.ResolutionModeVIP:
dockerData.NetworkSettings.Networks = make(map[string]*networkData) dockerData.NetworkSettings.Networks = make(map[string]*networkData)
for _, virtualIP := range service.Endpoint.VirtualIPs { for _, virtualIP := range service.Endpoint.VirtualIPs {
networkService := networkMap[virtualIP.NetworkID] networkService := networkMap[virtualIP.NetworkID]
@ -804,7 +804,7 @@ func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes
} }
dockerData.NetworkSettings.Networks[network.Name] = network dockerData.NetworkSettings.Networks[network.Name] = network
} else { } 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 ( import (
"fmt" "fmt"
"math" "net/http"
"strconv" "strconv"
"strings" "strings"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/types" "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 { func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 {
return func(container dockerData) int64 { return func(container dockerData) int64 {
if label, err := getLabel(container, labelName); err == nil { if rawValue, err := getLabel(container, labelName); err == nil {
i, errConv := strconv.ParseInt(label, 10, 64) value, errConv := strconv.ParseInt(rawValue, 10, 64)
if errConv != nil { if errConv == nil {
log.Errorf("Unable to parse traefik.backend.maxconn.amount %s", label) return value
return math.MaxInt64
} }
return i log.Errorf("Unable to parse %q: %q", labelName, rawValue)
} }
return defaultValue 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 { func parseMapLabel(container dockerData, labelName string) map[string]string {
customHeaders := make(map[string]string) if parts, err := getLabel(container, labelName); err == nil {
if label, err := getLabel(container, labelName); err == nil { if len(parts) == 0 {
for _, headers := range strings.Split(label, ",") { log.Errorf("Could not load %q", labelName)
pair := strings.Split(headers, ":") return nil
}
values := make(map[string]string)
for _, headers := range strings.Split(parts, "||") {
pair := strings.SplitN(headers, ":", 2)
if len(pair) != 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 { } 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 nil
}
return customHeaders
} }
func getFuncStringLabel(label string, defaultValue string) func(container dockerData) string { func getFuncStringLabel(label string, defaultValue string) func(container dockerData) string {
@ -96,7 +103,7 @@ func getSliceStringLabel(container dockerData, labelName string) []string {
var value []string var value []string
if label, err := getLabel(container, labelName); err == nil { if label, err := getLabel(container, labelName); err == nil {
value = provider.SplitAndTrimString(label) value = types.SplitAndTrimString(label)
} }
if len(value) == 0 { if len(value) == 0 {
@ -173,10 +180,8 @@ func hasLabel(label string) func(container dockerData) bool {
} }
func getLabel(container dockerData, label string) (string, error) { func getLabel(container dockerData, label string) (string, error) {
for key, value := range container.Labels { if value, ok := container.Labels[label]; ok {
if key == label { return value, nil
return value, nil
}
} }
return "", fmt.Errorf("label not found: %s", label) return "", fmt.Errorf("label not found: %s", label)
} }

View file

@ -5,6 +5,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
"time"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
@ -12,6 +13,7 @@ import (
dockertypes "github.com/docker/docker/api/types" dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context" "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 package kubernetes
import ( import (
"net/http"
"strings" "strings"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"k8s.io/client-go/pkg/apis/extensions/v1beta1" "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": case annotationStringValue == "true":
annotationValue = true annotationValue = true
default: 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 return annotationValue
} }
@ -33,7 +33,7 @@ func getStringAnnotation(meta *v1beta1.Ingress, name string) string {
func getSliceAnnotation(meta *v1beta1.Ingress, name string) []string { func getSliceAnnotation(meta *v1beta1.Ingress, name string) []string {
var value []string var value []string
if annotation, ok := meta.Annotations[name]; ok && annotation != "" { if annotation, ok := meta.Annotations[name]; ok && annotation != "" {
value = provider.SplitAndTrimString(annotation) value = types.SplitAndTrimString(annotation)
} }
if len(value) == 0 { if len(value) == 0 {
log.Debugf("Could not load %v annotation, skipping...", name) log.Debugf("Could not load %v annotation, skipping...", name)
@ -42,21 +42,30 @@ func getSliceAnnotation(meta *v1beta1.Ingress, name string) []string {
return value return value
} }
func getMapAnnotation(meta *v1beta1.Ingress, name string) map[string]string { func getMapAnnotation(meta *v1beta1.Ingress, annotName string) map[string]string {
value := make(map[string]string) if values, ok := meta.Annotations[annotName]; ok {
if annotation := meta.Annotations[name]; annotation != "" {
for _, v := range strings.Split(annotation, ",") { if len(values) == 0 {
pair := strings.Split(v, ":") 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 { 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 { } 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 nil
}
return value
} }

View file

@ -73,7 +73,7 @@ type Provider struct {
lastConfiguration safe.Safe lastConfiguration safe.Safe
} }
func (p Provider) newK8sClient() (Client, error) { func (p *Provider) newK8sClient() (Client, error) {
withEndpoint := "" withEndpoint := ""
if p.Endpoint != "" { if p.Endpoint != "" {
withEndpoint = fmt.Sprintf(" with endpoint %v", 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 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{} var FuncMap = template.FuncMap{}
configuration, err := p.GetConfiguration("templates/kubernetes.tmpl", FuncMap, templateObjects) configuration, err := p.GetConfiguration("templates/kubernetes.tmpl", FuncMap, templateObjects)
if err != nil { 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)) serverMiddlewares = append(serverMiddlewares, middlewares.NewMetricsWrapper(s.metricsRegistry, newServerEntryPointName))
} }
if s.globalConfiguration.API != nil { 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) serverMiddlewares = append(serverMiddlewares, s.globalConfiguration.API.Stats)
if s.globalConfiguration.API.Statistics != nil { 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) 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) log.Errorf("Skipping frontend %s...", frontendName)
continue frontend continue frontend
} }
var failedEntrypoints int
for _, entryPointName := range frontend.EntryPoints { for _, entryPointName := range frontend.EntryPoints {
log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName) log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName)
if _, ok := serverEntryPoints[entryPointName]; !ok { if _, ok := serverEntryPoints[entryPointName]; !ok {
log.Errorf("Undefined entrypoint '%s' for frontend %s", entryPointName, frontendName) log.Errorf("Undefined entrypoint '%s' for frontend %s", entryPointName, frontendName)
log.Errorf("Skipping frontend %s...", frontendName) failedEntrypoints++
continue frontend if failedEntrypoints == len(frontend.EntryPoints) {
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
}
continue
} }
newServerRoute := &serverRoute{route: serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)} newServerRoute := &serverRoute{route: serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)}

View file

@ -88,7 +88,7 @@
SSLTemporaryRedirect = {{getSSLTemporaryRedirectHeaders $container}} SSLTemporaryRedirect = {{getSSLTemporaryRedirectHeaders $container}}
{{end}} {{end}}
{{if hasSSLHostHeaders $container}} {{if hasSSLHostHeaders $container}}
SSLHost = {{getSSLHostHeaders $container}} SSLHost = "{{getSSLHostHeaders $container}}"
{{end}} {{end}}
{{if hasSTSSecondsHeaders $container}} {{if hasSTSSecondsHeaders $container}}
STSSeconds = {{getSTSSecondsHeaders $container}} STSSeconds = {{getSTSSecondsHeaders $container}}
@ -106,7 +106,7 @@
FrameDeny = {{getFrameDenyHeaders $container}} FrameDeny = {{getFrameDenyHeaders $container}}
{{end}} {{end}}
{{if hasCustomFrameOptionsValueHeaders $container}} {{if hasCustomFrameOptionsValueHeaders $container}}
CustomFrameOptionsValue = {{getCustomFrameOptionsValueHeaders $container}} CustomFrameOptionsValue = "{{getCustomFrameOptionsValueHeaders $container}}"
{{end}} {{end}}
{{if hasContentTypeNosniffHeaders $container}} {{if hasContentTypeNosniffHeaders $container}}
ContentTypeNosniff = {{getContentTypeNosniffHeaders $container}} ContentTypeNosniff = {{getContentTypeNosniffHeaders $container}}
@ -115,13 +115,13 @@
BrowserXSSFilter = {{getBrowserXSSFilterHeaders $container}} BrowserXSSFilter = {{getBrowserXSSFilterHeaders $container}}
{{end}} {{end}}
{{if hasContentSecurityPolicyHeaders $container}} {{if hasContentSecurityPolicyHeaders $container}}
ContentSecurityPolicy = {{getContentSecurityPolicyHeaders $container}} ContentSecurityPolicy = "{{getContentSecurityPolicyHeaders $container}}"
{{end}} {{end}}
{{if hasPublicKeyHeaders $container}} {{if hasPublicKeyHeaders $container}}
PublicKey = {{getPublicKeyHeaders $container}} PublicKey = "{{getPublicKeyHeaders $container}}"
{{end}} {{end}}
{{if hasReferrerPolicyHeaders $container}} {{if hasReferrerPolicyHeaders $container}}
ReferrerPolicy = {{getReferrerPolicyHeaders $container}} ReferrerPolicy = "{{getReferrerPolicyHeaders $container}}"
{{end}} {{end}}
{{if hasIsDevelopmentHeaders $container}} {{if hasIsDevelopmentHeaders $container}}
IsDevelopment = {{getIsDevelopmentHeaders $container}} IsDevelopment = {{getIsDevelopmentHeaders $container}}

View file

@ -32,8 +32,8 @@ const (
LabelFrontendRedirect = LabelPrefix + SuffixFrontendRedirect LabelFrontendRedirect = LabelPrefix + SuffixFrontendRedirect
LabelTraefikFrontendValue = LabelPrefix + "frontend.value" LabelTraefikFrontendValue = LabelPrefix + "frontend.value"
LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange" LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange"
LabelFrontendRequestHeader = LabelPrefix + "frontend.headers.customrequestheaders" LabelFrontendRequestHeaders = LabelPrefix + "frontend.headers.customRequestHeaders"
LabelFrontendResponseHeader = LabelPrefix + "frontend.headers.customresponseheaders" LabelFrontendResponseHeaders = LabelPrefix + "frontend.headers.customResponseHeaders"
LabelFrontendAllowedHosts = LabelPrefix + "frontend.headers.allowedHosts" LabelFrontendAllowedHosts = LabelPrefix + "frontend.headers.allowedHosts"
LabelFrontendHostsProxyHeaders = LabelPrefix + "frontend.headers.hostsProxyHeaders" LabelFrontendHostsProxyHeaders = LabelPrefix + "frontend.headers.hostsProxyHeaders"
LabelFrontendSSLRedirect = LabelPrefix + "frontend.headers.SSLRedirect" LabelFrontendSSLRedirect = LabelPrefix + "frontend.headers.SSLRedirect"
@ -75,3 +75,19 @@ func ServiceLabel(key, serviceName string) string {
} }
return key 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 ( import (
"testing" "testing"

View file

@ -343,7 +343,7 @@ func fillStructRecursive(objValue reflect.Value, defaultPointerValmap map[string
contains := false contains := false
for flag := range valmap { for flag := range valmap {
// TODO replace by regexp // TODO replace by regexp
if strings.Contains(flag, name+".") { if strings.HasPrefix(flag, name+".") {
contains = true contains = true
break break
} }