Merge branch 'v2.1' into master

This commit is contained in:
Fernandez Ludovic 2019-12-03 10:43:25 +01:00
commit 829649e905
73 changed files with 1497 additions and 517 deletions

View file

@ -1,3 +1,42 @@
## [v2.1.0-rc3](https://github.com/containous/traefik/tree/v2.1.0-rc3) (2019-12-02)
[All Commits](https://github.com/containous/traefik/compare/v2.1.0-rc2...v2.1.0-rc3)
**Bug fixes:**
- **[cli]** fix: sub command help ([#5887](https://github.com/containous/traefik/pull/5887) by [ldez](https://github.com/ldez))
- **[consulcatalog]** fix: consul catalog constraints. ([#5913](https://github.com/containous/traefik/pull/5913) by [ldez](https://github.com/ldez))
- **[consulcatalog]** Service registered with same id on Consul Catalog ([#5900](https://github.com/containous/traefik/pull/5900) by [mmatur](https://github.com/mmatur))
- **[webui]** Web UI: Avoid polling on /api/entrypoints ([#5863](https://github.com/containous/traefik/pull/5863) by [matthieuh](https://github.com/matthieuh))
- **[webui]** Web UI: Sync toolbar table state with url query params ([#5861](https://github.com/containous/traefik/pull/5861) by [matthieuh](https://github.com/matthieuh))
**Misc:**
- **[cli]** Add custom help function to command ([#5923](https://github.com/containous/traefik/pull/5923) by [Ullaakut](https://github.com/Ullaakut))
## [v2.0.6](https://github.com/containous/traefik/tree/v2.0.6) (2019-12-02)
[All Commits](https://github.com/containous/traefik/compare/v2.0.5...v2.0.6)
**Bug fixes:**
- **[acme]** Update go-acme/lego to 3.2.0 ([#5839](https://github.com/containous/traefik/pull/5839) by [kolaente](https://github.com/kolaente))
- **[cli,healthcheck]** Uses, if it exists, the ping entry point provided in the static configuration ([#5867](https://github.com/containous/traefik/pull/5867) by [jbdoumenjou](https://github.com/jbdoumenjou))
- **[healthcheck]** Healthcheck managed for all related services ([#5860](https://github.com/containous/traefik/pull/5860) by [jbdoumenjou](https://github.com/jbdoumenjou))
- **[logs,middleware]** Do not give responsewriter or its headers to asynchronous logging goroutine ([#5840](https://github.com/containous/traefik/pull/5840) by [mpl](https://github.com/mpl))
- **[middleware]** X-Forwarded-Proto must not skip the redirection. ([#5836](https://github.com/containous/traefik/pull/5836) by [ldez](https://github.com/ldez))
- **[middleware]** fix: location header rewrite. ([#5835](https://github.com/containous/traefik/pull/5835) by [ldez](https://github.com/ldez))
- **[middleware]** Remove Request Headers CORS Preflight Requirement ([#5903](https://github.com/containous/traefik/pull/5903) by [dtomcej](https://github.com/dtomcej))
- **[rancher]** Change service name in rancher provider to make webui service details view work ([#5895](https://github.com/containous/traefik/pull/5895) by [SantoDE](https://github.com/SantoDE))
- **[tracing]** Fix extraction for zipkin tracing ([#5920](https://github.com/containous/traefik/pull/5920) by [jcchavezs](https://github.com/jcchavezs))
- **[webui]** Web UI: Avoid unnecessary duplicated api calls ([#5884](https://github.com/containous/traefik/pull/5884) by [matthieuh](https://github.com/matthieuh))
- **[webui]** Web UI: Avoid some router properties to overflow their container ([#5872](https://github.com/containous/traefik/pull/5872) by [matthieuh](https://github.com/matthieuh))
- **[webui]** Web UI: Fix displayed tcp service details ([#5868](https://github.com/containous/traefik/pull/5868) by [matthieuh](https://github.com/matthieuh))
**Documentation:**
- **[acme]** doc: fix wrong acme information ([#5837](https://github.com/containous/traefik/pull/5837) by [ldez](https://github.com/ldez))
- **[docker,docker/swarm]** Add Swarm section to the Docker Provider Documentation ([#5874](https://github.com/containous/traefik/pull/5874) by [dduportal](https://github.com/dduportal))
- **[docker]** Update router entrypoint example ([#5766](https://github.com/containous/traefik/pull/5766) by [woto](https://github.com/woto))
- **[k8s/helm]** Mention the experimental Helm Chart in the installation section of documentation ([#5879](https://github.com/containous/traefik/pull/5879) by [dduportal](https://github.com/dduportal))
- doc: remove double quotes on CLI flags. ([#5862](https://github.com/containous/traefik/pull/5862) by [ldez](https://github.com/ldez))
- Fixed spelling error ([#5834](https://github.com/containous/traefik/pull/5834) by [blakebuthod](https://github.com/blakebuthod))
- Add back the security section from v1 ([#5832](https://github.com/containous/traefik/pull/5832) by [pascalandy](https://github.com/pascalandy))
## [v2.1.0-rc2](https://github.com/containous/traefik/tree/v2.0.4) (2019-11-15) ## [v2.1.0-rc2](https://github.com/containous/traefik/tree/v2.0.4) (2019-11-15)
[All Commits](https://github.com/containous/traefik/compare/v2.0.0-rc1...v2.1.0-rc2) [All Commits](https://github.com/containous/traefik/compare/v2.0.0-rc1...v2.1.0-rc2)

View file

@ -51,9 +51,14 @@ func Do(staticConfiguration static.Configuration) (*http.Response, error) {
return nil, errors.New("please enable `ping` to use health check") return nil, errors.New("please enable `ping` to use health check")
} }
pingEntryPoint, ok := staticConfiguration.EntryPoints["traefik"] ep := staticConfiguration.Ping.EntryPoint
if ep == "" {
ep = "traefik"
}
pingEntryPoint, ok := staticConfiguration.EntryPoints[ep]
if !ok { if !ok {
return nil, errors.New("missing `ping` entrypoint") return nil, fmt.Errorf("ping: missing %s entry point", ep)
} }
client := &http.Client{Timeout: 5 * time.Second} client := &http.Client{Timeout: 5 * time.Second}

View file

@ -3,6 +3,7 @@
You can install Traefik with the following flavors: You can install Traefik with the following flavors:
* [Use the official Docker image](./#use-the-official-docker-image) * [Use the official Docker image](./#use-the-official-docker-image)
* [(Experimental) Use the Helm Chart](./#use-the-helm-chart)
* [Use the binary distribution](./#use-the-binary-distribution) * [Use the binary distribution](./#use-the-binary-distribution)
* [Compile your binary from the sources](./#compile-your-binary-from-the-sources) * [Compile your binary from the sources](./#compile-your-binary-from-the-sources)
@ -24,6 +25,70 @@ For more details, go to the [Docker provider documentation](../providers/docker.
* Docker images are based from the [Alpine Linux Official image](https://hub.docker.com/_/alpine). * Docker images are based from the [Alpine Linux Official image](https://hub.docker.com/_/alpine).
* All the orchestrator using docker images could fetch the official Traefik docker image. * All the orchestrator using docker images could fetch the official Traefik docker image.
## Use the Helm Chart
!!! warning "Experimental Helm Chart"
Please note that the Helm Chart for Traefik v2 is still experimental.
The Traefik Stable Chart from
[Helm's default charts repository](https://github.com/helm/charts/tree/master/stable/traefik) is still using [Traefik v1.7](https://docs.traefik.io/v1.7).
Traefik can be installed in Kubernetes using the v2.0 Helm chart from <https://github.com/containous/traefik-helm-chart>.
Ensure that the following requirements are met:
* Kubernetes 1.14+
* Helm version 2.x is [installed](https://v2.helm.sh/docs/using_helm/) and initialized with Tiller
Retrieve the latest chart version from the repository:
```bash
# Retrieve Chart from the repository
git clone https://github.com/containous/traefik-helm-chart
```
And install it with the `helm` command line:
```bash
helm install ./traefik-helm-chart
```
!!! tip "Helm Features"
All [Helm features](https://v2.helm.sh/docs/using_helm/#using-helm) are supported.
For instance, installing the chart in a dedicated namespace:
```bash tab="Install in a Dedicated Namespace"
# Install in the namespace "traefik-v2"
helm install --namespace=traefik-v2 \
./traefik-helm-chart
```
??? example "Installing with Custom Values"
You can customize the installation by specifying custom values,
as with [any helm chart](https://v2.helm.sh/docs/using_helm/#customizing-the-chart-before-installing).
{: #helm-custom-values }
The values are not (yet) documented, but are self-explanatory:
you can look at the [default `values.yaml`](https://github.com/containous/traefik-helm-chart/blob/master/values.yaml) file to explore possibilities.
Example of installation with logging set to `DEBUG`:
```bash tab="Using Helm CLI"
helm install --namespace=traefik-v2 \
--set="logs.loglevel=DEBUG" \
./traefik-helm-chart
```
```yml tab="With a custom values file"
# File custom-values.yml
## Install with "helm install --values=./custom-values.yml ./traefik-helm-chart
logs:
loglevel: DEBUG
```
## Use the Binary Distribution ## Use the Binary Distribution
Grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page. Grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page.

View file

@ -47,11 +47,11 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
``` ```
```bash tab="CLI" ```bash tab="CLI"
--entryPoints.web.address=":80" --entryPoints.web.address=:80
--entryPoints.websecure.address=":443" --entryPoints.websecure.address=:443
# ... # ...
--certificatesResolvers.sample.acme.email="your-email@your-domain.org" --certificatesResolvers.sample.acme.email=your-email@your-domain.org
--certificatesResolvers.sample.acme.storage="acme.json" --certificatesResolvers.sample.acme.storage=acme.json
# used during the challenge # used during the challenge
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web --certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
``` ```
@ -156,8 +156,8 @@ when using the `HTTP-01` challenge, `certificatesResolvers.sample.acme.httpChall
``` ```
```bash tab="CLI" ```bash tab="CLI"
--entryPoints.web.address=":80" --entryPoints.web.address=:80
--entryPoints.websecure.address=":443" --entryPoints.websecure.address=:443
# ... # ...
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web --certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
``` ```
@ -312,7 +312,7 @@ certificatesResolvers:
```bash tab="CLI" ```bash tab="CLI"
# ... # ...
--certificatesResolvers.sample.acme.dnsChallenge.resolvers:="1.1.1.1:53,8.8.8.8:53" --certificatesResolvers.sample.acme.dnsChallenge.resolvers:=1.1.1.1:53,8.8.8.8:53
``` ```
#### Wildcard Domains #### Wildcard Domains
@ -342,7 +342,7 @@ As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/stagi
```bash tab="CLI" ```bash tab="CLI"
# ... # ...
--certificatesResolvers.sample.acme.caServer="https://acme-staging-v02.api.letsencrypt.org/directory" --certificatesResolvers.sample.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
# ... # ...
``` ```

View file

@ -4,13 +4,13 @@
# #
# Required # Required
# #
--certificatesResolvers.sample.acme.email="test@traefik.io" --certificatesResolvers.sample.acme.email=test@traefik.io
# File or key used for certificates storage. # File or key used for certificates storage.
# #
# Required # Required
# #
--certificatesResolvers.sample.acme.storage="acme.json" --certificatesResolvers.sample.acme.storage=acme.json
# CA server to use. # CA server to use.
# Uncomment the line to use Let's Encrypt's staging server, # Uncomment the line to use Let's Encrypt's staging server,
@ -19,7 +19,7 @@
# Optional # Optional
# Default: "https://acme-v02.api.letsencrypt.org/directory" # Default: "https://acme-v02.api.letsencrypt.org/directory"
# #
--certificatesResolvers.sample.acme.caServer="https://acme-staging-v02.api.letsencrypt.org/directory" --certificatesResolvers.sample.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
# KeyType to use. # KeyType to use.
# #
@ -75,7 +75,7 @@
# Optional # Optional
# Default: empty # Default: empty
# #
--certificatesResolvers.sample.acme.dnsChallenge.resolvers="1.1.1.1:53,8.8.8.8:53" --certificatesResolvers.sample.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53
# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. # Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
# #

View file

@ -718,11 +718,11 @@ with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, yo
``` ```
```bash tab="CLI" ```bash tab="CLI"
--entryPoints.web.address=":80" --entryPoints.web.address=:80
--entryPoints.websecure.address=":443" --entryPoints.websecure.address=:443
--certificatesResolvers.sample.acme.email: your-email@your-domain.org --certificatesResolvers.sample.acme.email=your-email@your-domain.org
--certificatesResolvers.sample.acme.storage: acme.json --certificatesResolvers.sample.acme.storage=acme.json
--certificatesResolvers.sample.acme.httpChallenge.entryPoint: web --certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
``` ```
## Traefik Logs ## Traefik Logs
@ -744,9 +744,9 @@ There is no more log configuration at the root level.
``` ```
```bash tab="CLI" ```bash tab="CLI"
--logLevel="DEBUG" --logLevel=DEBUG
--traefikLog.filePath="/path/to/traefik.log" --traefikLog.filePath=/path/to/traefik.log
--traefikLog.format="json" --traefikLog.format=json
``` ```
!!! info "v2" !!! info "v2"
@ -768,9 +768,9 @@ There is no more log configuration at the root level.
``` ```
```bash tab="CLI" ```bash tab="CLI"
--log.level="DEBUG" --log.level=DEBUG
--log.filePath="/path/to/traefik.log" --log.filePath=/path/to/traefik.log
--log.format="json" --log.format=json
``` ```
## Tracing ## Tracing
@ -794,12 +794,12 @@ Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.backend="jaeger" --tracing.backend=jaeger
--tracing.servicename="tracing" --tracing.servicename=tracing
--tracing.jaeger.localagenthostport="12.0.0.1:6831" --tracing.jaeger.localagenthostport=12.0.0.1:6831
--tracing.jaeger.samplingparam="1.0" --tracing.jaeger.samplingparam=1.0
--tracing.jaeger.samplingserverurl="http://12.0.0.1:5778/sampling" --tracing.jaeger.samplingserverurl=http://12.0.0.1:5778/sampling
--tracing.jaeger.samplingtype="const" --tracing.jaeger.samplingtype=const
``` ```
!!! info "v2" !!! info "v2"
@ -827,11 +827,11 @@ Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.servicename="tracing" --tracing.servicename=tracing
--tracing.jaeger.localagenthostport="12.0.0.1:6831" --tracing.jaeger.localagenthostport=12.0.0.1:6831
--tracing.jaeger.samplingparam="1.0" --tracing.jaeger.samplingparam=1.0
--tracing.jaeger.samplingserverurl="http://12.0.0.1:5778/sampling" --tracing.jaeger.samplingserverurl=http://12.0.0.1:5778/sampling
--tracing.jaeger.samplingtype="const" --tracing.jaeger.samplingtype=const
``` ```
## Metrics ## Metrics
@ -852,7 +852,7 @@ For a basic configuration, the [metrics configuration](../observability/metrics/
```bash tab="CLI" ```bash tab="CLI"
--metrics.prometheus.buckets=[0.1,0.3,1.2,5.0] --metrics.prometheus.buckets=[0.1,0.3,1.2,5.0]
--metrics.prometheus.entrypoint="traefik" --metrics.prometheus.entrypoint=traefik
``` ```
!!! info "v2" !!! info "v2"
@ -878,7 +878,7 @@ For a basic configuration, the [metrics configuration](../observability/metrics/
```bash tab="CLI" ```bash tab="CLI"
--metrics.prometheus.buckets=[0.1,0.3,1.2,5.0] --metrics.prometheus.buckets=[0.1,0.3,1.2,5.0]
--metrics.prometheus.entrypoint="metrics" --metrics.prometheus.entrypoint=metrics
``` ```
## No More Root Level Key/Values ## No More Root Level Key/Values
@ -908,14 +908,14 @@ Each root item has been moved to a related section or removed.
```bash tab="CLI" ```bash tab="CLI"
--checknewversion=false --checknewversion=false
--sendanonymoususage=true --sendanonymoususage=true
--loglevel="DEBUG" --loglevel=DEBUG
--insecureskipverify=true --insecureskipverify=true
--rootcas="/mycert.cert" --rootcas=/mycert.cert
--maxidleconnsperhost=200 --maxidleconnsperhost=200
--providersthrottleduration="2s" --providersthrottleduration=2s
--allowminweightzero=true --allowminweightzero=true
--debug=true --debug=true
--defaultentrypoints="web","web-secure" --defaultentrypoints=web,web-secure
--keeptrailingslash=true --keeptrailingslash=true
``` ```
@ -961,9 +961,9 @@ Each root item has been moved to a related section or removed.
```bash tab="CLI" ```bash tab="CLI"
--global.checknewversion=true --global.checknewversion=true
--global.sendanonymoususage=true --global.sendanonymoususage=true
--log.level="DEBUG" --log.level=DEBUG
--serverstransport.insecureskipverify=true --serverstransport.insecureskipverify=true
--serverstransport.rootcas="/mycert.cert" --serverstransport.rootcas=/mycert.cert
--serverstransport.maxidleconnsperhost=42 --serverstransport.maxidleconnsperhost=42
--providers.providersthrottleduration=42 --providers.providersthrottleduration=42
``` ```
@ -1029,7 +1029,7 @@ As the dashboard access is now secured by default you can either:
[api] [api]
[providers.file] [providers.file]
filename = "/dynamic-conf.toml" filename = "/dynamic-conf.toml"
##---------------------## ##---------------------##

View file

@ -61,7 +61,7 @@ accessLog:
```bash tab="CLI" ```bash tab="CLI"
# Configuring a buffer of 100 lines # Configuring a buffer of 100 lines
--accesslog=true --accesslog=true
--accesslog.filepath="/path/to/access.log" --accesslog.filepath=/path/to/access.log
--accesslog.bufferingsize=100 --accesslog.bufferingsize=100
``` ```
@ -104,11 +104,11 @@ accessLog:
```bash tab="CLI" ```bash tab="CLI"
# Configuring Multiple Filters # Configuring Multiple Filters
--accesslog=true --accesslog=true
--accesslog.filepath="/path/to/access.log" --accesslog.filepath=/path/to/access.log
--accesslog.format="json" --accesslog.format=json
--accesslog.filters.statuscodes="200, 300-302" --accesslog.filters.statuscodes=200,300-302
--accesslog.filters.retryattempts --accesslog.filters.retryattempts
--accesslog.filters.minduration="10ms" --accesslog.filters.minduration=10ms
``` ```
### Limiting the Fields ### Limiting the Fields
@ -164,14 +164,14 @@ accessLog:
```bash tab="CLI" ```bash tab="CLI"
# Limiting the Logs to Specific Fields # Limiting the Logs to Specific Fields
--accesslog=true --accesslog=true
--accesslog.filepath="/path/to/access.log" --accesslog.filepath=/path/to/access.log
--accesslog.format="json" --accesslog.format=json
--accesslog.fields.defaultmode="keep" --accesslog.fields.defaultmode=keep
--accesslog.fields.names.ClientUsername="drop" --accesslog.fields.names.ClientUsername=drop
--accesslog.fields.headers.defaultmode="keep" --accesslog.fields.headers.defaultmode=keep
--accesslog.fields.headers.names.User-Agent="redact" --accesslog.fields.headers.names.User-Agent=redact
--accesslog.fields.headers.names.Authorization="drop" --accesslog.fields.headers.names.Authorization=drop
--accesslog.fields.headers.names.Content-Type="keep" --accesslog.fields.headers.names.Content-Type=keep
``` ```
??? info "Available Fields" ??? info "Available Fields"

View file

@ -30,7 +30,7 @@ log:
```bash tab="CLI" ```bash tab="CLI"
# Writing Logs to a File # Writing Logs to a File
--log.filePath="/path/to/traefik.log" --log.filePath=/path/to/traefik.log
``` ```
#### `format` #### `format`
@ -53,8 +53,8 @@ log:
```bash tab="CLI" ```bash tab="CLI"
# Writing Logs to a File, in JSON # Writing Logs to a File, in JSON
--log.filePath="/path/to/traefik.log" --log.filePath=/path/to/traefik.log
--log.format="json" --log.format=json
``` ```
#### `level` #### `level`
@ -72,7 +72,7 @@ log:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--log.level="DEBUG" --log.level=DEBUG
``` ```
## Log Rotation ## Log Rotation

View file

@ -35,7 +35,7 @@ metrics:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--metrics.datadog.address="127.0.0.1:8125" --metrics.datadog.address=127.0.0.1:8125
``` ```
#### `addEntryPointsLabels` #### `addEntryPointsLabels`

View file

@ -35,7 +35,7 @@ metrics:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--metrics.influxdb.address="localhost:8089" --metrics.influxdb.address=localhost:8089
``` ```
#### `protocol` #### `protocol`
@ -57,7 +57,7 @@ metrics:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--metrics.influxdb.protocol="udp" --metrics.influxdb.protocol=udp
``` ```
#### `database` #### `database`
@ -69,17 +69,17 @@ InfluxDB database used when protocol is http.
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[metrics] [metrics]
[metrics.influxDB] [metrics.influxDB]
database = "" database = "db"
``` ```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
metrics: metrics:
influxDB: influxDB:
database: "" database: "db"
``` ```
```bash tab="CLI" ```bash tab="CLI"
--metrics.influxdb.database="" --metrics.influxdb.database=db
``` ```
#### `retentionPolicy` #### `retentionPolicy`
@ -91,17 +91,17 @@ InfluxDB retention policy used when protocol is http.
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[metrics] [metrics]
[metrics.influxDB] [metrics.influxDB]
retentionPolicy = "" retentionPolicy = "two_hours"
``` ```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
metrics: metrics:
influxDB: influxDB:
retentionPolicy: "" retentionPolicy: "two_hours"
``` ```
```bash tab="CLI" ```bash tab="CLI"
--metrics.influxdb.retentionPolicy="" --metrics.influxdb.retentionPolicy=two_hours
``` ```
#### `username` #### `username`
@ -113,17 +113,17 @@ InfluxDB username (only with http).
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[metrics] [metrics]
[metrics.influxDB] [metrics.influxDB]
username = "" username = "john"
``` ```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
metrics: metrics:
influxDB: influxDB:
username: "" username: "john"
``` ```
```bash tab="CLI" ```bash tab="CLI"
--metrics.influxdb.username="" --metrics.influxdb.username=john
``` ```
#### `password` #### `password`
@ -135,17 +135,17 @@ InfluxDB password (only with http).
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[metrics] [metrics]
[metrics.influxDB] [metrics.influxDB]
password = "" password = "secret"
``` ```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
metrics: metrics:
influxDB: influxDB:
password: "" password: "secret"
``` ```
```bash tab="CLI" ```bash tab="CLI"
--metrics.influxdb.password="" --metrics.influxdb.password=secret
``` ```
#### `addEntryPointsLabels` #### `addEntryPointsLabels`

View file

@ -113,8 +113,8 @@ metrics:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--entryPoints.metrics.address=":8082" --entryPoints.metrics.address=:8082
--metrics.prometheus.entryPoint="metrics" --metrics.prometheus.entryPoint=metrics
``` ```
#### `manualRouting` #### `manualRouting`

View file

@ -35,7 +35,7 @@ metrics:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--metrics.statsd.address="localhost:8125" --metrics.statsd.address=localhost:8125
``` ```
#### `addEntryPointsLabels` #### `addEntryPointsLabels`

View file

@ -35,7 +35,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.datadog.localAgentHostPort="127.0.0.1:8126" --tracing.datadog.localAgentHostPort=127.0.0.1:8126
``` ```
#### `debug` #### `debug`
@ -79,7 +79,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.datadog.globalTag="sample" --tracing.datadog.globalTag=sample
``` ```
#### `prioritySampling` #### `prioritySampling`

View file

@ -35,7 +35,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.haystack.localAgentHost="127.0.0.1" --tracing.haystack.localAgentHost=127.0.0.1
``` ```
#### `localAgentPort` #### `localAgentPort`
@ -79,7 +79,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.haystack.globalTag="sample:test" --tracing.haystack.globalTag=sample:test
``` ```
#### `traceIDHeaderName` #### `traceIDHeaderName`
@ -101,7 +101,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.haystack.traceIDHeaderName="sample" --tracing.haystack.traceIDHeaderName=sample
``` ```
#### `parentIDHeaderName` #### `parentIDHeaderName`
@ -123,7 +123,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.haystack.parentIDHeaderName="sample" --tracing.haystack.parentIDHeaderName=sample
``` ```
#### `spanIDHeaderName` #### `spanIDHeaderName`
@ -168,5 +168,5 @@ tracing:
```bash tab="CLI" ```bash tab="CLI"
--tracing.haystack.baggagePrefixHeaderName="sample" --tracing.haystack.baggagePrefixHeaderName=sample
``` ```

View file

@ -35,7 +35,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.instana.localAgentHost="127.0.0.1" --tracing.instana.localAgentHost=127.0.0.1
``` ```
#### `localAgentPort` #### `localAgentPort`
@ -86,5 +86,5 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.instana.logLevel="info" --tracing.instana.logLevel=info
``` ```

View file

@ -39,7 +39,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.jaeger.samplingServerURL="http://localhost:5778/sampling" --tracing.jaeger.samplingServerURL=http://localhost:5778/sampling
``` ```
#### `samplingType` #### `samplingType`
@ -61,7 +61,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.jaeger.samplingType="const" --tracing.jaeger.samplingType=const
``` ```
#### `samplingParam` #### `samplingParam`
@ -89,7 +89,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.jaeger.samplingParam="1.0" --tracing.jaeger.samplingParam=1.0
``` ```
#### `localAgentHostPort` #### `localAgentHostPort`
@ -111,7 +111,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.jaeger.localAgentHostPort="127.0.0.1:6831" --tracing.jaeger.localAgentHostPort=127.0.0.1:6831
``` ```
#### `gen128Bit` #### `gen128Bit`
@ -159,7 +159,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.jaeger.propagation="jaeger" --tracing.jaeger.propagation=jaeger
``` ```
#### `traceContextHeaderName` #### `traceContextHeaderName`
@ -182,7 +182,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.jaeger.traceContextHeaderName="uber-trace-id" --tracing.jaeger.traceContextHeaderName=uber-trace-id
``` ```
### `collector` ### `collector`
@ -206,7 +206,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.jaeger.collector.endpoint="http://127.0.0.1:14268/api/traces?format=jaeger.thrift" --tracing.jaeger.collector.endpoint=http://127.0.0.1:14268/api/traces?format=jaeger.thrift
``` ```
#### `user` #### `user`
@ -229,7 +229,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.jaeger.collector.user="my-user" --tracing.jaeger.collector.user=my-user
``` ```
#### `password` #### `password`
@ -252,5 +252,5 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.jaeger.collector.password="my-password" --tracing.jaeger.collector.password=my-password
``` ```

View file

@ -52,7 +52,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.serviceName="traefik" --tracing.serviceName=traefik
``` ```
#### `spanNameLimit` #### `spanNameLimit`

View file

@ -35,7 +35,7 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.zipkin.httpEndpoint="http://localhost:9411/api/v2/spans" --tracing.zipkin.httpEndpoint=http://localhost:9411/api/v2/spans
``` ```
#### `sameSpan` #### `sameSpan`
@ -101,5 +101,5 @@ tracing:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--tracing.zipkin.sampleRate="0.2" --tracing.zipkin.sampleRate=0.2
``` ```

View file

@ -55,8 +55,8 @@ ping:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--entryPoints.ping.address=":8082" --entryPoints.ping.address=:8082
--ping.entryPoint="ping" --ping.entryPoint=ping
``` ```
#### `manualRouting` #### `manualRouting`

View file

@ -1,17 +1,17 @@
# Traefik & Consul Catalog # Traefik & Consul Catalog
A Story of Labels, Services & Containers A Story of Tags, Services & Instances
{: .subtitle } {: .subtitle }
![Consul Catalog](../assets/img/providers/consul.png) ![Consul Catalog](../assets/img/providers/consul.png)
Attach labels to your services and let Traefik do the rest! Attach tags to your services and let Traefik do the rest!
## Configuration Examples ## Configuration Examples
??? example "Configuring Consul Catalog & Deploying / Exposing Services" ??? example "Configuring Consul Catalog & Deploying / Exposing Services"
Enabling the consulcatalog provider Enabling the consul catalog provider
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[providers.consulCatalog] [providers.consulCatalog]
@ -26,11 +26,10 @@ Attach labels to your services and let Traefik do the rest!
--providers.consulcatalog=true --providers.consulcatalog=true
``` ```
Attaching labels to services Attaching tags to services
```yaml ```yaml
labels: - traefik.http.services.my-service.rule=Host(`mydomain.com`)
- traefik.http.services.my-service.rule=Host(`mydomain.com`)
``` ```
## Routing Configuration ## Routing Configuration
@ -65,27 +64,27 @@ Defines the polling interval.
### `prefix` ### `prefix`
_Optional, Default=/latest_ _required, Default="traefik"_
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[providers.consulCatalog] [providers.consulCatalog]
prefix = "/test" prefix = "test"
# ... # ...
``` ```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
providers: providers:
consulCatalog: consulCatalog:
prefix: /test prefix: test
# ... # ...
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.consulcatalog.prefix=/test --providers.consulcatalog.prefix=test
# ... # ...
``` ```
Prefix used for accessing the Consul service metadata. The prefix for Consul Catalog tags defining traefik labels.
### `requireConsistent` ### `requireConsistent`
@ -161,7 +160,7 @@ Use local agent caching for catalog reads.
### `endpoint` ### `endpoint`
Defines Consul server endpoint. Defines the Consul server endpoint.
#### `address` #### `address`
@ -504,7 +503,7 @@ providers:
``` ```
Expose Consul Catalog services by default in Traefik. Expose Consul Catalog services by default in Traefik.
If set to false, services that don't have a `traefik.enable=true` label will be ignored from the resulting routing configuration. If set to false, services that don't have a `traefik.enable=true` tag will be ignored from the resulting routing configuration.
See also [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery). See also [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery).
@ -532,13 +531,13 @@ providers:
The default host rule for all services. The default host rule for all services.
For a given container if no routing rule was defined by a label, it is defined by this defaultRule instead. For a given service if no routing rule was defined by a tag, it is defined by this defaultRule instead.
It must be a valid [Go template](https://golang.org/pkg/text/template/), It must be a valid [Go template](https://golang.org/pkg/text/template/),
augmented with the [sprig template functions](http://masterminds.github.io/sprig/). augmented with the [sprig template functions](http://masterminds.github.io/sprig/).
The service name can be accessed as the `Name` identifier, The service name can be accessed as the `Name` identifier,
and the template has access to all the labels defined on this container. and the template has access to all the labels (i.e. tags beginning with the `prefix`) defined on this service.
This option can be overridden on a container basis with the `traefik.http.routers.Router1.rule` label. The option can be overridden on an instance basis with the `traefik.http.routers.{name-of-your-choice}.rule` tag.
### `constraints` ### `constraints`
@ -546,58 +545,59 @@ _Optional, Default=""_
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[providers.consulCatalog] [providers.consulCatalog]
constraints = "Label(`a.label.name`, `foo`)" constraints = "Tag(`a.tag.name`)"
# ... # ...
``` ```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
providers: providers:
consulCatalog: consulCatalog:
constraints: "Label(`a.label.name`, `foo`)" constraints: "Tag(`a.tag.name`)"
# ... # ...
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.consulcatalog.constraints="Label(`a.label.name`, `foo`)" --providers.consulcatalog.constraints="Tag(`a.tag.name`)"
# ... # ...
``` ```
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container. Constraints is an expression that Traefik matches against the service's tags to determine whether to create any route for that service.
That is to say, if none of the container's labels match the expression, no route for the container is created. That is to say, if none of the service's tags match the expression, no route for that service is created.
If the expression is empty, all detected containers are included. If the expression is empty, all detected services are included.
The expression syntax is based on the `Label("key", "value")`, and `LabelRegex("key", "value")` functions, as well as the usual boolean logic, as shown in examples below. The expression syntax is based on the `Tag("tag")`, and `TagRegex("tag")` functions,
as well as the usual boolean logic, as shown in examples below.
??? example "Constraints Expression Examples" ??? example "Constraints Expression Examples"
```toml ```toml
# Includes only containers having a label with key `a.label.name` and value `foo` # Includes only services having the tag `a.tag.name=foo`
constraints = "Label(`a.label.name`, `foo`)" constraints = "Tag(`a.tag.name=foo`)"
``` ```
```toml ```toml
# Excludes containers having any label with key `a.label.name` and value `foo` # Excludes services having any tag `a.tag.name=foo`
constraints = "!Label(`a.label.name`, `value`)" constraints = "!Tag(`a.tag.name=foo`)"
``` ```
```toml ```toml
# With logical AND. # With logical AND.
constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)" constraints = "Tag(`a.tag.name`) && Tag(`another.tag.name`)"
``` ```
```toml ```toml
# With logical OR. # With logical OR.
constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)" constraints = "Tag(`a.tag.name`) || Tag(`another.tag.name`)"
``` ```
```toml ```toml
# With logical AND and OR, with precedence set by parentheses. # With logical AND and OR, with precedence set by parentheses.
constraints = "Label(`a.label.name`, `valueA`) && (Label(`another.label.name`, `valueB`) || Label(`yet.another.label.name`, `valueC`))" constraints = "Tag(`a.tag.name`) && (Tag(`another.tag.name`) || Tag(`yet.another.tag.name`))"
``` ```
```toml ```toml
# Includes only containers having a label with key `a.label.name` and a value matching the `a.+` regular expression. # Includes only services having a tag matching the `a\.tag\.t.+` regular expression.
constraints = "LabelRegex(`a.label.name`, `a.+`)" constraints = "TagRegex(`a\.tag\.t.+`)"
``` ```
See also [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery). See also [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery).

View file

@ -7,6 +7,9 @@ A Story of Labels & Containers
Attach labels to your containers and let Traefik do the rest! Attach labels to your containers and let Traefik do the rest!
Traefik works with both [Docker (standalone) Engine](https://docs.docker.com/engine/)
and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
!!! tip "The Quick Start Uses Docker" !!! tip "The Quick Start Uses Docker"
If you haven't already, maybe you'd like to go through the [quick start](../getting-started/quick-start.md) that uses the docker provider! If you haven't already, maybe you'd like to go through the [quick start](../getting-started/quick-start.md) that uses the docker provider!
@ -64,7 +67,7 @@ Attach labels to your containers and let Traefik do the rest!
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.docker.endpoint="tcp://127.0.0.1:2375" --providers.docker.endpoint=tcp://127.0.0.1:2375
--providers.docker.swarmMode=true --providers.docker.swarmMode=true
``` ```
@ -80,15 +83,136 @@ Attach labels to your containers and let Traefik do the rest!
- traefik.http.services.my-container-service.loadbalancer.server.port=8080 - traefik.http.services.my-container-service.loadbalancer.server.port=8080
``` ```
!!! important "Labels in Docker Swarm Mode"
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the `deploy` part of your service.
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/#labels-1)).
## Routing Configuration ## Routing Configuration
See the dedicated section in [routing](../routing/providers/docker.md). When using Docker as a [provider](https://docs.traefik.io/providers/overview/),
Trafik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#set-metadata-on-container--l---label---label-file) to retrieve its routing configuration.
See the list of labels in the dedicated [routing](../routing/providers/docker.md) section.
### Routing Configuration with Labels
By default, Traefik watches for [container level labels](https://docs.docker.com/config/labels-custom-metadata/) on a standalone Docker Engine.
When using Docker Compose, labels are specified by the directive
[`labels`](https://docs.docker.com/compose/compose-file/#labels) from the
["services" objects](https://docs.docker.com/compose/compose-file/#service-configuration-reference).
!!! tip "Not Only Docker"
Please note that any tool like Nomad, Terraform, Ansible, etc.
that is able to define a Docker container with labels can work
with Traefik & the Docker provider.
### Port Detection
Traefik retrieves the private IP and port of containers from the Docker API.
Ports detection works as follows:
- If a container [exposes](https://docs.docker.com/engine/reference/builder/#expose) only one port,
then Traefik uses this port for private communication.
- If a container [exposes](https://docs.docker.com/engine/reference/builder/#expose) multiple ports,
or does not expose any port, then you must manually specify which port Traefik should use for communication
by using the label `traefik.http.services.<service_name>.loadbalancer.server.port`
(Read more on this label in the dedicated section in [routing](../routing/providers/docker.md#port)).
### Docker API Access
Traefik requires access to the docker socket to get its dynamic configuration.
You can specify which Docker API Endpoint to use with the directive [`endpoint`](#endpoint).
!!! warning "Security Note"
Accessing the Docker API without any restriction is a security concern:
If Traefik is attacked, then the attacker might get access to the underlying host.
{: #security-note }
As explained in the Docker documentation: ([Docker Daemon Attack Surface page](https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface)):
!!! quote
[...] only **trusted** users should be allowed to control your Docker daemon [...]
??? success "Solutions"
Expose the Docker socket over TCP, instead of the default Unix socket file.
It allows different implementation levels of the [AAA (Authentication, Authorization, Accounting) concepts](https://en.wikipedia.org/wiki/AAA_(computer_security)), depending on your security assessment:
- Authentication with Client Certificates as described in ["Protect the Docker daemon socket."](https://docs.docker.com/engine/security/https/)
- Authorize and filter requests to restrict possible actions with [the TecnativaDocker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy).
- Authorization with the [Docker Authorization Plugin Mechanism](https://docs.docker.com/engine/extend/plugins_authorization/)
- Accounting at networking level, by exposing the socket only inside a Docker private network, only available for Traefik.
- Accounting at container level, by exposing the socket on a another container than Traefik's.
With Swarm mode, it allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes.
- Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process).
??? info "More Resources and Examples"
- ["Paranoid about mounting /var/run/docker.sock?"](https://medium.com/@containeroo/traefik-2-0-paranoid-about-mounting-var-run-docker-sock-22da9cb3e78c)
- [Traefik and Docker: A Discussion with Docker Captain, Bret Fisher](https://blog.containo.us/traefik-and-docker-a-discussion-with-docker-captain-bret-fisher-7f0b9a54ff88)
- [KubeCon EU 2018 Keynote, Running with Scissors, from Liz Rice](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
- [Don't expose the Docker socket (not even to a container)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container/)
- [A thread on Stack Overflow about sharing the `/var/run/docker.sock` file](https://news.ycombinator.com/item?id=17983623)
- [To DinD or not to DinD](https://blog.loof.fr/2018/01/to-dind-or-not-do-dind.html)
- [Traefik issue GH-4174 about security with Docker socket](https://github.com/containous/traefik/issues/4174)
- [Inspecting Docker Activity with Socat](https://developers.redhat.com/blog/2015/02/25/inspecting-docker-activity-with-socat/)
- [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/)
- [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy)
## Docker Swarm Mode
To enable Docker Swarm (instead of standalone Docker) as a configuration provider,
set the [`swarmMode`](#swarmmode) directive to `true`.
### Routing Configuration with Labels
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the
[`deploy`](https://docs.docker.com/compose/compose-file/#labels-1) part of your service.
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file)).
### Port Detection
Docker Swarm does not provide any [port detection](#port-detection) information to Traefik.
Therefore you **must** specify the port to use for communication by using the label `traefik.http.services.<service_name>.loadbalancer.server.port`
(Check the reference for this label in the [routing section for Docker](../routing/providers/docker.md#port)).
### Docker API Access
Docker Swarm Mode follows the same rules as Docker [API Access](#docker-api-access).
As the Swarm API is only exposed on the [manager nodes](https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/#manager-nodes), you should schedule Traefik on the Swarm manager nodes by default,
by deploying Traefik with a [constraint](https://success.docker.com/article/using-contraints-and-labels-to-control-the-placement-of-containers) on the node's "role":
```shell tab="With Docker CLI"
docker service create \
--constraint=node.role==manager \
#... \
```
```yml tab="With Docker Compose"
version: '3'
services:
traefik:
# ...
deploy:
placement:
constraints:
- node.role == manager
```
!!! tip "Scheduling Traefik on Worker Nodes"
Following the guidelines given in the previous section ["Docker API Access"](#docker-api-access),
if you expose the Docker API through TCP, then Traefik can be scheduled on any node if the TCP
socket is reachable.
Please consider the security implications by reading the [Security Note](#security-note).
A good example can be found on [Bret Fisher's repository](https://github.com/BretFisher/dogvscat/blob/master/stack-proxy-global.yml#L124).
## Provider Configuration ## Provider Configuration
@ -108,51 +232,10 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.docker.endpoint="unix:///var/run/docker.sock" --providers.docker.endpoint=unix:///var/run/docker.sock
``` ```
Traefik requires access to the docker socket to get its dynamic configuration. See the sections [Docker API Access](#docker-api-access) and [Docker Swarm API Access](#docker-api-access_1) for more information.
??? warning "Security Notes"
Depending on your context, accessing the Docker API without any restriction can be a security concern: If Traefik is attacked, then the attacker might get access to the Docker (or Swarm Mode) backend.
As explained in the Docker documentation: ([Docker Daemon Attack Surface page](https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface)):
`[...] only **trusted** users should be allowed to control your Docker daemon [...]`
!!! tip "Improved Security"
[TraefikEE](https://containo.us/traefikee) solves this problem by separating the control plane (connected to Docker) and the data plane (handling the requests).
??? info "Resources about Docker's Security"
- [KubeCon EU 2018 Keynote, Running with Scissors, from Liz Rice](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
- [Don't expose the Docker socket (not even to a container)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container/)
- [A thread on Stack Overflow about sharing the `/var/run/docker.sock` file](https://news.ycombinator.com/item?id=17983623)
- [To DinD or not to DinD](https://blog.loof.fr/2018/01/to-dind-or-not-do-dind.html)
??? tip "Security Compensation"
Expose the Docker socket over TCP, instead of the default Unix socket file.
It allows different implementation levels of the [AAA (Authentication, Authorization, Accounting) concepts](https://en.wikipedia.org/wiki/AAA_(computer_security)), depending on your security assessment:
- Authentication with Client Certificates as described in ["Protect the Docker daemon socket."](https://docs.docker.com/engine/security/https/)
- Authorization with the [Docker Authorization Plugin Mechanism](https://docs.docker.com/engine/extend/plugins_authorization/)
- Accounting at networking level, by exposing the socket only inside a Docker private network, only available for Traefik.
- Accounting at container level, by exposing the socket on a another container than Traefik's.
With Swarm mode, it allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes.
- Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process).
??? info "Additional Resources"
- [Traefik issue GH-4174 about security with Docker socket](https://github.com/containous/traefik/issues/4174)
- [Inspecting Docker Activity with Socat](https://developers.redhat.com/blog/2015/02/25/inspecting-docker-activity-with-socat/)
- [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/)
- [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy)
!!! info "Traefik & Swarm Mode"
To let Traefik access the Docker Socket of the Swarm manager, it is mandatory to schedule Traefik on the Swarm manager nodes.
??? example "Using the docker.sock" ??? example "Using the docker.sock"
@ -186,7 +269,7 @@ Traefik requires access to the docker socket to get its dynamic configuration.
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.docker.endpoint="unix:///var/run/docker.sock" --providers.docker.endpoint=unix:///var/run/docker.sock
# ... # ...
``` ```
@ -311,7 +394,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.docker.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)" --providers.docker.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
# ... # ...
``` ```
@ -343,7 +426,7 @@ providers:
# ... # ...
``` ```
Activates the Swarm Mode. Activates the Swarm Mode (instead of standalone Docker).
### `swarmModeRefreshSeconds` ### `swarmModeRefreshSeconds`
@ -375,19 +458,19 @@ _Optional, Default=""_
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[providers.docker] [providers.docker]
constraints = "Label(`a.label.name`, `foo`)" constraints = "Label(`a.label.name`,`foo`)"
# ... # ...
``` ```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
providers: providers:
docker: docker:
constraints: "Label(`a.label.name`, `foo`)" constraints: "Label(`a.label.name`,`foo`)"
# ... # ...
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.docker.constraints="Label(`a.label.name`, `foo`)" --providers.docker.constraints=Label(`a.label.name`,`foo`)
# ... # ...
``` ```

View file

@ -32,7 +32,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetescrd.endpoint="http://localhost:8080" --providers.kubernetescrd.endpoint=http://localhost:8080
``` ```
The Kubernetes server endpoint as URL. The Kubernetes server endpoint as URL.
@ -66,7 +66,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetescrd.token="mytoken" --providers.kubernetescrd.token=mytoken
``` ```
Bearer token used for the Kubernetes client configuration. Bearer token used for the Kubernetes client configuration.
@ -89,7 +89,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetescrd.certauthfilepath="/my/ca.crt" --providers.kubernetescrd.certauthfilepath=/my/ca.crt
``` ```
Path to the certificate authority file. Path to the certificate authority file.
@ -115,7 +115,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetescrd.namespaces="default,production" --providers.kubernetescrd.namespaces=default,production
``` ```
Array of namespaces to watch. Array of namespaces to watch.
@ -164,7 +164,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetescrd.ingressclass="traefik-internal" --providers.kubernetescrd.ingressclass=traefik-internal
``` ```
Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed. Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed.
@ -190,7 +190,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetescrd.throttleDuration="10s" --providers.kubernetescrd.throttleDuration=10s
``` ```
## Further ## Further

View file

@ -67,7 +67,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetesingress.endpoint="http://localhost:8080" --providers.kubernetesingress.endpoint=http://localhost:8080
``` ```
The Kubernetes server endpoint as URL, which is only used when the behavior based on environment variables described below does not apply. The Kubernetes server endpoint as URL, which is only used when the behavior based on environment variables described below does not apply.
@ -99,7 +99,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetesingress.token="mytoken" --providers.kubernetesingress.token=mytoken
``` ```
Bearer token used for the Kubernetes client configuration. Bearer token used for the Kubernetes client configuration.
@ -122,7 +122,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetesingress.certauthfilepath="/my/ca.crt" --providers.kubernetesingress.certauthfilepath=/my/ca.crt
``` ```
Path to the certificate authority file. Path to the certificate authority file.
@ -171,7 +171,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetesingress.namespaces="default,production" --providers.kubernetesingress.namespaces=default,production
``` ```
Array of namespaces to watch. Array of namespaces to watch.
@ -220,7 +220,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetesingress.ingressclass="traefik-internal" --providers.kubernetesingress.ingressclass=traefik-internal
``` ```
Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed. Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed.
@ -249,7 +249,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetesingress.ingressendpoint.hostname="foo.com" --providers.kubernetesingress.ingressendpoint.hostname=foo.com
``` ```
Hostname used for Kubernetes Ingress endpoints. Hostname used for Kubernetes Ingress endpoints.
@ -273,7 +273,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetesingress.ingressendpoint.ip="1.2.3.4" --providers.kubernetesingress.ingressendpoint.ip=1.2.3.4
``` ```
IP used for Kubernetes Ingress endpoints. IP used for Kubernetes Ingress endpoints.
@ -297,7 +297,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetesingress.ingressendpoint.publishedservice="foo-service" --providers.kubernetesingress.ingressendpoint.publishedservice=foo-service
``` ```
Published Kubernetes Service to copy status from. Published Kubernetes Service to copy status from.
@ -320,7 +320,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.kubernetesingress.throttleDuration="10s" --providers.kubernetesingress.throttleDuration=10s
``` ```
## Further ## Further

View file

@ -74,8 +74,8 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.marathon.basic.httpbasicauthuser="foo" --providers.marathon.basic.httpbasicauthuser=foo
--providers.marathon.basic.httpbasicpassword="bar" --providers.marathon.basic.httpbasicpassword=bar
``` ```
Enables Marathon basic authentication. Enables Marathon basic authentication.
@ -98,7 +98,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.marathon.dcosToken="xxxxxx" --providers.marathon.dcosToken=xxxxxx
``` ```
DCOSToken for DCOS environment. DCOSToken for DCOS environment.
@ -123,7 +123,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.marathon.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)" --providers.marathon.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
# ... # ...
``` ```
@ -182,7 +182,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.marathon.endpoint="http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080" --providers.marathon.endpoint=http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080
``` ```
Marathon server endpoint. Marathon server endpoint.
@ -223,19 +223,19 @@ _Optional, Default=""_
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[providers.marathon] [providers.marathon]
constraints = "Label(`a.label.name`, `foo`)" constraints = "Label(`a.label.name`,`foo`)"
# ... # ...
``` ```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
providers: providers:
marathon: marathon:
constraints: "Label(`a.label.name`, `foo`)" constraints: "Label(`a.label.name`,`foo`)"
# ... # ...
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.marathon.constraints="Label(`a.label.name`, `foo`)" --providers.marathon.constraints=Label(`a.label.name`,`foo`)
# ... # ...
``` ```
@ -389,7 +389,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.marathon.responseHeaderTimeout="66s" --providers.marathon.responseHeaderTimeout=66s
# ... # ...
``` ```
@ -532,7 +532,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.marathon.responseHeaderTimeout="10s" --providers.marathon.responseHeaderTimeout=10s
# ... # ...
``` ```

View file

@ -104,7 +104,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.rancher.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)" --providers.rancher.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
# ... # ...
``` ```
@ -209,7 +209,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.rancher.prefix="/test" --providers.rancher.prefix=/test
# ... # ...
``` ```
@ -221,19 +221,19 @@ _Optional, Default=""_
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[providers.rancher] [providers.rancher]
constraints = "Label(`a.label.name`, `foo`)" constraints = "Label(`a.label.name`,`foo`)"
# ... # ...
``` ```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
providers: providers:
rancher: rancher:
constraints: "Label(`a.label.name`, `foo`)" constraints: "Label(`a.label.name`,`foo`)"
# ... # ...
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.rancher.constraints="Label(`a.label.name`, `foo`)" --providers.rancher.constraints=Label(`a.label.name`,`foo`)
# ... # ...
``` ```

View file

@ -17,4 +17,4 @@
--providers.rancher.intervalPoll=false --providers.rancher.intervalPoll=false
# Prefix used for accessing the Rancher metadata service # Prefix used for accessing the Rancher metadata service
--providers.rancher.prefix="/latest" --providers.rancher.prefix=/latest

View file

@ -18,4 +18,4 @@ providers:
intervalPoll: false intervalPoll: false
# Prefix used for accessing the Rancher metadata service # Prefix used for accessing the Rancher metadata service
prefix: "/latest" prefix: /latest

View file

@ -128,9 +128,9 @@ You can define them using a toml file, CLI arguments, or a key-value store.
--entryPoints.name.transport.respondingTimeouts.writeTimeout=42 --entryPoints.name.transport.respondingTimeouts.writeTimeout=42
--entryPoints.name.transport.respondingTimeouts.idleTimeout=42 --entryPoints.name.transport.respondingTimeouts.idleTimeout=42
--entryPoints.name.proxyProtocol.insecure=true --entryPoints.name.proxyProtocol.insecure=true
--entryPoints.name.proxyProtocol.trustedIPs="127.0.0.1,192.168.0.1" --entryPoints.name.proxyProtocol.trustedIPs=127.0.0.1,192.168.0.1
--entryPoints.name.forwardedHeaders.insecure=true --entryPoints.name.forwardedHeaders.insecure=true
--entryPoints.name.forwardedHeaders.trustedIPs="127.0.0.1,192.168.0.1" --entryPoints.name.forwardedHeaders.trustedIPs=127.0.0.1,192.168.0.1
``` ```
### Forwarded Header ### Forwarded Header

View file

@ -151,7 +151,7 @@ http:
```bash tab="CLI" ```bash tab="CLI"
# Listen on port 8081 for incoming requests # Listen on port 8081 for incoming requests
--entryPoints.web.address=":8081" --entryPoints.web.address=:8081
# Enable the file provider to define routers / middlewares / services in a file # Enable the file provider to define routers / middlewares / services in a file
--providers.file.filename=dynamic_conf.toml --providers.file.filename=dynamic_conf.toml

View file

@ -1,61 +1,61 @@
# Traefik & Consul Catalog # Traefik & Consul Catalog
A Story of Labels, Services & Containers A Story of Tags, Services & Instances
{: .subtitle } {: .subtitle }
![Rancher](../../assets/img/providers/consul.png) ![Rancher](../../assets/img/providers/consul.png)
Attach labels to your services and let Traefik do the rest! Attach tags to your services and let Traefik do the rest!
## Routing Configuration ## Routing Configuration
!!! info "Labels" !!! info "tags"
- Labels are case insensitive. - tags are case insensitive.
- The complete list of labels can be found [the reference page](../../reference/dynamic-configuration/consul-catalog.md) - The complete list of tags can be found [the reference page](../../reference/dynamic-configuration/consul-catalog.md)
### General ### General
Traefik creates, for each consul Catalog service, a corresponding [service](../services/index.md) and [router](../routers/index.md). Traefik creates, for each consul Catalog service, a corresponding [service](../services/index.md) and [router](../routers/index.md).
The Service automatically gets a server per container in this consul Catalog service, and the router gets a default rule attached to it, based on the service name. The Service automatically gets a server per instance in this consul Catalog service, and the router gets a default rule attached to it, based on the service name.
### Routers ### Routers
To update the configuration of the Router automatically attached to the container, add labels starting with `traefik.routers.{name-of-your-choice}.` and followed by the option you want to change. To update the configuration of the Router automatically attached to the service, add tags starting with `traefik.routers.{name-of-your-choice}.` and followed by the option you want to change.
For example, to change the rule, you could add the label ```traefik.http.routers.my-container.rule=Host(`mydomain.com`)```. For example, to change the rule, you could add the tag ```traefik.http.routers.my-service.rule=Host(`mydomain.com`)```.
??? info "`traefik.http.routers.<router_name>.rule`" ??? info "`traefik.http.routers.<router_name>.rule`"
See [rule](../routers/index.md#rule) for more information. See [rule](../routers/index.md#rule) for more information.
```yaml ```yaml
- "traefik.http.routers.myrouter.rule=Host(`mydomain.com`)" traefik.http.routers.myrouter.rule=Host(`mydomain.com`)
``` ```
??? info "`traefik.http.routers.<router_name>.entrypoints`" ??? info "`traefik.http.routers.<router_name>.entrypoints`"
See [entry points](../routers/index.md#entrypoints) for more information. See [entry points](../routers/index.md#entrypoints) for more information.
```yaml ```yaml
- "traefik.http.routers.myrouter.entrypoints=web,websecure" traefik.http.routers.myrouter.entrypoints=web,websecure
``` ```
??? info "`traefik.http.routers.<router_name>.middlewares`" ??? info "`traefik.http.routers.<router_name>.middlewares`"
See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information. See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information.
```yaml ```yaml
- "traefik.http.routers.myrouter.middlewares=auth,prefix,cb" traefik.http.routers.myrouter.middlewares=auth,prefix,cb
``` ```
??? info "`traefik.http.routers.<router_name>.service`" ??? info "`traefik.http.routers.<router_name>.service`"
See [rule](../routers/index.md#service) for more information. See [rule](../routers/index.md#service) for more information.
```yaml ```yaml
- "traefik.http.routers.myrouter.service=myservice" traefik.http.routers.myrouter.service=myservice
``` ```
??? info "`traefik.http.routers.<router_name>.tls`" ??? info "`traefik.http.routers.<router_name>.tls`"
@ -63,7 +63,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
See [tls](../routers/index.md#tls) for more information. See [tls](../routers/index.md#tls) for more information.
```yaml ```yaml
- "traefik.http.routers.myrouter>.tls=true" traefik.http.routers.myrouter>.tls=true
``` ```
??? info "`traefik.http.routers.<router_name>.tls.certresolver`" ??? info "`traefik.http.routers.<router_name>.tls.certresolver`"
@ -71,7 +71,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
See [certResolver](../routers/index.md#certresolver) for more information. See [certResolver](../routers/index.md#certresolver) for more information.
```yaml ```yaml
- "traefik.http.routers.myrouter.tls.certresolver=myresolver" traefik.http.routers.myrouter.tls.certresolver=myresolver
``` ```
??? info "`traefik.http.routers.<router_name>.tls.domains[n].main`" ??? info "`traefik.http.routers.<router_name>.tls.domains[n].main`"
@ -79,7 +79,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
See [domains](../routers/index.md#domains) for more information. See [domains](../routers/index.md#domains) for more information.
```yaml ```yaml
- "traefik.http.routers.myrouter.tls.domains[0].main=foobar.com" traefik.http.routers.myrouter.tls.domains[0].main=foobar.com
``` ```
??? info "`traefik.http.routers.<router_name>.tls.domains[n].sans`" ??? info "`traefik.http.routers.<router_name>.tls.domains[n].sans`"
@ -87,7 +87,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
See [domains](../routers/index.md#domains) for more information. See [domains](../routers/index.md#domains) for more information.
```yaml ```yaml
- "traefik.http.routers.myrouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com" traefik.http.routers.myrouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com
``` ```
??? info "`traefik.http.routers.<router_name>.tls.options`" ??? info "`traefik.http.routers.<router_name>.tls.options`"
@ -95,31 +95,31 @@ For example, to change the rule, you could add the label ```traefik.http.routers
See [options](../routers/index.md#options) for more information. See [options](../routers/index.md#options) for more information.
```yaml ```yaml
- "traefik.http.routers.myrouter.tls.options=foobar" traefik.http.routers.myrouter.tls.options=foobar
``` ```
??? info "`traefik.http.routers.<router_name>.priority`" ??? info "`traefik.http.routers.<router_name>.priority`"
<!-- TODO doc priority in routers page --> <!-- TODO doc priority in routers page -->
```yaml ```yaml
- "traefik.http.routers.myrouter.priority=42" traefik.http.routers.myrouter.priority=42
``` ```
### Services ### Services
To update the configuration of the Service automatically attached to the container, To update the configuration of the Service automatically attached to the service,
add labels starting with `traefik.http.services.{name-of-your-choice}.`, followed by the option you want to change. add tags starting with `traefik.http.services.{name-of-your-choice}.`, followed by the option you want to change.
For example, to change the `passHostHeader` behavior, For example, to change the `passHostHeader` behavior,
you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.passhostheader=false`. you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.passhostheader=false`.
??? info "`traefik.http.services.<service_name>.loadbalancer.server.port`" ??? info "`traefik.http.services.<service_name>.loadbalancer.server.port`"
Registers a port. Registers a port.
Useful when the container exposes multiples ports. Useful when the service exposes multiples ports.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.server.port=8080" traefik.http.services.myservice.loadbalancer.server.port=8080
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.server.scheme`" ??? info "`traefik.http.services.<service_name>.loadbalancer.server.scheme`"
@ -127,14 +127,14 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
Overrides the default scheme. Overrides the default scheme.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.server.scheme=http" traefik.http.services.myservice.loadbalancer.server.scheme=http
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.passhostheader`" ??? info "`traefik.http.services.<service_name>.loadbalancer.passhostheader`"
<!-- TODO doc passHostHeader in services page --> <!-- TODO doc passHostHeader in services page -->
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.passhostheader=true" traefik.http.services.myservice.loadbalancer.passhostheader=true
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.headers.<header_name>`" ??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.headers.<header_name>`"
@ -142,7 +142,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
See [health check](../services/index.md#health-check) for more information. See [health check](../services/index.md#health-check) for more information.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar" traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.hostname`" ??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.hostname`"
@ -150,7 +150,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
See [health check](../services/index.md#health-check) for more information. See [health check](../services/index.md#health-check) for more information.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.hostname=foobar.com" traefik.http.services.myservice.loadbalancer.healthcheck.hostname=foobar.com
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.interval`" ??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.interval`"
@ -158,7 +158,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
See [health check](../services/index.md#health-check) for more information. See [health check](../services/index.md#health-check) for more information.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.interval=10" traefik.http.services.myservice.loadbalancer.healthcheck.interval=10
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.path`" ??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.path`"
@ -166,7 +166,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
See [health check](../services/index.md#health-check) for more information. See [health check](../services/index.md#health-check) for more information.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo" traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.port`" ??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.port`"
@ -174,7 +174,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
See [health check](../services/index.md#health-check) for more information. See [health check](../services/index.md#health-check) for more information.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.port=42" traefik.http.services.myservice.loadbalancer.healthcheck.port=42
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.scheme`" ??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.scheme`"
@ -182,7 +182,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
See [health check](../services/index.md#health-check) for more information. See [health check](../services/index.md#health-check) for more information.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http" traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.timeout`" ??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.timeout`"
@ -190,7 +190,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
See [health check](../services/index.md#health-check) for more information. See [health check](../services/index.md#health-check) for more information.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10" traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky`" ??? info "`traefik.http.services.<service_name>.loadbalancer.sticky`"
@ -198,7 +198,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
See [sticky sessions](../services/index.md#sticky-sessions) for more information. See [sticky sessions](../services/index.md#sticky-sessions) for more information.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.sticky=true" traefik.http.services.myservice.loadbalancer.sticky=true
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.httponly`" ??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.httponly`"
@ -206,7 +206,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
See [sticky sessions](../services/index.md#sticky-sessions) for more information. See [sticky sessions](../services/index.md#sticky-sessions) for more information.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true" traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.name`" ??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.name`"
@ -214,7 +214,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
See [sticky sessions](../services/index.md#sticky-sessions) for more information. See [sticky sessions](../services/index.md#sticky-sessions) for more information.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar" traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.secure`" ??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.secure`"
@ -222,7 +222,7 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
See [sticky sessions](../services/index.md#sticky-sessions) for more information. See [sticky sessions](../services/index.md#sticky-sessions) for more information.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true" traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.responseforwarding.flushinterval`" ??? info "`traefik.http.services.<service_name>.loadbalancer.responseforwarding.flushinterval`"
@ -231,12 +231,12 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
FlushInterval specifies the flush interval to flush to the client while copying the response body. FlushInterval specifies the flush interval to flush to the client while copying the response body.
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10" traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10
``` ```
### Middleware ### Middleware
You can declare pieces of middleware using labels starting with `traefik.http.middlewares.{name-of-your-choice}.`, followed by the middleware type/options. You can declare pieces of middleware using tags starting with `traefik.http.middlewares.{name-of-your-choice}.`, followed by the middleware type/options.
For example, to declare a middleware [`redirectscheme`](../../middlewares/redirectscheme.md) named `my-redirect`, you'd write `traefik.http.middlewares.my-redirect.redirectscheme.scheme: https`. For example, to declare a middleware [`redirectscheme`](../../middlewares/redirectscheme.md) named `my-redirect`, you'd write `traefik.http.middlewares.my-redirect.redirectscheme.scheme: https`.
@ -246,11 +246,10 @@ More information about available middlewares in the dedicated [middlewares secti
```yaml ```yaml
# ... # ...
labels: # Declaring a middleware
# Declaring a middleware traefik.http.middlewares.my-redirect.redirectscheme.scheme=https
- traefik.http.middlewares.my-redirect.redirectscheme.scheme=https # Referencing a middleware
# Referencing a middleware traefik.http.routers.my-service.middlewares=my-redirect
- traefik.http.routers.my-container.middlewares=my-redirect
``` ```
!!! warning "Conflicts in Declaration" !!! warning "Conflicts in Declaration"
@ -259,24 +258,20 @@ More information about available middlewares in the dedicated [middlewares secti
### TCP ### TCP
You can declare TCP Routers and/or Services using labels. You can declare TCP Routers and/or Services using tags.
??? example "Declaring TCP Routers and Services" ??? example "Declaring TCP Routers and Services"
```yaml ```yaml
services: traefik.tcp.routers.my-router.rule=HostSNI(`my-host.com`)
my-container: traefik.tcp.routers.my-router.tls=true
# ... traefik.tcp.services.my-service.loadbalancer.server.port=4123
labels:
- "traefik.tcp.routers.my-router.rule=HostSNI(`my-host.com`)"
- "traefik.tcp.routers.my-router.tls=true"
- "traefik.tcp.services.my-service.loadbalancer.server.port=4123"
``` ```
!!! warning "TCP and HTTP" !!! warning "TCP and HTTP"
If you declare a TCP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no TCP Router/Service is defined). If you declare a TCP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no TCP Router/Service is defined).
You can declare both a TCP Router/Service and an HTTP Router/Service for the same container (but you have to do so manually). You can declare both a TCP Router/Service and an HTTP Router/Service for the same consul service (but you have to do so manually).
#### TCP Routers #### TCP Routers
@ -285,7 +280,7 @@ You can declare TCP Routers and/or Services using labels.
See [entry points](../routers/index.md#entrypoints_1) for more information. See [entry points](../routers/index.md#entrypoints_1) for more information.
```yaml ```yaml
- "traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2" traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2
``` ```
??? info "`traefik.tcp.routers.<router_name>.rule`" ??? info "`traefik.tcp.routers.<router_name>.rule`"
@ -293,7 +288,7 @@ You can declare TCP Routers and/or Services using labels.
See [rule](../routers/index.md#rule_1) for more information. See [rule](../routers/index.md#rule_1) for more information.
```yaml ```yaml
- "traefik.tcp.routers.mytcprouter.rule=HostSNI(`myhost.com`)" traefik.tcp.routers.mytcprouter.rule=HostSNI(`myhost.com`)
``` ```
??? info "`traefik.tcp.routers.<router_name>.service`" ??? info "`traefik.tcp.routers.<router_name>.service`"
@ -301,7 +296,7 @@ You can declare TCP Routers and/or Services using labels.
See [service](../routers/index.md#services) for more information. See [service](../routers/index.md#services) for more information.
```yaml ```yaml
- "traefik.tcp.routers.mytcprouter.service=myservice" traefik.tcp.routers.mytcprouter.service=myservice
``` ```
??? info "`traefik.tcp.routers.<router_name>.tls`" ??? info "`traefik.tcp.routers.<router_name>.tls`"
@ -309,7 +304,7 @@ You can declare TCP Routers and/or Services using labels.
See [TLS](../routers/index.md#tls_1) for more information. See [TLS](../routers/index.md#tls_1) for more information.
```yaml ```yaml
- "traefik.tcp.routers.mytcprouter.tls=true" traefik.tcp.routers.mytcprouter.tls=true
``` ```
??? info "`traefik.tcp.routers.<router_name>.tls.certresolver`" ??? info "`traefik.tcp.routers.<router_name>.tls.certresolver`"
@ -317,7 +312,7 @@ You can declare TCP Routers and/or Services using labels.
See [certResolver](../routers/index.md#certresolver_1) for more information. See [certResolver](../routers/index.md#certresolver_1) for more information.
```yaml ```yaml
- "traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver" traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver
``` ```
??? info "`traefik.tcp.routers.<router_name>.tls.domains[n].main`" ??? info "`traefik.tcp.routers.<router_name>.tls.domains[n].main`"
@ -325,7 +320,7 @@ You can declare TCP Routers and/or Services using labels.
See [domains](../routers/index.md#domains_1) for more information. See [domains](../routers/index.md#domains_1) for more information.
```yaml ```yaml
- "traefik.tcp.routers.mytcprouter.tls.domains[0].main=foobar.com" traefik.tcp.routers.mytcprouter.tls.domains[0].main=foobar.com
``` ```
??? info "`traefik.tcp.routers.<router_name>.tls.domains[n].sans`" ??? info "`traefik.tcp.routers.<router_name>.tls.domains[n].sans`"
@ -333,7 +328,7 @@ You can declare TCP Routers and/or Services using labels.
See [domains](../routers/index.md#domains_1) for more information. See [domains](../routers/index.md#domains_1) for more information.
```yaml ```yaml
- "traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com" traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com
``` ```
??? info "`traefik.tcp.routers.<router_name>.tls.options`" ??? info "`traefik.tcp.routers.<router_name>.tls.options`"
@ -341,7 +336,7 @@ You can declare TCP Routers and/or Services using labels.
See [options](../routers/index.md#options_1) for more information. See [options](../routers/index.md#options_1) for more information.
```yaml ```yaml
- "traefik.tcp.routers.mytcprouter.tls.options=mysoptions" traefik.tcp.routers.mytcprouter.tls.options=mysoptions
``` ```
??? info "`traefik.tcp.routers.<router_name>.tls.passthrough`" ??? info "`traefik.tcp.routers.<router_name>.tls.passthrough`"
@ -349,7 +344,7 @@ You can declare TCP Routers and/or Services using labels.
See [TLS](../routers/index.md#tls_1) for more information. See [TLS](../routers/index.md#tls_1) for more information.
```yaml ```yaml
- "traefik.tcp.routers.mytcprouter.tls.passthrough=true" traefik.tcp.routers.mytcprouter.tls.passthrough=true
``` ```
#### TCP Services #### TCP Services
@ -359,7 +354,7 @@ You can declare TCP Routers and/or Services using labels.
Registers a port of the application. Registers a port of the application.
```yaml ```yaml
- "traefik.tcp.services.mytcpservice.loadbalancer.server.port=423" traefik.tcp.services.mytcpservice.loadbalancer.server.port=423
``` ```
??? info "`traefik.tcp.services.<service_name>.loadbalancer.terminationdelay`" ??? info "`traefik.tcp.services.<service_name>.loadbalancer.terminationdelay`"
@ -367,7 +362,7 @@ You can declare TCP Routers and/or Services using labels.
See [termination delay](../services/index.md#termination-delay) for more information. See [termination delay](../services/index.md#termination-delay) for more information.
```yaml ```yaml
- "traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100" traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100
``` ```
### Specific Provider Options ### Specific Provider Options
@ -375,10 +370,10 @@ You can declare TCP Routers and/or Services using labels.
#### `traefik.enable` #### `traefik.enable`
```yaml ```yaml
- "traefik.enable=true" traefik.enable=true
``` ```
You can tell Traefik to consider (or not) the container by setting `traefik.enable` to true or false. You can tell Traefik to consider (or not) the service by setting `traefik.enable` to true or false.
This option overrides the value of `exposedByDefault`. This option overrides the value of `exposedByDefault`.

View file

@ -82,7 +82,7 @@ Attach labels to your containers and let Traefik do the rest!
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.docker.endpoint="tcp://127.0.0.1:2375" --providers.docker.endpoint=tcp://127.0.0.1:2375
--providers.docker.swarmMode=true --providers.docker.swarmMode=true
``` ```
@ -165,7 +165,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
See [entry points](../routers/index.md#entrypoints) for more information. See [entry points](../routers/index.md#entrypoints) for more information.
```yaml ```yaml
- "traefik.http.routers.myrouter.entrypoints=web,websecure" - "traefik.http.routers.myrouter.entrypoints=ep1,ep2"
``` ```
??? info "`traefik.http.routers.<router_name>.middlewares`" ??? info "`traefik.http.routers.<router_name>.middlewares`"
@ -243,11 +243,12 @@ you'd add the label `traefik.http.services.<name-of-your-choice>.loadbalancer.pa
!!! warning "The character `@` is not authorized in the service name `<service_name>`." !!! warning "The character `@` is not authorized in the service name `<service_name>`."
??? info "`traefik.http.services.<service_name>.loadbalancer.server.port`" ??? info "`traefik.http.services.<service_name>.loadbalancer.server.port`"
Registers a port. Registers a port.
Useful when the container exposes multiples ports. Useful when the container exposes multiples ports.
Mandatory for Docker Swarm. Mandatory for Docker Swarm (see the section ["Port Detection with Docker Swarm"](../../providers/docker.md#port-detection_1)).
{: #port }
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.server.port=8080" - "traefik.http.services.myservice.loadbalancer.server.port=8080"

View file

@ -67,7 +67,7 @@ For example, to change the routing rule, you could add the label ```"traefik.htt
See [entry points](../routers/index.md#entrypoints) for more information. See [entry points](../routers/index.md#entrypoints) for more information.
```json ```json
"traefik.http.routers.myrouter.entrypoints": "web,websecure" "traefik.http.routers.myrouter.entrypoints": "ep1,ep2"
``` ```
??? info "`traefik.http.routers.<router_name>.middlewares`" ??? info "`traefik.http.routers.<router_name>.middlewares`"

View file

@ -72,7 +72,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers
See [entry points](../routers/index.md#entrypoints) for more information. See [entry points](../routers/index.md#entrypoints) for more information.
```yaml ```yaml
- "traefik.http.routers.myrouter.entrypoints=web,websecure" - "traefik.http.routers.myrouter.entrypoints=ep1,ep2"
``` ```
??? info "`traefik.http.routers.<router_name>.middlewares`" ??? info "`traefik.http.routers.<router_name>.middlewares`"

View file

@ -78,8 +78,8 @@ In the process, routers may use pieces of [middleware](../../middlewares/overvie
```bash tab="CLI" ```bash tab="CLI"
## Static configuration ## Static configuration
--entryPoints.web.address=":80" --entryPoints.web.address=:80
--entryPoints.mysql.address=":3306" --entryPoints.mysql.address=:3306
``` ```
## Configuring HTTP Routers ## Configuring HTTP Routers
@ -140,9 +140,9 @@ If you want to limit the router scope to a set of entry points, set the `entryPo
```bash tab="CLI" ```bash tab="CLI"
## Static configuration ## Static configuration
--entrypoints.web.address=":80" --entrypoints.web.address=:80
--entrypoints.websecure.address=":443" --entrypoints.websecure.address=:443
--entrypoints.other.address=":9090" --entrypoints.other.address=:9090
``` ```
??? example "Listens to Specific EntryPoints" ??? example "Listens to Specific EntryPoints"
@ -198,9 +198,9 @@ If you want to limit the router scope to a set of entry points, set the `entryPo
```bash tab="CLI" ```bash tab="CLI"
## Static configuration ## Static configuration
--entrypoints.web.address=":80" --entrypoints.web.address=:80
--entrypoints.websecure.address=":443" --entrypoints.websecure.address=:443
--entrypoints.other.address=":9090" --entrypoints.other.address=:9090
``` ```
### Rule ### Rule
@ -700,9 +700,9 @@ If you want to limit the router scope to a set of entry points, set the entry po
```bash tab="CLI" ```bash tab="CLI"
## Static configuration ## Static configuration
--entrypoints.web.address=":80" --entrypoints.web.address=:80
--entrypoints.websecure.address=":443" --entrypoints.websecure.address=:443
--entrypoints.other.address=":9090" --entrypoints.other.address=:9090
``` ```
??? example "Listens to Specific Entry Points" ??? example "Listens to Specific Entry Points"
@ -764,9 +764,9 @@ If you want to limit the router scope to a set of entry points, set the entry po
```bash tab="CLI" ```bash tab="CLI"
## Static configuration ## Static configuration
--entrypoints.web.address=":80" --entrypoints.web.address=:80
--entrypoints.websecure.address=":443" --entrypoints.websecure.address=:443
--entrypoints.other.address=":9090" --entrypoints.other.address=:9090
``` ```
### Rule ### Rule

View file

@ -32,7 +32,7 @@ api: {}
``` ```
```yaml tab="CLI" ```yaml tab="CLI"
--entryPoints.web.address=":80" --entryPoints.web.address=:80
--providers.file.filename=dynamic_conf.toml --providers.file.filename=dynamic_conf.toml
--api.insecure=true --api.insecure=true
``` ```
@ -153,7 +153,7 @@ api: {}
``` ```
```yaml tab="CLI" ```yaml tab="CLI"
--entryPoints.websecure.address=":4443" --entryPoints.websecure.address=:4443
# For secure connection on backend.local # For secure connection on backend.local
--serversTransport.rootCAs=./backend.cert --serversTransport.rootCAs=./backend.cert
--providers.file.filename=dynamic_conf.toml --providers.file.filename=dynamic_conf.toml

View file

@ -1 +1 @@
3.6 3.7

2
go.mod
View file

@ -67,7 +67,7 @@ require (
github.com/opencontainers/runc v1.0.0-rc8 // indirect github.com/opencontainers/runc v1.0.0-rc8 // indirect
github.com/opentracing/basictracer-go v1.0.0 // indirect github.com/opentracing/basictracer-go v1.0.0 // indirect
github.com/opentracing/opentracing-go v1.1.0 github.com/opentracing/opentracing-go v1.1.0
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.4 github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5
github.com/openzipkin/zipkin-go v0.2.1 github.com/openzipkin/zipkin-go v0.2.1
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/philhofer/fwd v1.0.0 // indirect github.com/philhofer/fwd v1.0.0 // indirect

4
go.sum
View file

@ -472,8 +472,8 @@ github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7l
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.4 h1:bzTJRoOZEN7uI1gq594S5HhMYNSud4FKUEwd4aFbsEI= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.4/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx8PXcA= github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx8PXcA=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=

View file

@ -15,8 +15,10 @@ import (
type ConsulCatalogSuite struct { type ConsulCatalogSuite struct {
BaseSuite BaseSuite
consulClient *api.Client consulClient *api.Client
consulAddress string consulAgentClient *api.Client
consulAddress string
consulAgentAddress string
} }
func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) { func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
@ -32,6 +34,13 @@ func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
// Wait for consul to elect itself leader // Wait for consul to elect itself leader
err = s.waitToElectConsulLeader() err = s.waitToElectConsulLeader()
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
s.consulAgentAddress = "http://" + s.composeProject.Container(c, "consul-agent").NetworkSettings.IPAddress + ":8500"
clientAgent, err := api.NewClient(&api.Config{
Address: s.consulAgentAddress,
})
c.Check(err, check.IsNil)
s.consulAgentClient = clientAgent
} }
func (s *ConsulCatalogSuite) waitToElectConsulLeader() error { func (s *ConsulCatalogSuite) waitToElectConsulLeader() error {
@ -53,13 +62,17 @@ func (s *ConsulCatalogSuite) TearDownSuite(c *check.C) {
} }
} }
func (s *ConsulCatalogSuite) registerService(id, name, address, port string, tags []string) error { func (s *ConsulCatalogSuite) registerService(id, name, address, port string, tags []string, onAgent bool) error {
iPort, err := strconv.Atoi(port) iPort, err := strconv.Atoi(port)
if err != nil { if err != nil {
return err return err
} }
client := s.consulClient
if onAgent {
client = s.consulAgentClient
}
return s.consulClient.Agent().ServiceRegister(&api.AgentServiceRegistration{ return client.Agent().ServiceRegister(&api.AgentServiceRegistration{
ID: id, ID: id,
Name: name, Name: name,
Address: address, Address: address,
@ -68,16 +81,20 @@ func (s *ConsulCatalogSuite) registerService(id, name, address, port string, tag
}) })
} }
func (s *ConsulCatalogSuite) deregisterService(id string) error { func (s *ConsulCatalogSuite) deregisterService(id string, onAgent bool) error {
return s.consulClient.Agent().ServiceDeregister(id) client := s.consulClient
if onAgent {
client = s.consulAgentClient
}
return client.Agent().ServiceDeregister(id)
} }
func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *check.C) { func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *check.C) {
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}) err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}) err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = s.registerService("whoami3", "whoami", s.composeProject.Container(c, "whoami3").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}) err = s.registerService("whoami3", "whoami", s.composeProject.Container(c, "whoami3").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
tempObjects := struct { tempObjects := struct {
@ -102,11 +119,11 @@ func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *c
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3")) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = s.deregisterService("whoami1") err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = s.deregisterService("whoami2") err = s.deregisterService("whoami2", false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = s.deregisterService("whoami3") err = s.deregisterService("whoami3", false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
@ -118,7 +135,7 @@ func (s *ConsulCatalogSuite) TestByLabels(c *check.C) {
"traefik.http.services.service1.loadBalancer.server.url=http://" + s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "traefik.http.services.service1.loadBalancer.server.url=http://" + s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
} }
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels) err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
tempObjects := struct { tempObjects := struct {
@ -139,7 +156,7 @@ func (s *ConsulCatalogSuite) TestByLabels(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8000/whoami", 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3")) err = try.GetRequest("http://127.0.0.1:8000/whoami", 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = s.deregisterService("whoami1") err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
@ -155,7 +172,7 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
defer os.Remove(file) defer os.Remove(file)
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}) err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
cmd, display := s.traefikCmd(withConfigFile(file)) cmd, display := s.traefikCmd(withConfigFile(file))
@ -171,7 +188,7 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1")) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = s.deregisterService("whoami1") err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
@ -187,7 +204,7 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) {
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
defer os.Remove(file) defer os.Remove(file)
err := s.registerService("whoami1", "whoami", "", "80", []string{"traefik.enable=true"}) err := s.registerService("whoami1", "whoami", "", "80", []string{"traefik.enable=true"}, false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
cmd, display := s.traefikCmd(withConfigFile(file)) cmd, display := s.traefikCmd(withConfigFile(file))
@ -202,7 +219,7 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) {
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("whoami@consulcatalog", "\"http://127.0.0.1:80\": \"UP\"")) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("whoami@consulcatalog", "\"http://127.0.0.1:80\": \"UP\""))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = s.deregisterService("whoami1") err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
@ -219,7 +236,7 @@ func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) {
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects)
defer os.Remove(file) defer os.Remove(file)
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", nil) err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", nil, false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// Start traefik // Start traefik
@ -236,7 +253,7 @@ func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) {
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1")) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = s.deregisterService("whoami1") err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
@ -259,7 +276,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) {
"traefik.tcp.Services.Super.Loadbalancer.server.port=8080", "traefik.tcp.Services.Super.Loadbalancer.server.port=8080",
} }
err := s.registerService("whoamitcp", "whoamitcp", s.composeProject.Container(c, "whoamitcp").NetworkSettings.IPAddress, "8080", labels) err := s.registerService("whoamitcp", "whoamitcp", s.composeProject.Container(c, "whoamitcp").NetworkSettings.IPAddress, "8080", labels, false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// Start traefik // Start traefik
@ -277,7 +294,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) {
c.Assert(who, checker.Contains, "whoamitcp") c.Assert(who, checker.Contains, "whoamitcp")
err = s.deregisterService("whoamitcp") err = s.deregisterService("whoamitcp", false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
@ -297,14 +314,14 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) {
labels := []string{ labels := []string{
"traefik.http.Routers.Super.Rule=Host(`my.super.host`)", "traefik.http.Routers.Super.Rule=Host(`my.super.host`)",
} }
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels) err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// Start another container by replacing a '.' by a '-' // Start another container by replacing a '.' by a '-'
labels = []string{ labels = []string{
"traefik.http.Routers.SuperHost.Rule=Host(`my-super.host`)", "traefik.http.Routers.SuperHost.Rule=Host(`my-super.host`)",
} }
err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", labels) err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", labels, false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// Start traefik // Start traefik
@ -328,10 +345,63 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) {
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2")) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = s.deregisterService("whoami1") err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = s.deregisterService("whoami2") err = s.deregisterService("whoami2", false)
c.Assert(err, checker.IsNil)
}
func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent(c *check.C) {
tempObjects := struct {
ConsulAddress string
DefaultRule string
}{
ConsulAddress: s.consulAddress,
DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)",
}
file := s.adaptFile(c, "fixtures/consul_catalog/default_not_exposed.toml", tempObjects)
defer os.Remove(file)
// Start a container with some labels
labels := []string{
"traefik.enable=true",
"traefik.http.Routers.Super.service=whoami",
"traefik.http.Routers.Super.Rule=Host(`my.super.host`)",
}
err := s.registerService("whoami", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false)
c.Assert(err, checker.IsNil)
err = s.registerService("whoami", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", labels, true)
c.Assert(err, checker.IsNil)
// Start traefik
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "my.super.host"
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2"))
c.Assert(err, checker.IsNil)
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil)
c.Assert(err, checker.IsNil)
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200),
try.BodyContainsOr(s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress,
s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress))
c.Assert(err, checker.IsNil)
err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil)
err = s.deregisterService("whoami2", true)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
@ -351,7 +421,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) {
labels := []string{ labels := []string{
"traefik.random.value=my.super.host", "traefik.random.value=my.super.host",
} }
err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels) err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// Start traefik // Start traefik

View file

@ -0,0 +1,40 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.web1]
address = ":8000"
[entryPoints.web2]
address = ":9000"
[api]
insecure = true
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.routers]
[http.routers.router1]
entryPoints = ["web1"]
service = "service1"
rule = "Host(`test.localhost`)"
[http.routers.router2]
entryPoints = ["web2"]
service = "service1"
rule = "Host(`test.localhost`)"
[http.services]
[http.services.service1.loadBalancer]
[http.services.service1.loadBalancer.healthcheck]
path = "/health"
interval = "1s"
timeout = "0.9s"
[[http.services.service1.loadBalancer.servers]]
url = "http://{{.Server1}}:80"

View file

@ -205,3 +205,69 @@ func (s *HealthCheckSuite) TestPortOverload(c *check.C) {
err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
// Checks if all the loadbalancers created will correctly update the server status
func (s *HealthCheckSuite) TestMultipleRoutersOnSameService(c *check.C) {
file := s.adaptFile(c, "fixtures/healthcheck/multiple-routers-one-same-service.toml", struct {
Server1 string
}{s.whoami1IP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)"))
c.Assert(err, checker.IsNil)
// Set whoami health to 200 to be sure to start with the wanted status
client := &http.Client{}
statusOkReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("200")))
c.Assert(err, checker.IsNil)
_, err = client.Do(statusOkReq)
c.Assert(err, checker.IsNil)
// check healthcheck on web1 entrypoint
healthReqWeb1, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil)
c.Assert(err, checker.IsNil)
healthReqWeb1.Host = "test.localhost"
err = try.Request(healthReqWeb1, 1*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
// check healthcheck on web2 entrypoint
healthReqWeb2, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/health", nil)
c.Assert(err, checker.IsNil)
healthReqWeb2.Host = "test.localhost"
err = try.Request(healthReqWeb2, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
// Set whoami health to 500
statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("500")))
c.Assert(err, checker.IsNil)
_, err = client.Do(statusInternalServerErrorReq)
c.Assert(err, checker.IsNil)
// Verify no backend service is available due to failing health checks
err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
c.Assert(err, checker.IsNil)
err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
c.Assert(err, checker.IsNil)
// Change one whoami health to 200
statusOKReq1, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBuffer([]byte("200")))
c.Assert(err, checker.IsNil)
_, err = client.Do(statusOKReq1)
c.Assert(err, checker.IsNil)
// Verify health check
err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
}

View file

@ -1,7 +1,15 @@
consul: consul:
image: consul:1.6.1 image: consul:1.6.2
ports: ports:
- 8500:8500 - 8500:8500
command: "agent -server -bootstrap -ui -client 0.0.0.0"
consul-agent:
image: consul:1.6.2
ports:
- 8501:8500
command: "agent -retry-join consul -client 0.0.0.0"
links:
- consul
whoami1: whoami1:
image: containous/whoami:v1.3.0 image: containous/whoami:v1.3.0
hostname: whoami1 hostname: whoami1

View file

@ -4,18 +4,20 @@ package cli
import ( import (
"fmt" "fmt"
"io"
"os" "os"
"path/filepath" "path/filepath"
) )
// Command structure contains program/command information (command name and description). // Command structure contains program/command information (command name and description).
type Command struct { type Command struct {
Name string Name string
Description string Description string
Configuration interface{} Configuration interface{}
Resources []ResourceLoader Resources []ResourceLoader
Run func([]string) error Run func([]string) error
Hidden bool CustomHelpFunc func(io.Writer, *Command) error
Hidden bool
// AllowArg if not set, disallows any argument that is not a known command or a sub-command. // AllowArg if not set, disallows any argument that is not a known command or a sub-command.
AllowArg bool AllowArg bool
subCommands []*Command subCommands []*Command
@ -35,6 +37,15 @@ func (c *Command) AddCommand(cmd *Command) error {
return nil return nil
} }
// PrintHelp calls the custom help function of the command if it's set.
// Otherwise, it calls the default help function.
func (c *Command) PrintHelp(w io.Writer) error {
if c.CustomHelpFunc != nil {
return c.CustomHelpFunc(w, c)
}
return PrintHelp(w, c)
}
// Execute Executes a command. // Execute Executes a command.
func Execute(cmd *Command) error { func Execute(cmd *Command) error {
return execute(cmd, os.Args, true) return execute(cmd, os.Args, true)
@ -61,10 +72,12 @@ func execute(cmd *Command, args []string, root bool) error {
// Calls command by its name. // Calls command by its name.
if len(args) >= 2 && cmd.Name == args[1] { if len(args) >= 2 && cmd.Name == args[1] {
if err := run(cmd, args[2:]); err != nil { if len(args) < 3 || !contains(cmd.subCommands, args[2]) {
return fmt.Errorf("command %s error: %v", cmd.Name, err) if err := run(cmd, args[2:]); err != nil {
return fmt.Errorf("command %s error: %v", cmd.Name, err)
}
return nil
} }
return nil
} }
// No sub-command, calls the current command. // No sub-command, calls the current command.
@ -78,6 +91,9 @@ func execute(cmd *Command, args []string, root bool) error {
// Trying to find the sub-command. // Trying to find the sub-command.
for _, subCmd := range cmd.subCommands { for _, subCmd := range cmd.subCommands {
if len(args) >= 2 && subCmd.Name == args[1] { if len(args) >= 2 && subCmd.Name == args[1] {
return execute(subCmd, args, false)
}
if len(args) >= 3 && subCmd.Name == args[2] {
return execute(subCmd, args[1:], false) return execute(subCmd, args[1:], false)
} }
} }
@ -87,16 +103,16 @@ func execute(cmd *Command, args []string, root bool) error {
func run(cmd *Command, args []string) error { func run(cmd *Command, args []string) error {
if len(args) > 0 && !isFlag(args[0]) && !cmd.AllowArg { if len(args) > 0 && !isFlag(args[0]) && !cmd.AllowArg {
_ = PrintHelp(os.Stdout, cmd) _ = cmd.PrintHelp(os.Stdout)
return fmt.Errorf("command not found: %s", args[0]) return fmt.Errorf("command not found: %s", args[0])
} }
if isHelp(args) { if isHelp(args) {
return PrintHelp(os.Stdout, cmd) return cmd.PrintHelp(os.Stdout)
} }
if cmd.Run == nil { if cmd.Run == nil {
_ = PrintHelp(os.Stdout, cmd) _ = cmd.PrintHelp(os.Stdout)
return fmt.Errorf("command %s is not runnable", cmd.Name) return fmt.Errorf("command %s is not runnable", cmd.Name)
} }

View file

@ -1,6 +1,10 @@
package cli package cli
import ( import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
@ -55,6 +59,63 @@ func TestCommand_AddCommand(t *testing.T) {
} }
} }
func TestCommand_PrintHelp(t *testing.T) {
testCases := []struct {
desc string
command *Command
expectedOutput string
expectedError error
}{
{
desc: "print default help",
command: &Command{},
expectedOutput: " \n\nUsage: [command] [flags] [arguments]\n\nUse \" [command] --help\" for help on any command.\n\n",
},
{
desc: "print custom help",
command: &Command{
Name: "root",
Description: "Description for root",
Configuration: &struct {
Foo []struct {
Field string
}
}{},
Run: func(args []string) error {
return nil
},
CustomHelpFunc: func(w io.Writer, _ *Command) error {
_, _ = fmt.Fprintln(w, "test")
return nil
},
},
expectedOutput: "test\n",
},
{
desc: "error is returned from called help",
command: &Command{
CustomHelpFunc: func(_ io.Writer, _ *Command) error {
return errors.New("test")
},
},
expectedError: errors.New("test"),
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
buffer := &bytes.Buffer{}
err := test.command.PrintHelp(buffer)
assert.Equal(t, test.expectedError, err)
assert.Equal(t, test.expectedOutput, buffer.String())
})
}
}
func Test_execute(t *testing.T) { func Test_execute(t *testing.T) {
var called string var called string
@ -559,6 +620,88 @@ func Test_execute(t *testing.T) {
}, },
expected: expected{result: "root---foo=bar--fii=bir"}, expected: expected{result: "root---foo=bar--fii=bir"},
}, },
{
desc: "sub command help",
args: []string{"", "test", "subtest", "--help"},
command: func() *Command {
rootCmd := &Command{
Name: "test",
Resources: []ResourceLoader{&FlagLoader{}},
}
subCmd := &Command{
Name: "subtest",
Resources: []ResourceLoader{&FlagLoader{}},
}
err := rootCmd.AddCommand(subCmd)
require.NoError(t, err)
subSubCmd := &Command{
Name: "subsubtest",
Resources: []ResourceLoader{&FlagLoader{}},
}
err = subCmd.AddCommand(subSubCmd)
require.NoError(t, err)
subSubSubCmd := &Command{
Name: "subsubsubtest",
Resources: []ResourceLoader{&FlagLoader{}},
Run: func([]string) error {
called = "subsubsubtest"
return nil
},
}
err = subSubCmd.AddCommand(subSubSubCmd)
require.NoError(t, err)
return rootCmd
},
expected: expected{},
},
{
desc: "sub sub command help",
args: []string{"", "test", "subtest", "subsubtest", "--help"},
command: func() *Command {
rootCmd := &Command{
Name: "test",
Resources: []ResourceLoader{&FlagLoader{}},
}
subCmd := &Command{
Name: "subtest",
Resources: []ResourceLoader{&FlagLoader{}},
}
err := rootCmd.AddCommand(subCmd)
require.NoError(t, err)
subSubCmd := &Command{
Name: "subsubtest",
Resources: []ResourceLoader{&FlagLoader{}},
}
err = subCmd.AddCommand(subSubCmd)
require.NoError(t, err)
subSubSubCmd := &Command{
Name: "subsubsubtest",
Resources: []ResourceLoader{&FlagLoader{}},
Run: func([]string) error {
called = "subsubsubtest"
return nil
},
}
err = subSubCmd.AddCommand(subSubSubCmd)
require.NoError(t, err)
return rootCmd
},
expected: expected{},
},
} }
for _, test := range testCases { for _, test := range testCases {
@ -756,3 +899,43 @@ Flags:
`, string(out)) `, string(out))
} }
func TestName(t *testing.T) {
rootCmd := &Command{
Name: "test",
Resources: []ResourceLoader{&FlagLoader{}},
}
subCmd := &Command{
Name: "subtest",
Resources: []ResourceLoader{&FlagLoader{}},
}
err := rootCmd.AddCommand(subCmd)
require.NoError(t, err)
subSubCmd := &Command{
Name: "subsubtest",
Resources: []ResourceLoader{&FlagLoader{}},
Run: func([]string) error {
return nil
},
}
err = subCmd.AddCommand(subSubCmd)
require.NoError(t, err)
subSubSubCmd := &Command{
Name: "subsubsubtest",
Resources: []ResourceLoader{&FlagLoader{}},
Run: func([]string) error {
return nil
},
}
err = subSubCmd.AddCommand(subSubSubCmd)
require.NoError(t, err)
err = execute(rootCmd, []string{"", "test", "subtest", "subsubtest", "subsubsubtest", "--help"}, true)
require.NoError(t, err)
}

View file

@ -25,7 +25,7 @@ func (f *FileLoader) GetFilename() string {
func (f *FileLoader) Load(args []string, cmd *Command) (bool, error) { func (f *FileLoader) Load(args []string, cmd *Command) (bool, error) {
ref, err := flag.Parse(args, cmd.Configuration) ref, err := flag.Parse(args, cmd.Configuration)
if err != nil { if err != nil {
_ = PrintHelp(os.Stdout, cmd) _ = cmd.PrintHelp(os.Stdout)
return false, err return false, err
} }

View file

@ -25,14 +25,20 @@ const (
var singleton *HealthCheck var singleton *HealthCheck
var once sync.Once var once sync.Once
// BalancerHandler includes functionality for load-balancing management. // Balancer is the set of operations required to manage the list of servers in a
type BalancerHandler interface { // load-balancer.
ServeHTTP(w http.ResponseWriter, req *http.Request) type Balancer interface {
Servers() []*url.URL Servers() []*url.URL
RemoveServer(u *url.URL) error RemoveServer(u *url.URL) error
UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error
} }
// BalancerHandler includes functionality for load-balancing management.
type BalancerHandler interface {
ServeHTTP(w http.ResponseWriter, req *http.Request)
Balancer
}
// metricsRegistry is a local interface in the health check package, exposing only the required metrics // metricsRegistry is a local interface in the health check package, exposing only the required metrics
// necessary for the health check package. This makes it easier for the tests. // necessary for the health check package. This makes it easier for the tests.
type metricsRegistry interface { type metricsRegistry interface {
@ -49,7 +55,7 @@ type Options struct {
Transport http.RoundTripper Transport http.RoundTripper
Interval time.Duration Interval time.Duration
Timeout time.Duration Timeout time.Duration
LB BalancerHandler LB Balancer
} }
func (opt Options) String() string { func (opt Options) String() string {
@ -146,18 +152,18 @@ func (hc *HealthCheck) checkBackend(ctx context.Context, backend *BackendConfig)
enabledURLs := backend.LB.Servers() enabledURLs := backend.LB.Servers()
var newDisabledURLs []backendURL var newDisabledURLs []backendURL
// FIXME re enable metrics // FIXME re enable metrics
for _, disableURL := range backend.disabledURLs { for _, disabledURL := range backend.disabledURLs {
// FIXME serverUpMetricValue := float64(0) // FIXME serverUpMetricValue := float64(0)
if err := checkHealth(disableURL.url, backend); err == nil { if err := checkHealth(disabledURL.url, backend); err == nil {
logger.Warnf("Health check up: Returning to server list. Backend: %q URL: %q Weight: %d", logger.Warnf("Health check up: Returning to server list. Backend: %q URL: %q Weight: %d",
backend.name, disableURL.url.String(), disableURL.weight) backend.name, disabledURL.url.String(), disabledURL.weight)
if err = backend.LB.UpsertServer(disableURL.url, roundrobin.Weight(disableURL.weight)); err != nil { if err = backend.LB.UpsertServer(disabledURL.url, roundrobin.Weight(disabledURL.weight)); err != nil {
logger.Error(err) logger.Error(err)
} }
// FIXME serverUpMetricValue = 1 // FIXME serverUpMetricValue = 1
} else { } else {
logger.Warnf("Health check still failing. Backend: %q URL: %q Reason: %s", backend.name, disableURL.url.String(), err) logger.Warnf("Health check still failing. Backend: %q URL: %q Reason: %s", backend.name, disabledURL.url.String(), err)
newDisabledURLs = append(newDisabledURLs, disableURL) newDisabledURLs = append(newDisabledURLs, disabledURL)
} }
// FIXME labelValues := []string{"backend", backend.name, "url", backendurl.url.String()} // FIXME labelValues := []string{"backend", backend.name, "url", backendurl.url.String()}
// FIXME hc.metrics.BackendServerUpGauge().With(labelValues...).Set(serverUpMetricValue) // FIXME hc.metrics.BackendServerUpGauge().With(labelValues...).Set(serverUpMetricValue)
@ -177,7 +183,7 @@ func (hc *HealthCheck) checkBackend(ctx context.Context, backend *BackendConfig)
weight = 1 weight = 1
} }
} }
logger.Warnf("Health check failed: Remove from server list. Backend: %q URL: %q Weight: %d Reason: %s", backend.name, enableURL.String(), weight, err) logger.Warnf("Health check failed, removing from server list. Backend: %q URL: %q Weight: %d Reason: %s", backend.name, enableURL.String(), weight, err)
if err := backend.LB.RemoveServer(enableURL); err != nil { if err := backend.LB.RemoveServer(enableURL); err != nil {
logger.Error(err) logger.Error(err)
} }
@ -281,3 +287,38 @@ func (lb *LbStatusUpdater) UpsertServer(u *url.URL, options ...roundrobin.Server
} }
return err return err
} }
// Balancers is a list of Balancers(s) that implements the Balancer interface.
type Balancers []Balancer
// Servers returns the servers url from all the BalancerHandler
func (b Balancers) Servers() []*url.URL {
var servers []*url.URL
for _, lb := range b {
servers = append(servers, lb.Servers()...)
}
return servers
}
// RemoveServer removes the given server from all the BalancerHandler,
// and updates the status of the server to "DOWN".
func (b Balancers) RemoveServer(u *url.URL) error {
for _, lb := range b {
if err := lb.RemoveServer(u); err != nil {
return err
}
}
return nil
}
// UpsertServer adds the given server to all the BalancerHandler,
// and updates the status of the server to "UP".
func (b Balancers) UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error {
for _, lb := range b {
if err := lb.UpsertServer(u, options...); err != nil {
return err
}
}
return nil
}

View file

@ -116,7 +116,18 @@ type CoreLogData map[string]interface{}
// LogData is the data captured by the middleware so that it can be logged. // LogData is the data captured by the middleware so that it can be logged.
type LogData struct { type LogData struct {
Core CoreLogData Core CoreLogData
Request http.Header Request request
OriginResponse http.Header OriginResponse http.Header
DownstreamResponse http.Header DownstreamResponse downstreamResponse
}
type downstreamResponse struct {
headers http.Header
status int
size int64
}
type request struct {
headers http.Header
count int64
} }

View file

@ -47,8 +47,6 @@ func (n noopCloser) Close() error {
type handlerParams struct { type handlerParams struct {
logDataTable *LogData logDataTable *LogData
crr *captureRequestReader
crw *captureResponseWriter
} }
// Handler will write each request and its response to the access log. // Handler will write each request and its response to the access log.
@ -122,7 +120,7 @@ func NewHandler(config *types.AccessLog) (*Handler, error) {
go func() { go func() {
defer logHandler.wg.Done() defer logHandler.wg.Done()
for handlerParams := range logHandler.logHandlerChan { for handlerParams := range logHandler.logHandlerChan {
logHandler.logTheRoundTrip(handlerParams.logDataTable, handlerParams.crr, handlerParams.crw) logHandler.logTheRoundTrip(handlerParams.logDataTable)
} }
}() }()
} }
@ -162,7 +160,12 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
StartLocal: now.Local(), StartLocal: now.Local(),
} }
logDataTable := &LogData{Core: core, Request: req.Header} logDataTable := &LogData{
Core: core,
Request: request{
headers: req.Header,
},
}
reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable)) reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable))
@ -205,16 +208,21 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL) core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL)
} }
logDataTable.DownstreamResponse = crw.Header() logDataTable.DownstreamResponse = downstreamResponse{
headers: crw.Header().Clone(),
status: crw.Status(),
size: crw.Size(),
}
if crr != nil {
logDataTable.Request.count = crr.count
}
if h.config.BufferingSize > 0 { if h.config.BufferingSize > 0 {
h.logHandlerChan <- handlerParams{ h.logHandlerChan <- handlerParams{
logDataTable: logDataTable, logDataTable: logDataTable,
crr: crr,
crw: crw,
} }
} else { } else {
h.logTheRoundTrip(logDataTable, crr, crw) h.logTheRoundTrip(logDataTable)
} }
} }
@ -264,7 +272,7 @@ func usernameIfPresent(theURL *url.URL) string {
} }
// Logging handler to log frontend name, backend name, and elapsed time. // Logging handler to log frontend name, backend name, and elapsed time.
func (h *Handler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestReader, crw *captureResponseWriter) { func (h *Handler) logTheRoundTrip(logDataTable *LogData) {
core := logDataTable.Core core := logDataTable.Core
retryAttempts, ok := core[RetryAttempts].(int) retryAttempts, ok := core[RetryAttempts].(int)
@ -272,23 +280,22 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestRead
retryAttempts = 0 retryAttempts = 0
} }
core[RetryAttempts] = retryAttempts core[RetryAttempts] = retryAttempts
core[RequestContentSize] = logDataTable.Request.count
if crr != nil { status := logDataTable.DownstreamResponse.status
core[RequestContentSize] = crr.count core[DownstreamStatus] = status
}
core[DownstreamStatus] = crw.Status()
// n.b. take care to perform time arithmetic using UTC to avoid errors at DST boundaries. // n.b. take care to perform time arithmetic using UTC to avoid errors at DST boundaries.
totalDuration := time.Now().UTC().Sub(core[StartUTC].(time.Time)) totalDuration := time.Now().UTC().Sub(core[StartUTC].(time.Time))
core[Duration] = totalDuration core[Duration] = totalDuration
if h.keepAccessLog(crw.Status(), retryAttempts, totalDuration) { if h.keepAccessLog(status, retryAttempts, totalDuration) {
core[DownstreamContentSize] = crw.Size() size := logDataTable.DownstreamResponse.size
core[DownstreamContentSize] = size
if original, ok := core[OriginContentSize]; ok { if original, ok := core[OriginContentSize]; ok {
o64 := original.(int64) o64 := original.(int64)
if crw.Size() != o64 && crw.Size() != 0 { if size != o64 && size != 0 {
core[GzipRatio] = float64(o64) / float64(crw.Size()) core[GzipRatio] = float64(o64) / float64(size)
} }
} }
@ -305,9 +312,9 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestRead
} }
} }
h.redactHeaders(logDataTable.Request, fields, "request_") h.redactHeaders(logDataTable.Request.headers, fields, "request_")
h.redactHeaders(logDataTable.OriginResponse, fields, "origin_") h.redactHeaders(logDataTable.OriginResponse, fields, "origin_")
h.redactHeaders(logDataTable.DownstreamResponse, fields, "downstream_") h.redactHeaders(logDataTable.DownstreamResponse.headers, fields, "downstream_")
h.mu.Lock() h.mu.Lock()
defer h.mu.Unlock() defer h.mu.Unlock()

View file

@ -192,6 +192,7 @@ func TestLoggerJSON(t *testing.T) {
Format: JSONFormat, Format: JSONFormat,
}, },
expected: map[string]func(t *testing.T, value interface{}){ expected: map[string]func(t *testing.T, value interface{}){
RequestContentSize: assertFloat64(0),
RequestHost: assertString(testHostname), RequestHost: assertString(testHostname),
RequestAddr: assertString(testHostname), RequestAddr: assertString(testHostname),
RequestMethod: assertString(testMethod), RequestMethod: assertString(testMethod),

View file

@ -221,13 +221,11 @@ func (s *Header) processCorsHeaders(rw http.ResponseWriter, req *http.Request) b
} }
reqAcMethod := req.Header.Get("Access-Control-Request-Method") reqAcMethod := req.Header.Get("Access-Control-Request-Method")
reqAcHeaders := req.Header.Get("Access-Control-Request-Headers")
originHeader := req.Header.Get("Origin") originHeader := req.Header.Get("Origin")
if reqAcMethod != "" && reqAcHeaders != "" && originHeader != "" && req.Method == http.MethodOptions { if reqAcMethod != "" && originHeader != "" && req.Method == http.MethodOptions {
// If the request is an OPTIONS request with an Access-Control-Request-Method header, // If the request is an OPTIONS request with an Access-Control-Request-Method header,
// and Access-Control-Request-Headers headers, and Origin headers, // and Origin headers, then it is a CORS preflight request,
// then it is a CORS preflight request,
// and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request // and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request
if s.headers.AccessControlAllowCredentials { if s.headers.AccessControlAllowCredentials {
rw.Header().Set("Access-Control-Allow-Credentials", "true") rw.Header().Set("Access-Control-Allow-Credentials", "true")

View file

@ -275,6 +275,25 @@ func TestCORSPreflights(t *testing.T) {
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"}, "Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
}, },
}, },
{
desc: "No Request Headers Preflight",
header: NewHeader(emptyHandler, dynamic.Headers{
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
AccessControlAllowOrigin: "*",
AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"},
AccessControlMaxAge: 600,
}),
requestHeaders: map[string][]string{
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"*"},
"Access-Control-Max-Age": {"600"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
},
},
} }
for _, test := range testCases { for _, test := range testCases {

View file

@ -34,7 +34,11 @@ type entryPointMiddleware struct {
} }
func (e *entryPointMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (e *entryPointMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
spanCtx, _ := e.Extract(opentracing.HTTPHeaders, tracing.HTTPHeadersCarrier(req.Header)) spanCtx, err := e.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
if err != nil {
log.FromContext(middlewares.GetLoggerCtx(req.Context(), "tracing", entryPointTypeName)).
Debugf("Failed to extract the context: %v", err)
}
span, req, finish := e.StartSpanf(req, ext.SpanKindRPCServerEnum, "EntryPoint", []string{e.entryPoint, req.Host}, " ", ext.RPCServerOption(spanCtx)) span, req, finish := e.StartSpanf(req, ext.SpanKindRPCServerEnum, "EntryPoint", []string{e.entryPoint, req.Host}, " ", ext.RPCServerOption(spanCtx))
defer finish() defer finish()

View file

@ -12,23 +12,23 @@ import (
// It is used in order to create a specific and unique pattern for these labels. // It is used in order to create a specific and unique pattern for these labels.
const MarathonConstraintPrefix = "Traefik-Marathon-505F9E15-BDC7-45E7-828D-C06C7BAB8091" const MarathonConstraintPrefix = "Traefik-Marathon-505F9E15-BDC7-45E7-828D-C06C7BAB8091"
type constraintFunc func(map[string]string) bool type constraintLabelFunc func(map[string]string) bool
// Match reports whether the expression matches with the given labels. // MatchLabels reports whether the expression matches with the given labels.
// The expression must match any logical boolean combination of: // The expression must match any logical boolean combination of:
// - `Label(labelName, labelValue)` // - `Label(labelName, labelValue)`
// - `LabelRegex(labelName, regexValue)` // - `LabelRegex(labelName, regexValue)`
// - `MarathonConstraint(field:operator:value)` // - `MarathonConstraint(field:operator:value)`
func Match(labels map[string]string, expr string) (bool, error) { func MatchLabels(labels map[string]string, expr string) (bool, error) {
if expr == "" { if expr == "" {
return true, nil return true, nil
} }
p, err := predicate.NewParser(predicate.Def{ p, err := predicate.NewParser(predicate.Def{
Operators: predicate.Operators{ Operators: predicate.Operators{
AND: andFunc, AND: andLabelFunc,
NOT: notFunc, NOT: notLabelFunc,
OR: orFunc, OR: orLabelFunc,
}, },
Functions: map[string]interface{}{ Functions: map[string]interface{}{
"Label": labelFn, "Label": labelFn,
@ -45,20 +45,20 @@ func Match(labels map[string]string, expr string) (bool, error) {
return false, err return false, err
} }
fn, ok := parse.(constraintFunc) fn, ok := parse.(constraintLabelFunc)
if !ok { if !ok {
return false, errors.New("not a constraintFunc") return false, errors.New("not a constraintLabelFunc")
} }
return fn(labels), nil return fn(labels), nil
} }
func labelFn(name, value string) constraintFunc { func labelFn(name, value string) constraintLabelFunc {
return func(labels map[string]string) bool { return func(labels map[string]string) bool {
return labels[name] == value return labels[name] == value
} }
} }
func labelRegexFn(name, expr string) constraintFunc { func labelRegexFn(name, expr string) constraintLabelFunc {
return func(labels map[string]string) bool { return func(labels map[string]string) bool {
matched, err := regexp.MatchString(expr, labels[name]) matched, err := regexp.MatchString(expr, labels[name])
if err != nil { if err != nil {
@ -68,7 +68,7 @@ func labelRegexFn(name, expr string) constraintFunc {
} }
} }
func marathonFn(value string) constraintFunc { func marathonFn(value string) constraintLabelFunc {
return func(labels map[string]string) bool { return func(labels map[string]string) bool {
for k, v := range labels { for k, v := range labels {
if strings.HasPrefix(k, MarathonConstraintPrefix) { if strings.HasPrefix(k, MarathonConstraintPrefix) {
@ -81,19 +81,19 @@ func marathonFn(value string) constraintFunc {
} }
} }
func andFunc(a, b constraintFunc) constraintFunc { func andLabelFunc(a, b constraintLabelFunc) constraintLabelFunc {
return func(labels map[string]string) bool { return func(labels map[string]string) bool {
return a(labels) && b(labels) return a(labels) && b(labels)
} }
} }
func orFunc(a, b constraintFunc) constraintFunc { func orLabelFunc(a, b constraintLabelFunc) constraintLabelFunc {
return func(labels map[string]string) bool { return func(labels map[string]string) bool {
return a(labels) || b(labels) return a(labels) || b(labels)
} }
} }
func notFunc(a constraintFunc) constraintFunc { func notLabelFunc(a constraintLabelFunc) constraintLabelFunc {
return func(labels map[string]string) bool { return func(labels map[string]string) bool {
return !a(labels) return !a(labels)
} }

View file

@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestMatch(t *testing.T) { func TestMatchLabels(t *testing.T) {
testCases := []struct { testCases := []struct {
expr string expr string
labels map[string]string labels map[string]string
@ -192,7 +192,7 @@ func TestMatch(t *testing.T) {
t.Run(test.expr, func(t *testing.T) { t.Run(test.expr, func(t *testing.T) {
t.Parallel() t.Parallel()
matches, err := Match(test.labels, test.expr) matches, err := MatchLabels(test.labels, test.expr)
if test.expectedErr { if test.expectedErr {
require.Error(t, err) require.Error(t, err)
} else { } else {

View file

@ -0,0 +1,92 @@
package constraints
import (
"errors"
"regexp"
"github.com/vulcand/predicate"
)
type constraintTagFunc func([]string) bool
// MatchTags reports whether the expression matches with the given tags.
// The expression must match any logical boolean combination of:
// - `Tag(tagValue)`
// - `TagRegex(regexValue)`
func MatchTags(tags []string, expr string) (bool, error) {
if expr == "" {
return true, nil
}
p, err := predicate.NewParser(predicate.Def{
Operators: predicate.Operators{
AND: andTagFunc,
NOT: notTagFunc,
OR: orTagFunc,
},
Functions: map[string]interface{}{
"Tag": tagFn,
"TagRegex": tagRegexFn,
},
})
if err != nil {
return false, err
}
parse, err := p.Parse(expr)
if err != nil {
return false, err
}
fn, ok := parse.(constraintTagFunc)
if !ok {
return false, errors.New("not a constraintTagFunc")
}
return fn(tags), nil
}
func tagFn(name string) constraintTagFunc {
return func(tags []string) bool {
for _, tag := range tags {
if tag == name {
return true
}
}
return false
}
}
func tagRegexFn(expr string) constraintTagFunc {
return func(tags []string) bool {
exp, err := regexp.Compile(expr)
if err != nil {
return false
}
for _, tag := range tags {
if exp.MatchString(tag) {
return true
}
}
return false
}
}
func andTagFunc(a, b constraintTagFunc) constraintTagFunc {
return func(tags []string) bool {
return a(tags) && b(tags)
}
}
func orTagFunc(a, b constraintTagFunc) constraintTagFunc {
return func(tags []string) bool {
return a(tags) || b(tags)
}
}
func notTagFunc(a constraintTagFunc) constraintTagFunc {
return func(tags []string) bool {
return !a(tags)
}
}

View file

@ -0,0 +1,111 @@
package constraints
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMatchTags(t *testing.T) {
testCases := []struct {
expr string
tags []string
expected bool
expectedErr bool
}{
{
expr: `Tag("world")`,
tags: []string{"hello", "world"},
expected: true,
},
{
expr: `Tag("worlds")`,
tags: []string{"hello", "world"},
expected: false,
},
{
expr: `!Tag("world")`,
tags: []string{"hello", "world"},
expected: false,
},
{
expr: `Tag("hello") && Tag("world")`,
tags: []string{"hello", "world"},
expected: true,
},
{
expr: `Tag("hello") && Tag("worlds")`,
tags: []string{"hello", "world"},
expected: false,
},
{
expr: `Tag("hello") && !Tag("world")`,
tags: []string{"hello", "world"},
expected: false,
},
{
expr: `Tag("hello") || Tag( "world")`,
tags: []string{"hello", "world"},
expected: true,
},
{
expr: `Tag( "worlds") || Tag("hello")`,
tags: []string{"hello", "world"},
expected: true,
},
{
expr: `Tag("hello") || !Tag("world")`,
tags: []string{"hello", "world"},
expected: true,
},
{
expr: `Tag()`,
tags: []string{"hello", "world"},
expectedErr: true,
},
{
expr: `Foo("hello")`,
tags: []string{"hello", "world"},
expectedErr: true,
},
{
expr: `Tag("hello")`,
expected: false,
},
{
expr: ``,
expected: true,
},
{
expr: `TagRegex("hel\\w+")`,
tags: []string{"hello", "world"},
expected: true,
},
{
expr: `TagRegex("hell\\w+s")`,
tags: []string{"hello", "world"},
expected: false,
},
{
expr: `!TagRegex("hel\\w+")`,
tags: []string{"hello", "world"},
expected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.expr, func(t *testing.T) {
t.Parallel()
matches, err := MatchTags(test.tags, test.expr)
if test.expectedErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, test.expected, matches)
})
}
}

View file

@ -18,7 +18,7 @@ func (p *Provider) buildConfiguration(ctx context.Context, items []itemData) *dy
configurations := make(map[string]*dynamic.Configuration) configurations := make(map[string]*dynamic.Configuration)
for _, item := range items { for _, item := range items {
svcName := item.Name + "-" + item.ID svcName := item.Node + "-" + item.Name + "-" + item.ID
ctxSvc := log.With(ctx, log.Str("serviceName", svcName)) ctxSvc := log.With(ctx, log.Str("serviceName", svcName))
if !p.keepContainer(ctxSvc, item) { if !p.keepContainer(ctxSvc, item) {
@ -80,7 +80,7 @@ func (p *Provider) keepContainer(ctx context.Context, item itemData) bool {
return false return false
} }
matches, err := constraints.Match(item.Labels, p.Constraints) matches, err := constraints.MatchTags(item.Tags, p.Constraints)
if err != nil { if err != nil {
logger.Errorf("Error matching constraints expression: %v", err) logger.Errorf("Error matching constraints expression: %v", err)
return false return false

View file

@ -2,6 +2,7 @@ package consulcatalog
import ( import (
"context" "context"
"fmt"
"testing" "testing"
"github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/config/dynamic"
@ -25,6 +26,7 @@ func TestDefaultRule(t *testing.T) {
items: []itemData{ items: []itemData{
{ {
ID: "id", ID: "id",
Node: "Node1",
Name: "Test", Name: "Test",
Address: "127.0.0.1", Address: "127.0.0.1",
Port: "80", Port: "80",
@ -66,6 +68,7 @@ func TestDefaultRule(t *testing.T) {
items: []itemData{ items: []itemData{
{ {
ID: "id", ID: "id",
Node: "Node1",
Name: "Test", Name: "Test",
Address: "127.0.0.1", Address: "127.0.0.1",
Port: "80", Port: "80",
@ -109,6 +112,7 @@ func TestDefaultRule(t *testing.T) {
items: []itemData{ items: []itemData{
{ {
ID: "Test", ID: "Test",
Node: "Node1",
Name: "Test", Name: "Test",
Labels: map[string]string{}, Labels: map[string]string{},
Address: "127.0.0.1", Address: "127.0.0.1",
@ -145,6 +149,7 @@ func TestDefaultRule(t *testing.T) {
items: []itemData{ items: []itemData{
{ {
ID: "Test", ID: "Test",
Node: "Node1",
Name: "Test", Name: "Test",
Labels: map[string]string{}, Labels: map[string]string{},
Address: "127.0.0.1", Address: "127.0.0.1",
@ -181,6 +186,7 @@ func TestDefaultRule(t *testing.T) {
items: []itemData{ items: []itemData{
{ {
ID: "Test", ID: "Test",
Node: "Node1",
Name: "Test", Name: "Test",
Labels: map[string]string{}, Labels: map[string]string{},
Address: "127.0.0.1", Address: "127.0.0.1",
@ -257,6 +263,7 @@ func Test_buildConfiguration(t *testing.T) {
items: []itemData{ items: []itemData{
{ {
ID: "Test", ID: "Test",
Node: "Node1",
Name: "Test", Name: "Test",
Labels: map[string]string{}, Labels: map[string]string{},
Address: "127.0.0.1", Address: "127.0.0.1",
@ -297,6 +304,7 @@ func Test_buildConfiguration(t *testing.T) {
items: []itemData{ items: []itemData{
{ {
ID: "Test", ID: "Test",
Node: "Node1",
Name: "Test", Name: "Test",
Labels: map[string]string{}, Labels: map[string]string{},
Address: "127.0.0.1", Address: "127.0.0.1",
@ -305,6 +313,7 @@ func Test_buildConfiguration(t *testing.T) {
}, },
{ {
ID: "Test2", ID: "Test2",
Node: "Node1",
Name: "Test2", Name: "Test2",
Labels: map[string]string{}, Labels: map[string]string{},
Address: "127.0.0.2", Address: "127.0.0.2",
@ -359,6 +368,7 @@ func Test_buildConfiguration(t *testing.T) {
items: []itemData{ items: []itemData{
{ {
ID: "1", ID: "1",
Node: "Node1",
Name: "Test", Name: "Test",
Labels: map[string]string{}, Labels: map[string]string{},
Address: "127.0.0.1", Address: "127.0.0.1",
@ -367,6 +377,110 @@ func Test_buildConfiguration(t *testing.T) {
}, },
{ {
ID: "2", ID: "2",
Node: "Node1",
Name: "Test",
Labels: map[string]string{},
Address: "127.0.0.2",
Port: "80",
Status: api.HealthPassing,
},
},
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
Service: "Test",
Rule: "Host(`Test.traefik.wtf`)",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"Test": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1:80",
},
{
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
},
},
{
desc: "two containers with same service name & id no label on same node",
items: []itemData{
{
ID: "1",
Node: "Node1",
Name: "Test",
Labels: map[string]string{},
Address: "127.0.0.1",
Port: "80",
Status: api.HealthPassing,
},
{
ID: "1",
Node: "Node1",
Name: "Test",
Labels: map[string]string{},
Address: "127.0.0.2",
Port: "80",
Status: api.HealthPassing,
},
},
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
Service: "Test",
Rule: "Host(`Test.traefik.wtf`)",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"Test": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
},
},
{
desc: "two containers with same service name & id no label on different nodes",
items: []itemData{
{
ID: "1",
Node: "Node1",
Name: "Test",
Labels: map[string]string{},
Address: "127.0.0.1",
Port: "80",
Status: api.HealthPassing,
},
{
ID: "1",
Node: "Node2",
Name: "Test", Name: "Test",
Labels: map[string]string{}, Labels: map[string]string{},
Address: "127.0.0.2", Address: "127.0.0.2",
@ -1320,6 +1434,7 @@ func Test_buildConfiguration(t *testing.T) {
items: []itemData{ items: []itemData{
{ {
ID: "Test", ID: "Test",
Node: "Node1",
Name: "Test", Name: "Test",
Labels: map[string]string{}, Labels: map[string]string{},
Address: "127.0.0.2", Address: "127.0.0.2",
@ -1393,6 +1508,7 @@ func Test_buildConfiguration(t *testing.T) {
items: []itemData{ items: []itemData{
{ {
ID: "Test", ID: "Test",
Node: "Node1",
Name: "Test", Name: "Test",
Labels: map[string]string{}, Labels: map[string]string{},
Address: "127.0.0.1", Address: "127.0.0.1",
@ -1426,7 +1542,7 @@ func Test_buildConfiguration(t *testing.T) {
Status: api.HealthPassing, Status: api.HealthPassing,
}, },
}, },
constraints: `Label("traefik.tags", "bar")`, constraints: `Tag("traefik.tags=bar")`,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{ TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{}, Routers: map[string]*dynamic.TCPRouter{},
@ -1453,7 +1569,7 @@ func Test_buildConfiguration(t *testing.T) {
Status: api.HealthPassing, Status: api.HealthPassing,
}, },
}, },
constraints: `Label("traefik.tags", "foo")`, constraints: `Tag("traefik.tags=foo")`,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{ TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{}, Routers: map[string]*dynamic.TCPRouter{},
@ -1840,6 +1956,12 @@ func Test_buildConfiguration(t *testing.T) {
var err error var err error
test.items[i].ExtraConf, err = p.getConfiguration(test.items[i]) test.items[i].ExtraConf, err = p.getConfiguration(test.items[i])
require.NoError(t, err) require.NoError(t, err)
var tags []string
for k, v := range test.items[i].Labels {
tags = append(tags, fmt.Sprintf("%s=%s", k, v))
}
test.items[i].Tags = tags
} }
configuration := p.buildConfiguration(context.Background(), test.items) configuration := p.buildConfiguration(context.Background(), test.items)

View file

@ -24,11 +24,13 @@ var _ provider.Provider = (*Provider)(nil)
type itemData struct { type itemData struct {
ID string ID string
Node string
Name string Name string
Address string Address string
Port string Port string
Status string Status string
Labels map[string]string Labels map[string]string
Tags []string
ExtraConf configuration ExtraConf configuration
} }
@ -156,7 +158,6 @@ func (p *Provider) getConsulServicesData(ctx context.Context) ([]itemData, error
} }
for _, consulService := range consulServices { for _, consulService := range consulServices {
labels := tagsToNeutralLabels(consulService.ServiceTags, p.Prefix)
address := consulService.ServiceAddress address := consulService.ServiceAddress
if address == "" { if address == "" {
address = consulService.Address address = consulService.Address
@ -164,10 +165,12 @@ func (p *Provider) getConsulServicesData(ctx context.Context) ([]itemData, error
item := itemData{ item := itemData{
ID: consulService.ServiceID, ID: consulService.ServiceID,
Node: consulService.Node,
Name: consulService.ServiceName, Name: consulService.ServiceName,
Address: address, Address: address,
Port: strconv.Itoa(consulService.ServicePort), Port: strconv.Itoa(consulService.ServicePort),
Labels: labels, Labels: tagsToNeutralLabels(consulService.ServiceTags, p.Prefix),
Tags: consulService.ServiceTags,
Status: consulService.Checks.AggregatedStatus(), Status: consulService.Checks.AggregatedStatus(),
} }

View file

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestTagsToNeutralLabels(t *testing.T) { func Test_tagsToNeutralLabels(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
tags []string tags []string

View file

@ -127,7 +127,7 @@ func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool
return false return false
} }
matches, err := constraints.Match(container.Labels, p.Constraints) matches, err := constraints.MatchLabels(container.Labels, p.Constraints)
if err != nil { if err != nil {
logger.Errorf("Error matching constraints expression: %v", err) logger.Errorf("Error matching constraints expression: %v", err)
return false return false

View file

@ -185,7 +185,7 @@ func (p *Provider) keepApplication(ctx context.Context, extraConf configuration,
} }
// Filter by constraints. // Filter by constraints.
matches, err := constraints.Match(labels, p.Constraints) matches, err := constraints.MatchLabels(labels, p.Constraints)
if err != nil { if err != nil {
logger.Errorf("Error matching constraints expression: %v", err) logger.Errorf("Error matching constraints expression: %v", err)
return false return false

View file

@ -121,7 +121,7 @@ func (p *Provider) keepService(ctx context.Context, service rancherData) bool {
return false return false
} }
matches, err := constraints.Match(service.Labels, p.Constraints) matches, err := constraints.MatchLabels(service.Labels, p.Constraints)
if err != nil { if err != nil {
logger.Errorf("Error matching constraints expression: %v", err) logger.Errorf("Error matching constraints expression: %v", err)
return false return false

View file

@ -193,7 +193,7 @@ func (p *Provider) parseMetadataSourcedRancherData(ctx context.Context, stacks [
} }
service := rancherData{ service := rancherData{
Name: service.Name + "/" + stack.Name, Name: service.Name + "_" + stack.Name,
State: service.State, State: service.State,
Labels: service.Labels, Labels: service.Labels,
Port: servicePort, Port: servicePort,

View file

@ -40,7 +40,7 @@ func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper htt
metricsRegistry: metricsRegistry, metricsRegistry: metricsRegistry,
bufferPool: newBufferPool(), bufferPool: newBufferPool(),
defaultRoundTripper: defaultRoundTripper, defaultRoundTripper: defaultRoundTripper,
balancers: make(map[string][]healthcheck.BalancerHandler), balancers: make(map[string]healthcheck.Balancers),
configs: configs, configs: configs,
} }
} }
@ -51,8 +51,12 @@ type Manager struct {
metricsRegistry metrics.Registry metricsRegistry metrics.Registry
bufferPool httputil.BufferPool bufferPool httputil.BufferPool
defaultRoundTripper http.RoundTripper defaultRoundTripper http.RoundTripper
balancers map[string][]healthcheck.BalancerHandler // balancers is the map of all Balancers, keyed by service name.
configs map[string]*runtime.ServiceInfo // There is one Balancer per service handler, and there is one service handler per reference to a service
// (e.g. if 2 routers refer to the same service name, 2 service handlers are created),
// which is why there is not just one Balancer per service name.
balancers map[string]healthcheck.Balancers
configs map[string]*runtime.ServiceInfo
} }
// BuildHTTP Creates a http.Handler for a service configuration. // BuildHTTP Creates a http.Handler for a service configuration.
@ -92,14 +96,14 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons
} }
case conf.Weighted != nil: case conf.Weighted != nil:
var err error var err error
lb, err = m.getLoadBalancerWRRServiceHandler(ctx, serviceName, conf.Weighted, responseModifier) lb, err = m.getWRRServiceHandler(ctx, serviceName, conf.Weighted, responseModifier)
if err != nil { if err != nil {
conf.AddError(err, true) conf.AddError(err, true)
return nil, err return nil, err
} }
case conf.Mirroring != nil: case conf.Mirroring != nil:
var err error var err error
lb, err = m.getLoadBalancerMirrorServiceHandler(ctx, serviceName, conf.Mirroring, responseModifier) lb, err = m.getMirrorServiceHandler(ctx, conf.Mirroring, responseModifier)
if err != nil { if err != nil {
conf.AddError(err, true) conf.AddError(err, true)
return nil, err return nil, err
@ -113,7 +117,7 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons
return lb, nil return lb, nil
} }
func (m *Manager) getLoadBalancerMirrorServiceHandler(ctx context.Context, serviceName string, config *dynamic.Mirroring, responseModifier func(*http.Response) error) (http.Handler, error) { func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.Mirroring, responseModifier func(*http.Response) error) (http.Handler, error) {
serviceHandler, err := m.BuildHTTP(ctx, config.Service, responseModifier) serviceHandler, err := m.BuildHTTP(ctx, config.Service, responseModifier)
if err != nil { if err != nil {
return nil, err return nil, err
@ -134,7 +138,7 @@ func (m *Manager) getLoadBalancerMirrorServiceHandler(ctx context.Context, servi
return handler, nil return handler, nil
} }
func (m *Manager) getLoadBalancerWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin, responseModifier func(*http.Response) error) (http.Handler, error) { func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin, responseModifier func(*http.Response) error) (http.Handler, error) {
// TODO Handle accesslog and metrics with multiple service name // TODO Handle accesslog and metrics with multiple service name
if config.Sticky != nil && config.Sticky.Cookie != nil { if config.Sticky != nil && config.Sticky.Cookie != nil {
config.Sticky.Cookie.Name = cookie.GetName(config.Sticky.Cookie.Name, serviceName) config.Sticky.Cookie.Name = cookie.GetName(config.Sticky.Cookie.Name, serviceName)
@ -200,15 +204,12 @@ func (m *Manager) LaunchHealthCheck() {
for serviceName, balancers := range m.balancers { for serviceName, balancers := range m.balancers {
ctx := log.With(context.Background(), log.Str(log.ServiceName, serviceName)) ctx := log.With(context.Background(), log.Str(log.ServiceName, serviceName))
// TODO aggregate
balancer := balancers[0]
// TODO Should all the services handle healthcheck? Handle different types // TODO Should all the services handle healthcheck? Handle different types
service := m.configs[serviceName].LoadBalancer service := m.configs[serviceName].LoadBalancer
// Health Check // Health Check
var backendHealthCheck *healthcheck.BackendConfig var backendHealthCheck *healthcheck.BackendConfig
if hcOpts := buildHealthCheckOptions(ctx, balancer, serviceName, service.HealthCheck); hcOpts != nil { if hcOpts := buildHealthCheckOptions(ctx, balancers, serviceName, service.HealthCheck); hcOpts != nil {
log.FromContext(ctx).Debugf("Setting up healthcheck for service %s with %s", serviceName, *hcOpts) log.FromContext(ctx).Debugf("Setting up healthcheck for service %s with %s", serviceName, *hcOpts)
hcOpts.Transport = m.defaultRoundTripper hcOpts.Transport = m.defaultRoundTripper
@ -224,7 +225,7 @@ func (m *Manager) LaunchHealthCheck() {
healthcheck.GetHealthCheck().SetBackendsConfiguration(context.Background(), backendConfigs) healthcheck.GetHealthCheck().SetBackendsConfiguration(context.Background(), backendConfigs)
} }
func buildHealthCheckOptions(ctx context.Context, lb healthcheck.BalancerHandler, backend string, hc *dynamic.HealthCheck) *healthcheck.Options { func buildHealthCheckOptions(ctx context.Context, lb healthcheck.Balancer, backend string, hc *dynamic.HealthCheck) *healthcheck.Options {
if hc == nil || hc.Path == "" { if hc == nil || hc.Path == "" {
return nil return nil
} }

View file

@ -1,25 +0,0 @@
package tracing
import "net/http"
// HTTPHeadersCarrier custom implementation to fix duplicated headers
// It has been fixed in https://github.com/opentracing/opentracing-go/pull/191
type HTTPHeadersCarrier http.Header
// Set conforms to the TextMapWriter interface.
func (c HTTPHeadersCarrier) Set(key, val string) {
h := http.Header(c)
h.Set(key, val)
}
// ForeachKey conforms to the TextMapReader interface.
func (c HTTPHeadersCarrier) ForeachKey(handler func(key, val string) error) error {
for k, vals := range c {
for _, v := range vals {
if err := handler(k, v); err != nil {
return err
}
}
}
return nil
}

View file

@ -134,7 +134,7 @@ func InjectRequestHeaders(r *http.Request) {
err := opentracing.GlobalTracer().Inject( err := opentracing.GlobalTracer().Inject(
span.Context(), span.Context(),
opentracing.HTTPHeaders, opentracing.HTTPHeaders,
HTTPHeadersCarrier(r.Header)) opentracing.HTTPHeadersCarrier(r.Header))
if err != nil { if err != nil {
log.FromContext(r.Context()).Error(err) log.FromContext(r.Context()).Error(err)
} }

View file

@ -3,15 +3,12 @@ import { APP } from '../_helpers/APP'
const apiBase = '/http' const apiBase = '/http'
function getAllRouters (params) { function getAllRouters (params) {
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}`) return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
.then(body => { .then(body => {
const total = body.data ? body.data.length : 0 const total = body.data ? body.data.length : 0
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) console.log('Success -> HttpService -> getAllRouters', body.data)
.then(body => { // TODO - suggestion: add the total-pages in api response to optimize the query
console.log('Success -> HttpService -> getAllRouters', body.data) return { data: body.data || [], total }
// TODO - suggestion: add the total-pages in api response to optimize the query
return { data: body.data || [], total }
})
}) })
} }
@ -24,15 +21,12 @@ function getRouterByName (name) {
} }
function getAllServices (params) { function getAllServices (params) {
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}`) return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
.then(body => { .then(body => {
const total = body.data ? body.data.length : 0 const total = body.data ? body.data.length : 0
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) console.log('Success -> HttpService -> getAllServices', body.data)
.then(body => { // TODO - suggestion: add the total-pages in api response to optimize the query
console.log('Success -> HttpService -> getAllServices', body.data) return { data: body.data || [], total }
// TODO - suggestion: add the total-pages in api response to optimize the query
return { data: body.data || [], total }
})
}) })
} }
@ -45,15 +39,12 @@ function getServiceByName (name) {
} }
function getAllMiddlewares (params) { function getAllMiddlewares (params) {
return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}`) return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
.then(body => { .then(body => {
const total = body.data ? body.data.length : 0 const total = body.data ? body.data.length : 0
return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) console.log('Success -> HttpService -> getAllMiddlewares', body.data)
.then(body => { // TODO - suggestion: add the total-pages in api response to optimize the query
console.log('Success -> HttpService -> getAllMiddlewares', body.data) return { data: body.data || [], total }
// TODO - suggestion: add the total-pages in api response to optimize the query
return { data: body.data || [], total }
})
}) })
} }

View file

@ -3,15 +3,12 @@ import { APP } from '../_helpers/APP'
const apiBase = '/tcp' const apiBase = '/tcp'
function getAllRouters (params) { function getAllRouters (params) {
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}`) return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
.then(body => { .then(body => {
const total = body.data ? body.data.length : 0 const total = body.data ? body.data.length : 0
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) console.log('Success -> HttpService -> getAllRouters', body.data)
.then(body => { // TODO - suggestion: add the total-pages in api response to optimize the query
console.log('Success -> HttpService -> getAllRouters', body.data) return { data: body.data || [], total }
// TODO - suggestion: add the total-pages in api response to optimize the query
return { data: body.data || [], total }
})
}) })
} }
@ -24,15 +21,12 @@ function getRouterByName (name) {
} }
function getAllServices (params) { function getAllServices (params) {
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}`) return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
.then(body => { .then(body => {
const total = body.data ? body.data.length : 0 const total = body.data ? body.data.length : 0
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`) console.log('Success -> HttpService -> getAllServices', body.data)
.then(body => { // TODO - suggestion: add the total-pages in api response to optimize the query
console.log('Success -> HttpService -> getAllServices', body.data) return { data: body.data || [], total }
// TODO - suggestion: add the total-pages in api response to optimize the query
return { data: body.data || [], total }
})
}) })
} }

View file

@ -27,7 +27,7 @@
<div class="text-subtitle2">RULE</div> <div class="text-subtitle2">RULE</div>
<q-chip <q-chip
dense dense
class="app-chip app-chip-rule"> class="app-chip app-chip-wrap app-chip-rule">
{{ data.rule }} {{ data.rule }}
</q-chip> </q-chip>
</div> </div>
@ -39,7 +39,7 @@
<div class="text-subtitle2">NAME</div> <div class="text-subtitle2">NAME</div>
<q-chip <q-chip
dense dense
class="app-chip app-chip-name"> class="app-chip app-chip-wrap app-chip-name">
{{ data.name }} {{ data.name }}
</q-chip> </q-chip>
</div> </div>
@ -66,7 +66,7 @@
dense dense
clickable clickable
@click.native="$router.push({ path: `/${protocol}/services/${getServiceId()}`})" @click.native="$router.push({ path: `/${protocol}/services/${getServiceId()}`})"
class="app-chip app-chip-service"> class="app-chip app-chip-wrap app-chip-service">
{{ data.service }} {{ data.service }}
</q-chip> </q-chip>
</div> </div>

View file

@ -45,7 +45,7 @@
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
<q-card-section v-if="data.loadBalancer"> <q-card-section v-if="data.loadBalancer && $route.meta.protocol !== 'tcp'">
<div class="row items-start no-wrap"> <div class="row items-start no-wrap">
<div class="col"> <div class="col">
<div class="text-subtitle2">Pass Host Header</div> <div class="text-subtitle2">Pass Host Header</div>
@ -54,6 +54,19 @@
</div> </div>
</q-card-section> </q-card-section>
<q-card-section v-if="data.loadBalancer.terminationDelay">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">Termination Delay</div>
<q-chip
dense
class="app-chip app-chip-name">
{{ data.loadBalancer.terminationDelay }} ms
</q-chip>
</div>
</div>
</q-card-section>
<q-separator v-if="sticky" /> <q-separator v-if="sticky" />
<StickyServiceDetails v-if="sticky" :sticky="sticky" :dense="dense"/> <StickyServiceDetails v-if="sticky" :sticky="sticky" :dense="dense"/>
</q-scroll-area> </q-scroll-area>

View file

@ -26,6 +26,8 @@
</template> </template>
<script> <script>
import Helps from '../../_helpers/Helps'
export default { export default {
name: 'ToolBarTable', name: 'ToolBarTable',
props: ['status', 'filter'], props: ['status', 'filter'],
@ -36,6 +38,9 @@ export default {
return { return {
} }
}, },
mounted () {
this.routeToState(this.$route)
},
computed: { computed: {
getStatus: { getStatus: {
get () { get () {
@ -43,6 +48,7 @@ export default {
}, },
set (newValue) { set (newValue) {
this.$emit('update:status', newValue) this.$emit('update:status', newValue)
this.stateToRoute(this.$route, { status: newValue })
} }
}, },
getFilter: { getFilter: {
@ -51,11 +57,30 @@ export default {
}, },
set (newValue) { set (newValue) {
this.$emit('update:filter', newValue) this.$emit('update:filter', newValue)
this.stateToRoute(this.$route, { filter: newValue })
} }
} }
}, },
watch: {
$route (to, from) {
this.routeToState(to)
}
},
methods: { methods: {
routeToState (route) {
for (const query in route.query) {
this.$emit(`update:${query}`, route.query[query])
}
},
stateToRoute (route, values) {
this.$router.push({
path: route.path,
query: Helps.removeEmptyObjects({
...route.query,
...values
})
})
}
}, },
created () { created () {

View file

@ -144,8 +144,8 @@ export default {
loadingOverview: true, loadingOverview: true,
timeOutEntryGetAll: null, timeOutEntryGetAll: null,
timeOutOverviewAll: null, timeOutOverviewAll: null,
intervalRefreshAll: null, intervalRefresh: null,
intervalRefreshAllTime: 5000 intervalRefreshTime: 5000
} }
}, },
computed: { computed: {
@ -167,10 +167,7 @@ export default {
methods: { methods: {
...mapActions('entrypoints', { entryGetAll: 'getAll' }), ...mapActions('entrypoints', { entryGetAll: 'getAll' }),
...mapActions('core', { getOverview: 'getOverview' }), ...mapActions('core', { getOverview: 'getOverview' }),
refreshAll () { fetchEntries () {
this.onGetAll()
},
onGetAll () {
this.entryGetAll() this.entryGetAll()
.then(body => { .then(body => {
console.log('Success -> dashboard/entrypoints', body) console.log('Success -> dashboard/entrypoints', body)
@ -182,6 +179,8 @@ export default {
.catch(error => { .catch(error => {
console.log('Error -> dashboard/entrypoints', error) console.log('Error -> dashboard/entrypoints', error)
}) })
},
fetchOverview () {
this.getOverview() this.getOverview()
.then(body => { .then(body => {
console.log('Success -> dashboard/overview', body) console.log('Success -> dashboard/overview', body)
@ -193,16 +192,18 @@ export default {
.catch(error => { .catch(error => {
console.log('Error -> dashboard/overview', error) console.log('Error -> dashboard/overview', error)
}) })
},
fetchAll () {
this.fetchEntries()
this.fetchOverview()
} }
}, },
created () { created () {
this.refreshAll() this.fetchAll()
this.intervalRefreshAll = setInterval(() => { this.intervalRefresh = setInterval(this.fetchOverview, this.intervalRefreshTime)
this.refreshAll()
}, this.intervalRefreshAllTime)
}, },
beforeDestroy () { beforeDestroy () {
clearInterval(this.intervalRefreshAll) clearInterval(this.intervalRefresh)
clearTimeout(this.timeOutEntryGetAll) clearTimeout(this.timeOutEntryGetAll)
clearTimeout(this.timeOutOverviewAll) clearTimeout(this.timeOutOverviewAll)
this.$store.commit('entrypoints/getAllClear') this.$store.commit('entrypoints/getAllClear')