Merge branch 'v1.5' into master
This commit is contained in:
commit
dc74f76a03
21 changed files with 400 additions and 139 deletions
|
@ -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{}},
|
||||||
}},
|
}},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
```
|
|
@ -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||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||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||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
|
||||||
|
|
|
@ -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||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||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||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. |
|
||||||
|
|
|
@ -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
4
glide.lock
generated
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
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
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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))
|
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)}
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package provider
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"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
|
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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue