Merge branch 'v1.5' into master
This commit is contained in:
commit
f6520727a3
44 changed files with 1035 additions and 463 deletions
|
@ -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,7 +184,15 @@ 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}}"
|
||||
{{if hasServiceRequestHeaders $container $serviceName}}
|
||||
[frontends."frontend-{{getServiceBackend $container $serviceName}}".headers.customrequestheaders]
|
||||
|
@ -204,7 +211,6 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
|
|||
[frontends."frontend-{{$frontend}}"]
|
||||
backend = "backend-{{getBackend $container}}"
|
||||
passHostHeader = {{getPassHostHeader $container}}
|
||||
redirect = "{{getRedirect $container}}"
|
||||
{{if getWhitelistSourceRange $container}}
|
||||
whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
|
||||
"{{.}}",
|
||||
|
@ -217,6 +223,14 @@ 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}}
|
||||
|
||||
[frontends."frontend-{{$frontend}}".headers]
|
||||
{{if hasSSLRedirectHeaders $container}}
|
||||
SSLRedirect = {{getSSLRedirectHeaders $container}}
|
||||
|
@ -426,13 +440,20 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend
|
|||
backend = "{{$frontend.Backend}}"
|
||||
priority = {{$frontend.Priority}}
|
||||
passHostHeader = {{$frontend.PassHostHeader}}
|
||||
redirect = "{{$frontend.Redirect}}"
|
||||
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}}
|
||||
|
||||
[frontends."{{$frontendName}}".headers]
|
||||
SSLRedirect = {{$frontend.Headers.SSLRedirect}}
|
||||
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}
|
||||
|
@ -764,13 +785,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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -356,7 +356,7 @@ A backend is responsible to load-balance the traffic coming from one or more fro
|
|||
|
||||
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 +373,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 +496,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 +582,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.
|
||||
|
||||
|
|
|
@ -135,7 +135,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` |
|
||||
|
|
|
@ -88,6 +88,12 @@ endpoint = "127.0.0.1:8500"
|
|||
#
|
||||
exposedByDefault = false
|
||||
|
||||
# Default domain used.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
domain = "consul.localhost"
|
||||
|
||||
# Prefix for Consul catalog tags.
|
||||
#
|
||||
# Optional
|
||||
|
|
|
@ -149,29 +149,33 @@ To enable constraints see [backend-specific constraints section](/configuration/
|
|||
|
||||
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,18 +206,21 @@ 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`. |
|
||||
|
||||
#### Security Headers
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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) |
|
|
@ -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:
|
||||
|
||||
|
|
8
glide.lock
generated
8
glide.lock
generated
|
@ -1,5 +1,5 @@
|
|||
hash: 8c5908b11f5078edd9ed93e2710ebb3a29b7e02d1259fddd679f8c46540becc9
|
||||
updated: 2017-11-30T10:34:41.246378337+01:00
|
||||
hash: f0d5ef854a4c115306c63c15320b595c29f715950eaf5f18418149886ecda400
|
||||
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: c01c1ef60ed612c5e42c1ceae0c6f92e67619cc3
|
||||
- name: github.com/coreos/bbolt
|
||||
version: 3c6cbfb299c11444eb2f8c9d48f0d2ce09157423
|
||||
- name: github.com/coreos/etcd
|
||||
|
@ -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
|
||||
|
|
|
@ -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.4
|
||||
- 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)
|
||||
|
|
|
@ -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.")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ type Service struct {
|
|||
Name string
|
||||
Tags []string
|
||||
Nodes []string
|
||||
Ports []int
|
||||
}
|
||||
|
||||
type serviceUpdate struct {
|
||||
|
@ -185,19 +186,25 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c
|
|||
errorCh <- err
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// A critical note is that the return of a blocking request is no guarantee of a change.
|
||||
// It is possible that there was an idempotent write that does not affect the result of the query.
|
||||
// Thus it is required to do extra check for changes...
|
||||
|
@ -304,8 +311,11 @@ func (p *CatalogProvider) getNodes(index map[string][]string) ([]catalogUpdate,
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -318,20 +328,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) {
|
||||
|
@ -344,14 +358,32 @@ 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 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{}
|
||||
|
@ -364,7 +396,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),
|
||||
|
|
|
@ -28,7 +28,7 @@ func (p *CatalogProvider) buildConfiguration(catalog []catalogUpdate) *types.Con
|
|||
"getTag": getTag,
|
||||
"hasTag": hasTag,
|
||||
"getEntryPoints": getEntryPoints,
|
||||
"hasMaxconnAttributes": p.hasMaxconnAttributes,
|
||||
"hasMaxconnAttributes": p.hasMaxConnAttributes,
|
||||
}
|
||||
|
||||
var allNodes []*api.ServiceEntry
|
||||
|
@ -69,7 +69,7 @@ func (p *CatalogProvider) setupFrontEndTemplate() {
|
|||
}
|
||||
|
||||
func (p *CatalogProvider) getFrontendRule(service serviceUpdate) string {
|
||||
customFrontendRule := p.getAttribute("frontend.rule", service.Attributes, "")
|
||||
customFrontendRule := p.getAttribute(label.SuffixFrontendRule, service.Attributes, "")
|
||||
if customFrontendRule == "" {
|
||||
customFrontendRule = p.FrontEndRule
|
||||
}
|
||||
|
@ -102,16 +102,16 @@ func (p *CatalogProvider) getFrontendRule(service serviceUpdate) string {
|
|||
}
|
||||
|
||||
func (p *CatalogProvider) getBasicAuth(tags []string) []string {
|
||||
list := p.getAttribute("frontend.auth.basic", tags, "")
|
||||
list := p.getAttribute(label.SuffixFrontendAuthBasic, tags, "")
|
||||
if list != "" {
|
||||
return strings.Split(list, ",")
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (p *CatalogProvider) hasMaxconnAttributes(attributes []string) bool {
|
||||
amount := p.getAttribute("backend.maxconn.amount", attributes, "")
|
||||
extractorfunc := p.getAttribute("backend.maxconn.extractorfunc", attributes, "")
|
||||
func (p *CatalogProvider) hasMaxConnAttributes(attributes []string) bool {
|
||||
amount := p.getAttribute(label.SuffixBackendMaxConnAmount, attributes, "")
|
||||
extractorfunc := p.getAttribute(label.SuffixBackendMaxConnExtractorFunc, attributes, "")
|
||||
return amount != "" && extractorfunc != ""
|
||||
}
|
||||
|
||||
|
|
|
@ -1166,7 +1166,7 @@ func TestHasNodeOrTagschanged(t *testing.T) {
|
|||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "Change detected con tags",
|
||||
desc: "Change detected on tags",
|
||||
current: map[string]Service{
|
||||
"foo-service": {
|
||||
Name: "foo",
|
||||
|
@ -1183,6 +1183,66 @@ func TestHasNodeOrTagschanged(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 {
|
||||
|
@ -1190,7 +1250,7 @@ func TestHasNodeOrTagschanged(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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,18 +13,22 @@ import (
|
|||
|
||||
func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.Configuration {
|
||||
var DockerFuncMap = template.FuncMap{
|
||||
"getBackend": getBackend,
|
||||
"getIPAddress": p.getIPAddress,
|
||||
"getPort": getPort,
|
||||
"getWeight": getFuncStringLabel(label.TraefikWeight, label.DefaultWeight),
|
||||
"getDomain": getFuncStringLabel(label.TraefikDomain, p.Domain),
|
||||
"getProtocol": getFuncStringLabel(label.TraefikProtocol, label.DefaultProtocol),
|
||||
"getPassHostHeader": getFuncStringLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
|
||||
"getPriority": getFuncStringLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
|
||||
"getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints),
|
||||
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
|
||||
"getFrontendRule": p.getFrontendRule,
|
||||
"getRedirect": getFuncStringLabel(label.TraefikFrontendRedirect, label.DefaultFrontendRedirect),
|
||||
"getBackend": getBackend,
|
||||
"getIPAddress": p.getIPAddress,
|
||||
"getPort": getPort,
|
||||
"getWeight": getFuncStringLabel(label.TraefikWeight, label.DefaultWeight),
|
||||
"getDomain": getFuncStringLabel(label.TraefikDomain, p.Domain),
|
||||
"getProtocol": getFuncStringLabel(label.TraefikProtocol, label.DefaultProtocol),
|
||||
"getPassHostHeader": getFuncStringLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
|
||||
"getPriority": getFuncStringLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
|
||||
"getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints),
|
||||
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
|
||||
"getFrontendRule": p.getFrontendRule,
|
||||
"hasRedirect": hasRedirect,
|
||||
"getRedirectEntryPoint": getFuncStringLabel(label.TraefikFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint),
|
||||
"getRedirectRegex": getFuncStringLabel(label.TraefikFrontendRedirectRegex, ""),
|
||||
"getRedirectReplacement": getFuncStringLabel(label.TraefikFrontendRedirectReplacement, ""),
|
||||
|
||||
"hasCircuitBreakerLabel": hasFunc(label.TraefikBackendCircuitBreakerExpression),
|
||||
"getCircuitBreakerExpression": getFuncStringLabel(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
|
||||
"hasLoadBalancerLabel": hasLoadBalancerLabel,
|
||||
|
@ -36,8 +40,6 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
|
|||
"hasStickinessLabel": hasFunc(label.TraefikBackendLoadBalancerStickiness),
|
||||
"getStickinessCookieName": getFuncStringLabel(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
|
||||
"isBackendLBSwarm": isBackendLBSwarm, // FIXME DEAD ?
|
||||
"getServiceBackend": getServiceBackend,
|
||||
"getServiceRedirect": getFuncServiceStringLabel(label.SuffixFrontendRedirect, label.DefaultFrontendRedirect),
|
||||
"getWhitelistSourceRange": getFuncSliceStringLabel(label.TraefikFrontendWhitelistSourceRange),
|
||||
|
||||
"hasRequestHeaders": hasFunc(label.TraefikFrontendRequestHeaders),
|
||||
|
@ -81,20 +83,25 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
|
|||
"hasIsDevelopmentHeaders": hasFunc(label.TraefikFrontendIsDevelopment),
|
||||
"getIsDevelopmentHeaders": getFuncBoolLabel(label.TraefikFrontendIsDevelopment, false),
|
||||
|
||||
"hasServices": hasServices,
|
||||
"getServiceNames": getServiceNames,
|
||||
"getServicePort": getServicePort,
|
||||
"hasServiceRequestHeaders": hasFuncServiceLabel(label.SuffixFrontendRequestHeaders),
|
||||
"getServiceRequestHeaders": getFuncServiceMapLabel(label.SuffixFrontendRequestHeaders),
|
||||
"hasServiceResponseHeaders": hasFuncServiceLabel(label.SuffixFrontendResponseHeaders),
|
||||
"getServiceResponseHeaders": getFuncServiceMapLabel(label.SuffixFrontendResponseHeaders),
|
||||
"getServiceWeight": getFuncServiceStringLabel(label.SuffixWeight, label.DefaultWeight),
|
||||
"getServiceProtocol": getFuncServiceStringLabel(label.SuffixProtocol, label.DefaultProtocol),
|
||||
"getServiceEntryPoints": getFuncServiceSliceStringLabel(label.SuffixFrontendEntryPoints),
|
||||
"getServiceBasicAuth": getFuncServiceSliceStringLabel(label.SuffixFrontendAuthBasic),
|
||||
"getServiceFrontendRule": p.getServiceFrontendRule,
|
||||
"getServicePassHostHeader": getFuncServiceStringLabel(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeader),
|
||||
"getServicePriority": getFuncServiceStringLabel(label.SuffixFrontendPriority, label.DefaultFrontendPriority),
|
||||
"hasServices": hasServices,
|
||||
"getServiceBackend": getServiceBackend,
|
||||
"getServiceNames": getServiceNames,
|
||||
"getServicePort": getServicePort,
|
||||
"hasServiceRequestHeaders": hasFuncServiceLabel(label.SuffixFrontendRequestHeaders),
|
||||
"getServiceRequestHeaders": getFuncServiceMapLabel(label.SuffixFrontendRequestHeaders),
|
||||
"hasServiceResponseHeaders": hasFuncServiceLabel(label.SuffixFrontendResponseHeaders),
|
||||
"getServiceResponseHeaders": getFuncServiceMapLabel(label.SuffixFrontendResponseHeaders),
|
||||
"getServiceWeight": getFuncServiceStringLabel(label.SuffixWeight, label.DefaultWeight),
|
||||
"getServiceProtocol": getFuncServiceStringLabel(label.SuffixProtocol, label.DefaultProtocol),
|
||||
"getServiceEntryPoints": getFuncServiceSliceStringLabel(label.SuffixFrontendEntryPoints),
|
||||
"getServiceBasicAuth": getFuncServiceSliceStringLabel(label.SuffixFrontendAuthBasic),
|
||||
"getServiceFrontendRule": p.getServiceFrontendRule,
|
||||
"getServicePassHostHeader": getFuncServiceStringLabel(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeader),
|
||||
"getServicePriority": getFuncServiceStringLabel(label.SuffixFrontendPriority, label.DefaultFrontendPriority),
|
||||
"hasServiceRedirect": hasServiceRedirect,
|
||||
"getServiceRedirectEntryPoint": getFuncServiceStringLabel(label.SuffixFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint),
|
||||
"getServiceRedirectReplacement": getFuncServiceStringLabel(label.SuffixFrontendRedirectReplacement, ""),
|
||||
"getServiceRedirectRegex": getFuncServiceStringLabel(label.SuffixFrontendRedirectRegex, ""),
|
||||
}
|
||||
// filter containers
|
||||
filteredContainers := fun.Filter(func(container dockerData) bool {
|
||||
|
@ -125,11 +132,11 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
|
|||
Servers map[string][]dockerData
|
||||
Domain string
|
||||
}{
|
||||
filteredContainers,
|
||||
frontends,
|
||||
backends,
|
||||
servers,
|
||||
p.Domain,
|
||||
Containers: filteredContainers,
|
||||
Frontends: frontends,
|
||||
Backends: backends,
|
||||
Servers: servers,
|
||||
Domain: p.Domain,
|
||||
}
|
||||
|
||||
configuration, err := p.GetConfiguration("templates/docker.tmpl", DockerFuncMap, templateObjects)
|
||||
|
|
|
@ -167,6 +167,11 @@ func isBackendLBSwarm(container dockerData) bool {
|
|||
return label.GetBoolValue(container.Labels, labelBackendLoadBalancerSwarm, false)
|
||||
}
|
||||
|
||||
func hasRedirect(container dockerData) bool {
|
||||
return label.Has(container.Labels, label.TraefikFrontendRedirectEntryPoint) ||
|
||||
label.Has(container.Labels, label.TraefikFrontendRedirectReplacement) && label.Has(container.Labels, label.TraefikFrontendRedirectRegex)
|
||||
}
|
||||
|
||||
// Label functions
|
||||
|
||||
func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 {
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
docker "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
|
@ -39,7 +41,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",
|
||||
|
@ -64,10 +65,10 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
|||
containerJSON(
|
||||
name("test1"),
|
||||
labels(map[string]string{
|
||||
label.TraefikBackend: "foobar",
|
||||
label.TraefikFrontendEntryPoints: "http,https",
|
||||
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
label.TraefikFrontendRedirect: "https",
|
||||
label.TraefikBackend: "foobar",
|
||||
label.TraefikFrontendEntryPoints: "http,https",
|
||||
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
label.TraefikFrontendRedirectEntryPoint: "https",
|
||||
}),
|
||||
ports(nat.PortMap{
|
||||
"80/tcp": {},
|
||||
|
@ -91,7 +92,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",
|
||||
|
@ -103,7 +106,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",
|
||||
|
@ -151,7 +153,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",
|
||||
|
@ -197,13 +198,10 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
|||
ExposedByDefault: true,
|
||||
}
|
||||
actualConfig := provider.buildConfiguration(dockerDataList)
|
||||
// Compare backends
|
||||
if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) {
|
||||
t.Errorf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends)
|
||||
}
|
||||
if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) {
|
||||
t.Errorf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends)
|
||||
}
|
||||
require.NotNil(t, actualConfig, "actualConfig")
|
||||
|
||||
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
|
||||
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
"github.com/containous/traefik/types"
|
||||
docker "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSwarmGetFrontendName(t *testing.T) {
|
||||
|
@ -416,7 +418,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",
|
||||
|
@ -447,11 +448,11 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
|
|||
swarmService(
|
||||
serviceName("test1"),
|
||||
serviceLabels(map[string]string{
|
||||
label.TraefikPort: "80",
|
||||
label.TraefikBackend: "foobar",
|
||||
label.TraefikFrontendEntryPoints: "http,https",
|
||||
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
label.TraefikFrontendRedirect: "https",
|
||||
label.TraefikPort: "80",
|
||||
label.TraefikBackend: "foobar",
|
||||
label.TraefikFrontendEntryPoints: "http,https",
|
||||
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
label.TraefikFrontendRedirectEntryPoint: "https",
|
||||
}),
|
||||
withEndpointSpec(modeVIP),
|
||||
withEndpoint(virtualIP("1", "127.0.0.1/24")),
|
||||
|
@ -472,7 +473,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",
|
||||
|
@ -484,7 +487,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",
|
||||
|
@ -531,14 +533,12 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
|
|||
ExposedByDefault: true,
|
||||
SwarmMode: true,
|
||||
}
|
||||
|
||||
actualConfig := provider.buildConfiguration(dockerDataList)
|
||||
// Compare backends
|
||||
if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) {
|
||||
t.Errorf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends)
|
||||
}
|
||||
if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) {
|
||||
t.Errorf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends)
|
||||
}
|
||||
require.NotNil(t, actualConfig, "actualConfig")
|
||||
|
||||
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
|
||||
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,16 @@ func getServicePort(container dockerData, serviceName string) string {
|
|||
return getPort(container)
|
||||
}
|
||||
|
||||
func hasServiceRedirect(container dockerData, serviceName string) bool {
|
||||
serviceLabels := getServiceLabels(container, serviceName)
|
||||
if len(serviceLabels) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return label.Has(serviceLabels, label.SuffixFrontendRedirectEntryPoint) ||
|
||||
label.Has(serviceLabels, label.SuffixFrontendRedirectRegex) && label.Has(serviceLabels, label.SuffixFrontendRedirectReplacement)
|
||||
}
|
||||
|
||||
// Service label functions
|
||||
|
||||
func getFuncServiceMapLabel(labelSuffix string) func(container dockerData, serviceName string) map[string]string {
|
||||
|
|
|
@ -121,7 +121,7 @@ func TestDockerGetFuncServiceStringLabel(t *testing.T) {
|
|||
|
||||
actual := getFuncServiceStringLabel(test.suffixLabel, test.defaultValue)(dData, "myservice")
|
||||
if actual != test.expected {
|
||||
t.Fatalf("got %q, expected %q", actual, test.expected)
|
||||
t.Errorf("got %q, expected %q", actual, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ func TestDockerGetFuncServiceSliceStringLabel(t *testing.T) {
|
|||
actual := getFuncServiceSliceStringLabel(test.suffixLabel)(dData, "myservice")
|
||||
|
||||
if !reflect.DeepEqual(actual, test.expected) {
|
||||
t.Fatalf("for container %q: got %q, expected %q", dData.Name, actual, test.expected)
|
||||
t.Errorf("for container %q: got %q, expected %q", dData.Name, actual, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
|
@ -9,6 +8,8 @@ import (
|
|||
"github.com/containous/traefik/types"
|
||||
docker "github.com/docker/docker/api/types"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDockerGetServicePort(t *testing.T) {
|
||||
|
@ -41,7 +42,7 @@ func TestDockerGetServicePort(t *testing.T) {
|
|||
dData := parseContainer(test.container)
|
||||
actual := getServicePort(dData, "myservice")
|
||||
if actual != test.expected {
|
||||
t.Fatalf("expected %q, got %q", test.expected, actual)
|
||||
t.Errorf("expected %q, got %q", test.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -79,7 +80,7 @@ func TestDockerGetServiceFrontendRule(t *testing.T) {
|
|||
dData := parseContainer(test.container)
|
||||
actual := provider.getServiceFrontendRule(dData, "myservice")
|
||||
if actual != test.expected {
|
||||
t.Fatalf("expected %q, got %q", test.expected, actual)
|
||||
t.Errorf("expected %q, got %q", test.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -115,7 +116,7 @@ func TestDockerGetServiceBackend(t *testing.T) {
|
|||
dData := parseContainer(test.container)
|
||||
actual := getServiceBackend(dData, "myservice")
|
||||
if actual != test.expected {
|
||||
t.Fatalf("expected %q, got %q", test.expected, actual)
|
||||
t.Errorf("expected %q, got %q", test.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -137,10 +138,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": {},
|
||||
|
@ -154,7 +155,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",
|
||||
|
@ -179,16 +182,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": {},
|
||||
|
@ -215,7 +218,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",
|
||||
|
@ -227,7 +232,6 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
|
|||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
BasicAuth: []string{},
|
||||
Redirect: "",
|
||||
Routes: map[string]types.Route{
|
||||
"service-anotherservice": {
|
||||
Rule: "Path:/anotherpath",
|
||||
|
@ -274,13 +278,10 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
actualConfig := provider.buildConfiguration(dockerDataList)
|
||||
// Compare backends
|
||||
if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) {
|
||||
t.Fatalf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends)
|
||||
}
|
||||
if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) {
|
||||
t.Fatalf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends)
|
||||
}
|
||||
require.NotNil(t, actualConfig, "actualConfig")
|
||||
|
||||
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
|
||||
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,9 +152,22 @@ func priority(value int) func(*types.Frontend) {
|
|||
}
|
||||
}
|
||||
|
||||
func redirect(value string) func(*types.Frontend) {
|
||||
func redirectEntryPoint(name string) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
f.Redirect = value
|
||||
if f.Redirect == nil {
|
||||
f.Redirect = &types.Redirect{}
|
||||
}
|
||||
f.Redirect.EntryPoint = name
|
||||
}
|
||||
}
|
||||
|
||||
func redirectRegex(regex, replacement string) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
if f.Redirect == nil {
|
||||
f.Redirect = &types.Redirect{}
|
||||
}
|
||||
f.Redirect.Regex = regex
|
||||
f.Redirect.Replacement = replacement
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -203,8 +203,6 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
|||
|
||||
whitelistSourceRange := label.GetSliceStringValue(i.Annotations, annotationKubernetesWhitelistSourceRange)
|
||||
|
||||
entryPointRedirect := i.Annotations[label.TraefikFrontendRedirect]
|
||||
|
||||
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
|
||||
basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient)
|
||||
if err != nil {
|
||||
|
@ -245,7 +243,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,
|
||||
}
|
||||
|
@ -470,3 +468,23 @@ func equalPorts(servicePort v1.ServicePort, ingressPort intstr.IntOrString) bool
|
|||
func shouldProcessIngress(ingressClass string) bool {
|
||||
return ingressClass == "" || ingressClass == "traefik"
|
||||
}
|
||||
|
||||
func getFrontendRedirect(i *v1beta1.Ingress) *types.Redirect {
|
||||
frontendRedirectEntryPoint, ok := i.Annotations[label.TraefikFrontendRedirectEntryPoint]
|
||||
frep := ok && len(frontendRedirectEntryPoint) > 0
|
||||
|
||||
frontendRedirectRegex, ok := i.Annotations[label.TraefikFrontendRedirectRegex]
|
||||
frrg := ok && len(frontendRedirectRegex) > 0
|
||||
|
||||
frontendRedirectReplacement, ok := i.Annotations[label.TraefikFrontendRedirectReplacement]
|
||||
frrp := ok && len(frontendRedirectReplacement) > 0
|
||||
|
||||
if frep || frrg && frrp {
|
||||
return &types.Redirect{
|
||||
EntryPoint: frontendRedirectEntryPoint,
|
||||
Regex: frontendRedirectRegex,
|
||||
Replacement: frontendRedirectReplacement,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -608,7 +608,7 @@ func TestIngressAnnotations(t *testing.T) {
|
|||
buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesIngressClass, "traefik"),
|
||||
iAnnotation(label.TraefikFrontendRedirect, "https"),
|
||||
iAnnotation(label.TraefikFrontendRedirectEntryPoint, "https"),
|
||||
iRules(
|
||||
iRule(
|
||||
iHost("redirect"),
|
||||
|
@ -752,7 +752,7 @@ func TestIngressAnnotations(t *testing.T) {
|
|||
),
|
||||
frontend("redirect/https",
|
||||
passHostHeader(),
|
||||
redirect("https"),
|
||||
redirectEntryPoint("https"),
|
||||
routes(
|
||||
route("/https", "PathPrefix:/https"),
|
||||
route("redirect", "Host:redirect")),
|
||||
|
@ -1182,6 +1182,7 @@ func TestBasicAuthInTemplate(t *testing.T) {
|
|||
require.NoError(t, err, "error loading ingresses")
|
||||
|
||||
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)
|
||||
|
|
|
@ -22,7 +22,7 @@ const (
|
|||
DefaultPassHostHeader = "true"
|
||||
DefaultFrontendPriority = "0"
|
||||
DefaultCircuitBreakerExpression = "NetworkErrorRatio() > 1"
|
||||
DefaultFrontendRedirect = ""
|
||||
DefaultFrontendRedirectEntryPoint = ""
|
||||
DefaultBackendLoadBalancerMethod = "wrr"
|
||||
DefaultBackendMaxconnExtractorFunc = "request.host"
|
||||
DefaultBackendLoadbalancerStickinessCookieName = ""
|
||||
|
|
|
@ -48,7 +48,9 @@ const (
|
|||
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
|
||||
SuffixFrontendPassTLSCert = "frontend.passTLSCert"
|
||||
SuffixFrontendPriority = "frontend.priority"
|
||||
SuffixFrontendRedirect = "frontend.redirect"
|
||||
SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint"
|
||||
SuffixFrontendRedirectRegex = "frontend.redirect.regex"
|
||||
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
|
||||
SuffixFrontendRule = "frontend.rule"
|
||||
SuffixFrontendRuleType = "frontend.rule.type"
|
||||
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange"
|
||||
|
@ -79,7 +81,9 @@ const (
|
|||
TraefikFrontendPriority = Prefix + SuffixFrontendPriority
|
||||
TraefikFrontendRule = Prefix + SuffixFrontendRule
|
||||
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType
|
||||
TraefikFrontendRedirect = Prefix + SuffixFrontendRedirect
|
||||
TraefikFrontendRedirectEntryPoint = Prefix + SuffixFrontendRedirectEntryPoint
|
||||
TraefikFrontendRedirectRegex = Prefix + SuffixFrontendRedirectRegex
|
||||
TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement
|
||||
TraefikFrontendValue = Prefix + SuffixFrontendValue
|
||||
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
|
||||
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders
|
||||
|
|
|
@ -27,15 +27,18 @@ func (p *Provider) buildConfiguration(services []rancherData) *types.Configurati
|
|||
"getFrontendRule": p.getFrontendRule,
|
||||
"hasCircuitBreakerLabel": hasFunc(label.TraefikBackendCircuitBreakerExpression),
|
||||
"getCircuitBreakerExpression": getFuncString(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
|
||||
"hasLoadBalancerLabel": hasLoadBalancerLabel, // OK
|
||||
"getLoadBalancerMethod": getFuncString(label.TraefikFrontendRedirect, label.DefaultBackendLoadBalancerMethod),
|
||||
"hasMaxConnLabels": hasMaxConnLabels, // OK
|
||||
"hasLoadBalancerLabel": hasLoadBalancerLabel,
|
||||
"getLoadBalancerMethod": getFuncString(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
|
||||
"hasMaxConnLabels": hasMaxConnLabels,
|
||||
"getMaxConnAmount": getFuncInt64(label.TraefikBackendMaxConnAmount, math.MaxInt64),
|
||||
"getMaxConnExtractorFunc": getFuncString(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc),
|
||||
"getSticky": getSticky, // deprecated
|
||||
"hasStickinessLabel": hasFunc(label.TraefikBackendLoadBalancerStickiness),
|
||||
"getStickinessCookieName": getFuncString(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
|
||||
"getRedirect": getFuncString(label.TraefikFrontendRedirect, label.DefaultFrontendRedirect),
|
||||
"hasRedirect": hasRedirect,
|
||||
"getRedirectEntryPoint": getFuncString(label.TraefikFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint),
|
||||
"getRedirectRegex": getFuncString(label.TraefikFrontendRedirectRegex, ""),
|
||||
"getRedirectReplacement": getFuncString(label.TraefikFrontendRedirectReplacement, ""),
|
||||
}
|
||||
|
||||
// filter services
|
||||
|
@ -116,7 +119,8 @@ func (p *Provider) getFrontendName(service rancherData) string {
|
|||
}
|
||||
|
||||
// TODO: Deprecated
|
||||
// Deprecated replaced by Stickiness
|
||||
// replaced by Stickiness
|
||||
// Deprecated
|
||||
func getSticky(service rancherData) string {
|
||||
if label.Has(service.Labels, label.TraefikBackendLoadBalancerSticky) {
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
|
||||
|
@ -143,6 +147,11 @@ func getBackend(service rancherData) string {
|
|||
return provider.Normalize(backend)
|
||||
}
|
||||
|
||||
func hasRedirect(service rancherData) bool {
|
||||
return label.Has(service.Labels, label.TraefikFrontendRedirectEntryPoint) ||
|
||||
label.Has(service.Labels, label.TraefikFrontendRedirectRegex) && label.Has(service.Labels, label.TraefikFrontendRedirectReplacement)
|
||||
}
|
||||
|
||||
// Label functions
|
||||
|
||||
func getFuncString(labelName string, defaultValue string) func(service rancherData) string {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/containous/traefik/provider/label"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProviderServiceFilter(t *testing.T) {
|
||||
|
@ -361,9 +362,9 @@ func TestProviderLoadRancherConfig(t *testing.T) {
|
|||
{
|
||||
Name: "test/service",
|
||||
Labels: map[string]string{
|
||||
label.TraefikPort: "80",
|
||||
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
label.TraefikFrontendRedirect: "https",
|
||||
label.TraefikPort: "80",
|
||||
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
label.TraefikFrontendRedirectEntryPoint: "https",
|
||||
},
|
||||
Health: "healthy",
|
||||
Containers: []string{"127.0.0.1"},
|
||||
|
@ -376,7 +377,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",
|
||||
|
@ -405,9 +408,77 @@ func TestProviderLoadRancherConfig(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
actualConfig := provider.buildConfiguration(test.services)
|
||||
require.NotNil(t, actualConfig)
|
||||
|
||||
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
|
||||
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
label.TraefikFrontendRedirectEntryPoint: "https",
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "with Redirect regex label",
|
||||
service: rancherData{
|
||||
Name: "test-service",
|
||||
Labels: map[string]string{
|
||||
label.TraefikFrontendRedirectRegex: `(.+)`,
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "with Redirect replacement label",
|
||||
service: rancherData{
|
||||
Name: "test-service",
|
||||
Labels: map[string]string{
|
||||
label.TraefikFrontendRedirectReplacement: "$1",
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "with Redirect regex & replacement labels",
|
||||
service: rancherData{
|
||||
Name: "test-service",
|
||||
Labels: map[string]string{
|
||||
label.TraefikFrontendRedirectRegex: `(.+)`,
|
||||
label.TraefikFrontendRedirectReplacement: "$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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,10 @@ import (
|
|||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRedirectRegex = `^(?:https?:\/\/)?([\w\._-]+)(?::\d+)?(.*)$`
|
||||
)
|
||||
|
||||
var (
|
||||
httpServerLogger = stdlog.New(log.WriterLevel(logrus.DebugLevel), "", 0)
|
||||
)
|
||||
|
@ -944,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
|
||||
|
@ -1125,28 +1129,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)
|
||||
if frontend.Redirect != nil {
|
||||
rewrite, err := s.buildRedirectHandler(entryPointName, frontend.Redirect)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating Frontend Redirect: %v", err)
|
||||
}
|
||||
rewrite, err := middlewares.NewRewrite(regex, replacement, true)
|
||||
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 {
|
||||
|
@ -1296,41 +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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
|
|
|
@ -924,55 +924,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 {
|
||||
|
@ -982,9 +1136,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,7 +59,15 @@
|
|||
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}}"
|
||||
{{if hasServiceRequestHeaders $container $serviceName}}
|
||||
[frontends."frontend-{{getServiceBackend $container $serviceName}}".headers.customrequestheaders]
|
||||
|
@ -79,7 +86,6 @@
|
|||
[frontends."frontend-{{$frontend}}"]
|
||||
backend = "backend-{{getBackend $container}}"
|
||||
passHostHeader = {{getPassHostHeader $container}}
|
||||
redirect = "{{getRedirect $container}}"
|
||||
{{if getWhitelistSourceRange $container}}
|
||||
whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
|
||||
"{{.}}",
|
||||
|
@ -92,6 +98,14 @@
|
|||
basicAuth = [{{range getBasicAuth $container}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
|
||||
{{if hasRedirect $container}}
|
||||
[frontends."frontend-{{$frontend}}".redirect]
|
||||
entryPoint = "{{getRedirectEntryPoint $container}}"
|
||||
regex = "{{getRedirectRegex $container}}"
|
||||
replacement = "{{getRedirectReplacement $container}}"
|
||||
{{end}}
|
||||
|
||||
[frontends."frontend-{{$frontend}}".headers]
|
||||
{{if hasSSLRedirectHeaders $container}}
|
||||
SSLRedirect = {{getSSLRedirectHeaders $container}}
|
||||
|
|
|
@ -25,13 +25,20 @@
|
|||
backend = "{{$frontend.Backend}}"
|
||||
priority = {{$frontend.Priority}}
|
||||
passHostHeader = {{$frontend.PassHostHeader}}
|
||||
redirect = "{{$frontend.Redirect}}"
|
||||
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}}
|
||||
|
||||
[frontends."{{$frontendName}}".headers]
|
||||
SSLRedirect = {{$frontend.Headers.SSLRedirect}}
|
||||
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -153,7 +153,14 @@ type Frontend struct {
|
|||
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
|
||||
}
|
||||
|
|
188
vendor/github.com/containous/traefik-extra-service-fabric/servicefabric.go
generated
vendored
188
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,25 +297,6 @@ 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"
|
||||
|
@ -290,7 +307,7 @@ func isHealthy(instanceData *sf.ReplicaItemBase) bool {
|
|||
}
|
||||
|
||||
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}}
|
||||
|
|
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.
|
||||
|
|
Loading…
Reference in a new issue