mirror of
https://github.com/containous/traefik.git
synced 2024-12-22 13:34:03 +03:00
Merge branch 'v1.5' into master
This commit is contained in:
commit
dc74f76a03
@ -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{}},
|
||||
}},
|
||||
}}
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
```
|
@ -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||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||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||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
|
||||
|
@ -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||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||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||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. |
|
||||
|
@ -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
4
glide.lock
generated
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
30
integration/fixtures/simple_stats.toml
Normal file
30
integration/fixtures/simple_stats.toml
Normal 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"
|
4
integration/resources/compose/stats.yml
Normal file
4
integration/resources/compose/stats.yml
Normal file
@ -0,0 +1,4 @@
|
||||
whoami1:
|
||||
image: emilevauge/whoami
|
||||
whoami2:
|
||||
image: emilevauge/whoami
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
@ -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)}
|
||||
|
@ -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}}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package provider
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
2
vendor/github.com/containous/flaeg/flaeg.go
generated
vendored
2
vendor/github.com/containous/flaeg/flaeg.go
generated
vendored
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user