forked from Ivasoft/traefik
Compare commits
41 Commits
v1.5.0-rc2
...
v1.5.0-rc4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22bdbd2498 | ||
|
|
287fb78654 | ||
|
|
5b24403c8e | ||
|
|
e83599dd08 | ||
|
|
f30ad20c9b | ||
|
|
01e17b6c3e | ||
|
|
3e13ebec93 | ||
|
|
23c1a9ca8e | ||
|
|
741c739ef1 | ||
|
|
52f16e11a8 | ||
|
|
0ee6973e2f | ||
|
|
4819974a1c | ||
|
|
e8e8b41eed | ||
|
|
7d23d3c0a4 | ||
|
|
718fc7a79d | ||
|
|
bfd142b13b | ||
|
|
75533b2beb | ||
|
|
9a7821b8fa | ||
|
|
e8333883df | ||
|
|
1e44e339ad | ||
|
|
89a79d0f1b | ||
|
|
9e41485ff1 | ||
|
|
3c7c6c4d9f | ||
|
|
cd1b3904da | ||
|
|
b23b2611b3 | ||
|
|
877770f7cf | ||
|
|
3142a4f4b3 | ||
|
|
b4dc96527d | ||
|
|
35b5ca4c63 | ||
|
|
daf3023b02 | ||
|
|
b17d5b80b8 | ||
|
|
48b4eb5c0d | ||
|
|
7ecd6d20ba | ||
|
|
bddad57a7b | ||
|
|
799136a714 | ||
|
|
350d61b4a6 | ||
|
|
b6f5a66fab | ||
|
|
b0c12e2422 | ||
|
|
623a7dc7e6 | ||
|
|
709c7e5707 | ||
|
|
ee04f52a16 |
58
CHANGELOG.md
58
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
```
|
||||
93
docs/configuration/backends/consulcatalog.md
Normal file
93
docs/configuration/backends/consulcatalog.md
Normal 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
|
||||
```
|
||||
@@ -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>`
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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 |
|
||||
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------|
|
||||
|
||||
@@ -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) |
|
||||
@@ -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 :)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
4
docs/theme/partials/footer.html
vendored
4
docs/theme/partials/footer.html
vendored
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
12
glide.lock
generated
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ defaultEntryPoints = ["https"]
|
||||
[entryPoints.https]
|
||||
address = ":4443"
|
||||
[entryPoints.https.tls]
|
||||
[entryPoints.https02]
|
||||
address = ":8443"
|
||||
[entryPoints.https02.tls]
|
||||
|
||||
[web]
|
||||
address = ":8080"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
38
mkdocs.yml
38
mkdocs.yml
@@ -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 © 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 © 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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
sticky = {{ getSticky . }}
|
||||
{{if hasStickinessLabel $backend}}
|
||||
[backends."{{$backendName}}".loadBalancer.stickiness]
|
||||
cookieName = {{getStickinessCookieName $backend}}
|
||||
cookieName = "{{getStickinessCookieName $backend}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
62
vendor/github.com/containous/traefik-extra-service-fabric/labels.go
generated
vendored
62
vendor/github.com/containous/traefik-extra-service-fabric/labels.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
190
vendor/github.com/containous/traefik-extra-service-fabric/servicefabric.go
generated
vendored
190
vendor/github.com/containous/traefik-extra-service-fabric/servicefabric.go
generated
vendored
@@ -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)
|
||||
}
|
||||
|
||||
64
vendor/github.com/containous/traefik-extra-service-fabric/servicefabric_tmpl.go
generated
vendored
64
vendor/github.com/containous/traefik-extra-service-fabric/servicefabric_tmpl.go
generated
vendored
@@ -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}}
|
||||
|
||||
76
vendor/github.com/gambol99/go-marathon/application.go
generated
vendored
76
vendor/github.com/gambol99/go-marathon/application.go
generated
vendored
@@ -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
|
||||
|
||||
106
vendor/github.com/gambol99/go-marathon/application_marshalling.go
generated
vendored
Normal file
106
vendor/github.com/gambol99/go-marathon/application_marshalling.go
generated
vendored
Normal 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)
|
||||
}
|
||||
76
vendor/github.com/gambol99/go-marathon/client.go
generated
vendored
76
vendor/github.com/gambol99/go-marathon/client.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
|
||||
23
vendor/github.com/gambol99/go-marathon/cluster.go
generated
vendored
23
vendor/github.com/gambol99/go-marathon/cluster.go
generated
vendored
@@ -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
|
||||
|
||||
6
vendor/github.com/gambol99/go-marathon/config.go
generated
vendored
6
vendor/github.com/gambol99/go-marathon/config.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
2
vendor/github.com/gambol99/go-marathon/const.go
generated
vendored
2
vendor/github.com/gambol99/go-marathon/const.go
generated
vendored
@@ -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.
|
||||
|
||||
2
vendor/github.com/gambol99/go-marathon/deployment.go
generated
vendored
2
vendor/github.com/gambol99/go-marathon/deployment.go
generated
vendored
@@ -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.
|
||||
|
||||
84
vendor/github.com/gambol99/go-marathon/docker.go
generated
vendored
84
vendor/github.com/gambol99/go-marathon/docker.go
generated
vendored
@@ -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)
|
||||
|
||||
2
vendor/github.com/gambol99/go-marathon/error.go
generated
vendored
2
vendor/github.com/gambol99/go-marathon/error.go
generated
vendored
@@ -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.
|
||||
|
||||
2
vendor/github.com/gambol99/go-marathon/events.go
generated
vendored
2
vendor/github.com/gambol99/go-marathon/events.go
generated
vendored
@@ -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.
|
||||
|
||||
4
vendor/github.com/gambol99/go-marathon/group.go
generated
vendored
4
vendor/github.com/gambol99/go-marathon/group.go
generated
vendored
@@ -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
|
||||
|
||||
14
vendor/github.com/gambol99/go-marathon/health.go
generated
vendored
14
vendor/github.com/gambol99/go-marathon/health.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
2
vendor/github.com/gambol99/go-marathon/info.go
generated
vendored
2
vendor/github.com/gambol99/go-marathon/info.go
generated
vendored
@@ -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.
|
||||
|
||||
2
vendor/github.com/gambol99/go-marathon/last_task_failure.go
generated
vendored
2
vendor/github.com/gambol99/go-marathon/last_task_failure.go
generated
vendored
@@ -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"`
|
||||
|
||||
30
vendor/github.com/gambol99/go-marathon/port_definition.go
generated
vendored
30
vendor/github.com/gambol99/go-marathon/port_definition.go
generated
vendored
@@ -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()
|
||||
}
|
||||
|
||||
8
vendor/github.com/gambol99/go-marathon/queue.go
generated
vendored
8
vendor/github.com/gambol99/go-marathon/queue.go
generated
vendored
@@ -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)
|
||||
}
|
||||
|
||||
2
vendor/github.com/gambol99/go-marathon/readiness.go
generated
vendored
2
vendor/github.com/gambol99/go-marathon/readiness.go
generated
vendored
@@ -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
48
vendor/github.com/gambol99/go-marathon/residency.go
generated
vendored
Normal 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
|
||||
}
|
||||
38
vendor/github.com/gambol99/go-marathon/subscription.go
generated
vendored
38
vendor/github.com/gambol99/go-marathon/subscription.go
generated
vendored
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
4
vendor/github.com/gambol99/go-marathon/task.go
generated
vendored
4
vendor/github.com/gambol99/go-marathon/task.go
generated
vendored
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
6
vendor/github.com/gambol99/go-marathon/unreachable_strategy.go
generated
vendored
6
vendor/github.com/gambol99/go-marathon/unreachable_strategy.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
6
vendor/github.com/gambol99/go-marathon/upgrade_strategy.go
generated
vendored
6
vendor/github.com/gambol99/go-marathon/upgrade_strategy.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
2
vendor/github.com/gambol99/go-marathon/utils.go
generated
vendored
2
vendor/github.com/gambol99/go-marathon/utils.go
generated
vendored
@@ -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.
|
||||
|
||||
1
vendor/github.com/jjcollinge/servicefabric/servicefabric.go
generated
vendored
1
vendor/github.com/jjcollinge/servicefabric/servicefabric.go
generated
vendored
@@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultAPIVersion is a default Service Fabric REST API version
|
||||
const DefaultAPIVersion = "3.0"
|
||||
|
||||
// Client for Service Fabric.
|
||||
|
||||
37
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
37
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user