Compare commits

...

41 Commits

Author SHA1 Message Date
SALLEYRON Julien
22bdbd2498 Prepare release 1.5.0-rc4 2018-01-04 15:22:03 +01:00
Ludovic Fernandez
287fb78654 Split Consul and Consul Catalog documentation 2018-01-04 14:48:03 +01:00
SALLEYRON Julien
5b24403c8e Don't panic if ResponseWriter does not implement CloseNotify 2018-01-04 11:18:03 +01:00
Julien Maitrehenry
e83599dd08 Add a note on how to add label to a docker compose file 2018-01-04 10:34:03 +01:00
SALLEYRON Julien
f30ad20c9b Use gorilla readMessage and writeMessage instead of just an io.Copy 2018-01-03 15:32:03 +01:00
Timo Reimann
01e17b6c3e k8s guide: Leave note about assumed DaemonSet usage. 2018-01-03 09:12:03 +01:00
SALLEYRON Julien
3e13ebec93 We need to flush the end of the body when retry is streamed 2018-01-02 16:02:03 +01:00
Fernandez Ludovic
23c1a9ca8e Merge branch 'v1.4' into v1.5 2018-01-02 13:10:11 +01:00
Michael
741c739ef1 Prepare release v1.4.6 2018-01-02 12:54:03 +01:00
SALLEYRON Julien
52f16e11a8 Use gorilla readMessage and writeMessage instead of just an io.Copy 2018-01-02 12:30:05 +01:00
Michael
0ee6973e2f Upgrade docs dependencies and adapt configuration 2018-01-02 11:28:02 +01:00
Timo Reimann
4819974a1c Improve Marathon service label documentation. 2018-01-02 11:08:02 +01:00
Michael
e8e8b41eed Normalize serviceName added to the service backend names 2018-01-02 10:52:03 +01:00
Krzysztof Pędrys
7d23d3c0a4 Typo in docker.endpoint TCP port. 2018-01-02 10:38:03 +01:00
Ludovic Fernandez
718fc7a79d Fix bug report command 2018-01-02 10:14:03 +01:00
Ludovic Fernandez
bfd142b13b Fix custom headers template 2018-01-02 10:10:04 +01:00
Ludovic Fernandez
75533b2beb Use prefix for sticky and stickiness tags. 2018-01-02 09:44:02 +01:00
NicoMen
9a7821b8fa Send empty configuration from file provider 2017-12-21 21:24:03 +01:00
lishaoxiong
e8333883df Add tests for TLS dynamic configuration in ETCD3 2017-12-21 18:02:04 +01:00
NicoMen
1e44e339ad Allow deleting dynamically all TLS certificates from an entryPoint 2017-12-21 14:16:03 +01:00
Ludovic Fernandez
89a79d0f1b Prepare release 1.5.0-rc3 2017-12-20 15:10:06 +01:00
NicoMen
9e41485ff1 Modify ACME configuration migration into KV store 2017-12-20 14:40:07 +01:00
Nimi Wariboko Jr
3c7c6c4d9f Mesos: Use slave.PID.Host as task SlaveIP. 2017-12-20 12:12:03 +01:00
Ludovic Fernandez
cd1b3904da Add missing entrypoints template. 2017-12-20 10:26:03 +01:00
Emile Vauge
b23b2611b3 Add non regex pathPrefix 2017-12-19 17:00:12 +01:00
Timo Reimann
877770f7cf Update go-marathon 2017-12-19 16:00:09 +01:00
lishaoxiong
3142a4f4b3 Fix stickiness bug due to template syntax error 2017-12-19 14:08:03 +01:00
Ludovic Fernandez
b4dc96527d Move rate limit documentation. 2017-12-19 09:48:03 +01:00
Ludovic Fernandez
35b5ca4c63 fix isHealthy logic. 2017-12-18 10:30:08 +01:00
Ludovic Fernandez
daf3023b02 Change Zookeeper default prefix. 2017-12-18 09:22:03 +01:00
Michael
b17d5b80b8 Reload configuration when port change for one service 2017-12-15 20:52:03 +01:00
Michael
48b4eb5c0d Fix bad Træfik update on Consul Catalog 2017-12-15 16:00:14 +01:00
Ludovic Fernandez
7ecd6d20ba Support regex redirect by frontend 2017-12-15 11:48:03 +01:00
Kevin Risden
bddad57a7b Fix RawPath handling in addPrefix 2017-12-15 03:50:07 +01:00
Ludovic Fernandez
799136a714 fix: backend name for Stateful services. (Service Fabric) 2017-12-15 01:22:03 +01:00
Timo Reimann
350d61b4a6 Fix github.com/containous/traefik-extra-service-fabric dep to v1.0.1. 2017-12-14 16:06:03 +01:00
Gérald Croës
b6f5a66fab Grammar 2017-12-13 18:22:05 +01:00
Ludovic Fernandez
b0c12e2422 Fix: frontend redirect 2017-12-13 17:02:04 +01:00
Michael MATUR
623a7dc7e6 Fix small missing property in documentation for consul catalog 2017-12-13 11:56:02 +01:00
Michael MATUR
709c7e5707 Improve documentation for Cloudflare API key 2017-12-13 11:56:02 +01:00
Mikhail Vasin
ee04f52a16 Fix broken links and improve ResponseCodeRatio() description 2017-12-08 16:12:04 +01:00
98 changed files with 2522 additions and 1020 deletions

View File

@@ -1,5 +1,63 @@
# Change Log
## [v1.5.0-rc4](https://github.com/containous/traefik/tree/v1.5.0-rc4) (2018-01-04)
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc3...v1.5.0-rc4)
**Bug fixes:**
- **[consulcatalog]** Use prefix for sticky and stickiness tags. ([#2624](https://github.com/containous/traefik/pull/2624) by [ldez](https://github.com/ldez))
- **[file,tls]** Send empty configuration from file provider ([#2609](https://github.com/containous/traefik/pull/2609) by [nmengin](https://github.com/nmengin))
- **[middleware,docker,k8s]** Fix custom headers template ([#2621](https://github.com/containous/traefik/pull/2621) by [ldez](https://github.com/ldez))
- **[middleware]** Don't panic if ResponseWriter does not implement CloseNotify ([#2651](https://github.com/containous/traefik/pull/2651) by [Juliens](https://github.com/Juliens))
- **[middleware]** We need to flush the end of the body when retry is streamed ([#2644](https://github.com/containous/traefik/pull/2644) by [Juliens](https://github.com/Juliens))
- **[tls]** Allow deleting dynamically all TLS certificates from an entryPoint ([#2603](https://github.com/containous/traefik/pull/2603) by [nmengin](https://github.com/nmengin))
- **[websocket]** Use gorilla readMessage and writeMessage instead of just an io.Copy ([#2650](https://github.com/containous/traefik/pull/2650) by [Juliens](https://github.com/Juliens))
**Documentation:**
- **[consul,consulcatalog]** Split Consul and Consul Catalog documentation ([#2654](https://github.com/containous/traefik/pull/2654) by [ldez](https://github.com/ldez))
- **[docker/swarm]** Typo in docker.endpoint TCP port. ([#2626](https://github.com/containous/traefik/pull/2626) by [redhandpl](https://github.com/redhandpl))
- **[docker]** Add a note on how to add label to a docker compose file ([#2611](https://github.com/containous/traefik/pull/2611) by [jmaitrehenry](https://github.com/jmaitrehenry))
- **[k8s]** k8s guide: Leave note about assumed DaemonSet usage. ([#2634](https://github.com/containous/traefik/pull/2634) by [timoreimann](https://github.com/timoreimann))
- **[marathon]** Improve Marathon service label documentation. ([#2635](https://github.com/containous/traefik/pull/2635) by [timoreimann](https://github.com/timoreimann))
**Misc:**
- **[etcd,kv,tls]** Add tests for TLS dynamic configuration in ETCD3 ([#2606](https://github.com/containous/traefik/pull/2606) by [dahefanteng](https://github.com/dahefanteng))
- Merge v1.4.6 into v1.5 ([#2642](https://github.com/containous/traefik/pull/2642) by [ldez](https://github.com/ldez))
## [v1.4.6](https://github.com/containous/traefik/tree/v1.4.6) (2018-01-02)
[All Commits](https://github.com/containous/traefik/compare/v1.4.5...v1.4.6)
**Bug fixes:**
- **[docker]** Normalize serviceName added to the service backend names ([#2631](https://github.com/containous/traefik/pull/2631) by [mmatur](https://github.com/mmatur))
- **[websocket]** Use gorilla readMessage and writeMessage instead of just an io.Copy ([#2640](https://github.com/containous/traefik/pull/2640) by [Juliens](https://github.com/Juliens))
- Fix bug report command ([#2638](https://github.com/containous/traefik/pull/2638) by [ldez](https://github.com/ldez))
## [v1.5.0-rc3](https://github.com/containous/traefik/tree/v1.5.0-rc3) (2017-12-20)
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc2...v1.5.0-rc3)
**Enhancements:**
- **[docker,k8s,rancher]** Support regex redirect by frontend ([#2570](https://github.com/containous/traefik/pull/2570) by [ldez](https://github.com/ldez))
**Bug fixes:**
- **[acme,docker]** Modify ACME configuration migration into KV store ([#2598](https://github.com/containous/traefik/pull/2598) by [nmengin](https://github.com/nmengin))
- **[consulcatalog]** Reload configuration when port change for one service ([#2574](https://github.com/containous/traefik/pull/2574) by [mmatur](https://github.com/mmatur))
- **[consulcatalog]** Fix bad Træfik update on Consul Catalog ([#2573](https://github.com/containous/traefik/pull/2573) by [mmatur](https://github.com/mmatur))
- **[k8s]** Add missing entrypoints template. ([#2594](https://github.com/containous/traefik/pull/2594) by [ldez](https://github.com/ldez))
- **[kv]** Fix stickiness bug due to template syntax error ([#2591](https://github.com/containous/traefik/pull/2591) by [dahefanteng](https://github.com/dahefanteng))
- **[marathon]** Update go-marathon ([#2585](https://github.com/containous/traefik/pull/2585) by [timoreimann](https://github.com/timoreimann))
- **[mesos]** Mesos: Use slave.PID.Host as task SlaveIP. ([#2590](https://github.com/containous/traefik/pull/2590) by [nemosupremo](https://github.com/nemosupremo))
- **[middleware]** Fix RawPath handling in addPrefix ([#2560](https://github.com/containous/traefik/pull/2560) by [risdenk](https://github.com/risdenk))
- **[rules]** Add non regex pathPrefix ([#2592](https://github.com/containous/traefik/pull/2592) by [emilevauge](https://github.com/emilevauge))
- **[servicefabric]** Fix backend name for Stateful services. (Service Fabric) ([#2559](https://github.com/containous/traefik/pull/2559) by [ldez](https://github.com/ldez))
- **[servicefabric]** Fix isHealthy logic. ([#2577](https://github.com/containous/traefik/pull/2577) by [ldez](https://github.com/ldez))
- **[zk]** Change Zookeeper default prefix. ([#2580](https://github.com/containous/traefik/pull/2580) by [ldez](https://github.com/ldez))
- Fix frontend redirect ([#2544](https://github.com/containous/traefik/pull/2544) by [ldez](https://github.com/ldez))
**Documentation:**
- **[acme]** Improve documentation for Cloudflare API key ([#2558](https://github.com/containous/traefik/pull/2558) by [mmatur](https://github.com/mmatur))
- Move rate limit documentation. ([#2588](https://github.com/containous/traefik/pull/2588) by [ldez](https://github.com/ldez))
- Grammar ([#2562](https://github.com/containous/traefik/pull/2562) by [geraldcroes](https://github.com/geraldcroes))
- Fix broken links and improve ResponseCodeRatio() description ([#2538](https://github.com/containous/traefik/pull/2538) by [mvasin](https://github.com/mvasin))
## [v1.5.0-rc2](https://github.com/containous/traefik/tree/v1.5.0-rc2) (2017-12-06)
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc1...v1.5.0-rc2)

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016-2017 Containous SAS
Copyright (c) 2016-2018 Containous SAS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -43,7 +43,7 @@ If you want your users to access some of your microservices from the Internet, y
- path `domain.com/web` will point the microservice `web` in your private network
- domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
But a microservices architecture is dynamic... Services are added, removed, killed or upgraded often, eventually several times a day.
Microservices are often deployed in dynamic environments where services are added, removed, killed, upgraded or scaled many times a day.
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.

View File

@@ -172,7 +172,6 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
[frontends."frontend-{{getServiceBackend $container $serviceName}}"]
backend = "backend-{{getServiceBackend $container $serviceName}}"
passHostHeader = {{getServicePassHostHeader $container $serviceName}}
redirect = "{{getServiceRedirect $container $serviceName}}"
{{if getWhitelistSourceRange $container}}
whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
"{{.}}",
@@ -185,14 +184,21 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
basicAuth = [{{range getServiceBasicAuth $container $serviceName}}
"{{.}}",
{{end}}]
[frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"]
{{if hasServiceRedirect $container $serviceName}}
[frontends."frontend-{{getServiceBackend $container $serviceName}}".redirect]
entryPoint = "{{getServiceRedirectEntryPoint $container $serviceName}}"
regex = "{{getServiceRedirectRegex $container $serviceName}}"
replacement = "{{getServiceRedirectReplacement $container $serviceName}}"
{{end}}
[frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"]
rule = "{{getServiceFrontendRule $container $serviceName}}"
{{end}}
{{else}}
[frontends."frontend-{{$frontend}}"]
backend = "backend-{{getBackend $container}}"
passHostHeader = {{getPassHostHeader $container}}
redirect = "{{getRedirect $container}}"
{{if getWhitelistSourceRange $container}}
whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
"{{.}}",
@@ -205,6 +211,15 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
basicAuth = [{{range getBasicAuth $container}}
"{{.}}",
{{end}}]
{{if hasRedirect $container}}
[frontends."frontend-{{$frontend}}".redirect]
entryPoint = "{{getRedirectEntryPoint $container}}"
regex = "{{getRedirectRegex $container}}"
replacement = "{{getRedirectReplacement $container}}"
{{end}}
{{ if hasHeaders $container}}
[frontends."frontend-{{$frontend}}".headers]
{{if hasSSLRedirectHeaders $container}}
SSLRedirect = {{getSSLRedirectHeaders $container}}
@@ -251,6 +266,16 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{if hasIsDevelopmentHeaders $container}}
IsDevelopment = {{getIsDevelopmentHeaders $container}}
{{end}}
{{if hasAllowedHostsHeaders $container}}
AllowedHosts = [{{range getAllowedHostsHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasHostsProxyHeaders $container}}
HostsProxyHeaders = [{{range getHostsProxyHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasRequestHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.customrequestheaders]
{{range $k, $v := getRequestHeaders $container}}
@@ -263,24 +288,14 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if hasAllowedHostsHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.AllowedHosts]
{{range getAllowedHostsHeaders $container}}
"{{.}}"
{{end}}
{{end}}
{{if hasHostsProxyHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.HostsProxyHeaders]
{{range getHostsProxyHeaders $container}}
"{{.}}"
{{end}}
{{end}}
{{if hasSSLProxyHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.SSLProxyHeaders]
{{range $k, $v := getSSLProxyHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
rule = "{{getFrontendRule $container}}"
{{end}}
@@ -414,13 +429,24 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend
backend = "{{$frontend.Backend}}"
priority = {{$frontend.Priority}}
passHostHeader = {{$frontend.PassHostHeader}}
redirect = "{{$frontend.Redirect}}"
entryPoints = [{{range $frontend.EntryPoints}}
"{{.}}",
{{end}}]
basicAuth = [{{range $frontend.BasicAuth}}
"{{.}}",
{{end}}]
whitelistSourceRange = [{{range $frontend.WhitelistSourceRange}}
"{{.}}",
{{end}}]
{{if $frontend.Redirect}}
[frontends."{{$frontendName}}".redirect]
entryPoint = "{{$frontend.RedirectEntryPoint}}"
regex = "{{$frontend.RedirectRegex}}"
replacement = "{{$frontend.RedirectReplacement}}"
{{end}}
{{ if $frontend.Headers }}
[frontends."{{$frontendName}}".headers]
SSLRedirect = {{$frontend.Headers.SSLRedirect}}
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}
@@ -437,40 +463,45 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend
PublicKey = "{{$frontend.Headers.PublicKey}}"
ReferrerPolicy = "{{$frontend.Headers.ReferrerPolicy}}"
IsDevelopment = {{$frontend.Headers.IsDevelopment}}
{{if $frontend.Headers.CustomRequestHeaders}}
[frontends."{{$frontendName}}".headers.customrequestheaders]
{{if $frontend.Headers.AllowedHosts}}
AllowedHosts = [{{range $frontend.Headers.AllowedHosts}}
"{{.}}",
{{end}}]
{{end}}
{{if $frontend.Headers.HostsProxyHeaders}}
HostsProxyHeaders = [{{range $frontend.Headers.HostsProxyHeaders}}
"{{.}}",
{{end}}]
{{end}}
{{if $frontend.Headers.CustomRequestHeaders}}
[frontends."{{$frontendName}}".headers.customrequestheaders]
{{range $k, $v := $frontend.Headers.CustomRequestHeaders}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $frontend.Headers.CustomResponseHeaders}}
[frontends."{{$frontendName}}".headers.customresponseheaders]
{{end}}
{{if $frontend.Headers.CustomResponseHeaders}}
[frontends."{{$frontendName}}".headers.customresponseheaders]
{{range $k, $v := $frontend.Headers.CustomResponseHeaders}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $frontend.Headers.AllowedHosts}}
[frontends."{{$frontendName}}".headers.AllowedHosts]
{{range $frontend.Headers.AllowedHosts}}
"{{.}}"
{{end}}
{{end}}
{{if $frontend.Headers.HostsProxyHeaders}}
[frontends."{{$frontendName}}".headers.HostsProxyHeaders]
{{range $frontend.Headers.HostsProxyHeaders}}
"{{.}}"
{{end}}
{{end}}
{{if $frontend.Headers.SSLProxyHeaders}}
[frontends."{{$frontendName}}".headers.SSLProxyHeaders]
{{end}}
{{if $frontend.Headers.SSLProxyHeaders}}
[frontends."{{$frontendName}}".headers.SSLProxyHeaders]
{{range $k, $v := $frontend.Headers.SSLProxyHeaders}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
{{range $routeName, $route := $frontend.Routes}}
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
{{range $routeName, $route := $frontend.Routes}}
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
rule = "{{$route.Rule}}"
{{end}}
{{end}}
{{end}}
`)
@@ -511,7 +542,7 @@ var _templatesKvTmpl = []byte(`{{$frontends := List .Prefix "/frontends/" }}
sticky = {{ getSticky . }}
{{if hasStickinessLabel $backend}}
[backends."{{$backendName}}".loadBalancer.stickiness]
cookieName = {{getStickinessCookieName $backend}}
cookieName = "{{getStickinessCookieName $backend}}"
{{end}}
{{end}}
@@ -752,13 +783,20 @@ var _templatesRancherTmpl = []byte(`{{$backendServers := .Backends}}
backend = "backend-{{getBackend $service}}"
passHostHeader = {{getPassHostHeader $service}}
priority = {{getPriority $service}}
redirect = "{{getRedirect $service}}"
entryPoints = [{{range getEntryPoints $service}}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $service}}
"{{.}}",
{{end}}]
{{if hasRedirect $service}}
[frontends."frontend-{{$frontendName}}".redirect]
entryPoint = "{{getRedirectEntryPoint $service}}"
regex = "{{getRedirectRegex $service}}"
replacement = "{{getRedirectReplacement $service}}"
{{end}}
[frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"]
rule = "{{getFrontendRule $service}}"
{{end}}

View File

@@ -57,7 +57,7 @@ func TestDo_globalConfiguration(t *testing.T) {
Optional: false,
},
},
Redirect: &configuration.Redirect{
Redirect: &types.Redirect{
Replacement: "foo Replacement",
Regex: "foo Regex",
EntryPoint: "foo EntryPoint",
@@ -103,7 +103,7 @@ func TestDo_globalConfiguration(t *testing.T) {
Optional: false,
},
},
Redirect: &configuration.Redirect{
Redirect: &types.Redirect{
Replacement: "fii Replacement",
Regex: "fii Regex",
EntryPoint: "fii EntryPoint",

View File

@@ -84,7 +84,7 @@ Add more configuration information here.
)
// newBugCmd builds a new Bug command
func newBugCmd(traefikConfiguration interface{}, traefikPointersConfiguration interface{}) *flaeg.Command {
func newBugCmd(traefikConfiguration *TraefikConfiguration, traefikPointersConfiguration *TraefikConfiguration) *flaeg.Command {
//version Command init
return &flaeg.Command{
@@ -99,7 +99,7 @@ func newBugCmd(traefikConfiguration interface{}, traefikPointersConfiguration in
}
}
func runBugCmd(traefikConfiguration interface{}) func() error {
func runBugCmd(traefikConfiguration *TraefikConfiguration) func() error {
return func() error {
body, err := createBugReport(traefikConfiguration)
@@ -113,7 +113,7 @@ func runBugCmd(traefikConfiguration interface{}) func() error {
}
}
func createBugReport(traefikConfiguration interface{}) (string, error) {
func createBugReport(traefikConfiguration *TraefikConfiguration) (string, error) {
var version bytes.Buffer
if err := getVersionPrint(&version); err != nil {
return "", err
@@ -124,7 +124,7 @@ func createBugReport(traefikConfiguration interface{}) (string, error) {
return "", err
}
config, err := anonymize.Do(&traefikConfiguration, true)
config, err := anonymize.Do(traefikConfiguration, true)
if err != nil {
return "", err
}

View File

@@ -7,16 +7,27 @@ import (
"github.com/containous/traefik/configuration"
"github.com/containous/traefik/provider/file"
"github.com/containous/traefik/tls"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
)
func Test_createBugReport(t *testing.T) {
traefikConfiguration := TraefikConfiguration{
traefikConfiguration := &TraefikConfiguration{
ConfigFile: "FOO",
GlobalConfiguration: configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{
"goo": &configuration.EntryPoint{
Address: "hoo.bar",
Auth: &types.Auth{
Basic: &types.Basic{
UsersFile: "foo Basic UsersFile",
Users: types.Users{"foo Basic Users 1", "foo Basic Users 2", "foo Basic Users 3"},
},
Digest: &types.Digest{
UsersFile: "foo Digest UsersFile",
Users: types.Users{"foo Digest Users 1", "foo Digest Users 2", "foo Digest Users 3"},
},
},
},
},
File: &file.Provider{
@@ -28,6 +39,11 @@ func Test_createBugReport(t *testing.T) {
report, err := createBugReport(traefikConfiguration)
assert.NoError(t, err, report)
// exported anonymous configuration
assert.NotContains(t, "web Basic Users ", report)
assert.NotContains(t, "foo Digest Users ", report)
assert.NotContains(t, "hoo.bar", report)
}
func Test_anonymize_traefikConfiguration(t *testing.T) {

View File

@@ -112,7 +112,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
var defaultZookeeper zk.Provider
defaultZookeeper.Watch = true
defaultZookeeper.Endpoint = "127.0.0.1:2181"
defaultZookeeper.Prefix = "/traefik"
defaultZookeeper.Prefix = "traefik"
defaultZookeeper.Constraints = types.Constraints{}
//default Boltdb

View File

@@ -67,12 +67,21 @@ func runStoreConfig(kv *staert.KvSource, traefikConfiguration *TraefikConfigurat
return err
}
}
if traefikConfiguration.GlobalConfiguration.ACME != nil && len(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) > 0 {
// convert ACME json file to KV store
localStore := acme.NewLocalStore(traefikConfiguration.GlobalConfiguration.ACME.StorageFile)
object, err := localStore.Load()
if err != nil {
return err
if traefikConfiguration.GlobalConfiguration.ACME != nil {
var object cluster.Object
if len(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) > 0 {
// convert ACME json file to KV store
localStore := acme.NewLocalStore(traefikConfiguration.GlobalConfiguration.ACME.StorageFile)
object, err = localStore.Load()
if err != nil {
return err
}
} else {
// Create an empty account to create all the keys into the KV store
account := &acme.Account{}
account.Init()
object = account
}
meta := cluster.NewMetadata(object)
@@ -89,6 +98,11 @@ func runStoreConfig(kv *staert.KvSource, traefikConfiguration *TraefikConfigurat
if err != nil {
return err
}
// Force to delete storagefile
err = kv.Delete(kv.Prefix + "/acme/storagefile")
if err != nil {
return err
}
}
return nil
}

View File

@@ -317,9 +317,9 @@ func (ep *EntryPoints) Set(value string) error {
Optional: optional,
}
}
var redirect *Redirect
var redirect *types.Redirect
if len(result["redirect_entrypoint"]) > 0 || len(result["redirect_regex"]) > 0 || len(result["redirect_replacement"]) > 0 {
redirect = &Redirect{
redirect = &types.Redirect{
EntryPoint: result["redirect_entrypoint"],
Regex: result["redirect_regex"],
Replacement: result["redirect_replacement"],
@@ -422,22 +422,15 @@ func (ep *EntryPoints) Type() string {
type EntryPoint struct {
Network string
Address string
TLS *tls.TLS `export:"true"`
Redirect *Redirect `export:"true"`
Auth *types.Auth `export:"true"`
TLS *tls.TLS `export:"true"`
Redirect *types.Redirect `export:"true"`
Auth *types.Auth `export:"true"`
WhitelistSourceRange []string
Compress bool `export:"true"`
ProxyProtocol *ProxyProtocol `export:"true"`
ForwardedHeaders *ForwardedHeaders `export:"true"`
}
// Redirect configures a redirection of an entry point to another, or to an URL
type Redirect struct {
EntryPoint string
Regex string
Replacement string
}
// Retry contains request retry config
type Retry struct {
Attempts int `description:"Number of attempts" export:"true"`

View File

@@ -8,6 +8,7 @@ import (
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/file"
"github.com/containous/traefik/tls"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -138,7 +139,7 @@ func TestEntryPoints_Set(t *testing.T) {
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
Address: ":8000",
Redirect: &Redirect{
Redirect: &types.Redirect{
EntryPoint: "RedirectEntryPoint",
Regex: "RedirectRegex",
Replacement: "RedirectReplacement",
@@ -171,7 +172,7 @@ func TestEntryPoints_Set(t *testing.T) {
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
Address: ":8000",
Redirect: &Redirect{
Redirect: &types.Redirect{
EntryPoint: "RedirectEntryPoint",
Regex: "RedirectRegex",
Replacement: "RedirectReplacement",

View File

@@ -321,42 +321,13 @@ In this example, traffic routed through the first frontend will have the `X-Fram
!!! note
The detailed documentation for those security headers can be found in [unrolled/secure](https://github.com/unrolled/secure#available-options).
#### Rate limiting
Rate limiting can be configured per frontend.
Multiple sets of rates can be added to each frontend, but the time periods must be unique.
```toml
[frontends]
[frontends.frontend1]
passHostHeader = true
entrypoints = ["http"]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Path:/"
[frontends.frontend1.ratelimit]
extractorfunc = "client.ip"
[frontends.frontend1.ratelimit.rateset.rateset1]
period = "10s"
average = 100
burst = 200
[frontends.frontend1.ratelimit.rateset.rateset2]
period = "3s"
average = 5
burst = 10
```
In the above example, frontend1 is configured to limit requests by the client's ip address.
An average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
These can "burst" up to 10 and 200 in each period respectively.
### Backends
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
Various methods of load-balancing are supported:
- `wrr`: Weighted Round Robin
- `wrr`: Weighted Round Robin.
- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others.
It also rolls back to original weights if the servers have changed.
@@ -373,16 +344,13 @@ It can be configured using:
For example:
- `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window for a frontend
- `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window for a frontend.
- `LatencyAtQuantileMS(50.0) > 50`: watch latency at quantile in milliseconds.
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in range [500-600) to [0-600)
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in ranges [500-600) and [0-600).
To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can
also be applied to each backend.
To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can also be applied to each backend.
Maximum connections can be configured by specifying an integer value for `maxconn.amount` and
`maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to
evaluate the maximum connections.
Maximum connections can be configured by specifying an integer value for `maxconn.amount` and `maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to evaluate the maximum connections.
For example:
```toml
@@ -499,8 +467,8 @@ Here is an example of backends and servers definition:
Træfik's configuration has two parts:
- The [static Træfik configuration](/basics#static-trfk-configuration) which is loaded only at the beginning.
- The [dynamic Træfik configuration](/basics#dynamic-trfk-configuration) which can be hot-reloaded (no need to restart the process).
- The [static Træfik configuration](/basics#static-trfik-configuration) which is loaded only at the beginning.
- The [dynamic Træfik configuration](/basics#dynamic-trfik-configuration) which can be hot-reloaded (no need to restart the process).
### Static Træfik configuration
@@ -585,7 +553,7 @@ traefik [command] [--flag=flag_argument]
List of Træfik available commands with description :
- `version` : Print version
- `storeconfig` : Store the static Traefik configuration into a Key-value stores. Please refer to the [Store Træfik configuration](/user-guide/kv-config/#store-trfk-configuration) section to get documentation on it.
- `storeconfig` : Store the static Traefik configuration into a Key-value stores. Please refer to the [Store Træfik configuration](/user-guide/kv-config/#store-configuration-in-key-value-store) section to get documentation on it.
- `bug`: The easiest way to submit a pre-filled issue.
- `healthcheck`: Calls Traefik `/ping` to check health.

View File

@@ -20,6 +20,12 @@ See also [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) an
#
email = "test@traefik.io"
# File used for certificates storage.
#
# Optional (Deprecated)
#
#storageFile = "acme.json"
# File or key used for certificates storage.
#
# Required
@@ -55,7 +61,7 @@ entryPoint = "https"
#
# acmeLogging = true
# Enable on demand certificate.
# Enable on demand certificate. (Deprecated)
#
# Optional
#
@@ -89,6 +95,10 @@ entryPoint = "https"
# main = "local4.com"
```
!!! note
ACME entryPoint has to be relied to the port 443, otherwise ACME Challenges can not be done.
It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
### `storage`
```toml
@@ -100,7 +110,7 @@ storage = "acme.json"
File or key used for certificates storage.
**WARNING** If you use Traefik in Docker, you have 2 options:
**WARNING** If you use Træfik in Docker, you have 2 options:
- create a file on your host and mount it as a volume:
```toml
@@ -118,6 +128,14 @@ storage = "/etc/traefik/acme/acme.json"
docker run -v "/my/host/acme:/etc/traefik/acme" traefik
```
!!! note
`storage` replaces `storageFile` which is deprecated.
!!! note
During Træfik configuration migration from a configuration file to a KV store (thanks to `storeconfig` subcommand as described [here](/user-guide/kv-config/#store-configuration-in-key-value-store)), if ACME certificates have to be migrated too, use both `storageFile` and `storage`.
`storageFile` will contain the path to the `acme.json` file to migrate.
`storage` will contain the key where the certificates will be stored.
### `dnsProvider`
```toml
@@ -135,7 +153,7 @@ Select the provider that matches the DNS domain that will host the challenge TXT
|--------------------------------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------|
| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` |
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP` |
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` |
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` - The Cloudflare `Global API Key` needs to be used and not the `Origin CA Key` |
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` |
| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` |
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` |
@@ -146,7 +164,7 @@ Select the provider that matches the DNS domain that will host the challenge TXT
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` |
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` |
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` |
| manual | - | none, but run Traefik interactively & turn on `acmeLogging` to see instructions & press <kbd>Enter</kbd>. |
| manual | - | none, but run Træfik interactively & turn on `acmeLogging` to see instructions & press <kbd>Enter</kbd>. |
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` |
| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` |
| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` |
@@ -171,7 +189,7 @@ If `delayDontCheckDNS` is greater than zero, avoid this & instead just wait so m
Useful if internal networks block external DNS queries.
### `onDemand`
### `onDemand` (Deprecated)
```toml
[acme]
@@ -188,7 +206,10 @@ This will request a certificate from Let's Encrypt during the first TLS handshak
TLS handshakes will be slow when requesting a hostname certificate for the first time, this can lead to DoS attacks.
!!! warning
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits)
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
!!! warning
This option is deprecated.
### `onHostRule`
@@ -238,7 +259,7 @@ main = "local4.com"
```
You can provide SANs (alternative domains) to each main domain.
All domains must have A/AAAA records pointing to Traefik.
All domains must have A/AAAA records pointing to Træfik.
!!! warning
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).

View File

@@ -1,6 +1,4 @@
# Consul Backend
## Consul Key-Value backend
# Consul Key-Value backend
Træfik can be configured to use Consul as a backend configuration.
@@ -61,91 +59,3 @@ prefix = "traefik"
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
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.
```toml
################################################################
# Consul Catalog configuration backend
################################################################
# Enable Consul Catalog configuration backend.
[consulCatalog]
# Consul server endpoint.
#
# Required
# Default: "127.0.0.1:8500"
#
endpoint = "127.0.0.1:8500"
# Expose Consul catalog services by default in Traefik.
#
# Optional
# Default: true
#
exposedByDefault = false
# Prefix for Consul catalog tags.
#
# Optional
# Default: "traefik"
#
prefix = "traefik"
# Default frontEnd Rule for Consul services.
#
# The format is a Go Template with:
# - ".ServiceName", ".Domain" and ".Attributes" available
# - "getTag(name, tags, defaultValue)", "hasTag(name, tags)" and "getAttribute(name, tags, defaultValue)" functions are available
# - "getAttribute(...)" function uses prefixed tag names based on "prefix" value
#
# Optional
# Default: "Host:{{.ServiceName}}.{{.Domain}}"
#
#frontEndRule = "Host:{{.ServiceName}}.{{Domain}}"
```
This backend will create routes matching on hostname based on the service name used in Consul.
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
### Tags
Additional settings can be defined using Consul Catalog tags.
| Tag | Description |
|-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.protocol=https` | Override the default `http` protocol |
| `traefik.backend.weight=10` | Assign this weight to the container |
| `traefik.backend.circuitbreaker=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend, ex: `NetworkErrorRatio() > 0.` |
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. |
| `traefik.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{{.ServiceName}}.{{.Domain}}`). |
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
| `traefik.frontend.priority=10` | Override default frontend priority |
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
| `traefik.backend.loadbalancer=drr` | override the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) |
### Examples
If you want that Træfik uses Consul tags correctly you need to defined them like that:
```json
traefik.enable=true
traefik.tags=api
traefik.tags=external
```
If the prefix defined in Træfik configuration is `bla`, tags need to be defined like that:
```json
bla.enable=true
bla.tags=api
bla.tags=external
```

View File

@@ -0,0 +1,93 @@
# Consul Catalog backend
Træfik can be configured to use service discovery catalog of Consul as a backend configuration.
```toml
################################################################
# Consul Catalog configuration backend
################################################################
# Enable Consul Catalog configuration backend.
[consulCatalog]
# Consul server endpoint.
#
# Required
# Default: "127.0.0.1:8500"
#
endpoint = "127.0.0.1:8500"
# Expose Consul catalog services by default in Traefik.
#
# Optional
# Default: true
#
exposedByDefault = false
# Default domain used.
#
# Optional
#
domain = "consul.localhost"
# Prefix for Consul catalog tags.
#
# Optional
# Default: "traefik"
#
prefix = "traefik"
# Default frontEnd Rule for Consul services.
#
# The format is a Go Template with:
# - ".ServiceName", ".Domain" and ".Attributes" available
# - "getTag(name, tags, defaultValue)", "hasTag(name, tags)" and "getAttribute(name, tags, defaultValue)" functions are available
# - "getAttribute(...)" function uses prefixed tag names based on "prefix" value
#
# Optional
# Default: "Host:{{.ServiceName}}.{{.Domain}}"
#
#frontEndRule = "Host:{{.ServiceName}}.{{.Domain}}"
```
This backend will create routes matching on hostname based on the service name used in Consul.
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
### Tags
Additional settings can be defined using Consul Catalog tags.
| Tag | Description |
|-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.protocol=https` | Override the default `http` protocol |
| `traefik.backend.weight=10` | Assign this weight to the container |
| `traefik.backend.circuitbreaker=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend, ex: `NetworkErrorRatio() > 0.` |
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. |
| `traefik.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{{.ServiceName}}.{{.Domain}}`). |
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
| `traefik.frontend.priority=10` | Override default frontend priority |
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
| `traefik.backend.loadbalancer=drr` | override the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) |
### Examples
If you want that Træfik uses Consul tags correctly you need to defined them like that:
```json
traefik.enable=true
traefik.tags=api
traefik.tags=external
```
If the prefix defined in Træfik configuration is `bla`, tags need to be defined like that:
```json
bla.enable=true
bla.tags=api
bla.tags=external
```

View File

@@ -145,33 +145,51 @@ To enable constraints see [backend-specific constraints section](/configuration/
## Labels: overriding default behaviour
!!! note
If you use a compose file, labels should be defined in the `deploy` part of your service.
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/#labels-1)).
```yaml
version: "3"
services:
whoami:
deploy:
labels:
traefik.docker.network: traefik
```
### On Containers
Labels can be used on containers to override default behaviour.
| Label | Description |
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. |
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. |
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
| `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) |
| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). |
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
| `traefik.protocol=https` | Override the default `http` protocol |
| `traefik.weight=10` | Assign this weight to the container |
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
| `traefik.frontend.priority=10` | Override default frontend priority |
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints` |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
| `traefik.frontend.whitelistSourceRange:RANGE` | List of IP-Ranges which are allowed to access. An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
| `traefik.docker.network` | Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect <container_id>`) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name. |
| `traefik.frontend.redirect=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
| Label | Description |
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. |
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. |
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
| `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) |
| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). |
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
| `traefik.protocol=https` | Override the default `http` protocol |
| `traefik.weight=10` | Assign this weight to the container |
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
| `traefik.frontend.priority=10` | Override default frontend priority |
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints` |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
| `traefik.frontend.whitelistSourceRange:RANGE` | List of IP-Ranges which are allowed to access. An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
| `traefik.docker.network` | Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect <container_id>`) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name. |
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.replacement`. |
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.regex`. |
#### Security Headers
@@ -202,26 +220,31 @@ Labels can be used on containers to override default behaviour.
Services labels can be used for overriding default behaviour
| Label | Description |
|---------------------------------------------------|--------------------------------------------------------------------------------------------------|
| `traefik.<service-name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
| `traefik.<service-name>.protocol` | Overrides `traefik.protocol`. |
| `traefik.<service-name>.weight` | Assign this service weight. Overrides `traefik.weight`. |
| `traefik.<service-name>.frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. |
| `traefik.<service-name>.frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` |
| `traefik.<service-name>.frontend.auth.basic` | Sets a Basic Auth for that frontend |
| `traefik.<service-name>.frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. |
| `traefik.<service-name>.frontend.priority` | Overrides `traefik.frontend.priority`. |
| `traefik.<service-name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
| `traefik.<service-name>.frontend.redirect` | Overrides `traefik.frontend.redirect`. |
| Label | Description |
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
| `traefik.<service-name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
| `traefik.<service-name>.protocol` | Overrides `traefik.protocol`. |
| `traefik.<service-name>.weight` | Assign this service weight. Overrides `traefik.weight`. |
| `traefik.<service-name>.frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. |
| `traefik.<service-name>.frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` |
| `traefik.<service-name>.frontend.auth.basic` | Sets a Basic Auth for that frontend |
| `traefik.<service-name>.frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. |
| `traefik.<service-name>.frontend.priority` | Overrides `traefik.frontend.priority`. |
| `traefik.<service-name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
| `traefik.<service-name>.frontend.redirect` | Overrides `traefik.frontend.redirect`. |
| `traefik.<service-name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
| `traefik.<service-name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
| `traefik.<service-name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
!!! note
if a label is defined both as a `container label` and a `service label` (for example `traefik.<service-name>.port=PORT` and `traefik.port=PORT` ), the `service label` is used to defined the `<service-name>` property (`port` in the example).
If a label is defined both as a `container label` and a `service label` (for example `traefik.<service-name>.port=PORT` and `traefik.port=PORT` ), the `service label` is used to defined the `<service-name>` property (`port` in the example).
It's possible to mix `container labels` and `service labels`, in this case `container labels` are used as default value for missing `service labels` but no frontends are going to be created with the `container labels`.
More details in this [example](/user-guide/docker-and-lets-encrypt/#labels).
!!! warning
when running inside a container, Træfik will need network access through:
When running inside a container, Træfik will need network access through:
`docker network connect <network> <traefik-container>`

View File

@@ -102,13 +102,21 @@ Annotations can be used on containers to override default behaviour for the whol
Override the default frontend rule type. Default: `PathPrefix`.
- `traefik.frontend.priority: "3"`
Override the default frontend rule priority.
- `traefik.frontend.redirect: https`:
- `traefik.frontend.redirect.entryPoint: https`:
Enables Redirect to another entryPoint for that frontend (e.g. HTTPS).
- `traefik.frontend.redirect.regex: ^http://localhost/(.*)`:
Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.replacement`.
- `traefik.frontend.redirect.replacement: http://mydomain/$1`:
Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.regex`.
- `traefik.frontend.entryPoints: http,https`
Override the default frontend endpoints.
- `traefik.frontend.passTLSCert: true`
Override the default frontend PassTLSCert value. Default: `false`.
!!! note
Please note that `traefik.frontend.redirect.regex` and `traefik.frontend.redirect.replacement` do not have to be set if `traefik.frontend.redirect.entryPoint` is defined for the redirection (they will not be used in this case).
Annotations can be used on the Kubernetes service to override default behaviour:
- `traefik.backend.loadbalancer.method=drr`

View File

@@ -150,12 +150,15 @@ domain = "marathon.localhost"
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
## Labels: overriding default behaviour
### On Containers
Marathon labels may be used to dynamically change the routing and forwarding behaviour.
Labels can be used on containers to override default behaviour:
They may be specified on one of two levels: Application or service.
### Application Level
The following labels can be defined on Marathon applications. They adjust the behaviour for the entire application.
| Label | Description |
|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -180,9 +183,9 @@ Labels can be used on containers to override default behaviour:
| `traefik.frontend.entryPoints=http,https` | assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. |
### On Services
### Service Level
If several ports need to be exposed from a container, the services labels can be used:
For applications that expose multiple ports, specific labels can be used to extract one frontend/backend configuration pair per port. Each such pair is called a _service_. The (freely choosable) name of the service is an integral part of the service label name.
| Label | Description |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------|

View File

@@ -120,19 +120,21 @@ secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Labels can be used on task containers to override default behaviour:
| Label | Description |
|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------|
| `traefik.protocol=https` | Override the default `http` protocol |
| `traefik.weight=10` | Assign this weight to the container |
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{containerName}.{domain}`). |
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
| `traefik.frontend.priority=10` | Override default frontend priority |
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. |
| `traefik.frontend.redirect=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
| `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
| `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) |
| Label | Description |
|-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| `traefik.protocol=https` | Override the default `http` protocol |
| `traefik.weight=10` | Assign this weight to the container |
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{containerName}.{domain}`). |
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
| `traefik.frontend.priority=10` | Override default frontend priority |
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. |
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
| `traefik.frontend.redirect.regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
| `traefik.frontend.redirect.replacement: http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
| `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
| `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) |

View File

@@ -27,9 +27,9 @@ watch = true
# Prefix used for KV store.
#
# Optional
# Default: "/traefik"
# Default: "traefik"
#
prefix = "/traefik"
prefix = "traefik"
# Override default configuration template.
# For advanced users :)

View File

@@ -277,6 +277,36 @@ Custom error pages are easiest to implement using the file provider.
For dynamic providers, the corresponding template file needs to be customized accordingly and referenced in the Traefik configuration.
## Rate limiting
Rate limiting can be configured per frontend.
Multiple sets of rates can be added to each frontend, but the time periods must be unique.
```toml
[frontends]
[frontends.frontend1]
passHostHeader = true
entrypoints = ["http"]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Path:/"
[frontends.frontend1.ratelimit]
extractorfunc = "client.ip"
[frontends.frontend1.ratelimit.rateset.rateset1]
period = "10s"
average = 100
burst = 200
[frontends.frontend1.ratelimit.rateset.rateset2]
period = "3s"
average = 5
burst = 10
```
In the above example, frontend1 is configured to limit requests by the client's ip address.
An average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
These can "burst" up to 10 and 200 in each period respectively.
## Retry Configuration
```toml

View File

@@ -22,7 +22,7 @@ If you want your users to access some of your microservices from the Internet, y
- path `domain.com/web` will point the microservice `web` in your private network
- domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
But a microservices architecture is dynamic... Services are added, removed, killed or upgraded often, eventually several times a day.
Microservices are often deployed in dynamic environments where services are added, removed, killed, upgraded or scaled many times a day.
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.
@@ -129,7 +129,7 @@ Start it from within the `traefik` folder:
docker-compose up -d
```
In a browser you may open [http://localhost:8080](http://localhost:8080) to access Træfik's dashboard and observe the following magic.
In a browser, you may open [http://localhost:8080](http://localhost:8080) to access Træfik's dashboard and observe the following magic.
Now, create a folder named `test` and create a `docker-compose.yml` in it with this content:

View File

@@ -20,7 +20,7 @@
IN THE SOFTWARE.
-->
{% import "partials/language.html" as lang %}
{% import "partials/language.html" as lang with context %}
<!-- Application footer -->
<footer class="md-footer">
@@ -97,7 +97,7 @@
<!-- Social links -->
{% block social %}
{% include "partials/social.html" %}
{% include "partials/social.html" %}
{% endblock %}
</div>
</div>

View File

@@ -257,7 +257,7 @@ curl $(minikube ip)
404 page not found
```
If you decided to use the deployment, then you need to target the correct NodePort, which can be seen then you execute `kubectl get services --namespace=kube-system`.
If you decided to use the deployment, then you need to target the correct NodePort, which can be seen when you execute `kubectl get services --namespace=kube-system`.
```sh
curl $(minikube ip):<NODEPORT>
@@ -269,6 +269,8 @@ curl $(minikube ip):<NODEPORT>
!!! note
We expect to see a 404 response here as we haven't yet given Træfik any configuration.
All further examples below assume a DaemonSet installation. Deployment users will need to append the NodePort when constructing requests.
## Deploy Træfik using Helm Chart
Instead of installing Træfik via an own object, you can also use the Træfik Helm chart.

View File

@@ -111,7 +111,7 @@ And there, the same global configuration in the Key-value Store (using `prefix =
| `/traefik/entrypoints/https/tls/certificates/0/keyfile` | `integration/fixtures/https/snitest.com.key` |
| `/traefik/entrypoints/https/tls/certificates/1/certfile` | `--BEGIN CERTIFICATE--<cert file content>--END CERTIFICATE--` |
| `/traefik/entrypoints/https/tls/certificates/1/keyfile` | `--BEGIN CERTIFICATE--<key file content>--END CERTIFICATE--` |
| `/traefik/entrypoints/other-https/address` | `:4443`
| `/traefik/entrypoints/other-https/address` | `:4443` |
| `/traefik/consul/endpoint` | `127.0.0.1:8500` |
| `/traefik/consul/watch` | `true` |
| `/traefik/consul/prefix` | `traefik` |
@@ -341,9 +341,10 @@ And there, the same dynamic configuration in a KV Store (using `prefix = "traefi
| Key | Value |
|----------------------------------------------------|-----------------------|
| `/traefik/tlsconfiguration/2/entrypoints` | `https,other-https` |
| `/traefik/tlsconfiguration/2/entrypoints` | `https,other-https` |
| `/traefik/tlsconfiguration/2/certificate/certfile` | `<cert file content>` |
| `/traefik/tlsconfiguration/2/certificate/certfile` | `<key file content>` |
### Atomic configuration changes
Træfik can watch the backends/frontends configuration changes and generate its configuration automatically.
@@ -378,9 +379,9 @@ Here, although the `/traefik_configurations/2/...` keys have been set, the old c
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically.
@@ -392,9 +393,9 @@ Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://1
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
!!! note
Træfik *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik/alias`.

View File

@@ -28,6 +28,24 @@ Following is the order by which Traefik tries to identify the port (the first on
1. The port from the application's `portDefinitions` field (possibly indexed through the `traefik.portIndex` label, otherwise the first one).
1. The port from the application's `ipAddressPerTask` field (possibly indexed through the `traefik.portIndex` label, otherwise the first one).
## Applications with multiple ports
Some Marathon applications may expose multiple ports. Traefik supports creating one so-called _service_ per port using [specific labels](/configuration/backends/marathon#service-level).
For instance, assume that a Marathon application exposes a web API on port 80 and an admin interface on port 8080. It would then be possible to make each service available by specifying the following Marathon labels:
```
traefik.web.port=80
```
```
traefik.admin.port=8080
```
(Note that the service names `web` and `admin` can be chosen arbitrarily.)
Technically, Traefik will create one pair of frontend and backend configurations for each service.
## Achieving high availability
### Scenarios

View File

@@ -86,7 +86,7 @@ docker $(docker-machine config mhs-demo0) run \
-c /dev/null \
--docker \
--docker.domain=traefik \
--docker.endpoint=tcp://$(docker-machine ip mhs-demo0):3376 \
--docker.endpoint=tcp://$(docker-machine ip mhs-demo0):2376 \
--docker.tls \
--docker.tls.ca=/ssl/ca.pem \
--docker.tls.cert=/ssl/server.pem \
@@ -105,7 +105,7 @@ Let's explain this command:
| `-v /var/lib/boot2docker/:/ssl` | mount the ssl keys generated by docker-machine |
| `-c /dev/null` | empty config file |
| `--docker` | enable docker backend |
| `--docker.endpoint=tcp://172.18.0.1:3376` | connect to the swarm master using the docker_gwbridge network |
| `--docker.endpoint=tcp://172.18.0.1:2376` | connect to the swarm master using the docker_gwbridge network |
| `--docker.tls` | enable TLS using the docker-machine keys |
| `--web` | activate the webUI on port 8080 |

View File

@@ -38,16 +38,7 @@ services:
etcdctl-ping:
image: tenstartups/etcdctl
command: --endpoints=[10.0.1.12:2379] get "traefik/acme/storagefile"
environment:
ETCDCTL_DIAL_: "TIMEOUT 10s"
ETCDCTL_API : "3"
networks:
- net
etcdctl-rm:
image: tenstartups/etcdctl
command: --endpoints=[10.0.1.12:2379] del "/traefik/acme/storagefile"
command: --endpoints=[10.0.1.12:2379] get "traefik/acme/storage"
environment:
ETCDCTL_DIAL_: "TIMEOUT 10s"
ETCDCTL_API : "3"
@@ -129,7 +120,6 @@ services:
image: containous/traefik
volumes:
- "./traefik.toml:/traefik.toml:ro"
- "./acme.json:/acme.json:ro"
command: storeconfig --debug
networks:
- net

View File

@@ -32,15 +32,6 @@ delete_services() {
return 0
}
# Init the environment : get IP address and create needed files
init_acme_json() {
echo "CREATE empty acme.json file"
rm -f $basedir/acme.json && \
touch $basedir/acme.json && \
echo "{}" > $basedir/acme.json && \
chmod 600 $basedir/acme.json # Needed for ACME
}
start_consul() {
up_environment consul
waiting_counter=12
@@ -76,7 +67,6 @@ start_etcd3() {
}
start_storeconfig_consul() {
init_acme_json
# Create traefik.toml with consul provider
cp $basedir/traefik.toml.tmpl $basedir/traefik.toml
echo '
@@ -85,29 +75,13 @@ start_storeconfig_consul() {
watch = true
prefix = "traefik"' >> $basedir/traefik.toml
up_environment traefik-storeconfig
rm -f $basedir/traefik.toml && rm -f $basedir/acme.json
# Delete acme-storage-file key
rm -f $basedir/traefik.toml
waiting_counter=5
# Not start Traefik store config if consul is not started
echo "Delete storage file key..."
while [[ -z $(curl -s http://10.0.1.2:8500/v1/kv/traefik/acme/storagefile) && $waiting_counter -gt 0 ]]; do
sleep 5
let waiting_counter-=1
done
if [[ $waiting_counter -eq 0 ]]; then
echo "[WARN] Unable to get storagefile key in consul"
else
curl -s --request DELETE http://10.0.1.2:8500/v1/kv/traefik/acme/storagefile
ret=$1
if [[ $ret -ne 0 ]]; then
echo "[ERROR] Unable to delete storagefile key from consul kv."
fi
fi
delete_services traefik-storeconfig
}
start_storeconfig_etcd3() {
init_acme_json
# Create traefik.toml with consul provider
cp $basedir/traefik.toml.tmpl $basedir/traefik.toml
echo '
@@ -117,20 +91,15 @@ start_storeconfig_etcd3() {
prefix = "/traefik"
useAPIV3 = true' >> $basedir/traefik.toml
up_environment traefik-storeconfig
rm -f $basedir/traefik.toml && rm -f $basedir/acme.json
# Delete acme-storage-file key
rm -f $basedir/traefik.toml
waiting_counter=5
# Not start Traefik store config if consul is not started
# Don't start Traefik store config if ETCD3 is not started
echo "Delete storage file key..."
while [[ $(docker-compose -f $doc_file up --exit-code-from etcdctl-ping etcdctl-ping &>/dev/null) -ne 0 && $waiting_counter -gt 0 ]]; do
sleep 5
let waiting_counter-=1
done
# Not start Traefik store config if consul is not started
echo "Delete storage file key from ETCD3..."
up_environment etcdctl-rm && \
delete_services etcdctl-rm traefik-storeconfig etcdctl-ping
delete_services traefik-storeconfig etcdctl-ping
}
start_traefik() {

View File

@@ -12,7 +12,6 @@ defaultEntryPoints = ["http", "https"]
[acme]
email = "test@traefik.io"
storage = "traefik/acme/account"
storageFile = "/acme.json"
entryPoint = "https"
OnHostRule = true
caServer = "http://traefik.boulder.com:4000/directory"

12
glide.lock generated
View File

@@ -1,5 +1,5 @@
hash: 4595cac0a682ce8e36b78630f12a3cfab75307fc58cb4a1f5e416017d3ae20d6
updated: 2017-11-30T10:34:41.246378337+01:00
hash: 2ca4d2b4f55342c6a722f70e0ef2e85ac2a38d8395dc206ad3f71a785b9f050f
updated: 2017-12-15T10:34:41.246378337+01:00
imports:
- name: cloud.google.com/go
version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c
@@ -94,7 +94,7 @@ imports:
- name: github.com/containous/staert
version: af517d5b70db9c4b0505e0144fcc62b054057d2a
- name: github.com/containous/traefik-extra-service-fabric
version: 8076098dbfe814cba9e895ecbd896f1896b6b2d5
version: ca1fb57108293caad285b1c366b763f6c6ab71c9
- name: github.com/coreos/bbolt
version: 3c6cbfb299c11444eb2f8c9d48f0d2ce09157423
- name: github.com/coreos/etcd
@@ -261,7 +261,7 @@ imports:
- name: github.com/fatih/color
version: 62e9147c64a1ed519147b62a56a14e83e2be02c1
- name: github.com/gambol99/go-marathon
version: dd6cbd4c2d71294a19fb89158f2a00d427f174ab
version: 03b46169666c53b9cc953b875ac5714e5103e064
- name: github.com/ghodss/yaml
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
- name: github.com/go-ini/ini
@@ -348,7 +348,7 @@ imports:
subpackages:
- lib
- name: github.com/jjcollinge/servicefabric
version: 93a44e59fc887cda489913c6fc5bda834989f3bd
version: 8026935326c842b71dee8e2329c1fda41a7a92f4
- name: github.com/jmespath/go-jmespath
version: bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
- name: github.com/jonboulle/clockwork
@@ -520,7 +520,7 @@ imports:
- name: github.com/VividCortex/gohistogram
version: 51564d9861991fb0ad0f531c99ef602d0f9866e6
- name: github.com/vulcand/oxy
version: 7b6e758ab449705195df638765c4ca472248908a
version: 812cebb8c764f2a78cb806267648b8728b4599ad
repo: https://github.com/containous/oxy.git
vcs: git
subpackages:

View File

@@ -12,7 +12,7 @@ import:
- package: github.com/cenk/backoff
- package: github.com/containous/flaeg
- package: github.com/containous/traefik-extra-service-fabric
version: ^v1.0.1
version: v1.0.5
- package: github.com/vulcand/oxy
version: 7b6e758ab449705195df638765c4ca472248908a
repo: https://github.com/containous/oxy.git

View File

@@ -419,6 +419,46 @@ func (s *ConsulCatalogSuite) TestCircuitBreaker(c *check.C) {
c.Assert(err, checker.IsNil)
}
func (s *ConsulCatalogSuite) TestRefreshConfigPortChange(c *check.C) {
cmd, display := s.traefikCmd(
withConfigFile("fixtures/consul_catalog/simple.toml"),
"--consulCatalog",
"--consulCatalog.exposedByDefault=false",
"--consulCatalog.watch=true",
"--consulCatalog.endpoint="+s.consulIP+":8500",
"--consulCatalog.domain=consul.localhost")
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
nginx := s.composeProject.Container(c, "nginx1")
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 81, []string{"name=nginx1", "traefik.enable=true"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.consul.localhost"
err = try.Request(req, 20*time.Second, try.StatusCodeIs(http.StatusBadGateway))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 5*time.Second, try.BodyContains("nginx1"))
c.Assert(err, checker.IsNil)
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"name=nginx1", "traefik.enable=true"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 60*time.Second, try.BodyContains("nginx1"))
c.Assert(err, checker.IsNil)
err = try.Request(req, 20*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil)
}
func (s *ConsulCatalogSuite) TestRetryWithConsulServer(c *check.C) {
//Scale consul to 0 to be able to start traefik before and test retry
s.composeProject.Scale(c, "consul", 0)

View File

@@ -573,6 +573,113 @@ func (s *Etcd3Suite) TestSNIDynamicTlsConfig(c *check.C) {
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
req.Header.Set("Accept", "*/*")
resp, err = client.Do(req)
c.Assert(err, checker.IsNil)
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
c.Assert(cn, checker.Equals, "snitest.org")
}
func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) {
// start Træfik
cmd, display := s.traefikCmd(
withConfigFile("fixtures/etcd/simple_https.toml"),
"--etcd",
"--etcd.endpoint="+ipEtcd+":4001",
"--etcd.useAPIV3=true")
defer display(c)
// prepare to config
snitestComCert, err := ioutil.ReadFile("fixtures/https/snitest.com.cert")
c.Assert(err, checker.IsNil)
snitestComKey, err := ioutil.ReadFile("fixtures/https/snitest.com.key")
c.Assert(err, checker.IsNil)
backend1 := map[string]string{
"/traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
"/traefik/backends/backend1/servers/server1/url": "http://" + ipWhoami01 + ":80",
"/traefik/backends/backend1/servers/server1/weight": "1",
"/traefik/backends/backend1/servers/server2/url": "http://" + ipWhoami02 + ":80",
"/traefik/backends/backend1/servers/server2/weight": "1",
}
frontend1 := map[string]string{
"/traefik/frontends/frontend1/backend": "backend1",
"/traefik/frontends/frontend1/entrypoints": "https",
"/traefik/frontends/frontend1/priority": "1",
"/traefik/frontends/frontend1/routes/test_1/rule": "Host:snitest.com",
}
tlsconfigure1 := map[string]string{
"/traefik/tlsconfiguration/snitestcom/entrypoints": "https",
"/traefik/tlsconfiguration/snitestcom/certificate/keyfile": string(snitestComKey),
"/traefik/tlsconfiguration/snitestcom/certificate/certfile": string(snitestComCert),
}
// config backends,frontends and first tls keypair
for key, value := range backend1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range frontend1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range tlsconfigure1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
tr1 := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.com",
},
}
// wait for etcd
err = try.Do(60*time.Second, func() error {
_, err := s.kv.Get("/traefik/tlsconfiguration/snitestcom/certificate/keyfile", nil)
return err
})
c.Assert(err, checker.IsNil)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// wait for Træfik
err = try.GetRequest(traefikWebEtcdURL+"api/providers", 60*time.Second, try.BodyContains(string("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h")))
c.Assert(err, checker.IsNil)
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
c.Assert(err, checker.IsNil)
client := &http.Client{Transport: tr1}
req.Host = tr1.TLSClientConfig.ServerName
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
req.Header.Set("Accept", "*/*")
var resp *http.Response
resp, err = client.Do(req)
c.Assert(err, checker.IsNil)
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
c.Assert(cn, checker.Equals, "snitest.com")
// now we delete the tls cert/key pairs,so the endpoint show use default cert/key pair
for key := range tlsconfigure1 {
err := s.kv.Delete(key)
c.Assert(err, checker.IsNil)
}
// waiting for Træfik to pull configuration
err = try.GetRequest(traefikWebEtcdURL+"api/providers", 30*time.Second, try.BodyNotContains("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h"))
c.Assert(err, checker.IsNil)
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
c.Assert(err, checker.IsNil)
client = &http.Client{Transport: tr1}
req.Host = tr1.TLSClientConfig.ServerName
req.Header.Set("Host", tr1.TLSClientConfig.ServerName)
req.Header.Set("Accept", "*/*")
resp, err = client.Do(req)
c.Assert(err, checker.IsNil)
cn = resp.TLS.PeerCertificates[0].Subject.CommonName
c.Assert(cn, checker.Equals, "TRAEFIK DEFAULT CERT")
}

View File

@@ -6,6 +6,9 @@ defaultEntryPoints = ["https"]
[entryPoints.https]
address = ":4443"
[entryPoints.https.tls]
[entryPoints.https02]
address = ":8443"
[entryPoints.https02.tls]
[web]
address = ":8080"

View File

@@ -353,7 +353,7 @@ func startTestServer(port string, statusCode int) (ts *httptest.Server) {
return ts
}
// TestWithSNIConfigRoute involves a client sending HTTPS requests with
// TestWithSNIDynamicConfigRouteWithNoChange involves a client sending HTTPS requests with
// SNI hostnames of "snitest.org" and "snitest.com". The test verifies
// that traefik routes the requests to the expected backends thanks to given certificate if possible
// otherwise thanks to the default one.
@@ -424,7 +424,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange(c *check.C) {
c.Assert(resp.StatusCode, checker.Equals, http.StatusNoContent)
}
// TestWithSNIConfigRoute involves a client sending HTTPS requests with
// TestWithSNIDynamicConfigRouteWithChange involves a client sending HTTPS requests with
// SNI hostnames of "snitest.org" and "snitest.com". The test verifies
// that traefik updates its configuration when the HTTPS configuration is modified and
// it routes the requests to the expected backends thanks to given certificate if possible
@@ -479,7 +479,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) {
req.Header.Set("Accept", "*/*")
// Change certificates configuration file content
modifyCertificateConfFileContent(c, tr1.TLSClientConfig.ServerName, dynamicConfFileName)
modifyCertificateConfFileContent(c, tr1.TLSClientConfig.ServerName, dynamicConfFileName, "https")
var resp *http.Response
err = try.Do(30*time.Second, func() error {
resp, err = client.Do(req)
@@ -525,30 +525,121 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) {
c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound)
}
// modifyCertificateConfFileContent replaces the content of a HTTPS configuration file.
func modifyCertificateConfFileContent(c *check.C, certFileName, confFileName string) {
tlsConf := types.Configuration{
TLSConfiguration: []*traefikTls.Configuration{
{
Certificate: &traefikTls.Certificate{
CertFile: traefikTls.FileOrContent("fixtures/https/" + certFileName + ".cert"),
KeyFile: traefikTls.FileOrContent("fixtures/https/" + certFileName + ".key"),
},
EntryPoints: []string{"https"},
},
// TestWithSNIDynamicConfigRouteWithChangeForEmptyTlsConfiguration involves a client sending HTTPS requests with
// SNI hostnames of "snitest.org" and "snitest.com". The test verifies
// that traefik updates its configuration when the HTTPS configuration is modified, even if it totally deleted, and
// it routes the requests to the expected backends thanks to given certificate if possible
// otherwise thanks to the default one.
func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion(c *check.C) {
dynamicConfFileName := s.adaptFile(c, "fixtures/https/dynamic_https.toml", struct{}{})
defer os.Remove(dynamicConfFileName)
confFileName := s.adaptFile(c, "fixtures/https/dynamic_https_sni.toml", struct {
DynamicConfFileName string
}{
DynamicConfFileName: dynamicConfFileName,
})
defer os.Remove(confFileName)
cmd, display := s.traefikCmd(withConfigFile(confFileName))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
tr2 := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.org",
},
}
var confBuffer bytes.Buffer
e := toml.NewEncoder(&confBuffer)
err := e.Encode(tlsConf)
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:"+tr2.TLSClientConfig.ServerName))
c.Assert(err, checker.IsNil)
backend2 := startTestServer("9020", http.StatusResetContent)
defer backend2.Close()
err = try.GetRequest(backend2.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusResetContent))
c.Assert(err, checker.IsNil)
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
client := &http.Client{Transport: tr2}
req.Host = tr2.TLSClientConfig.ServerName
req.Header.Set("Host", tr2.TLSClientConfig.ServerName)
req.Header.Set("Accept", "*/*")
var resp *http.Response
err = try.Do(30*time.Second, func() error {
resp, err = client.Do(req)
// /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\
req.Close = true
if err != nil {
return err
}
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
if cn != tr2.TLSClientConfig.ServerName {
return fmt.Errorf("domain %s found in place of %s", cn, tr2.TLSClientConfig.ServerName)
}
return nil
})
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, http.StatusResetContent)
// Change certificates configuration file content
modifyCertificateConfFileContent(c, "", dynamicConfFileName, "https02")
err = try.Do(60*time.Second, func() error {
resp, err = client.Do(req)
// /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\
req.Close = true
if err != nil {
return err
}
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
if cn == tr2.TLSClientConfig.ServerName {
return fmt.Errorf("domain %s found in place of default one", tr2.TLSClientConfig.ServerName)
}
return nil
})
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound)
}
// modifyCertificateConfFileContent replaces the content of a HTTPS configuration file.
func modifyCertificateConfFileContent(c *check.C, certFileName, confFileName, entryPoint string) {
f, err := os.OpenFile("./"+confFileName, os.O_WRONLY, os.ModeExclusive)
c.Assert(err, checker.IsNil)
defer func() {
f.Close()
}()
f.Truncate(0)
_, err = f.Write(confBuffer.Bytes())
c.Assert(err, checker.IsNil)
// If certificate file is not provided, just truncate the configuration file
if len(certFileName) > 0 {
tlsConf := types.Configuration{
TLSConfiguration: []*traefikTls.Configuration{
{
Certificate: &traefikTls.Certificate{
CertFile: traefikTls.FileOrContent("fixtures/https/" + certFileName + ".cert"),
KeyFile: traefikTls.FileOrContent("fixtures/https/" + certFileName + ".key"),
},
EntryPoints: []string{entryPoint},
},
},
}
var confBuffer bytes.Buffer
e := toml.NewEncoder(&confBuffer)
err := e.Encode(tlsConf)
c.Assert(err, checker.IsNil)
_, err = f.Write(confBuffer.Bytes())
c.Assert(err, checker.IsNil)
}
}

View File

@@ -34,6 +34,25 @@ func BodyContains(values ...string) ResponseCondition {
}
}
// BodyNotContains returns a retry condition function.
// The condition returns an error if the request body contain one of the given
// strings.
func BodyNotContains(values ...string) ResponseCondition {
return func(res *http.Response) error {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %s", err)
}
for _, value := range values {
if strings.Contains(string(body), value) {
return fmt.Errorf("find '%s' in body '%s'", value, string(body))
}
}
return nil
}
}
// BodyContainsOr returns a retry condition function.
// The condition returns an error if the request body does not contain one of the given
// strings.

View File

@@ -12,6 +12,9 @@ type AddPrefix struct {
func (s *AddPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.URL.Path = s.Prefix + r.URL.Path
if r.URL.RawPath != "" {
r.URL.RawPath = s.Prefix + r.URL.RawPath
}
r.RequestURI = r.URL.RequestURI()
s.Handler.ServeHTTP(w, r)
}

View File

@@ -9,21 +9,56 @@ import (
)
func TestAddPrefix(t *testing.T) {
path := "/bar"
prefix := "/foo"
var expectedPath string
handler := &AddPrefix{
Prefix: prefix,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
expectedPath = r.URL.Path
}),
tests := []struct {
desc string
prefix string
path string
expectedPath string
expectedRawPath string
}{
{
desc: "regular path",
prefix: "/a",
path: "/b",
expectedPath: "/a/b",
},
{
desc: "raw path is supported",
prefix: "/a",
path: "/b%2Fc",
expectedPath: "/a/b/c",
expectedRawPath: "/a/b%2Fc",
},
}
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+path, nil)
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler.ServeHTTP(nil, req)
var actualPath, actualRawPath, requestURI string
handler := &AddPrefix{
Prefix: test.prefix,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
actualPath = r.URL.Path
actualRawPath = r.URL.RawPath
requestURI = r.RequestURI
}),
}
assert.Equal(t, expectedPath, "/foo/bar", "Unexpected path.")
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+test.path, nil)
handler.ServeHTTP(nil, req)
assert.Equal(t, test.expectedPath, actualPath, "Unexpected path.")
assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.")
expectedURI := test.expectedPath
if test.expectedRawPath != "" {
// go HTTP uses the raw path when existent in the RequestURI
expectedURI = test.expectedRawPath
}
assert.Equal(t, expectedURI, requestURI, "Unexpected request URI.")
})
}
}

View File

@@ -52,18 +52,18 @@ func NewErrorPagesHandler(errorPage types.ErrorPage, backendURL string) (*ErrorP
}
func (ep *ErrorPagesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
recorder := newRetryResponseRecorder()
recorder.responseWriter = w
recorder := newRetryResponseRecorder(w)
next.ServeHTTP(recorder, req)
w.WriteHeader(recorder.Code)
w.WriteHeader(recorder.GetCode())
//check the recorder code against the configured http status code ranges
for _, block := range ep.HTTPCodeRanges {
if recorder.Code >= block[0] && recorder.Code <= block[1] {
log.Errorf("Caught HTTP Status Code %d, returning error page", recorder.Code)
finalURL := strings.Replace(ep.BackendURL, "{status}", strconv.Itoa(recorder.Code), -1)
if recorder.GetCode() >= block[0] && recorder.GetCode() <= block[1] {
log.Errorf("Caught HTTP Status Code %d, returning error page", recorder.GetCode())
finalURL := strings.Replace(ep.BackendURL, "{status}", strconv.Itoa(recorder.GetCode()), -1)
if newReq, err := http.NewRequest(http.MethodGet, finalURL, nil); err != nil {
w.Write([]byte(http.StatusText(recorder.Code)))
w.Write([]byte(http.StatusText(recorder.GetCode())))
} else {
ep.errorPageForwarder.ServeHTTP(w, newReq)
}
@@ -73,5 +73,5 @@ func (ep *ErrorPagesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request,
//did not catch a configured status code so proceed with the request
utils.CopyHeaders(w.Header(), recorder.Header())
w.Write(recorder.Body.Bytes())
w.Write(recorder.GetBody().Bytes())
}

View File

@@ -24,14 +24,16 @@ type HeaderStruct struct {
}
// NewHeaderFromStruct constructs a new header instance from supplied frontend header struct.
func NewHeaderFromStruct(headers types.Headers) *HeaderStruct {
o := HeaderOptions{
CustomRequestHeaders: headers.CustomRequestHeaders,
CustomResponseHeaders: headers.CustomResponseHeaders,
func NewHeaderFromStruct(headers *types.Headers) *HeaderStruct {
if headers == nil || !headers.HasCustomHeadersDefined() {
return nil
}
return &HeaderStruct{
opt: o,
opt: HeaderOptions{
CustomRequestHeaders: headers.CustomRequestHeaders,
CustomResponseHeaders: headers.CustomResponseHeaders,
},
}
}

View File

@@ -17,16 +17,14 @@ var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// newHeader constructs a new header instance with supplied options.
func newHeader(options ...HeaderOptions) *HeaderStruct {
var o HeaderOptions
var opt HeaderOptions
if len(options) == 0 {
o = HeaderOptions{}
opt = HeaderOptions{}
} else {
o = options[0]
opt = options[0]
}
return &HeaderStruct{
opt: o,
}
return &HeaderStruct{opt: opt}
}
func TestNoConfig(t *testing.T) {

View File

@@ -14,7 +14,7 @@ import (
// Compile time validation responseRecorder implements http interfaces correctly.
var (
_ Stateful = &retryResponseRecorder{}
_ Stateful = &retryResponseRecorderWithCloseNotify{}
)
// Retry is a middleware that retries requests
@@ -48,21 +48,21 @@ func (retry *Retry) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
// when proxying the HTTP requests to the backends. This happens in the custom RecordingErrorHandler.
newCtx := context.WithValue(r.Context(), defaultNetErrCtxKey, &netErrorOccurred)
recorder := newRetryResponseRecorder()
recorder.responseWriter = rw
recorder := newRetryResponseRecorder(rw)
retry.next.ServeHTTP(recorder, r.WithContext(newCtx))
// It's a stream request and the body gets already sent to the client.
// Therefore we should not send the response a second time.
if recorder.streamingResponseStarted {
if recorder.IsStreamingResponseStarted() {
recorder.Flush()
break
}
if !netErrorOccurred || attempts >= retry.attempts {
utils.CopyHeaders(rw.Header(), recorder.Header())
rw.WriteHeader(recorder.Code)
rw.Write(recorder.Body.Bytes())
rw.WriteHeader(recorder.GetCode())
rw.Write(recorder.GetBody().Bytes())
break
}
attempts++
@@ -114,9 +114,31 @@ func (l RetryListeners) Retried(req *http.Request, attempt int) {
}
}
// retryResponseRecorder is an implementation of http.ResponseWriter that
type retryResponseRecorder interface {
http.ResponseWriter
http.Flusher
GetCode() int
GetBody() *bytes.Buffer
IsStreamingResponseStarted() bool
}
// newRetryResponseRecorder returns an initialized retryResponseRecorder.
func newRetryResponseRecorder(rw http.ResponseWriter) retryResponseRecorder {
recorder := &retryResponseRecorderWithoutCloseNotify{
HeaderMap: make(http.Header),
Body: new(bytes.Buffer),
Code: http.StatusOK,
responseWriter: rw,
}
if _, ok := rw.(http.CloseNotifier); ok {
return &retryResponseRecorderWithCloseNotify{recorder}
}
return recorder
}
// retryResponseRecorderWithoutCloseNotify is an implementation of http.ResponseWriter that
// records its mutations for later inspection.
type retryResponseRecorder struct {
type retryResponseRecorderWithoutCloseNotify struct {
Code int // the HTTP response code from WriteHeader
HeaderMap http.Header // the HTTP response headers
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
@@ -126,17 +148,19 @@ type retryResponseRecorder struct {
streamingResponseStarted bool
}
// newRetryResponseRecorder returns an initialized retryResponseRecorder.
func newRetryResponseRecorder() *retryResponseRecorder {
return &retryResponseRecorder{
HeaderMap: make(http.Header),
Body: new(bytes.Buffer),
Code: http.StatusOK,
}
type retryResponseRecorderWithCloseNotify struct {
*retryResponseRecorderWithoutCloseNotify
}
// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone
// away.
func (rw *retryResponseRecorderWithCloseNotify) CloseNotify() <-chan bool {
return rw.responseWriter.(http.CloseNotifier).CloseNotify()
}
// Header returns the response headers.
func (rw *retryResponseRecorder) Header() http.Header {
func (rw *retryResponseRecorderWithoutCloseNotify) Header() http.Header {
m := rw.HeaderMap
if m == nil {
m = make(http.Header)
@@ -145,8 +169,20 @@ func (rw *retryResponseRecorder) Header() http.Header {
return m
}
func (rw *retryResponseRecorderWithoutCloseNotify) GetCode() int {
return rw.Code
}
func (rw *retryResponseRecorderWithoutCloseNotify) GetBody() *bytes.Buffer {
return rw.Body
}
func (rw *retryResponseRecorderWithoutCloseNotify) IsStreamingResponseStarted() bool {
return rw.streamingResponseStarted
}
// Write always succeeds and writes to rw.Body, if not nil.
func (rw *retryResponseRecorder) Write(buf []byte) (int, error) {
func (rw *retryResponseRecorderWithoutCloseNotify) Write(buf []byte) (int, error) {
if rw.err != nil {
return 0, rw.err
}
@@ -154,24 +190,17 @@ func (rw *retryResponseRecorder) Write(buf []byte) (int, error) {
}
// WriteHeader sets rw.Code.
func (rw *retryResponseRecorder) WriteHeader(code int) {
func (rw *retryResponseRecorderWithoutCloseNotify) WriteHeader(code int) {
rw.Code = code
}
// Hijack hijacks the connection
func (rw *retryResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
func (rw *retryResponseRecorderWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return rw.responseWriter.(http.Hijacker).Hijack()
}
// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone
// away.
func (rw *retryResponseRecorder) CloseNotify() <-chan bool {
return rw.responseWriter.(http.CloseNotifier).CloseNotify()
}
// Flush sends any buffered data to the client.
func (rw *retryResponseRecorder) Flush() {
func (rw *retryResponseRecorderWithoutCloseNotify) Flush() {
if !rw.streamingResponseStarted {
utils.CopyHeaders(rw.responseWriter.Header(), rw.Header())
rw.responseWriter.WriteHeader(rw.Code)

View File

@@ -7,6 +7,8 @@ import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRetry(t *testing.T) {
@@ -134,3 +136,69 @@ type countingRetryListener struct {
func (l *countingRetryListener) Retried(req *http.Request, attempt int) {
l.timesCalled++
}
func TestRetryWithFlush(t *testing.T) {
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(200)
rw.Write([]byte("FULL "))
rw.(http.Flusher).Flush()
rw.Write([]byte("DATA"))
})
retry := NewRetry(1, next, &countingRetryListener{})
responseRecorder := httptest.NewRecorder()
retry.ServeHTTP(responseRecorder, &http.Request{})
if responseRecorder.Body.String() != "FULL DATA" {
t.Errorf("Wrong body %q want %q", responseRecorder.Body.String(), "FULL DATA")
}
}
func TestNewRetryResponseRecorder(t *testing.T) {
testCases := []struct {
desc string
rw http.ResponseWriter
expected http.ResponseWriter
}{
{
desc: "Without Close Notify",
rw: httptest.NewRecorder(),
expected: &retryResponseRecorderWithoutCloseNotify{},
},
{
desc: "With Close Notify",
rw: &mockRWCloseNotify{},
expected: &retryResponseRecorderWithCloseNotify{},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rec := newRetryResponseRecorder(test.rw)
assert.IsType(t, rec, test.expected)
})
}
}
type mockRWCloseNotify struct{}
func (m *mockRWCloseNotify) CloseNotify() <-chan bool {
panic("implement me")
}
func (m *mockRWCloseNotify) Header() http.Header {
panic("implement me")
}
func (m *mockRWCloseNotify) Write([]byte) (int, error) {
panic("implement me")
}
func (m *mockRWCloseNotify) WriteHeader(int) {
panic("implement me")
}

View File

@@ -6,7 +6,11 @@ import (
)
// NewSecure constructs a new Secure instance with supplied options.
func NewSecure(headers types.Headers) *secure.Secure {
func NewSecure(headers *types.Headers) *secure.Secure {
if headers == nil || !headers.HasSecureHeadersDefined() {
return nil
}
opt := secure.Options{
AllowedHosts: headers.AllowedHosts,
HostsProxyHeaders: headers.HostsProxyHeaders,

View File

@@ -7,24 +7,14 @@ dev_addr: 0.0.0.0:8000
repo_name: 'GitHub'
repo_url: 'https://github.com/containous/traefik'
# Documentation
docs_dir: 'docs'
#theme: united
#theme: readthedocs
theme: 'material'
#theme: bootstrap
site_favicon: 'img/traefik.icon.png'
copyright: "Copyright &copy; 2016-2017 Containous SAS"
google_analytics:
- 'UA-51880359-3'
- 'docs.traefik.io'
# Options
extra:
theme:
name: 'material'
custom_dir: 'docs/theme'
language: en
include_sidebar: true
favicon: img/traefik.icon.png
logo: img/traefik.logo.png
palette:
primary: 'blue'
@@ -37,7 +27,16 @@ extra:
i18n:
prev: 'Previous'
next: 'Next'
copyright: "Copyright &copy; 2016-2018 Containous SAS"
google_analytics:
- 'UA-51880359-3'
- 'docs.traefik.io'
# Options
# Comment because the call of the CDN is very slow.
#extra:
# social:
# - type: 'github'
# link: 'https://github.com/containous/traefik'
@@ -48,8 +47,6 @@ extra:
# - type: 'twitter'
# link: 'https://twitter.com/traefikproxy'
theme_dir: docs/theme/
extra_css:
- theme/styles/extra.css
- theme/styles/atom-one-light.css
@@ -60,8 +57,8 @@ extra_javascript:
markdown_extensions:
- admonition
# - codehilite(guess_lang=false)
- toc(permalink=true)
- toc:
permalink: true
# Page tree
pages:
@@ -74,6 +71,7 @@ pages:
- 'Backend: Web': 'configuration/backends/web.md'
- 'Backend: BoltDB': 'configuration/backends/boltdb.md'
- 'Backend: Consul': 'configuration/backends/consul.md'
- 'Backend: Consul Catalog': 'configuration/backends/consulcatalog.md'
- 'Backend: Docker': 'configuration/backends/docker.md'
- 'Backend: DynamoDB': 'configuration/backends/dynamodb.md'
- 'Backend: ECS': 'configuration/backends/ecs.md'

View File

@@ -78,8 +78,11 @@ func (a nodeSorter) Less(i int, j int) bool {
}
func hasChanged(current map[string]Service, previous map[string]Service) bool {
if len(current) != len(previous) {
return true
}
addedServiceKeys, removedServiceKeys := getChangedServiceKeys(current, previous)
return len(removedServiceKeys) > 0 || len(addedServiceKeys) > 0 || hasNodeOrTagsChanged(current, previous)
return len(removedServiceKeys) > 0 || len(addedServiceKeys) > 0 || hasServiceChanged(current, previous)
}
func getChangedServiceKeys(current map[string]Service, previous map[string]Service) ([]string, []string) {
@@ -92,20 +95,24 @@ func getChangedServiceKeys(current map[string]Service, previous map[string]Servi
return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string)
}
func hasNodeOrTagsChanged(current map[string]Service, previous map[string]Service) bool {
var added []string
var removed []string
func hasServiceChanged(current map[string]Service, previous map[string]Service) bool {
for key, value := range current {
if prevValue, ok := previous[key]; ok {
addedNodesKeys, removedNodesKeys := getChangedStringKeys(value.Nodes, prevValue.Nodes)
added = append(added, addedNodesKeys...)
removed = append(removed, removedNodesKeys...)
if len(addedNodesKeys) > 0 || len(removedNodesKeys) > 0 {
return true
}
addedTagsKeys, removedTagsKeys := getChangedStringKeys(value.Tags, prevValue.Tags)
added = append(added, addedTagsKeys...)
removed = append(removed, removedTagsKeys...)
if len(addedTagsKeys) > 0 || len(removedTagsKeys) > 0 {
return true
}
addedPortsKeys, removedPortsKeys := getChangedIntKeys(value.Ports, prevValue.Ports)
if len(addedPortsKeys) > 0 || len(removedPortsKeys) > 0 {
return true
}
}
}
return len(added) > 0 || len(removed) > 0
return false
}
func getChangedStringKeys(currState []string, prevState []string) ([]string, []string) {
@@ -118,6 +125,16 @@ func getChangedStringKeys(currState []string, prevState []string) ([]string, []s
return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string)
}
func getChangedIntKeys(currState []int, prevState []int) ([]int, []int) {
currKeySet := fun.Set(currState).(map[int]bool)
prevKeySet := fun.Set(prevState).(map[int]bool)
addedKeys := fun.Difference(currKeySet, prevKeySet).(map[int]bool)
removedKeys := fun.Difference(prevKeySet, currKeySet).(map[int]bool)
return fun.Keys(addedKeys).([]int), fun.Keys(removedKeys).([]int)
}
func (p *CatalogProvider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[string][]string, errorCh chan<- error) {
health := p.client.Health()
catalog := p.client.Catalog()
@@ -194,6 +211,7 @@ type Service struct {
Name string
Tags []string
Nodes []string
Ports []int
}
func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh chan<- map[string][]string, errorCh chan<- error) {
@@ -235,14 +253,17 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c
return
}
nodesID := getServiceIds(nodes)
ports := getServicePorts(nodes)
if service, ok := current[key]; ok {
service.Tags = value
service.Nodes = nodesID
service.Ports = ports
} else {
service := Service{
Name: key,
Tags: value,
Nodes: nodesID,
Ports: ports,
}
current[key] = service
}
@@ -262,11 +283,19 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c
func getServiceIds(services []*api.CatalogService) []string {
var serviceIds []string
for _, service := range services {
serviceIds = append(serviceIds, service.ServiceID)
serviceIds = append(serviceIds, service.ID)
}
return serviceIds
}
func getServicePorts(services []*api.CatalogService) []int {
var servicePorts []int
for _, service := range services {
servicePorts = append(servicePorts, service.ServicePort)
}
return servicePorts
}
func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) {
health := p.client.Health()
opts := &api.QueryOptions{}
@@ -279,7 +308,7 @@ func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) {
return p.nodeFilter(service, node)
}, data).([]*api.ServiceEntry)
//Merge tags of nodes matching constraints, in a single slice.
// Merge tags of nodes matching constraints, in a single slice.
tags := fun.Foldl(func(node *api.ServiceEntry, set []string) []string {
return fun.Keys(fun.Union(
fun.Set(set),
@@ -401,7 +430,7 @@ func (p *CatalogProvider) getBasicAuth(tags []string) []string {
}
func (p *CatalogProvider) getSticky(tags []string) string {
stickyTag := p.getTag(types.LabelBackendLoadbalancerSticky, tags, "")
stickyTag := p.getAttribute(types.SuffixBackendLoadBalancerSticky, tags, "")
if len(stickyTag) > 0 {
log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
} else {
@@ -411,12 +440,12 @@ func (p *CatalogProvider) getSticky(tags []string) string {
}
func (p *CatalogProvider) hasStickinessLabel(tags []string) bool {
stickinessTag := p.getTag(types.LabelBackendLoadbalancerStickiness, tags, "")
stickinessTag := p.getAttribute(types.SuffixBackendLoadBalancerStickiness, tags, "")
return len(stickinessTag) > 0 && strings.EqualFold(strings.TrimSpace(stickinessTag), "true")
}
func (p *CatalogProvider) getStickinessCookieName(tags []string) string {
return p.getTag(types.LabelBackendLoadbalancerStickinessCookieName, tags, "")
return p.getAttribute(types.SuffixBackendLoadBalancerStickinessCookieName, tags, "")
}
func (p *CatalogProvider) getAttribute(name string, tags []string, defaultValue string) string {
@@ -480,8 +509,8 @@ func (p *CatalogProvider) buildConfig(catalog []catalogUpdate) *types.Configurat
"hasMaxconnAttributes": p.hasMaxconnAttributes,
}
allNodes := []*api.ServiceEntry{}
services := []*serviceUpdate{}
var allNodes []*api.ServiceEntry
var services []*serviceUpdate
for _, info := range catalog {
if len(info.Nodes) > 0 {
services = append(services, info.Service)
@@ -519,7 +548,7 @@ func (p *CatalogProvider) hasMaxconnAttributes(attributes []string) bool {
func (p *CatalogProvider) getNodes(index map[string][]string) ([]catalogUpdate, error) {
visited := make(map[string]bool)
nodes := []catalogUpdate{}
var nodes []catalogUpdate
for service := range index {
name := strings.ToLower(service)
if !strings.Contains(name, " ") && !visited[name] {
@@ -555,7 +584,7 @@ func (p *CatalogProvider) watch(configurationChan chan<- types.ConfigMessage, st
return nil
case index, ok := <-watchCh:
if !ok {
return errors.New("Consul service list nil")
return errors.New("consul service list nil")
}
log.Debug("List of services changed")
nodes, err := p.getNodes(index)

View File

@@ -12,14 +12,6 @@ import (
)
func TestConsulCatalogGetFrontendRule(t *testing.T) {
provider := &CatalogProvider{
Domain: "localhost",
Prefix: "traefik",
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
}
provider.setupFrontEndTemplate()
testCases := []struct {
desc string
service serviceUpdate
@@ -71,6 +63,14 @@ func TestConsulCatalogGetFrontendRule(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
provider := &CatalogProvider{
Domain: "localhost",
Prefix: "traefik",
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
}
provider.setupFrontEndTemplate()
actual := provider.getFrontendRule(test.service)
assert.Equal(t, test.expected, actual)
})
@@ -1094,7 +1094,7 @@ func TestConsulCatalogHasNodeOrTagschanged(t *testing.T) {
expected: false,
},
{
desc: "Change detected con tags",
desc: "Change detected on tags",
current: map[string]Service{
"foo-service": {
Name: "foo",
@@ -1111,6 +1111,66 @@ func TestConsulCatalogHasNodeOrTagschanged(t *testing.T) {
},
expected: true,
},
{
desc: "Change detected on ports",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo=bar"},
Ports: []int{80},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo"},
Ports: []int{81},
},
},
expected: true,
},
{
desc: "Change detected on ports",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo=bar"},
Ports: []int{80},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo"},
Ports: []int{81, 82},
},
},
expected: true,
},
{
desc: "No Change detected",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo"},
Ports: []int{80},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo"},
Ports: []int{80},
},
},
expected: false,
},
}
for _, test := range testCases {
@@ -1118,7 +1178,7 @@ func TestConsulCatalogHasNodeOrTagschanged(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := hasNodeOrTagsChanged(test.current, test.previous)
actual := hasServiceChanged(test.current, test.previous)
assert.Equal(t, test.expected, actual)
})
}

View File

@@ -42,7 +42,7 @@ const (
defaultPassHostHeader = "true"
defaultFrontendPriority = "0"
defaultCircuitBreakerExpression = "NetworkErrorRatio() > 1"
defaultFrontendRedirect = ""
defaultFrontendRedirectEntryPoint = ""
defaultBackendLoadBalancerMethod = "wrr"
defaultBackendMaxconnExtractorfunc = "request.host"
)
@@ -276,7 +276,6 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con
"getEntryPoints": getFuncSliceStringLabel(types.LabelFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringLabel(types.LabelFrontendAuthBasic),
"getFrontendRule": p.getFrontendRule,
"getRedirect": getFuncStringLabel(types.LabelFrontendRedirect, ""),
"hasCircuitBreakerLabel": hasLabel(types.LabelBackendCircuitbreakerExpression),
"getCircuitBreakerExpression": getFuncStringLabel(types.LabelBackendCircuitbreakerExpression, defaultCircuitBreakerExpression),
"hasLoadBalancerLabel": hasLoadBalancerLabel,
@@ -289,9 +288,9 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con
"getStickinessCookieName": getFuncStringLabel(types.LabelBackendLoadbalancerStickinessCookieName, ""),
"getIsBackendLBSwarm": getIsBackendLBSwarm,
"getServiceBackend": getServiceBackend,
"getServiceRedirect": getFuncServiceStringLabel(types.SuffixFrontendRedirect, defaultFrontendRedirect),
"getWhitelistSourceRange": getFuncSliceStringLabel(types.LabelTraefikFrontendWhitelistSourceRange),
"hasHeaders": hasHeaders,
"hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeaders),
"getRequestHeaders": getFuncMapLabel(types.LabelFrontendRequestHeaders),
"hasResponseHeaders": hasLabel(types.LabelFrontendResponseHeaders),
@@ -343,6 +342,15 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con
"getServiceFrontendRule": p.getServiceFrontendRule,
"getServicePassHostHeader": getFuncServiceStringLabel(types.SuffixFrontendPassHostHeader, defaultPassHostHeader),
"getServicePriority": getFuncServiceStringLabel(types.SuffixFrontendPriority, defaultFrontendPriority),
"hasRedirect": hasRedirect,
"getRedirectEntryPoint": getFuncStringLabel(types.LabelFrontendRedirectEntryPoint, defaultFrontendRedirectEntryPoint),
"getRedirectRegex": getFuncStringLabel(types.LabelFrontendRedirectRegex, ""),
"getRedirectReplacement": getFuncStringLabel(types.LabelFrontendRedirectReplacement, ""),
"hasServiceRedirect": hasServiceRedirect,
"getServiceRedirectEntryPoint": getFuncServiceStringLabel(types.SuffixFrontendRedirectEntryPoint, defaultFrontendRedirectEntryPoint),
"getServiceRedirectReplacement": getFuncServiceStringLabel(types.SuffixFrontendRedirectReplacement, ""),
"getServiceRedirectRegex": getFuncServiceStringLabel(types.SuffixFrontendRedirectRegex, ""),
}
// filter containers
filteredContainers := fun.Filter(func(container dockerData) bool {
@@ -410,9 +418,9 @@ func getServiceNames(container dockerData) []string {
// Extract backend from labels for a given service and a given docker container
func getServiceBackend(container dockerData, serviceName string) string {
if value, ok := getContainerServiceLabel(container, serviceName, types.SuffixFrontendBackend); ok {
return container.ServiceName + "-" + value
return provider.Normalize(container.ServiceName + "-" + value)
}
return strings.TrimPrefix(container.ServiceName, "/") + "-" + getBackend(container) + "-" + provider.Normalize(serviceName)
return provider.Normalize(container.ServiceName + "-" + getBackend(container) + "-" + serviceName)
}
// Extract rule from labels for a given service and a given docker container
@@ -865,3 +873,38 @@ func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, networkMap m
}
return dockerData
}
// TODO will be rewrite when merge on master
func hasServiceRedirect(container dockerData, serviceName string) bool {
serviceLabels, ok := extractServicesLabels(container.Labels)[serviceName]
if !ok || len(serviceLabels) == 0 {
return false
}
value, ok := serviceLabels[types.SuffixFrontendRedirectEntryPoint]
frep := ok && len(value) > 0
value, ok = serviceLabels[types.SuffixFrontendRedirectRegex]
frrg := ok && len(value) > 0
value, ok = serviceLabels[types.SuffixFrontendRedirectReplacement]
frrp := ok && len(value) > 0
return frep || frrg && frrp
}
// TODO will be rewrite when merge on master
func hasRedirect(container dockerData) bool {
return hasLabel(types.LabelFrontendRedirectEntryPoint)(container) ||
hasLabel(types.LabelFrontendRedirectReplacement)(container) && hasLabel(types.LabelFrontendRedirectRegex)(container)
}
// TODO will be rewrite when merge on master
func hasHeaders(container dockerData) bool {
// LabelPrefix + "frontend.headers.
for key := range container.Labels {
if strings.HasPrefix(key, types.LabelPrefix+"frontend.headers.") {
return true
}
}
return false
}

View File

@@ -712,7 +712,6 @@ func TestDockerLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{
"route-frontend-Host-test-docker-localhost-0": {
Rule: "Host:test.docker.localhost",
@@ -737,10 +736,10 @@ func TestDockerLoadDockerConfig(t *testing.T) {
containerJSON(
name("test1"),
labels(map[string]string{
types.LabelBackend: "foobar",
types.LabelFrontendEntryPoints: "http,https",
types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
types.LabelFrontendRedirect: "https",
types.LabelBackend: "foobar",
types.LabelFrontendEntryPoints: "http,https",
types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
types.LabelFrontendRedirectEntryPoint: "https",
}),
ports(nat.PortMap{
"80/tcp": {},
@@ -764,7 +763,9 @@ func TestDockerLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: "https",
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost",
@@ -776,7 +777,6 @@ func TestDockerLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{
"route-frontend-Host-test2-docker-localhost-1": {
Rule: "Host:test2.docker.localhost",
@@ -824,7 +824,6 @@ func TestDockerLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost",

View File

@@ -8,6 +8,7 @@ import (
"github.com/containous/traefik/types"
docker "github.com/docker/docker/api/types"
"github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert"
)
func TestDockerGetServicePort(t *testing.T) {
@@ -93,12 +94,22 @@ func TestDockerGetServiceBackend(t *testing.T) {
container: containerJSON(name("foo")),
expected: "foo-foo-myservice",
},
{
container: containerJSON(name("foo.bar")),
expected: "foo-bar-foo-bar-myservice",
},
{
container: containerJSON(labels(map[string]string{
types.LabelBackend: "another-backend",
})),
expected: "fake-another-backend-myservice",
},
{
container: containerJSON(labels(map[string]string{
types.LabelBackend: "another.backend",
})),
expected: "fake-another-backend-myservice",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.frontend.backend": "custom-backend",
@@ -136,10 +147,10 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
containerJSON(
name("foo"),
labels(map[string]string{
"traefik.service.port": "2503",
"traefik.service.frontend.entryPoints": "http,https",
"traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
"traefik.service.frontend.redirect": "https",
"traefik.service.port": "2503",
"traefik.service.frontend.entryPoints": "http,https",
"traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
"traefik.service.frontend.redirect.entryPoint": "https",
}),
ports(nat.PortMap{
"80/tcp": {},
@@ -153,7 +164,9 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: "https",
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"service-service": {
Rule: "Host:foo.docker.localhost",
@@ -178,16 +191,16 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
containerJSON(
name("test1"),
labels(map[string]string{
"traefik.service.port": "2503",
"traefik.service.protocol": "https",
"traefik.service.weight": "80",
"traefik.service.frontend.backend": "foobar",
"traefik.service.frontend.passHostHeader": "false",
"traefik.service.frontend.rule": "Path:/mypath",
"traefik.service.frontend.priority": "5000",
"traefik.service.frontend.entryPoints": "http,https,ws",
"traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
"traefik.service.frontend.redirect": "https",
"traefik.service.port": "2503",
"traefik.service.protocol": "https",
"traefik.service.weight": "80",
"traefik.service.frontend.backend": "foobar",
"traefik.service.frontend.passHostHeader": "false",
"traefik.service.frontend.rule": "Path:/mypath",
"traefik.service.frontend.priority": "5000",
"traefik.service.frontend.entryPoints": "http,https,ws",
"traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
"traefik.service.frontend.redirect.entryPoint": "https",
}),
ports(nat.PortMap{
"80/tcp": {},
@@ -214,7 +227,9 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
Priority: 5000,
EntryPoints: []string{"http", "https", "ws"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: "https",
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"service-service": {
Rule: "Path:/mypath",
@@ -226,7 +241,6 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{
"service-anotherservice": {
Rule: "Path:/anotherpath",
@@ -274,9 +288,11 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
actualConfig := provider.loadDockerConfig(dockerDataList)
// Compare backends
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) {
t.Fatalf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends)
}
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) {
t.Fatalf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends)
}

View File

@@ -516,7 +516,6 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{
"route-frontend-Host-test-docker-localhost-0": {
Rule: "Host:test.docker.localhost",
@@ -547,11 +546,11 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
swarmService(
serviceName("test1"),
serviceLabels(map[string]string{
types.LabelPort: "80",
types.LabelBackend: "foobar",
types.LabelFrontendEntryPoints: "http,https",
types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
types.LabelFrontendRedirect: "https",
types.LabelPort: "80",
types.LabelBackend: "foobar",
types.LabelFrontendEntryPoints: "http,https",
types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
types.LabelFrontendRedirectEntryPoint: "https",
}),
withEndpointSpec(modeVIP),
withEndpoint(virtualIP("1", "127.0.0.1/24")),
@@ -572,7 +571,9 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: "https",
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost",
@@ -584,7 +585,6 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{
"route-frontend-Host-test2-docker-localhost-1": {
Rule: "Host:test2.docker.localhost",

View File

@@ -126,7 +126,10 @@ func sendConfigToChannel(configurationChan chan<- types.ConfigMessage, configura
}
func loadFileConfig(filename string) (*types.Configuration, error) {
configuration := new(types.Configuration)
configuration := &types.Configuration{
Frontends: make(map[string]*types.Frontend),
Backends: make(map[string]*types.Backend),
}
if _, err := toml.DecodeFile(filename, configuration); err != nil {
return nil, fmt.Errorf("error reading configuration file: %s", err)
}
@@ -142,9 +145,8 @@ func loadFileConfigFromDirectory(directory string, configuration *types.Configur
if configuration == nil {
configuration = &types.Configuration{
Frontends: make(map[string]*types.Frontend),
Backends: make(map[string]*types.Backend),
TLSConfiguration: make([]*tls.Configuration, 0),
Frontends: make(map[string]*types.Frontend),
Backends: make(map[string]*types.Backend),
}
}

View File

@@ -199,8 +199,6 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
whitelistSourceRange := getSliceAnnotation(i, annotationKubernetesWhitelistSourceRange)
entryPointRedirect, _ := i.Annotations[types.LabelFrontendRedirect]
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient)
if err != nil {
@@ -210,7 +208,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
priority := getPriority(i)
headers := types.Headers{
headers := &types.Headers{
CustomRequestHeaders: getMapAnnotation(i, annotationKubernetesCustomRequestHeaders),
CustomResponseHeaders: getMapAnnotation(i, annotationKubernetesCustomResponseHeaders),
AllowedHosts: getSliceAnnotation(i, annotationKubernetesAllowedHosts),
@@ -241,7 +239,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
Priority: priority,
BasicAuth: basicAuthCreds,
WhitelistSourceRange: whitelistSourceRange,
Redirect: entryPointRedirect,
Redirect: getFrontendRedirect(i),
EntryPoints: entryPoints,
Headers: headers,
}
@@ -366,7 +364,7 @@ func (p *Provider) loadConfig(templateObjects types.Configuration) *types.Config
}
func getSTSSeconds(i *v1beta1.Ingress) int64 {
value, err := strconv.ParseInt(i.ObjectMeta.Annotations[annotationKubernetesHSTSMaxAge], 10, 64)
value, err := strconv.ParseInt(i.Annotations[annotationKubernetesHSTSMaxAge], 10, 64)
if err == nil && value > 0 {
return value
}
@@ -492,3 +490,22 @@ func shouldProcessIngress(ingressClass string) bool {
return false
}
}
// TODO will be rewrite when merge on master
func getFrontendRedirect(i *v1beta1.Ingress) *types.Redirect {
frontendRedirectEntryPoint, ok := i.Annotations[types.LabelFrontendRedirectEntryPoint]
frep := ok && len(frontendRedirectEntryPoint) > 0
frontendRedirectRegex, ok := i.Annotations[types.LabelFrontendRedirectRegex]
frrg := ok && len(frontendRedirectRegex) > 0
frontendRedirectReplacement, ok := i.Annotations[types.LabelFrontendRedirectReplacement]
frrp := ok && len(frontendRedirectReplacement) > 0
if frep || frrg && frrp {
return &types.Redirect{
EntryPoint: frontendRedirectEntryPoint,
Regex: frontendRedirectRegex,
Replacement: frontendRedirectReplacement,
}
}
return nil
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
"k8s.io/client-go/pkg/util/intstr"
@@ -279,6 +280,7 @@ func TestLoadIngresses(t *testing.T) {
"foo/bar": {
Backend: "foo/bar",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -291,6 +293,7 @@ func TestLoadIngresses(t *testing.T) {
"foo/namedthing": {
Backend: "foo/namedthing",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/namedthing": {
Rule: "PathPrefix:/namedthing",
@@ -303,6 +306,7 @@ func TestLoadIngresses(t *testing.T) {
"bar": {
Backend: "bar",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"bar": {
Rule: "Host:bar",
@@ -408,6 +412,7 @@ func TestRuleType(t *testing.T) {
expected := map[string]*types.Frontend{
"host/path": {
Backend: "host/path",
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/path": {
Rule: fmt.Sprintf("%s:/path", test.frontendRuleType),
@@ -493,6 +498,7 @@ func TestGetPassHostHeader(t *testing.T) {
Frontends: map[string]*types.Frontend{
"foo/bar": {
Backend: "foo/bar",
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -579,6 +585,7 @@ func TestGetPassTLSCert(t *testing.T) {
Backend: "foo/bar",
PassTLSCert: true,
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -681,6 +688,7 @@ func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) {
"foo": {
Backend: "foo",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"foo": {
Rule: "Host:foo",
@@ -761,6 +769,7 @@ func TestHostlessIngress(t *testing.T) {
Frontends: map[string]*types.Frontend{
"/bar": {
Backend: "/bar",
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -979,6 +988,7 @@ func TestServiceAnnotations(t *testing.T) {
"foo/bar": {
Backend: "foo/bar",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -991,6 +1001,7 @@ func TestServiceAnnotations(t *testing.T) {
"bar": {
Backend: "bar",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"bar": {
Rule: "Host:bar",
@@ -1266,8 +1277,8 @@ func TestIngressAnnotations(t *testing.T) {
ObjectMeta: v1.ObjectMeta{
Namespace: "testing",
Annotations: map[string]string{
"kubernetes.io/ingress.class": "traefik",
types.LabelFrontendRedirect: "https",
"kubernetes.io/ingress.class": "traefik",
types.LabelFrontendRedirectEntryPoint: "https",
},
},
Spec: v1beta1.IngressSpec{
@@ -1444,6 +1455,7 @@ func TestIngressAnnotations(t *testing.T) {
"foo/bar": {
Backend: "foo/bar",
PassHostHeader: false,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -1452,11 +1464,11 @@ func TestIngressAnnotations(t *testing.T) {
Rule: "Host:foo",
},
},
Redirect: "",
},
"other/stuff": {
Backend: "other/stuff",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/stuff": {
Rule: "PathPrefix:/stuff",
@@ -1465,11 +1477,11 @@ func TestIngressAnnotations(t *testing.T) {
Rule: "Host:other",
},
},
Redirect: "",
},
"other/": {
Backend: "other/",
PassHostHeader: true,
Headers: &types.Headers{},
EntryPoints: []string{"http", "https"},
Routes: map[string]types.Route{
"/": {
@@ -1484,6 +1496,7 @@ func TestIngressAnnotations(t *testing.T) {
Backend: "other/sslstuff",
PassHostHeader: true,
PassTLSCert: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/sslstuff": {
Rule: "PathPrefix:/sslstuff",
@@ -1496,6 +1509,7 @@ func TestIngressAnnotations(t *testing.T) {
"basic/auth": {
Backend: "basic/auth",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/auth": {
Rule: "PathPrefix:/auth",
@@ -1505,11 +1519,11 @@ func TestIngressAnnotations(t *testing.T) {
},
},
BasicAuth: []string{"myUser:myEncodedPW"},
Redirect: "",
},
"redirect/https": {
Backend: "redirect/https",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/https": {
Rule: "PathPrefix:/https",
@@ -1518,12 +1532,15 @@ func TestIngressAnnotations(t *testing.T) {
Rule: "Host:redirect",
},
},
Redirect: "https",
Redirect: &types.Redirect{
EntryPoint: "https",
},
},
"test/whitelist-source-range": {
Backend: "test/whitelist-source-range",
PassHostHeader: true,
Headers: &types.Headers{},
WhitelistSourceRange: []string{
"1.1.1.1/24",
"1234:abcd::42/32",
@@ -1536,11 +1553,11 @@ func TestIngressAnnotations(t *testing.T) {
Rule: "Host:test",
},
},
Redirect: "",
},
"rewrite/api": {
Backend: "rewrite/api",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/api": {
Rule: "PathPrefix:/api;ReplacePath:/",
@@ -1549,7 +1566,6 @@ func TestIngressAnnotations(t *testing.T) {
Rule: "Host:rewrite",
},
},
Redirect: "",
},
},
}
@@ -1643,6 +1659,7 @@ func TestPriorityHeaderValue(t *testing.T) {
Backend: "foo/bar",
PassHostHeader: true,
Priority: 1337,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -1744,6 +1761,7 @@ func TestInvalidPassTLSCertValue(t *testing.T) {
Backend: "foo/bar",
PassTLSCert: false,
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -1844,6 +1862,7 @@ func TestInvalidPassHostHeaderValue(t *testing.T) {
"foo/bar": {
Backend: "foo/bar",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
@@ -2145,6 +2164,7 @@ func TestMissingResources(t *testing.T) {
"fully_working": {
Backend: "fully_working",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"fully_working": {
Rule: "Host:fully_working",
@@ -2154,6 +2174,7 @@ func TestMissingResources(t *testing.T) {
"missing_endpoints": {
Backend: "missing_endpoints",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"missing_endpoints": {
Rule: "Host:missing_endpoints",
@@ -2163,6 +2184,7 @@ func TestMissingResources(t *testing.T) {
"missing_endpoint_subsets": {
Backend: "missing_endpoint_subsets",
PassHostHeader: true,
Headers: &types.Headers{},
Routes: map[string]types.Route{
"missing_endpoint_subsets": {
Rule: "Host:missing_endpoint_subsets",
@@ -2256,6 +2278,7 @@ func TestBasicAuthInTemplate(t *testing.T) {
}
actual = provider.loadConfig(*actual)
require.NotNil(t, actual)
got := actual.Frontends["basic/auth"].BasicAuth
if !reflect.DeepEqual(got, []string{"myUser:myEncodedPW"}) {
t.Fatalf("unexpected credentials: %+v", got)

View File

@@ -408,7 +408,7 @@ func (p *Provider) taskRecords(sj state.State) []state.Task {
for _, task := range f.Tasks {
for _, slave := range sj.Slaves {
if task.SlaveID == slave.ID {
task.SlaveIP = slave.Hostname
task.SlaveIP = slave.PID.Host
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/containous/traefik/log"
"github.com/containous/traefik/types"
"github.com/mesos/mesos-go/upid"
"github.com/mesosphere/mesos-dns/records/state"
)
@@ -194,6 +195,9 @@ func TestTaskRecords(t *testing.T) {
ID: "s_id",
Hostname: "127.0.0.1",
}
slave.PID.UPID = &upid.UPID{}
slave.PID.Host = slave.Hostname
var state = state.State{
Slaves: []state.Slave{slave},
Frameworks: []state.Framework{framework},

View File

@@ -76,13 +76,6 @@ func (p *Provider) getBasicAuth(service rancherData) []string {
return []string{}
}
func (p *Provider) getRedirect(service rancherData) string {
if redirect, err := getServiceLabel(service, types.LabelFrontendRedirect); err == nil {
return redirect
}
return ""
}
func (p *Provider) getFrontendName(service rancherData) string {
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
return provider.Normalize(p.getFrontendRule(service))
@@ -246,7 +239,10 @@ func (p *Provider) loadRancherConfig(services []rancherData) *types.Configuratio
"getSticky": p.getSticky,
"hasStickinessLabel": p.hasStickinessLabel,
"getStickinessCookieName": p.getStickinessCookieName,
"getRedirect": p.getRedirect,
"hasRedirect": hasRedirect,
"getRedirectEntryPoint": getRedirectEntryPoint,
"getRedirectRegex": getRedirectRegex,
"getRedirectReplacement": getRedirectReplacement,
}
// filter services
@@ -340,3 +336,42 @@ func isServiceEnabled(service rancherData, exposedByDefault bool) bool {
}
return exposedByDefault
}
// TODO will be rewrite when merge on master
func hasRedirect(service rancherData) bool {
value, err := getServiceLabel(service, types.LabelFrontendRedirectEntryPoint)
frep := err == nil && len(value) > 0
value, err = getServiceLabel(service, types.LabelFrontendRedirectRegex)
frrg := err == nil && len(value) > 0
value, err = getServiceLabel(service, types.LabelFrontendRedirectReplacement)
frrp := err == nil && len(value) > 0
return frep || frrg && frrp
}
// TODO will be rewrite when merge on master
func getRedirectEntryPoint(service rancherData) string {
value, err := getServiceLabel(service, types.LabelFrontendRedirectEntryPoint)
if err != nil || len(value) == 0 {
return ""
}
return value
}
// TODO will be rewrite when merge on master
func getRedirectRegex(service rancherData) string {
value, err := getServiceLabel(service, types.LabelFrontendRedirectRegex)
if err != nil || len(value) == 0 {
return ""
}
return value
}
// TODO will be rewrite when merge on master
func getRedirectReplacement(service rancherData) string {
value, err := getServiceLabel(service, types.LabelFrontendRedirectReplacement)
if err != nil || len(value) == 0 {
return ""
}
return value
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestProviderServiceFilter(t *testing.T) {
@@ -529,44 +530,6 @@ func TestProviderGetPassHostHeader(t *testing.T) {
}
}
func TestProviderGetRedirect(t *testing.T) {
provider := &Provider{Domain: "rancher.localhost"}
testCases := []struct {
desc string
service rancherData
expected string
}{
{
desc: "without label",
service: rancherData{
Name: "test-service",
},
expected: "",
},
{
desc: "with label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
types.LabelFrontendRedirect: "https",
},
},
expected: "https",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.getRedirect(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestProviderGetLabel(t *testing.T) {
testCases := []struct {
desc string
@@ -634,9 +597,9 @@ func TestProviderLoadRancherConfig(t *testing.T) {
{
Name: "test/service",
Labels: map[string]string{
types.LabelPort: "80",
types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
types.LabelFrontendRedirect: "https",
types.LabelPort: "80",
types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
types.LabelFrontendRedirectEntryPoint: "https",
},
Health: "healthy",
Containers: []string{"127.0.0.1"},
@@ -649,7 +612,9 @@ func TestProviderLoadRancherConfig(t *testing.T) {
EntryPoints: []string{},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Priority: 0,
Redirect: "https",
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"route-frontend-Host-test-service-rancher-localhost": {
Rule: "Host:test.service.rancher.localhost",
@@ -679,6 +644,7 @@ func TestProviderLoadRancherConfig(t *testing.T) {
actualConfig := provider.loadRancherConfig(test.services)
require.NotNil(t, actualConfig)
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
})
@@ -732,3 +698,70 @@ func TestProviderHasStickinessLabel(t *testing.T) {
})
}
}
func TestHasRedirect(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected bool
}{
{
desc: "without redirect labels",
service: rancherData{
Name: "test-service",
},
expected: false,
},
{
desc: "with Redirect EntryPoint label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
types.LabelFrontendRedirectEntryPoint: "https",
},
},
expected: true,
},
{
desc: "with Redirect regex label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
types.LabelFrontendRedirectRegex: `(.+)`,
},
},
expected: false,
},
{
desc: "with Redirect replacement label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
types.LabelFrontendRedirectReplacement: "$1",
},
},
expected: false,
},
{
desc: "with Redirect regex & replacement labels",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
types.LabelFrontendRedirectRegex: `(.+)`,
types.LabelFrontendRedirectReplacement: "$1",
},
},
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := hasRedirect(test.service)
assert.Equal(t, test.expected, actual)
})
}
}

View File

@@ -1,4 +1,4 @@
mkdocs==0.16.3
mkdocs>=0.17.2
pymdown-extensions>=1.4
mkdocs-bootswatch>=0.4.0
mkdocs-material==1.12.2
mkdocs-material>=2.2.6

View File

@@ -54,11 +54,32 @@ func (r *Rules) path(paths ...string) *mux.Route {
func (r *Rules) pathPrefix(paths ...string) *mux.Route {
router := r.route.route.Subrouter()
for _, path := range paths {
router.PathPrefix(strings.TrimSpace(path))
buildPath(path, router)
}
return r.route.route
}
func buildPath(path string, router *mux.Router) {
cleanPath := strings.TrimSpace(path)
// {} are used to define a regex pattern in http://www.gorillatoolkit.org/pkg/mux.
// if we find a { in the path, that means we use regex, then the gorilla/mux implementation is chosen
// otherwise, we use a lightweight implementation
if strings.Contains(cleanPath, "{") {
router.PathPrefix(cleanPath)
} else {
m := &prefixMatcher{prefix: cleanPath}
router.NewRoute().MatcherFunc(m.Match)
}
}
type prefixMatcher struct {
prefix string
}
func (m *prefixMatcher) Match(r *http.Request, _ *mux.RouteMatch) bool {
return strings.HasPrefix(r.URL.Path, m.prefix) || strings.HasPrefix(r.URL.Path, m.prefix+"/")
}
type bySize []string
func (a bySize) Len() int { return len(a) }
@@ -111,7 +132,7 @@ func (r *Rules) pathPrefixStrip(paths ...string) *mux.Route {
r.route.stripPrefixes = paths
router := r.route.route.Subrouter()
for _, path := range paths {
router.PathPrefix(strings.TrimSpace(path))
buildPath(path, router)
}
return r.route.route
}

View File

@@ -192,3 +192,67 @@ type fakeHandler struct {
}
func (h *fakeHandler) ServeHTTP(http.ResponseWriter, *http.Request) {}
func TestPathPrefix(t *testing.T) {
testCases := []struct {
desc string
path string
urls map[string]bool
}{
{
desc: "leading slash",
path: "/bar",
urls: map[string]bool{
"http://foo.com/bar": true,
"http://foo.com/bar/": true,
},
},
{
desc: "leading trailing slash",
path: "/bar/",
urls: map[string]bool{
"http://foo.com/bar": false,
"http://foo.com/bar/": true,
},
},
{
desc: "no slash",
path: "bar",
urls: map[string]bool{
"http://foo.com/bar": false,
"http://foo.com/bar/": false,
},
},
{
desc: "trailing slash",
path: "bar/",
urls: map[string]bool{
"http://foo.com/bar": false,
"http://foo.com/bar/": false,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rls := &Rules{
route: &serverRoute{
route: &mux.Route{},
},
}
rt := rls.pathPrefix(test.path)
for testURL, expectedMatch := range test.urls {
req := testhelpers.MustNewRequest(http.MethodGet, testURL, nil)
match := rt.Match(req, &mux.RouteMatch{})
if match != expectedMatch {
t.Errorf("Error matching %s with %s, got %v expected %v", test.path, testURL, match, expectedMatch)
}
}
})
}
}

View File

@@ -49,6 +49,10 @@ import (
"golang.org/x/net/http2"
)
const (
defaultRedirectRegex = `^(?:https?:\/\/)?([\w\._-]+)(?::\d+)?(.*)$`
)
var (
httpServerLogger = stdlog.New(log.WriterLevel(logrus.DebugLevel), "", 0)
)
@@ -435,7 +439,11 @@ func (s *Server) loadConfiguration(configMsg types.ConfigMessage) {
if err == nil {
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
s.serverEntryPoints[newServerEntryPointName].httpRouter.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler())
if &newServerEntryPoint.certs != nil {
if s.globalConfiguration.EntryPoints[newServerEntryPointName].TLS == nil {
if newServerEntryPoint.certs.Get() != nil {
log.Debugf("Certificates not added to non-TLS entryPoint %s.", newServerEntryPointName)
}
} else {
s.serverEntryPoints[newServerEntryPointName].certs.Set(newServerEntryPoint.certs.Get())
}
log.Infof("Server configuration reloaded on %s", s.serverEntryPoints[newServerEntryPointName].httpServer.Addr)
@@ -940,7 +948,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
if entryPoint.Redirect != nil {
if redirectHandlers[entryPointName] != nil {
n.Use(redirectHandlers[entryPointName])
} else if handler, err := s.loadEntryPointConfig(entryPointName, entryPoint); err != nil {
} else if handler, err := s.buildRedirectHandler(entryPointName, entryPoint.Redirect); err != nil {
log.Errorf("Error loading entrypoint configuration for frontend %s: %v", frontendName, err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
@@ -972,10 +980,9 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
continue frontend
}
var headerMiddleware *middlewares.HeaderStruct
headerMiddleware := middlewares.NewHeaderFromStruct(frontend.Headers)
var responseModifier func(res *http.Response) error
if frontend.Headers.HasCustomHeadersDefined() {
headerMiddleware = middlewares.NewHeaderFromStruct(frontend.Headers)
if headerMiddleware != nil {
responseModifier = headerMiddleware.ModifyResponseHeaders
}
@@ -1121,25 +1128,19 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
ipWhitelistMiddleware, err := configureIPWhitelistMiddleware(frontend.WhitelistSourceRange)
if err != nil {
log.Fatalf("Error creating IP Whitelister: %s", err)
log.Errorf("Error creating IP Whitelister: %s", err)
} else if ipWhitelistMiddleware != nil {
n.Use(ipWhitelistMiddleware)
log.Infof("Configured IP Whitelists: %s", frontend.WhitelistSourceRange)
}
if len(frontend.Redirect) > 0 {
proto := "http"
if s.globalConfiguration.EntryPoints[frontend.Redirect].TLS != nil {
proto = "https"
}
regex, replacement, err := s.buildRedirect(proto, entryPoint)
rewrite, err := middlewares.NewRewrite(regex, replacement, true)
if frontend.Redirect != nil {
rewrite, err := s.buildRedirectHandler(entryPointName, frontend.Redirect)
if err != nil {
log.Fatalf("Error creating Frontend Redirect: %v", err)
log.Errorf("Error creating Frontend Redirect: %v", err)
}
n.Use(rewrite)
log.Debugf("Creating frontend %s redirect to %s", frontendName, proto)
log.Debugf("Frontend %s redirect created", frontendName)
}
if len(frontend.BasicAuth) > 0 {
@@ -1164,8 +1165,9 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
log.Debugf("Adding header middleware for frontend %s", frontendName)
n.Use(headerMiddleware)
}
if frontend.Headers.HasSecureHeadersDefined() {
secureMiddleware := middlewares.NewSecure(frontend.Headers)
secureMiddleware := middlewares.NewSecure(frontend.Headers)
if secureMiddleware != nil {
log.Debugf("Adding secure middleware for frontend %s", frontendName)
n.UseFunc(secureMiddleware.HandlerFuncWithNext)
}
@@ -1289,38 +1291,57 @@ func (s *Server) wireFrontendBackend(serverRoute *serverRoute, handler http.Hand
serverRoute.route.Handler(handler)
}
func (s *Server) loadEntryPointConfig(entryPointName string, entryPoint *configuration.EntryPoint) (negroni.Handler, error) {
regex := entryPoint.Redirect.Regex
replacement := entryPoint.Redirect.Replacement
var err error
if len(entryPoint.Redirect.EntryPoint) > 0 {
var protocol = "http"
if s.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].TLS != nil {
protocol = "https"
}
regex, replacement, err = s.buildRedirect(protocol, entryPoint)
func (s *Server) buildRedirectHandler(srcEntryPointName string, redirect *types.Redirect) (*middlewares.Rewrite, error) {
// entry point redirect
if len(redirect.EntryPoint) > 0 {
return s.buildEntryPointRedirect(srcEntryPointName, redirect.EntryPoint)
}
rewrite, err := middlewares.NewRewrite(regex, replacement, true)
// regex redirect
rewrite, err := middlewares.NewRewrite(redirect.Regex, redirect.Replacement, true)
if err != nil {
return nil, err
}
log.Debugf("Creating entryPoint redirect %s -> %s : %s -> %s", entryPointName, entryPoint.Redirect.EntryPoint, regex, replacement)
log.Debugf("Creating entryPoint redirect %s -> %s -> %s", srcEntryPointName, redirect.Regex, redirect.Replacement)
return rewrite, nil
}
func (s *Server) buildRedirect(protocol string, entryPoint *configuration.EntryPoint) (string, string, error) {
regex := `^(?:https?:\/\/)?([\w\._-]+)(?::\d+)?(.*)$`
if s.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint] == nil {
return "", "", fmt.Errorf("unknown target entrypoint %q", entryPoint.Redirect.EntryPoint)
func (s *Server) buildEntryPointRedirect(srcEntryPointName string, redirectEntryPoint string) (*middlewares.Rewrite, error) {
regex, replacement, err := s.buildRedirect(redirectEntryPoint)
if err != nil {
return nil, err
}
r, _ := regexp.Compile(`(:\d+)`)
match := r.FindStringSubmatch(s.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].Address)
rewrite, err := middlewares.NewRewrite(regex, replacement, true)
if err != nil {
// Impossible case because error is always nil
return nil, err
}
log.Debugf("Creating entryPoint redirect %s -> %s : %s -> %s", srcEntryPointName, redirectEntryPoint, regex, replacement)
return rewrite, nil
}
func (s *Server) buildRedirect(entryPointName string) (string, string, error) {
entryPoint := s.globalConfiguration.EntryPoints[entryPointName]
if entryPoint == nil {
return "", "", fmt.Errorf("unknown target entrypoint %q", entryPointName)
}
exp := regexp.MustCompile(`(:\d+)`)
match := exp.FindStringSubmatch(entryPoint.Address)
if len(match) == 0 {
return "", "", fmt.Errorf("bad Address format %q", s.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].Address)
return "", "", fmt.Errorf("bad Address format %q", entryPoint.Address)
}
var protocol = "http"
if s.globalConfiguration.EntryPoints[entryPointName].TLS != nil {
protocol = "https"
}
replacement := protocol + "://$1" + match[0] + "$2"
return regex, replacement, nil
return defaultRedirectRegex, replacement, nil
}
func (s *Server) buildDefaultHTTPRouter() *mux.Router {

View File

@@ -903,55 +903,209 @@ func TestServerResponseEmptyBackend(t *testing.T) {
}
}
func TestServerLoadConfigBuildRedirect(t *testing.T) {
func TestBuildEntryPointRedirect(t *testing.T) {
srv := Server{
globalConfiguration: configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{
"http": &configuration.EntryPoint{Address: ":80"},
"https": &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}},
},
},
}
testCases := []struct {
desc string
replacementProtocol string
globalConfiguration configuration.GlobalConfiguration
originEntryPointName string
expectedReplacement string
desc string
srcEntryPointName string
url string
entryPoint *configuration.EntryPoint
redirect *types.Redirect
expectedURL string
}{
{
desc: "Redirect endpoint http to https with HTTPS protocol",
replacementProtocol: "https",
originEntryPointName: "http",
globalConfiguration: configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{
"http": &configuration.EntryPoint{
Address: ":80",
Redirect: &configuration.Redirect{
EntryPoint: "https",
},
},
"https": &configuration.EntryPoint{
Address: ":443",
TLS: &tls.TLS{},
},
desc: "redirect regex",
srcEntryPointName: "http",
url: "http://foo.com",
redirect: &types.Redirect{
Regex: `^(?:http?:\/\/)(foo)(\.com)$`,
Replacement: "https://$1{{\"bar\"}}$2",
},
entryPoint: &configuration.EntryPoint{
Address: ":80",
Redirect: &types.Redirect{
Regex: `^(?:http?:\/\/)(foo)(\.com)$`,
Replacement: "https://$1{{\"bar\"}}$2",
},
},
expectedURL: "https://foobar.com",
},
{
desc: "redirect entry point",
srcEntryPointName: "http",
url: "http://foo:80",
redirect: &types.Redirect{
EntryPoint: "https",
},
entryPoint: &configuration.EntryPoint{
Address: ":80",
Redirect: &types.Redirect{
EntryPoint: "https",
},
},
expectedURL: "https://foo:443",
},
{
desc: "redirect entry point with regex (ignored)",
srcEntryPointName: "http",
url: "http://foo.com:80",
redirect: &types.Redirect{
EntryPoint: "https",
Regex: `^(?:http?:\/\/)(foo)(\.com)$`,
Replacement: "https://$1{{\"bar\"}}$2",
},
entryPoint: &configuration.EntryPoint{
Address: ":80",
Redirect: &types.Redirect{
EntryPoint: "https",
Regex: `^(?:http?:\/\/)(foo)(\.com)$`,
Replacement: "https://$1{{\"bar\"}}$2",
},
},
expectedURL: "https://foo.com:443",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rewrite, err := srv.buildRedirectHandler(test.srcEntryPointName, test.redirect)
require.NoError(t, err)
req := testhelpers.MustNewRequest(http.MethodGet, test.url, nil)
recorder := httptest.NewRecorder()
rewrite.ServeHTTP(recorder, req, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Location", "fail")
}))
location, err := recorder.Result().Location()
require.NoError(t, err)
assert.Equal(t, test.expectedURL, location.String())
})
}
}
func TestServerBuildEntryPointRedirect(t *testing.T) {
srv := Server{
globalConfiguration: configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{
"http": &configuration.EntryPoint{Address: ":80"},
"https": &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}},
},
},
}
testCases := []struct {
desc string
srcEntryPointName string
redirectEntryPoint string
url string
expectedURL string
errorExpected bool
}{
{
desc: "existing redirect entry point",
srcEntryPointName: "http",
redirectEntryPoint: "https",
url: "http://foo:80",
expectedURL: "https://foo:443",
},
{
desc: "non-existing redirect entry point",
srcEntryPointName: "http",
redirectEntryPoint: "foo",
url: "http://foo:80",
errorExpected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rewrite, err := srv.buildEntryPointRedirect(test.srcEntryPointName, test.redirectEntryPoint)
if test.errorExpected {
require.Error(t, err)
} else {
require.NoError(t, err)
recorder := httptest.NewRecorder()
r := testhelpers.MustNewRequest(http.MethodGet, test.url, nil)
rewrite.ServeHTTP(recorder, r, nil)
location, err := recorder.Result().Location()
require.NoError(t, err)
assert.Equal(t, test.expectedURL, location.String())
}
})
}
}
func TestServerBuildRedirect(t *testing.T) {
testCases := []struct {
desc string
globalConfiguration configuration.GlobalConfiguration
redirectEntryPointName string
expectedReplacement string
errorExpected bool
}{
{
desc: "Redirect endpoint http to https with HTTPS protocol",
redirectEntryPointName: "https",
globalConfiguration: configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{
"http": &configuration.EntryPoint{Address: ":80"},
"https": &configuration.EntryPoint{Address: ":443", TLS: &tls.TLS{}},
},
},
expectedReplacement: "https://$1:443$2",
},
{
desc: "Redirect endpoint http to http02 with HTTP protocol",
replacementProtocol: "http",
originEntryPointName: "http",
desc: "Redirect endpoint http to http02 with HTTP protocol",
redirectEntryPointName: "http02",
globalConfiguration: configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{
"http": &configuration.EntryPoint{
Address: ":80",
Redirect: &configuration.Redirect{
EntryPoint: "http02",
},
},
"http02": &configuration.EntryPoint{
Address: ":88",
},
"http": &configuration.EntryPoint{Address: ":80"},
"http02": &configuration.EntryPoint{Address: ":88"},
},
},
expectedReplacement: "http://$1:88$2",
},
{
desc: "Redirect endpoint to non-existent entry point",
redirectEntryPointName: "foobar",
globalConfiguration: configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{
"http": &configuration.EntryPoint{Address: ":80"},
"http02": &configuration.EntryPoint{Address: ":88"},
},
},
errorExpected: true,
},
{
desc: "Redirect endpoint to an entry point with a malformed address",
redirectEntryPointName: "http02",
globalConfiguration: configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{
"http": &configuration.EntryPoint{Address: ":80"},
"http02": &configuration.EntryPoint{Address: "88"},
},
},
errorExpected: true,
},
}
for _, test := range testCases {
@@ -961,9 +1115,9 @@ func TestServerLoadConfigBuildRedirect(t *testing.T) {
srv := Server{globalConfiguration: test.globalConfiguration}
_, replacement, err := srv.buildRedirect(test.replacementProtocol, srv.globalConfiguration.EntryPoints[test.originEntryPointName])
_, replacement, err := srv.buildRedirect(test.redirectEntryPointName)
require.NoError(t, err, "build redirect sent an unexpected error")
require.Equal(t, test.errorExpected, err != nil, "Expected an error but don't have error, or Expected no error but have an error: %v", err)
assert.Equal(t, test.expectedReplacement, replacement, "build redirect does not return the right replacement pattern")
})
}

View File

@@ -47,7 +47,6 @@
[frontends."frontend-{{getServiceBackend $container $serviceName}}"]
backend = "backend-{{getServiceBackend $container $serviceName}}"
passHostHeader = {{getServicePassHostHeader $container $serviceName}}
redirect = "{{getServiceRedirect $container $serviceName}}"
{{if getWhitelistSourceRange $container}}
whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
"{{.}}",
@@ -60,14 +59,21 @@
basicAuth = [{{range getServiceBasicAuth $container $serviceName}}
"{{.}}",
{{end}}]
[frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"]
{{if hasServiceRedirect $container $serviceName}}
[frontends."frontend-{{getServiceBackend $container $serviceName}}".redirect]
entryPoint = "{{getServiceRedirectEntryPoint $container $serviceName}}"
regex = "{{getServiceRedirectRegex $container $serviceName}}"
replacement = "{{getServiceRedirectReplacement $container $serviceName}}"
{{end}}
[frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"]
rule = "{{getServiceFrontendRule $container $serviceName}}"
{{end}}
{{else}}
[frontends."frontend-{{$frontend}}"]
backend = "backend-{{getBackend $container}}"
passHostHeader = {{getPassHostHeader $container}}
redirect = "{{getRedirect $container}}"
{{if getWhitelistSourceRange $container}}
whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
"{{.}}",
@@ -80,6 +86,15 @@
basicAuth = [{{range getBasicAuth $container}}
"{{.}}",
{{end}}]
{{if hasRedirect $container}}
[frontends."frontend-{{$frontend}}".redirect]
entryPoint = "{{getRedirectEntryPoint $container}}"
regex = "{{getRedirectRegex $container}}"
replacement = "{{getRedirectReplacement $container}}"
{{end}}
{{ if hasHeaders $container}}
[frontends."frontend-{{$frontend}}".headers]
{{if hasSSLRedirectHeaders $container}}
SSLRedirect = {{getSSLRedirectHeaders $container}}
@@ -126,6 +141,16 @@
{{if hasIsDevelopmentHeaders $container}}
IsDevelopment = {{getIsDevelopmentHeaders $container}}
{{end}}
{{if hasAllowedHostsHeaders $container}}
AllowedHosts = [{{range getAllowedHostsHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasHostsProxyHeaders $container}}
HostsProxyHeaders = [{{range getHostsProxyHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasRequestHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.customrequestheaders]
{{range $k, $v := getRequestHeaders $container}}
@@ -138,24 +163,14 @@
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if hasAllowedHostsHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.AllowedHosts]
{{range getAllowedHostsHeaders $container}}
"{{.}}"
{{end}}
{{end}}
{{if hasHostsProxyHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.HostsProxyHeaders]
{{range getHostsProxyHeaders $container}}
"{{.}}"
{{end}}
{{end}}
{{if hasSSLProxyHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.SSLProxyHeaders]
{{range $k, $v := getSSLProxyHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
rule = "{{getFrontendRule $container}}"
{{end}}

View File

@@ -25,13 +25,24 @@
backend = "{{$frontend.Backend}}"
priority = {{$frontend.Priority}}
passHostHeader = {{$frontend.PassHostHeader}}
redirect = "{{$frontend.Redirect}}"
entryPoints = [{{range $frontend.EntryPoints}}
"{{.}}",
{{end}}]
basicAuth = [{{range $frontend.BasicAuth}}
"{{.}}",
{{end}}]
whitelistSourceRange = [{{range $frontend.WhitelistSourceRange}}
"{{.}}",
{{end}}]
{{if $frontend.Redirect}}
[frontends."{{$frontendName}}".redirect]
entryPoint = "{{$frontend.RedirectEntryPoint}}"
regex = "{{$frontend.RedirectRegex}}"
replacement = "{{$frontend.RedirectReplacement}}"
{{end}}
{{ if $frontend.Headers }}
[frontends."{{$frontendName}}".headers]
SSLRedirect = {{$frontend.Headers.SSLRedirect}}
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}
@@ -48,38 +59,43 @@
PublicKey = "{{$frontend.Headers.PublicKey}}"
ReferrerPolicy = "{{$frontend.Headers.ReferrerPolicy}}"
IsDevelopment = {{$frontend.Headers.IsDevelopment}}
{{if $frontend.Headers.CustomRequestHeaders}}
[frontends."{{$frontendName}}".headers.customrequestheaders]
{{if $frontend.Headers.AllowedHosts}}
AllowedHosts = [{{range $frontend.Headers.AllowedHosts}}
"{{.}}",
{{end}}]
{{end}}
{{if $frontend.Headers.HostsProxyHeaders}}
HostsProxyHeaders = [{{range $frontend.Headers.HostsProxyHeaders}}
"{{.}}",
{{end}}]
{{end}}
{{if $frontend.Headers.CustomRequestHeaders}}
[frontends."{{$frontendName}}".headers.customrequestheaders]
{{range $k, $v := $frontend.Headers.CustomRequestHeaders}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $frontend.Headers.CustomResponseHeaders}}
[frontends."{{$frontendName}}".headers.customresponseheaders]
{{end}}
{{if $frontend.Headers.CustomResponseHeaders}}
[frontends."{{$frontendName}}".headers.customresponseheaders]
{{range $k, $v := $frontend.Headers.CustomResponseHeaders}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $frontend.Headers.AllowedHosts}}
[frontends."{{$frontendName}}".headers.AllowedHosts]
{{range $frontend.Headers.AllowedHosts}}
"{{.}}"
{{end}}
{{end}}
{{if $frontend.Headers.HostsProxyHeaders}}
[frontends."{{$frontendName}}".headers.HostsProxyHeaders]
{{range $frontend.Headers.HostsProxyHeaders}}
"{{.}}"
{{end}}
{{end}}
{{if $frontend.Headers.SSLProxyHeaders}}
[frontends."{{$frontendName}}".headers.SSLProxyHeaders]
{{end}}
{{if $frontend.Headers.SSLProxyHeaders}}
[frontends."{{$frontendName}}".headers.SSLProxyHeaders]
{{range $k, $v := $frontend.Headers.SSLProxyHeaders}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
{{range $routeName, $route := $frontend.Routes}}
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
{{range $routeName, $route := $frontend.Routes}}
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
rule = "{{$route.Rule}}"
{{end}}
{{end}}
{{end}}

View File

@@ -20,7 +20,7 @@
sticky = {{ getSticky . }}
{{if hasStickinessLabel $backend}}
[backends."{{$backendName}}".loadBalancer.stickiness]
cookieName = {{getStickinessCookieName $backend}}
cookieName = "{{getStickinessCookieName $backend}}"
{{end}}
{{end}}

View File

@@ -34,13 +34,20 @@
backend = "backend-{{getBackend $service}}"
passHostHeader = {{getPassHostHeader $service}}
priority = {{getPriority $service}}
redirect = "{{getRedirect $service}}"
entryPoints = [{{range getEntryPoints $service}}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $service}}
"{{.}}",
{{end}}]
{{if hasRedirect $service}}
[frontends."frontend-{{$frontendName}}".redirect]
entryPoint = "{{getRedirectEntryPoint $service}}"
regex = "{{getRedirectRegex $service}}"
replacement = "{{getRedirectReplacement $service}}"
{{end}}
[frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"]
rule = "{{getFrontendRule $service}}"
{{end}}

View File

@@ -4,66 +4,73 @@ import "strings"
// Traefik labels
const (
LabelPrefix = "traefik."
SuffixPort = "port"
SuffixProtocol = "protocol"
SuffixWeight = "weight"
SuffixFrontendAuthBasic = "frontend.auth.basic"
SuffixFrontendBackend = "frontend.backend"
SuffixFrontendEntryPoints = "frontend.entryPoints"
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
SuffixFrontendPriority = "frontend.priority"
SuffixFrontendRedirect = "frontend.redirect"
SuffixFrontendRule = "frontend.rule"
LabelDomain = LabelPrefix + "domain"
LabelEnable = LabelPrefix + "enable"
LabelPort = LabelPrefix + SuffixPort
LabelPortIndex = LabelPrefix + "portIndex"
LabelProtocol = LabelPrefix + SuffixProtocol
LabelTags = LabelPrefix + "tags"
LabelWeight = LabelPrefix + SuffixWeight
LabelFrontendAuthBasic = LabelPrefix + SuffixFrontendAuthBasic
LabelFrontendEntryPoints = LabelPrefix + SuffixFrontendEntryPoints
LabelFrontendPassHostHeader = LabelPrefix + SuffixFrontendPassHostHeader
LabelFrontendPassTLSCert = LabelPrefix + "frontend.passTLSCert"
LabelFrontendPriority = LabelPrefix + SuffixFrontendPriority
LabelFrontendRule = LabelPrefix + SuffixFrontendRule
LabelFrontendRuleType = LabelPrefix + "frontend.rule.type"
LabelFrontendRedirect = LabelPrefix + SuffixFrontendRedirect
LabelTraefikFrontendValue = LabelPrefix + "frontend.value"
LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange"
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"
LabelFrontendSSLTemporaryRedirect = LabelPrefix + "frontend.headers.SSLTemporaryRedirect"
LabelFrontendSSLHost = LabelPrefix + "frontend.headers.SSLHost"
LabelFrontendSSLProxyHeaders = LabelPrefix + "frontend.headers.SSLProxyHeaders"
LabelFrontendSTSSeconds = LabelPrefix + "frontend.headers.STSSeconds"
LabelFrontendSTSIncludeSubdomains = LabelPrefix + "frontend.headers.STSIncludeSubdomains"
LabelFrontendSTSPreload = LabelPrefix + "frontend.headers.STSPreload"
LabelFrontendForceSTSHeader = LabelPrefix + "frontend.headers.forceSTSHeader"
LabelFrontendFrameDeny = LabelPrefix + "frontend.headers.frameDeny"
LabelFrontendCustomFrameOptionsValue = LabelPrefix + "frontend.headers.customFrameOptionsValue"
LabelFrontendContentTypeNosniff = LabelPrefix + "frontend.headers.contentTypeNosniff"
LabelFrontendBrowserXSSFilter = LabelPrefix + "frontend.headers.browserXSSFilter"
LabelFrontendContentSecurityPolicy = LabelPrefix + "frontend.headers.contentSecurityPolicy"
LabelFrontendPublicKey = LabelPrefix + "frontend.headers.publicKey"
LabelFrontendReferrerPolicy = LabelPrefix + "frontend.headers.referrerPolicy"
LabelFrontendIsDevelopment = LabelPrefix + "frontend.headers.isDevelopment"
LabelBackend = LabelPrefix + "backend"
LabelBackendID = LabelPrefix + "backend.id"
LabelTraefikBackendCircuitbreaker = LabelPrefix + "backend.circuitbreaker"
LabelBackendCircuitbreakerExpression = LabelPrefix + "backend.circuitbreaker.expression"
LabelBackendHealthcheckPath = LabelPrefix + "backend.healthcheck.path"
LabelBackendHealthcheckInterval = LabelPrefix + "backend.healthcheck.interval"
LabelBackendLoadbalancerMethod = LabelPrefix + "backend.loadbalancer.method"
LabelBackendLoadbalancerSticky = LabelPrefix + "backend.loadbalancer.sticky"
LabelBackendLoadbalancerStickiness = LabelPrefix + "backend.loadbalancer.stickiness"
LabelBackendLoadbalancerStickinessCookieName = LabelPrefix + "backend.loadbalancer.stickiness.cookieName"
LabelBackendMaxconnAmount = LabelPrefix + "backend.maxconn.amount"
LabelBackendMaxconnExtractorfunc = LabelPrefix + "backend.maxconn.extractorfunc"
LabelPrefix = "traefik."
SuffixPort = "port"
SuffixProtocol = "protocol"
SuffixWeight = "weight"
SuffixFrontendAuthBasic = "frontend.auth.basic"
SuffixFrontendBackend = "frontend.backend"
SuffixFrontendEntryPoints = "frontend.entryPoints"
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
SuffixFrontendPriority = "frontend.priority"
SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint"
SuffixFrontendRedirectRegex = "frontend.redirect.regex"
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
SuffixFrontendRule = "frontend.rule"
SuffixBackendLoadBalancerSticky = "backend.loadbalancer.sticky"
SuffixBackendLoadBalancerStickiness = "backend.loadbalancer.stickiness"
SuffixBackendLoadBalancerStickinessCookieName = "backend.loadbalancer.stickiness.cookieName"
LabelDomain = LabelPrefix + "domain"
LabelEnable = LabelPrefix + "enable"
LabelPort = LabelPrefix + SuffixPort
LabelPortIndex = LabelPrefix + "portIndex"
LabelProtocol = LabelPrefix + SuffixProtocol
LabelTags = LabelPrefix + "tags"
LabelWeight = LabelPrefix + SuffixWeight
LabelFrontendAuthBasic = LabelPrefix + SuffixFrontendAuthBasic
LabelFrontendEntryPoints = LabelPrefix + SuffixFrontendEntryPoints
LabelFrontendPassHostHeader = LabelPrefix + SuffixFrontendPassHostHeader
LabelFrontendPassTLSCert = LabelPrefix + "frontend.passTLSCert"
LabelFrontendPriority = LabelPrefix + SuffixFrontendPriority
LabelFrontendRule = LabelPrefix + SuffixFrontendRule
LabelFrontendRuleType = LabelPrefix + "frontend.rule.type"
LabelFrontendRedirectEntryPoint = LabelPrefix + SuffixFrontendRedirectEntryPoint
LabelFrontendRedirectRegex = LabelPrefix + SuffixFrontendRedirectRegex
LabelFrontendRedirectReplacement = LabelPrefix + SuffixFrontendRedirectReplacement
LabelTraefikFrontendValue = LabelPrefix + "frontend.value"
LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange"
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"
LabelFrontendSSLTemporaryRedirect = LabelPrefix + "frontend.headers.SSLTemporaryRedirect"
LabelFrontendSSLHost = LabelPrefix + "frontend.headers.SSLHost"
LabelFrontendSSLProxyHeaders = LabelPrefix + "frontend.headers.SSLProxyHeaders"
LabelFrontendSTSSeconds = LabelPrefix + "frontend.headers.STSSeconds"
LabelFrontendSTSIncludeSubdomains = LabelPrefix + "frontend.headers.STSIncludeSubdomains"
LabelFrontendSTSPreload = LabelPrefix + "frontend.headers.STSPreload"
LabelFrontendForceSTSHeader = LabelPrefix + "frontend.headers.forceSTSHeader"
LabelFrontendFrameDeny = LabelPrefix + "frontend.headers.frameDeny"
LabelFrontendCustomFrameOptionsValue = LabelPrefix + "frontend.headers.customFrameOptionsValue"
LabelFrontendContentTypeNosniff = LabelPrefix + "frontend.headers.contentTypeNosniff"
LabelFrontendBrowserXSSFilter = LabelPrefix + "frontend.headers.browserXSSFilter"
LabelFrontendContentSecurityPolicy = LabelPrefix + "frontend.headers.contentSecurityPolicy"
LabelFrontendPublicKey = LabelPrefix + "frontend.headers.publicKey"
LabelFrontendReferrerPolicy = LabelPrefix + "frontend.headers.referrerPolicy"
LabelFrontendIsDevelopment = LabelPrefix + "frontend.headers.isDevelopment"
LabelBackend = LabelPrefix + "backend"
LabelBackendID = LabelPrefix + "backend.id"
LabelTraefikBackendCircuitbreaker = LabelPrefix + "backend.circuitbreaker"
LabelBackendCircuitbreakerExpression = LabelPrefix + "backend.circuitbreaker.expression"
LabelBackendHealthcheckPath = LabelPrefix + "backend.healthcheck.path"
LabelBackendHealthcheckInterval = LabelPrefix + "backend.healthcheck.interval"
LabelBackendLoadbalancerMethod = LabelPrefix + "backend.loadbalancer.method"
LabelBackendLoadbalancerSticky = LabelPrefix + SuffixBackendLoadBalancerSticky
LabelBackendLoadbalancerStickiness = LabelPrefix + SuffixBackendLoadBalancerStickiness
LabelBackendLoadbalancerStickinessCookieName = LabelPrefix + SuffixBackendLoadBalancerStickinessCookieName
LabelBackendMaxconnAmount = LabelPrefix + "backend.maxconn.amount"
LabelBackendMaxconnExtractorfunc = LabelPrefix + "backend.maxconn.extractorfunc"
)
//ServiceLabel converts a key value of Label*, given a serviceName, into a pattern <LabelPrefix>.<serviceName>.<property>

View File

@@ -113,14 +113,14 @@ type Headers struct {
}
// HasCustomHeadersDefined checks to see if any of the custom header elements have been set
func (h Headers) HasCustomHeadersDefined() bool {
return len(h.CustomResponseHeaders) != 0 ||
len(h.CustomRequestHeaders) != 0
func (h *Headers) HasCustomHeadersDefined() bool {
return h != nil && (len(h.CustomResponseHeaders) != 0 ||
len(h.CustomRequestHeaders) != 0)
}
// HasSecureHeadersDefined checks to see if any of the secure header elements have been set
func (h Headers) HasSecureHeadersDefined() bool {
return len(h.AllowedHosts) != 0 ||
func (h *Headers) HasSecureHeadersDefined() bool {
return h != nil && (len(h.AllowedHosts) != 0 ||
len(h.HostsProxyHeaders) != 0 ||
h.SSLRedirect ||
h.SSLTemporaryRedirect ||
@@ -137,7 +137,7 @@ func (h Headers) HasSecureHeadersDefined() bool {
h.ContentSecurityPolicy != "" ||
h.PublicKey != "" ||
h.ReferrerPolicy != "" ||
h.IsDevelopment
h.IsDevelopment)
}
// Frontend holds frontend configuration.
@@ -150,10 +150,17 @@ type Frontend struct {
Priority int `json:"priority"`
BasicAuth []string `json:"basicAuth"`
WhitelistSourceRange []string `json:"whitelistSourceRange,omitempty"`
Headers Headers `json:"headers,omitempty"`
Headers *Headers `json:"headers,omitempty"`
Errors map[string]ErrorPage `json:"errors,omitempty"`
RateLimit *RateLimit `json:"ratelimit,omitempty"`
Redirect string `json:"redirect,omitempty"`
Redirect *Redirect `json:"redirect,omitempty"`
}
// Redirect configures a redirection of an entry point to another, or to an URL
type Redirect struct {
EntryPoint string `json:"entryPoint,omitempty"`
Regex string `json:"regex,omitempty"`
Replacement string `json:"replacement,omitempty"`
}
// LoadBalancerMethod holds the method of load balancing to use.

View File

@@ -1,23 +1,59 @@
package servicefabric
import "strings"
import (
"strconv"
"strings"
)
func hasServiceLabel(service ServiceItemExtended, key string) bool {
_, exists := service.Labels[key]
return exists
}
func getFuncBoolLabel(labelName string) func(service ServiceItemExtended) bool {
func getFuncBoolLabel(labelName string, defaultValue bool) func(service ServiceItemExtended) bool {
return func(service ServiceItemExtended) bool {
return getBoolLabel(service, labelName)
return getBoolValue(service.Labels, labelName, defaultValue)
}
}
func getBoolLabel(service ServiceItemExtended, labelName string) bool {
value, exists := service.Labels[labelName]
return exists && strings.EqualFold(strings.TrimSpace(value), "true")
func getFuncServiceStringLabel(service ServiceItemExtended, labelName string, defaultValue string) string {
return getStringValue(service.Labels, labelName, defaultValue)
}
func getServiceLabelValue(service ServiceItemExtended, key string) string {
return service.Labels[key]
func hasFuncService(service ServiceItemExtended, labelName string) bool {
return hasLabel(service.Labels, labelName)
}
func getServiceLabelsWithPrefix(service ServiceItemExtended, prefix string) map[string]string {
results := make(map[string]string)
for k, v := range service.Labels {
if strings.HasPrefix(k, prefix) {
results[k] = v
}
}
return results
}
// must be replace by label.Has()
// Deprecated
func hasLabel(labels map[string]string, labelName string) bool {
value, ok := labels[labelName]
return ok && len(value) > 0
}
// must be replace by label.GetStringValue()
// Deprecated
func getStringValue(labels map[string]string, labelName string, defaultValue string) string {
if value, ok := labels[labelName]; ok && len(value) > 0 {
return value
}
return defaultValue
}
// must be replace by label.GetBoolValue()
// Deprecated
func getBoolValue(labels map[string]string, labelName string, defaultValue bool) bool {
rawValue, ok := labels[labelName]
if ok {
v, err := strconv.ParseBool(rawValue)
if err == nil {
return v
}
}
return defaultValue
}

View File

@@ -69,34 +69,7 @@ func (p *Provider) updateConfig(configurationChan chan<- types.ConfigMessage, po
log.Info("Checking service fabric config")
}
services, err := getClusterServices(sfClient)
if err != nil {
return err
}
templateObjects := struct {
Services []ServiceItemExtended
}{
services,
}
var sfFuncMap = template.FuncMap{
"isPrimary": isPrimary,
"getDefaultEndpoint": p.getDefaultEndpoint,
"getNamedEndpoint": p.getNamedEndpoint,
"getApplicationParameter": p.getApplicationParameter,
"doesAppParamContain": p.doesAppParamContain,
"hasServiceLabel": hasServiceLabel,
"getServiceLabelValue": getServiceLabelValue,
"getServiceLabelValueWithDefault": getServiceLabelValueWithDefault,
"getServiceLabelsWithPrefix": getServiceLabelsWithPrefix,
"getServicesWithLabelValueMap": getServicesWithLabelValueMap,
"getServicesWithLabelValue": getServicesWithLabelValue,
"isExposed": getFuncBoolLabel("expose"),
}
configuration, err := p.GetConfiguration(tmpl, sfFuncMap, templateObjects)
configuration, err := p.buildConfiguration(sfClient)
if err != nil {
return err
}
@@ -120,24 +93,39 @@ func (p *Provider) updateConfig(configurationChan chan<- types.ConfigMessage, po
return nil
}
func (p Provider) doesAppParamContain(app sf.ApplicationItem, key, shouldContain string) bool {
value := p.getApplicationParameter(app, key)
return strings.Contains(value, shouldContain)
}
func (p Provider) getApplicationParameter(app sf.ApplicationItem, key string) string {
for _, param := range app.Parameters {
if param.Key == key {
return param.Value
}
func (p *Provider) buildConfiguration(sfClient sfClient) (*types.Configuration, error) {
var sfFuncMap = template.FuncMap{
"getServices": getServices,
"hasLabel": hasFuncService,
"getLabelValue": getFuncServiceStringLabel,
"getLabelsWithPrefix": getServiceLabelsWithPrefix,
"isPrimary": isPrimary,
"isExposed": getFuncBoolLabel("expose", false),
"getBackendName": getBackendName,
"getDefaultEndpoint": getDefaultEndpoint,
"getNamedEndpoint": getNamedEndpoint, // FIXME unused
"getApplicationParameter": getApplicationParameter, // FIXME unused
"doesAppParamContain": doesAppParamContain, // FIXME unused
"filterServicesByLabelValue": filterServicesByLabelValue, // FIXME unused
}
log.Errorf("Parameter %s doesn't exist in app %s", key, app.Name)
return ""
services, err := getClusterServices(sfClient)
if err != nil {
return nil, err
}
templateObjects := struct {
Services []ServiceItemExtended
}{
Services: services,
}
return p.GetConfiguration(tmpl, sfFuncMap, templateObjects)
}
func (p Provider) getDefaultEndpoint(instance replicaInstance) string {
func getDefaultEndpoint(instance replicaInstance) string {
id, data := instance.GetReplicaData()
endpoint, err := getDefaultEndpoint(data.Address)
endpoint, err := getReplicaDefaultEndpoint(data)
if err != nil {
log.Warnf("No default endpoint for replica %s in service %s endpointData: %s", id, data.Address)
return ""
@@ -145,16 +133,64 @@ func (p Provider) getDefaultEndpoint(instance replicaInstance) string {
return endpoint
}
func (p Provider) getNamedEndpoint(instance replicaInstance, endpointName string) string {
id, data := instance.GetReplicaData()
endpoint, err := getNamedEndpoint(data.Address, endpointName)
func getReplicaDefaultEndpoint(replicaData *sf.ReplicaItemBase) (string, error) {
endpoints, err := decodeEndpointData(replicaData.Address)
if err != nil {
log.Warnf("No names endpoint of %s for replica %s in endpointData: %s", endpointName, id, data.Address)
return "", err
}
var defaultHTTPEndpoint string
for _, v := range endpoints {
if strings.Contains(v, "http") {
defaultHTTPEndpoint = v
break
}
}
if len(defaultHTTPEndpoint) == 0 {
return "", errors.New("no default endpoint found")
}
return defaultHTTPEndpoint, nil
}
func getNamedEndpoint(instance replicaInstance, endpointName string) string {
id, data := instance.GetReplicaData()
endpoint, err := getReplicaNamedEndpoint(data, endpointName)
if err != nil {
log.Warnf("No names endpoint of %s for replica %s in endpointData: %s. Error: %v", endpointName, id, data.Address, err)
return ""
}
return endpoint
}
func getReplicaNamedEndpoint(replicaData *sf.ReplicaItemBase, endpointName string) (string, error) {
endpoints, err := decodeEndpointData(replicaData.Address)
if err != nil {
return "", err
}
endpoint, exists := endpoints[endpointName]
if !exists {
return "", errors.New("endpoint doesn't exist")
}
return endpoint, nil
}
func doesAppParamContain(app sf.ApplicationItem, key, shouldContain string) bool {
value := getApplicationParameter(app, key)
return strings.Contains(value, shouldContain)
}
func getApplicationParameter(app sf.ApplicationItem, key string) string {
for _, param := range app.Parameters {
if param.Key == key {
return param.Value
}
}
log.Errorf("Parameter %s doesn't exist in app %s", key, app.Name)
return ""
}
func getClusterServices(sfClient sfClient) ([]ServiceItemExtended, error) {
apps, err := sfClient.GetApplications()
if err != nil {
@@ -236,7 +272,7 @@ func getValidInstances(sfClient sfClient, app sf.ApplicationItem, service sf.Ser
return validInstances
}
func getServicesWithLabelValueMap(services []ServiceItemExtended, key string) map[string][]ServiceItemExtended {
func getServices(services []ServiceItemExtended, key string) map[string][]ServiceItemExtended {
result := map[string][]ServiceItemExtended{}
for _, service := range services {
if value, exists := service.Labels[key]; exists {
@@ -250,7 +286,7 @@ func getServicesWithLabelValueMap(services []ServiceItemExtended, key string) ma
return result
}
func getServicesWithLabelValue(services []ServiceItemExtended, key, expectedValue string) []ServiceItemExtended {
func filterServicesByLabelValue(services []ServiceItemExtended, key, expectedValue string) []ServiceItemExtended {
var srvWithLabel []ServiceItemExtended
for _, service := range services {
value, exists := service.Labels[key]
@@ -261,36 +297,17 @@ func getServicesWithLabelValue(services []ServiceItemExtended, key, expectedValu
return srvWithLabel
}
func getServiceLabelValueWithDefault(service ServiceItemExtended, key, defaultValue string) string {
value, exists := service.Labels[key]
if !exists {
return defaultValue
}
return value
}
func getServiceLabelsWithPrefix(service ServiceItemExtended, prefix string) map[string]string {
results := make(map[string]string)
for k, v := range service.Labels {
if strings.HasPrefix(k, prefix) {
results[k] = v
}
}
return results
}
func isPrimary(instance replicaInstance) bool {
_, data := instance.GetReplicaData()
return data.ReplicaRole == "Primary"
}
func isHealthy(instanceData *sf.ReplicaItemBase) bool {
return instanceData != nil && (instanceData.ReplicaStatus == "Ready" || instanceData.HealthState != "Error")
return instanceData != nil && (instanceData.ReplicaStatus == "Ready" && instanceData.HealthState != "Error")
}
func hasHTTPEndpoint(instanceData *sf.ReplicaItemBase) bool {
_, err := getDefaultEndpoint(instanceData.Address)
_, err := getReplicaDefaultEndpoint(instanceData)
return err == nil
}
@@ -314,37 +331,6 @@ func decodeEndpointData(endpointData string) (map[string]string, error) {
return endpoints, nil
}
func getDefaultEndpoint(endpointData string) (string, error) {
endpoints, err := decodeEndpointData(endpointData)
if err != nil {
return "", err
}
var defaultHTTPEndpointExists bool
var defaultHTTPEndpoint string
for _, v := range endpoints {
if strings.Contains(v, "http") {
defaultHTTPEndpoint = v
defaultHTTPEndpointExists = true
break
}
}
if !defaultHTTPEndpointExists {
return "", errors.New("no default endpoint found")
}
return defaultHTTPEndpoint, nil
}
func getNamedEndpoint(endpointData string, endpointName string) (string, error) {
endpoints, err := decodeEndpointData(endpointData)
if err != nil {
return "", err
}
endpoint, exists := endpoints[endpointName]
if !exists {
return "", errors.New("endpoint doesn't exist")
}
return endpoint, nil
func getBackendName(service ServiceItemExtended, partition PartitionItemExtended) string {
return provider.Normalize(service.Name + partition.PartitionInformation.ID)
}

View File

@@ -1,8 +1,8 @@
package servicefabric
const tmpl = `
{{$groupedServiceMap := getServices .Services "backend.group.name"}}
[backends]
{{$groupedServiceMap := getServicesWithLabelValueMap .Services "backend.group.name"}}
{{range $aggName, $aggServices := $groupedServiceMap }}
[backends."{{$aggName}}"]
{{range $service := $aggServices}}
@@ -10,7 +10,7 @@ const tmpl = `
{{range $instance := $partition.Instances}}
[backends."{{$aggName}}".servers."{{$service.ID}}-{{$instance.ID}}"]
url = "{{getDefaultEndpoint $instance}}"
weight = {{getServiceLabelValueWithDefault $service "backend.group.weight" "1"}}
weight = {{getLabelValue $service "backend.group.weight" "1"}}
{{end}}
{{end}}
{{end}}
@@ -20,45 +20,45 @@ const tmpl = `
{{if eq $partition.ServiceKind "Stateless"}}
[backends."{{$service.Name}}"]
[backends."{{$service.Name}}".LoadBalancer]
{{if hasServiceLabel $service "backend.loadbalancer.method"}}
method = "{{getServiceLabelValue $service "backend.loadbalancer.method" }}"
{{if hasLabel $service "backend.loadbalancer.method"}}
method = "{{getLabelValue $service "backend.loadbalancer.method" "" }}"
{{else}}
method = "drr"
{{end}}
{{if hasServiceLabel $service "backend.healthcheck"}}
{{if hasLabel $service "backend.healthcheck"}}
[backends."{{$service.Name}}".healthcheck]
path = "{{getServiceLabelValue $service "backend.healthcheck"}}"
interval = "{{getServiceLabelValueWithDefault $service "backend.healthcheck.interval" "10s"}}"
path = "{{getLabelValue $service "backend.healthcheck" ""}}"
interval = "{{getLabelValue $service "backend.healthcheck.interval" "10s"}}"
{{end}}
{{if hasServiceLabel $service "backend.loadbalancer.stickiness"}}
{{if hasLabel $service "backend.loadbalancer.stickiness"}}
[backends."{{$service.Name}}".LoadBalancer.stickiness]
{{end}}
{{if hasServiceLabel $service "backend.circuitbreaker"}}
{{if hasLabel $service "backend.circuitbreaker"}}
[backends."{{$service.Name}}".circuitbreaker]
expression = "{{getServiceLabelValue $service "backend.circuitbreaker"}}"
expression = "{{getLabelValue $service "backend.circuitbreaker" ""}}"
{{end}}
{{if hasServiceLabel $service "backend.maxconn.amount"}}
{{if hasLabel $service "backend.maxconn.amount"}}
[backends."{{$service.Name}}".maxconn]
amount = {{getServiceLabelValue $service "backend.maxconn.amount"}}
{{if hasServiceLabel $service "backend.maxconn.extractorfunc"}}
extractorfunc = "{{getServiceLabelValue $service "backend.maxconn.extractorfunc"}}"
amount = {{getLabelValue $service "backend.maxconn.amount" ""}}
{{if hasLabel $service "backend.maxconn.extractorfunc"}}
extractorfunc = "{{getLabelValue $service "backend.maxconn.extractorfunc" ""}}"
{{end}}
{{end}}
{{range $instance := $partition.Instances}}
[backends."{{$service.Name}}".servers."{{$instance.ID}}"]
url = "{{getDefaultEndpoint $instance}}"
weight = {{getServiceLabelValueWithDefault $service "backend.weight" "1"}}
weight = {{getLabelValue $service "backend.weight" "1"}}
{{end}}
{{else if eq $partition.ServiceKind "Stateful"}}
{{range $replica := $partition.Replicas}}
{{if isPrimary $replica}}
{{$backendName := (print $service.Name $partition.PartitionInformation.ID)}}
{{$backendName := getBackendName $service.Name $partition}}
[backends."{{$backendName}}".servers."{{$replica.ID}}"]
url = "{{getDefaultEndpoint $replica}}"
weight = 1
@@ -81,11 +81,11 @@ const tmpl = `
[frontends."{{$groupName}}"]
backend = "{{$groupName}}"
{{if hasServiceLabel $service "frontend.priority"}}
{{if hasLabel $service "frontend.priority"}}
priority = 100
{{end}}
{{range $key, $value := getServiceLabelsWithPrefix $service "frontend.rule"}}
{{range $key, $value := getLabelsWithPrefix $service "frontend.rule"}}
[frontends."{{$groupName}}".routes."{{$key}}"]
rule = "{{$value}}"
{{end}}
@@ -97,27 +97,27 @@ const tmpl = `
[frontends."{{$service.Name}}"]
backend = "{{$service.Name}}"
{{if hasServiceLabel $service "frontend.passHostHeader"}}
passHostHeader = {{getServiceLabelValue $service "frontend.passHostHeader" }}
{{if hasLabel $service "frontend.passHostHeader"}}
passHostHeader = {{getLabelValue $service "frontend.passHostHeader" ""}}
{{end}}
{{if hasServiceLabel $service "frontend.whitelistSourceRange"}}
whitelistSourceRange = {{getServiceLabelValue $service "frontend.whitelistSourceRange" }}
{{if hasLabel $service "frontend.whitelistSourceRange"}}
whitelistSourceRange = {{getLabelValue $service "frontend.whitelistSourceRange" ""}}
{{end}}
{{if hasServiceLabel $service "frontend.priority"}}
priority = {{getServiceLabelValue $service "frontend.priority"}}
{{if hasLabel $service "frontend.priority"}}
priority = {{getLabelValue $service "frontend.priority" ""}}
{{end}}
{{if hasServiceLabel $service "frontend.basicAuth"}}
basicAuth = {{getServiceLabelValue $service "frontend.basicAuth"}}
{{if hasLabel $service "frontend.basicAuth"}}
basicAuth = {{getLabelValue $service "frontend.basicAuth" ""}}
{{end}}
{{if hasServiceLabel $service "frontend.entryPoints"}}
entryPoints = {{getServiceLabelValue $service "frontend.entryPoints"}}
{{if hasLabel $service "frontend.entryPoints"}}
entryPoints = {{getLabelValue $service "frontend.entryPoints" ""}}
{{end}}
{{range $key, $value := getServiceLabelsWithPrefix $service "frontend.rule"}}
{{range $key, $value := getLabelsWithPrefix $service "frontend.rule"}}
[frontends."{{$service.Name}}".routes."{{$key}}"]
rule = "{{$value}}"
{{end}}
@@ -126,11 +126,11 @@ const tmpl = `
{{range $partition := $service.Partitions}}
{{$partitionId := $partition.PartitionInformation.ID}}
{{if hasServiceLabel $service "frontend.rule"}}
{{if hasLabel $service "frontend.rule"}}
[frontends."{{$service.Name}}/{{$partitionId}}"]
backend = "{{$service.Name}}/{{$partitionId}}"
backend = "{{getBackendName $service.Name $partition}}"
[frontends."{{$service.Name}}/{{$partitionId}}".routes.default]
rule = {{getServiceLabelValue $service "frontend.rule.partition.$partitionId"}}
rule = {{getLabelValue $service "frontend.rule.partition.$partitionId" ""}}
{{end}}
{{end}}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -56,15 +56,16 @@ type Port struct {
// Application is the definition for an application in marathon
type Application struct {
ID string `json:"id,omitempty"`
Cmd *string `json:"cmd,omitempty"`
Args *[]string `json:"args,omitempty"`
Constraints *[][]string `json:"constraints,omitempty"`
Container *Container `json:"container,omitempty"`
CPUs float64 `json:"cpus,omitempty"`
GPUs *float64 `json:"gpus,omitempty"`
Disk *float64 `json:"disk,omitempty"`
Env *map[string]string `json:"env,omitempty"`
ID string `json:"id,omitempty"`
Cmd *string `json:"cmd,omitempty"`
Args *[]string `json:"args,omitempty"`
Constraints *[][]string `json:"constraints,omitempty"`
Container *Container `json:"container,omitempty"`
CPUs float64 `json:"cpus,omitempty"`
GPUs *float64 `json:"gpus,omitempty"`
Disk *float64 `json:"disk,omitempty"`
// Contains non-secret environment variables. Secrets environment variables are part of the Secrets map.
Env *map[string]string `json:"-"`
Executor *string `json:"executor,omitempty"`
HealthChecks *[]HealthCheck `json:"healthChecks,omitempty"`
ReadinessChecks *[]ReadinessCheck `json:"readinessChecks,omitempty"`
@@ -99,6 +100,8 @@ type Application struct {
LastTaskFailure *LastTaskFailure `json:"lastTaskFailure,omitempty"`
Fetch *[]Fetch `json:"fetch,omitempty"`
IPAddressPerTask *IPAddressPerTask `json:"ipAddress,omitempty"`
Residency *Residency `json:"residency,omitempty"`
Secrets *map[string]Secret `json:"-"`
}
// ApplicationVersions is a collection of application versions for a specific app in marathon
@@ -149,6 +152,14 @@ type Stats struct {
LifeTime map[string]float64 `json:"lifeTime"`
}
// Secret is the environment variable and secret store path associated with a secret.
// The value for EnvVar is populated from the env field, and Source is populated from
// the secrets field of the application json.
type Secret struct {
EnvVar string
Source string
}
// SetIPAddressPerTask defines that the application will have a IP address defines by a external agent.
// This configuration is not allowed to be used with Port or PortDefinitions. Thus, the implementation
// clears both.
@@ -355,8 +366,8 @@ func (r *Application) EmptyLabels() *Application {
}
// AddEnv adds an environment variable to the application
// name: the name of the variable
// value: go figure, the value associated to the above
// name: the name of the variable
// value: go figure, the value associated to the above
func (r *Application) AddEnv(name, value string) *Application {
if r.Env == nil {
r.EmptyEnvs()
@@ -375,6 +386,28 @@ func (r *Application) EmptyEnvs() *Application {
return r
}
// AddSecret adds a secret declaration
// envVar: the name of the environment variable
// name: the name of the secret
// source: the source ID of the secret
func (r *Application) AddSecret(envVar, name, source string) *Application {
if r.Secrets == nil {
r.EmptySecrets()
}
(*r.Secrets)[name] = Secret{EnvVar: envVar, Source: source}
return r
}
// EmptySecrets explicitly empties the secrets -- use this if you need to empty
// the secrets of an application that already has secrets set (setting secrets to nil will
// keep the current value)
func (r *Application) EmptySecrets() *Application {
r.Secrets = &map[string]Secret{}
return r
}
// SetExecutor sets the executor
func (r *Application) SetExecutor(executor string) *Application {
r.Executor = &executor
@@ -571,6 +604,23 @@ func (r *Application) EmptyUnreachableStrategy() *Application {
return r
}
// SetResidency sets behavior for resident applications, an application is resident when
// it has local persistent volumes set
func (r *Application) SetResidency(whenLost TaskLostBehaviorType) *Application {
r.Residency = &Residency{
TaskLostBehavior: whenLost,
}
return r
}
// EmptyResidency explicitly empties the residency -- use this if
// you need to empty the residency of an application that already has
// the residency set (setting it to nil will keep the current value).
func (r *Application) EmptyResidency() *Application {
r.Residency = &Residency{}
return r
}
// String returns the json representation of this application
func (r *Application) String() string {
s, err := json.MarshalIndent(r, "", " ")
@@ -639,7 +689,7 @@ func (r *marathonClient) ApplicationVersions(name string) (*ApplicationVersions,
// name: the id used to identify the application
// version: the version (normally a timestamp) you wish to change to
func (r *marathonClient) SetApplicationVersion(name string, version *ApplicationVersion) (*DeploymentID, error) {
path := fmt.Sprintf(buildPath(name))
path := buildPath(name)
deploymentID := new(DeploymentID)
if err := r.apiPut(path, version, deploymentID); err != nil {
return nil, err

View File

@@ -0,0 +1,106 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package marathon
import (
"encoding/json"
"fmt"
)
// Alias aliases the Application struct so that it will be marshaled/unmarshaled automatically
type Alias Application
// TmpEnvSecret holds the secret values deserialized from the environment variables field
type TmpEnvSecret struct {
Secret string `json:"secret,omitempty"`
}
// TmpSecret holds the deserialized secrets field in a Marathon application configuration
type TmpSecret struct {
Source string `json:"source,omitempty"`
}
// UnmarshalJSON unmarshals the given Application JSON as expected except for environment variables and secrets.
// Environment varialbes are stored in the Env field. Secrets, including the environment variable part,
// are stored in the Secrets field.
func (app *Application) UnmarshalJSON(b []byte) error {
aux := &struct {
*Alias
Env map[string]interface{} `json:"env"`
Secrets map[string]TmpSecret `json:"secrets"`
}{
Alias: (*Alias)(app),
}
if err := json.Unmarshal(b, aux); err != nil {
return fmt.Errorf("malformed application definition %v", err)
}
env := &map[string]string{}
secrets := &map[string]Secret{}
for envName, genericEnvValue := range aux.Env {
switch envValOrSecret := genericEnvValue.(type) {
case string:
(*env)[envName] = envValOrSecret
case map[string]interface{}:
for secret, secretStore := range envValOrSecret {
if secStore, ok := secretStore.(string); ok && secret == "secret" {
(*secrets)[secStore] = Secret{EnvVar: envName}
break
}
return fmt.Errorf("unexpected secret field %v or value type %T", secret, envValOrSecret[secret])
}
default:
return fmt.Errorf("unexpected environment variable type %T", envValOrSecret)
}
}
app.Env = env
for k, v := range aux.Secrets {
tmp := (*secrets)[k]
tmp.Source = v.Source
(*secrets)[k] = tmp
}
app.Secrets = secrets
return nil
}
// MarshalJSON marshals the given Application as expected except for environment variables and secrets,
// which are marshaled from specialized structs. The environment variable piece of the secrets and other
// normal environment variables are combined and marshaled to the env field. The secrets and the related
// source are marshaled into the secrets field.
func (app *Application) MarshalJSON() ([]byte, error) {
env := make(map[string]interface{})
secrets := make(map[string]TmpSecret)
if app.Env != nil {
for k, v := range *app.Env {
env[string(k)] = string(v)
}
}
if app.Secrets != nil {
for k, v := range *app.Secrets {
env[v.EnvVar] = TmpEnvSecret{Secret: k}
secrets[k] = TmpSecret{v.Source}
}
}
aux := &struct {
*Alias
Env map[string]interface{} `json:"env,omitempty"`
Secrets map[string]TmpSecret `json:"secrets,omitempty"`
}{Alias: (*Alias)(app), Env: env, Secrets: secrets}
return json.Marshal(aux)
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import (
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"regexp"
@@ -154,6 +155,24 @@ var (
ErrMarathonDown = errors.New("all the Marathon hosts are presently down")
// ErrTimeoutError is thrown when the operation has timed out
ErrTimeoutError = errors.New("the operation has timed out")
// Default HTTP client used for SSE subscription requests
// It is invalid to set client.Timeout because it includes time to read response so
// set dial, tls handshake and response header timeouts instead
defaultHTTPSSEClient = &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 5 * time.Second,
}).Dial,
ResponseHeaderTimeout: 10 * time.Second,
TLSHandshakeTimeout: 5 * time.Second,
},
}
// Default HTTP client used for non SSE requests
defaultHTTPClient = &http.Client{
Timeout: 10 * time.Second,
}
)
// EventsChannelContext holds contextual data for an EventsChannel.
@@ -177,8 +196,8 @@ type marathonClient struct {
hosts *cluster
// a map of service you wish to listen to
listeners map[EventsChannel]EventsChannelContext
// a custom logger for debug log messages
debugLog *log.Logger
// a custom log function for debug messages
debugLog func(format string, v ...interface{})
// the marathon HTTP client to ensure consistency in requests
client *httpClient
}
@@ -196,9 +215,18 @@ type newRequestError struct {
// NewClient creates a new marathon client
// config: the configuration to use
func NewClient(config Config) (Marathon, error) {
// step: if no http client, set to default
// step: if the SSE HTTP client is missing, prefer a configured regular
// client, and otherwise use the default SSE HTTP client.
if config.HTTPSSEClient == nil {
config.HTTPSSEClient = defaultHTTPSSEClient
if config.HTTPClient != nil {
config.HTTPSSEClient = config.HTTPClient
}
}
// step: if a regular HTTP client is missing, use the default one.
if config.HTTPClient == nil {
config.HTTPClient = http.DefaultClient
config.HTTPClient = defaultHTTPClient
}
// step: if no polling wait time is set, default to 500 milliseconds.
@@ -215,16 +243,19 @@ func NewClient(config Config) (Marathon, error) {
return nil, err
}
debugLogOutput := config.LogOutput
if debugLogOutput == nil {
debugLogOutput = ioutil.Discard
debugLog := func(string, ...interface{}) {}
if config.LogOutput != nil {
logger := log.New(config.LogOutput, "", 0)
debugLog = func(format string, v ...interface{}) {
logger.Printf(format, v...)
}
}
return &marathonClient{
config: config,
listeners: make(map[EventsChannel]EventsChannelContext),
hosts: hosts,
debugLog: log.New(debugLogOutput, "", 0),
debugLog: debugLog,
client: client,
}, nil
}
@@ -280,7 +311,7 @@ func (r *marathonClient) apiCall(method, path string, body, result interface{})
if err != nil {
r.hosts.markDown(member)
// step: attempt the request on another member
r.debugLog.Printf("apiCall(): request failed on host: %s, error: %s, trying another\n", member, err)
r.debugLog("apiCall(): request failed on host: %s, error: %s, trying another", member, err)
continue
}
defer response.Body.Close()
@@ -292,9 +323,9 @@ func (r *marathonClient) apiCall(method, path string, body, result interface{})
}
if len(requestBody) > 0 {
r.debugLog.Printf("apiCall(): %v %v %s returned %v %s\n", request.Method, request.URL.String(), requestBody, response.Status, oneLogLine(respBody))
r.debugLog("apiCall(): %v %v %s returned %v %s", request.Method, request.URL.String(), requestBody, response.Status, oneLogLine(respBody))
} else {
r.debugLog.Printf("apiCall(): %v %v returned %v %s\n", request.Method, request.URL.String(), response.Status, oneLogLine(respBody))
r.debugLog("apiCall(): %v %v returned %v %s", request.Method, request.URL.String(), response.Status, oneLogLine(respBody))
}
// step: check for a successfull response
@@ -311,7 +342,7 @@ func (r *marathonClient) apiCall(method, path string, body, result interface{})
if response.StatusCode >= 500 && response.StatusCode <= 599 {
// step: mark the host as down
r.hosts.markDown(member)
r.debugLog.Printf("apiCall(): request failed, host: %s, status: %d, trying another\n", member, response.StatusCode)
r.debugLog("apiCall(): request failed, host: %s, status: %d, trying another", member, response.StatusCode)
continue
}
@@ -329,16 +360,28 @@ func (r *marathonClient) buildAPIRequest(method, path string, reader io.Reader)
}
// Build the HTTP request to Marathon
request, err = r.client.buildMarathonRequest(method, member, path, reader)
request, err = r.client.buildMarathonJSONRequest(method, member, path, reader)
if err != nil {
return nil, member, newRequestError{err}
}
return request, member, nil
}
// buildMarathonJSONRequest is like buildMarathonRequest but sets the
// Content-Type and Accept headers to application/json.
func (rc *httpClient) buildMarathonJSONRequest(method, member, path string, reader io.Reader) (request *http.Request, err error) {
req, err := rc.buildMarathonRequest(method, member, path, reader)
if err == nil {
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
}
return req, err
}
// buildMarathonRequest creates a new HTTP request and configures it according to the *httpClient configuration.
// The path must not contain a leading "/", otherwise buildMarathonRequest will panic.
func (rc *httpClient) buildMarathonRequest(method string, member string, path string, reader io.Reader) (request *http.Request, err error) {
func (rc *httpClient) buildMarathonRequest(method, member, path string, reader io.Reader) (request *http.Request, err error) {
if strings.HasPrefix(path, "/") {
panic(fmt.Sprintf("Path '%s' must not start with a leading slash", path))
}
@@ -361,9 +404,6 @@ func (rc *httpClient) buildMarathonRequest(method string, member string, path st
request.Header.Add("Authorization", "token="+rc.config.DCOSToken)
}
request.Header.Add("Content-Type", "application/json")
request.Header.Add("Accept", "application/json")
return request, nil
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -39,6 +39,9 @@ type cluster struct {
members []*member
// the marathon HTTP client to ensure consistency in requests
client *httpClient
// healthCheckInterval is the interval by which we probe down nodes for
// availability again.
healthCheckInterval time.Duration
}
// member represents an individual endpoint
@@ -94,8 +97,9 @@ func newCluster(client *httpClient, marathonURL string, isDCOS bool) (*cluster,
}
return &cluster{
client: client,
members: members,
client: client,
members: members,
healthCheckInterval: 5 * time.Second,
}, nil
}
@@ -130,20 +134,21 @@ func (c *cluster) markDown(endpoint string) {
// healthCheckNode performs a health check on the node and when active updates the status
func (c *cluster) healthCheckNode(node *member) {
// step: wait for the node to become active ... we are assuming a /ping is enough here
for {
ticker := time.NewTicker(c.healthCheckInterval)
defer ticker.Stop()
for range ticker.C {
req, err := c.client.buildMarathonRequest("GET", node.endpoint, "ping", nil)
if err == nil {
res, err := c.client.Do(req)
if err == nil && res.StatusCode == 200 {
// step: mark the node as active again
c.Lock()
node.status = memberStatusUp
c.Unlock()
break
}
}
<-time.After(time.Duration(5 * time.Second))
}
// step: mark the node as active again
c.Lock()
defer c.Unlock()
node.status = memberStatusUp
}
// activeMembers returns a list of active members

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -50,8 +50,10 @@ type Config struct {
DCOSToken string
// LogOutput the output for debug log messages
LogOutput io.Writer
// HTTPClient is the http client
// HTTPClient is the HTTP client
HTTPClient *http.Client
// HTTPSSEClient is the HTTP client used for SSE subscriptions, can't have client.Timeout set
HTTPSSEClient *http.Client
// wait time (in milliseconds) between repetitive requests to the API during polling
PollingWaitTime time.Duration
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -46,10 +46,71 @@ type Parameters struct {
// Volume is the docker volume details associated to the container
type Volume struct {
ContainerPath string `json:"containerPath,omitempty"`
HostPath string `json:"hostPath,omitempty"`
External *ExternalVolume `json:"external,omitempty"`
Mode string `json:"mode,omitempty"`
ContainerPath string `json:"containerPath,omitempty"`
HostPath string `json:"hostPath,omitempty"`
External *ExternalVolume `json:"external,omitempty"`
Mode string `json:"mode,omitempty"`
Persistent *PersistentVolume `json:"persistent,omitempty"`
}
type PersistentVolumeType string
const (
PersistentVolumeTypeRoot PersistentVolumeType = "root"
PersistentVolumeTypePath PersistentVolumeType = "path"
PersistentVolumeTypeMount PersistentVolumeType = "mount"
)
// PersistentVolume declares a Volume to be Persistent, and sets
// the size (in MiB) and optional type, max size (MiB) and constraints for the Volume.
type PersistentVolume struct {
Type PersistentVolumeType `json:"type,omitempty"`
Size int `json:"size"`
MaxSize int `json:"maxSize,omitempty"`
Constraints *[][]string `json:"constraints,omitempty"`
}
// SetType sets the type of mesos disk resource to use
// type: PersistentVolumeType enum
func (p *PersistentVolume) SetType(tp PersistentVolumeType) *PersistentVolume {
p.Type = tp
return p
}
// SetSize sets size of the persistent volume
// size: size in MiB
func (p *PersistentVolume) SetSize(size int) *PersistentVolume {
p.Size = size
return p
}
// SetMaxSize sets maximum size of an exclusive mount-disk resource to consider;
// does not apply to root or path disk resource types
// maxSize: size in MiB
func (p *PersistentVolume) SetMaxSize(maxSize int) *PersistentVolume {
p.MaxSize = maxSize
return p
}
// AddConstraint adds a new constraint
// constraints: the constraint definition, one constraint per array element
func (p *PersistentVolume) AddConstraint(constraints ...string) *PersistentVolume {
if p.Constraints == nil {
p.EmptyConstraints()
}
c := *p.Constraints
c = append(c, constraints)
p.Constraints = &c
return p
}
// EmptyConstraints explicitly empties constraints -- use this if you need to empty
// constraints of an application that already has constraints set (setting constraints to nil will
// keep the current value)
func (p *PersistentVolume) EmptyConstraints() *PersistentVolume {
p.Constraints = &[][]string{}
return p
}
// ExternalVolume is an external volume definition
@@ -98,6 +159,19 @@ func (container *Container) EmptyVolumes() *Container {
return container
}
// SetPersistentVolume defines persistent properties for volume
func (v *Volume) SetPersistentVolume() *PersistentVolume {
ev := &PersistentVolume{}
v.Persistent = ev
return ev
}
// EmptyPersistentVolume empties the persistent volume definition
func (v *Volume) EmptyPersistentVolume() *Volume {
v.Persistent = &PersistentVolume{}
return v
}
// SetExternalVolume define external elements for a volume
// name: the name of the volume
// provider: the provider of the volume (e.g. dvdi)

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015 Rohith All rights reserved.
Copyright 2015 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -136,7 +136,7 @@ func (r *marathonClient) GroupBy(name string, opts *GetGroupOpts) (*Group, error
// name: the identifier for the group
func (r *marathonClient) HasGroup(name string) (bool, error) {
path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
err := r.apiCall("GET", path, "", nil)
err := r.apiGet(path, "", nil)
if err != nil {
if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound {
return false, nil

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -31,37 +31,37 @@ type HealthCheck struct {
}
// SetCommand sets the given command on the health check.
func (h HealthCheck) SetCommand(c Command) HealthCheck {
func (h *HealthCheck) SetCommand(c Command) *HealthCheck {
h.Command = &c
return h
}
// SetPortIndex sets the given port index on the health check.
func (h HealthCheck) SetPortIndex(i int) HealthCheck {
func (h *HealthCheck) SetPortIndex(i int) *HealthCheck {
h.PortIndex = &i
return h
}
// SetPort sets the given port on the health check.
func (h HealthCheck) SetPort(i int) HealthCheck {
func (h *HealthCheck) SetPort(i int) *HealthCheck {
h.Port = &i
return h
}
// SetPath sets the given path on the health check.
func (h HealthCheck) SetPath(p string) HealthCheck {
func (h *HealthCheck) SetPath(p string) *HealthCheck {
h.Path = &p
return h
}
// SetMaxConsecutiveFailures sets the maximum consecutive failures on the health check.
func (h HealthCheck) SetMaxConsecutiveFailures(i int) HealthCheck {
func (h *HealthCheck) SetMaxConsecutiveFailures(i int) *HealthCheck {
h.MaxConsecutiveFailures = &i
return h
}
// SetIgnoreHTTP1xx sets ignore http 1xx on the health check.
func (h HealthCheck) SetIgnoreHTTP1xx(ignore bool) HealthCheck {
func (h *HealthCheck) SetIgnoreHTTP1xx(ignore bool) *HealthCheck {
h.IgnoreHTTP1xx = &ignore
return h
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,5 @@
/*
Copyright 2015 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -20,6 +21,7 @@ type LastTaskFailure struct {
AppID string `json:"appId,omitempty"`
Host string `json:"host,omitempty"`
Message string `json:"message,omitempty"`
SlaveID string `json:"slaveId,omitempty"`
State string `json:"state,omitempty"`
TaskID string `json:"taskId,omitempty"`
Timestamp string `json:"timestamp,omitempty"`

View File

@@ -1,5 +1,5 @@
/*
Copyright 2016 Rohith All rights reserved.
Copyright 2016 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -27,15 +27,39 @@ type PortDefinition struct {
}
// SetPort sets the given port for the PortDefinition
func (p PortDefinition) SetPort(port int) PortDefinition {
func (p *PortDefinition) SetPort(port int) *PortDefinition {
if p.Port == nil {
p.EmptyPort()
}
p.Port = &port
return p
}
// EmptyPort sets the port to 0 for the PortDefinition
func (p *PortDefinition) EmptyPort() *PortDefinition {
port := 0
p.Port = &port
return p
}
// SetProtocol sets the protocol for the PortDefinition
// protocol: the protocol as a string
func (p *PortDefinition) SetProtocol(protocol string) *PortDefinition {
p.Protocol = protocol
return p
}
// SetName sets the name for the PortDefinition
// name: the name of the PortDefinition
func (p *PortDefinition) SetName(name string) *PortDefinition {
p.Name = name
return p
}
// AddLabel adds a label to the PortDefinition
// name: the name of the label
// value: value for this label
func (p PortDefinition) AddLabel(name, value string) PortDefinition {
func (p *PortDefinition) AddLabel(name, value string) *PortDefinition {
if p.Labels == nil {
p.EmptyLabels()
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2016 Rohith All rights reserved.
Copyright 2016 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -52,9 +52,5 @@ func (r *marathonClient) Queue() (*Queue, error) {
// appID: the ID of the application
func (r *marathonClient) DeleteQueueDelay(appID string) error {
path := fmt.Sprintf("%s/%s/delay", marathonAPIQueue, trimRootPath(appID))
err := r.apiDelete(path, nil, nil)
if err != nil {
return err
}
return nil
return r.apiDelete(path, nil, nil)
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2017 Rohith All rights reserved.
Copyright 2017 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

48
vendor/github.com/gambol99/go-marathon/residency.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package marathon
import "time"
// TaskLostBehaviorType sets action taken when the resident task is lost
type TaskLostBehaviorType string
const (
// TaskLostBehaviorTypeWaitForever indicates to not take any action when the resident task is lost
TaskLostBehaviorTypeWaitForever TaskLostBehaviorType = "WAIT_FOREVER"
// TaskLostBehaviorTypeWaitForever indicates to try relaunching the lost resident task on
// another node after the relaunch escalation timeout has elapsed
TaskLostBehaviorTypeRelaunchAfterTimeout TaskLostBehaviorType = "RELAUNCH_AFTER_TIMEOUT"
)
// Residency defines how terminal states of tasks with local persistent volumes are handled
type Residency struct {
TaskLostBehavior TaskLostBehaviorType `json:"taskLostBehavior,omitempty"`
RelaunchEscalationTimeoutSeconds int `json:"relaunchEscalationTimeoutSeconds,omitempty"`
}
// SetTaskLostBehavior sets the residency behavior
func (r *Residency) SetTaskLostBehavior(behavior TaskLostBehaviorType) *Residency {
r.TaskLostBehavior = behavior
return r
}
// SetRelaunchEscalationTimeout sets the residency relaunch escalation timeout with seconds precision
func (r *Residency) SetRelaunchEscalationTimeout(timeout time.Duration) *Residency {
r.RelaunchEscalationTimeoutSeconds = int(timeout.Seconds())
return r
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -103,8 +103,7 @@ func (r *marathonClient) registerSubscription() error {
case EventsTransportCallback:
return r.registerCallbackSubscription()
case EventsTransportSSE:
r.registerSSESubscription()
return nil
return r.registerSSESubscription()
default:
return fmt.Errorf("the events transport: %d is not supported", r.config.EventsTransport)
}
@@ -167,27 +166,34 @@ func (r *marathonClient) registerCallbackSubscription() error {
// connect to the SSE stream and to process the received events. To establish
// the connection it tries the active cluster members until no more member is
// active. When this happens it will retry to get a connection every 5 seconds.
func (r *marathonClient) registerSSESubscription() {
func (r *marathonClient) registerSSESubscription() error {
if r.subscribedToSSE {
return
return nil
}
if r.config.HTTPSSEClient.Timeout != 0 {
return fmt.Errorf(
"global timeout must not be set for SSE connections (found %s) -- remove global timeout from HTTP client or provide separate SSE HTTP client without global timeout",
r.config.HTTPSSEClient.Timeout,
)
}
go func() {
for {
stream, err := r.connectToSSE()
if err != nil {
r.debugLog.Printf("Error connecting SSE subscription: %s", err)
r.debugLog("Error connecting SSE subscription: %s", err)
<-time.After(5 * time.Second)
continue
}
err = r.listenToSSE(stream)
stream.Close()
r.debugLog.Printf("Error on SSE subscription: %s", err)
r.debugLog("Error on SSE subscription: %s", err)
}
}()
r.subscribedToSSE = true
return nil
}
// connectToSSE tries to establish an *eventsource.Stream to any of the Marathon cluster members, marking the
@@ -209,15 +215,15 @@ func (r *marathonClient) connectToSSE() (*eventsource.Stream, error) {
// its underlying fields for performance reasons. See note that at least the Transport
// should be reused here: https://golang.org/pkg/net/http/#Client
httpClient := &http.Client{
Transport: r.config.HTTPClient.Transport,
CheckRedirect: r.config.HTTPClient.CheckRedirect,
Jar: r.config.HTTPClient.Jar,
Timeout: r.config.HTTPClient.Timeout,
Transport: r.config.HTTPSSEClient.Transport,
CheckRedirect: r.config.HTTPSSEClient.CheckRedirect,
Jar: r.config.HTTPSSEClient.Jar,
Timeout: r.config.HTTPSSEClient.Timeout,
}
stream, err := eventsource.SubscribeWith("", httpClient, request)
if err != nil {
r.debugLog.Printf("Error subscribing to Marathon event stream: %s", err)
r.debugLog("Error subscribing to Marathon event stream: %s", err)
r.hosts.markDown(member)
continue
}
@@ -231,7 +237,7 @@ func (r *marathonClient) listenToSSE(stream *eventsource.Stream) error {
select {
case ev := <-stream.Events:
if err := r.handleEvent(ev.Data()); err != nil {
r.debugLog.Printf("listenToSSE(): failed to handle event: %v", err)
r.debugLog("listenToSSE(): failed to handle event: %v", err)
}
case err := <-stream.Errors:
return err
@@ -319,12 +325,12 @@ func (r *marathonClient) handleCallbackEvent(writer http.ResponseWriter, request
body, err := ioutil.ReadAll(request.Body)
if err != nil {
// TODO should this return a 500?
r.debugLog.Printf("handleCallbackEvent(): failed to read request body, error: %s\n", err)
r.debugLog("handleCallbackEvent(): failed to read request body, error: %s", err)
return
}
if err := r.handleEvent(string(body[:])); err != nil {
// TODO should this return a 500?
r.debugLog.Printf("handleCallbackEvent(): failed to handle event: %v\n", err)
r.debugLog("handleCallbackEvent(): failed to handle event: %v", err)
}
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -217,7 +217,7 @@ func (r *Task) allHealthChecksAlive() bool {
}
// step: check the health results then
for _, check := range r.HealthCheckResults {
if check.Alive == false {
if !check.Alive {
return false
}
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2017 Rohith All rights reserved.
Copyright 2017 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -65,13 +65,13 @@ func (us *UnreachableStrategy) MarshalJSON() ([]byte, error) {
}
// SetInactiveAfterSeconds sets the period after which instance will be marked as inactive.
func (us UnreachableStrategy) SetInactiveAfterSeconds(cap float64) UnreachableStrategy {
func (us *UnreachableStrategy) SetInactiveAfterSeconds(cap float64) *UnreachableStrategy {
us.InactiveAfterSeconds = &cap
return us
}
// SetExpungeAfterSeconds sets the period after which instance will be expunged.
func (us UnreachableStrategy) SetExpungeAfterSeconds(cap float64) UnreachableStrategy {
func (us *UnreachableStrategy) SetExpungeAfterSeconds(cap float64) *UnreachableStrategy {
us.ExpungeAfterSeconds = &cap
return us
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -23,13 +23,13 @@ type UpgradeStrategy struct {
}
// SetMinimumHealthCapacity sets the minimum health capacity.
func (us UpgradeStrategy) SetMinimumHealthCapacity(cap float64) UpgradeStrategy {
func (us *UpgradeStrategy) SetMinimumHealthCapacity(cap float64) *UpgradeStrategy {
us.MinimumHealthCapacity = &cap
return us
}
// SetMaximumOverCapacity sets the maximum over capacity.
func (us UpgradeStrategy) SetMaximumOverCapacity(cap float64) UpgradeStrategy {
func (us *UpgradeStrategy) SetMaximumOverCapacity(cap float64) *UpgradeStrategy {
us.MaximumOverCapacity = &cap
return us
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2014 Rohith All rights reserved.
Copyright 2014 The go-marathon Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -12,6 +12,7 @@ import (
"strings"
)
// DefaultAPIVersion is a default Service Fabric REST API version
const DefaultAPIVersion = "3.0"
// Client for Service Fabric.

View File

@@ -5,7 +5,6 @@ package forward
import (
"crypto/tls"
"io"
"net/http"
"net/http/httptest"
"net/http/httputil"
@@ -214,7 +213,7 @@ func (f *Forwarder) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if f.log.Level >= log.DebugLevel {
logEntry := f.log.WithField("Request", utils.DumpHttpRequest(req))
logEntry.Debug("vulcand/oxy/forward: begin ServeHttp on request")
defer logEntry.Debug("vulcand/oxy/forward: competed ServeHttp on request")
defer logEntry.Debug("vulcand/oxy/forward: completed ServeHttp on request")
}
if f.stateListener != nil {
@@ -333,27 +332,27 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request,
defer targetConn.Close()
errc := make(chan error, 2)
replicate := func(dst io.Writer, src io.Reader, dstName string, srcName string) {
_, errCopy := io.Copy(dst, src)
if errCopy != nil {
f.log.Errorf("vulcand/oxy/forward/websocket: Error when copying from %s to %s using io.Copy: %v", srcName, dstName, errCopy)
} else {
f.log.Infof("vulcand/oxy/forward/websocket: Copying from %s to %s using io.Copy completed without error.", srcName, dstName)
replicateWebsocketConn := func(dst, src *websocket.Conn, dstName, srcName string) {
var err error
for {
msgType, msg, err := src.ReadMessage()
if err != nil {
f.log.Errorf("vulcand/oxy/forward/websocket: Error when copying from %s to %s using ReadMessage: %v", srcName, dstName, err)
break
}
err = dst.WriteMessage(msgType, msg)
if err != nil {
f.log.Errorf("vulcand/oxy/forward/websocket: Error when copying from %s to %s using WriteMessage: %v", srcName, dstName, err)
break
}
}
errc <- errCopy
errc <- err
}
go replicate(targetConn.UnderlyingConn(), underlyingConn.UnderlyingConn(), "backend", "client")
go replicateWebsocketConn(underlyingConn, targetConn, "client", "backend")
go replicateWebsocketConn(targetConn, underlyingConn, "backend", "client")
// Try to read the first message
msgType, msg, err := targetConn.ReadMessage()
if err != nil {
log.Errorf("vulcand/oxy/forward/websocket: Couldn't read first message : %v", err)
} else {
underlyingConn.WriteMessage(msgType, msg)
}
go replicate(underlyingConn.UnderlyingConn(), targetConn.UnderlyingConn(), "client", "backend")
<-errc
}