mirror of
https://github.com/containous/traefik.git
synced 2025-09-24 21:44:26 +03:00
Compare commits
17 Commits
v2.0.0-alp
...
v2.0.0-alp
Author | SHA1 | Date | |
---|---|---|---|
|
fdb1701d1b | ||
|
80b35575df | ||
|
69cf05df9a | ||
|
69a1817c3f | ||
|
a918dcd5a4 | ||
|
adc9a65ae3 | ||
|
1e779f7135 | ||
|
fe68e9e243 | ||
|
e9792b446f | ||
|
4012599264 | ||
|
429b1d8574 | ||
|
a34876d700 | ||
|
68ecf78f0e | ||
|
38344b342d | ||
|
346ff96de2 | ||
|
31614bebc4 | ||
|
be888b59a6 |
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,5 +1,35 @@
|
||||
# Change Log
|
||||
|
||||
## [v2.0.0-alpha7](https://github.com/containous/traefik/tree/v2.0.0-alpha7) (2019-06-21)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.0.0-alpha6...v2.0.0-alpha7)
|
||||
|
||||
**Enhancements:**
|
||||
- **[api]** API: new contract ([#4964](https://github.com/containous/traefik/pull/4964) by [mpl](https://github.com/mpl))
|
||||
- **[k8s,k8s/crd,tls]** Define TLS options on the Router configuration for Kubernetes ([#4973](https://github.com/containous/traefik/pull/4973) by [jbdoumenjou](https://github.com/jbdoumenjou))
|
||||
- **[middleware,provider]** Change the provider separator from . to @ ([#4982](https://github.com/containous/traefik/pull/4982) by [ldez](https://github.com/ldez))
|
||||
- **[provider]** Use name@provider instead of provider@name. ([#4990](https://github.com/containous/traefik/pull/4990) by [ldez](https://github.com/ldez))
|
||||
- **[provider]** New constraints management. ([#4965](https://github.com/containous/traefik/pull/4965) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Bug fixes:**
|
||||
- **[cli]** Fix some CLI bugs ([#4989](https://github.com/containous/traefik/pull/4989) by [ldez](https://github.com/ldez))
|
||||
- **[cli]** Filter env vars configuration ([#4985](https://github.com/containous/traefik/pull/4985) by [ldez](https://github.com/ldez))
|
||||
- **[cli]** Return an error when help is called on a non existing command. ([#4977](https://github.com/containous/traefik/pull/4977) by [ldez](https://github.com/ldez))
|
||||
- **[tls]** Fix panic in TLS stores handling ([#4997](https://github.com/containous/traefik/pull/4997) by [juliens](https://github.com/juliens))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme,tls]** docs: rewrite of the HTTPS and TLS section ([#4980](https://github.com/containous/traefik/pull/4980) by [mpl](https://github.com/mpl))
|
||||
- Improve various parts of the documentation. ([#4996](https://github.com/containous/traefik/pull/4996) by [ldez](https://github.com/ldez))
|
||||
|
||||
## [v2.0.0-alpha6](https://github.com/containous/traefik/tree/v2.0.0-alpha6) (2019-06-18)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.0.0-alpha5...v2.0.0-alpha6)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[cli]** Don't allow non flag arguments by default. ([#4970](https://github.com/containous/traefik/pull/4970) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[middleware,k8s/crd]** doc: fix middleware names for CRD. ([#4966](https://github.com/containous/traefik/pull/4966) by [ldez](https://github.com/ldez))
|
||||
- **[middleware]** Kubernetes CRD documentation fixes ([#4971](https://github.com/containous/traefik/pull/4971) by [orhanhenrik](https://github.com/orhanhenrik))
|
||||
|
||||
## [v2.0.0-alpha5](https://github.com/containous/traefik/tree/v2.0.0-alpha5) (2019-06-17)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.0.0-alpha4...v2.0.0-alpha5)
|
||||
|
||||
|
15
Gopkg.lock
generated
15
Gopkg.lock
generated
@@ -1415,14 +1415,6 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "1f30fe9094a513ce4c700b9a54458bbb0c96996c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:09d61699d553a4e6ec998ad29816177b1f3d3ed0c18fe923d2c174ec065c99c8"
|
||||
name = "github.com/ryanuber/go-glob"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "256dc444b735e061061cf46c809487313d5b0065"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:253f275bd72c42f8d234712d1574c8b222fe9b72838bfaca11b21ace9c0e3d0a"
|
||||
name = "github.com/sacloud/libsacloud"
|
||||
@@ -1598,12 +1590,12 @@
|
||||
revision = "3d629cff40b7040e0519628e7774ed11a95d9aff"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ca6bac407fedc14fbeeba861dd33a821ba3a1624c10126ec6003b0a28d4139c5"
|
||||
digest = "1:b9d8cc221fb40078c7eb78d73b1702b5b548511b3d62bbd56b2f8180089c79af"
|
||||
name = "github.com/vulcand/predicate"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "939c094524d124c55fa8afe0e077701db4a865e2"
|
||||
version = "v1.0.0"
|
||||
revision = "8fbfb3ab0e94276b6b58bec378600829adc7a203"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -2289,7 +2281,6 @@
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp",
|
||||
"github.com/prometheus/client_model/go",
|
||||
"github.com/rancher/go-rancher-metadata/metadata",
|
||||
"github.com/ryanuber/go-glob",
|
||||
"github.com/sirupsen/logrus",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/stretchr/testify/mock",
|
||||
|
@@ -146,10 +146,6 @@ required = [
|
||||
name = "github.com/rancher/go-rancher-metadata"
|
||||
source = "github.com/containous/go-rancher-metadata"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/ryanuber/go-glob"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/Masterminds/sprig"
|
||||
version = "2.19.0"
|
||||
|
@@ -204,16 +204,11 @@ func configureLogging(staticConfiguration *static.Configuration) {
|
||||
// an explicitly defined log level always has precedence. if none is
|
||||
// given and debug mode is disabled, the default is ERROR, and DEBUG
|
||||
// otherwise.
|
||||
var levelStr string
|
||||
if staticConfiguration.Log != nil {
|
||||
levelStr := "error"
|
||||
if staticConfiguration.Log != nil && staticConfiguration.Log.Level != "" {
|
||||
levelStr = strings.ToLower(staticConfiguration.Log.Level)
|
||||
}
|
||||
if levelStr == "" {
|
||||
levelStr = "error"
|
||||
if staticConfiguration.Global.Debug {
|
||||
levelStr = "debug"
|
||||
}
|
||||
}
|
||||
|
||||
level, err := logrus.ParseLevel(levelStr)
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Error getting level: %v", err)
|
||||
|
@@ -10,10 +10,10 @@ Configuration in Traefik can refer to two different things:
|
||||
- The fully dynamic routing configuration (referred to as the _dynamic configuration_)
|
||||
- The startup configuration (referred to as the _static configuration_)
|
||||
|
||||
Elements in the _static configuration_ set up connections to [providers](../../providers/overview/) and define the [entrypoints](../../routing/entrypoints/) Traefik will listen to (these elements don't change often).
|
||||
Elements in the _static configuration_ set up connections to [providers](../providers/overview.md) and define the [entrypoints](../routing/entrypoints.md) Traefik will listen to (these elements don't change often).
|
||||
|
||||
The _dynamic configuration_ contains everything that defines how the requests are handled by your system.
|
||||
This configuration can change and is seamlessly hot-reloaded, without any request interuption or connection loss.
|
||||
This configuration can change and is seamlessly hot-reloaded, without any request interruption or connection loss.
|
||||
|
||||
!!! warning "Incompatible Configuration"
|
||||
Please be aware that the old configurations for Traefik v1.X are NOT compatible with the v2.X config as of now.
|
||||
@@ -70,6 +70,12 @@ docker run traefik[:version] --help
|
||||
# ex: docker run traefik:2.0 --help
|
||||
```
|
||||
|
||||
All available arguments can also be found [here](../reference/static-configuration/cli.md).
|
||||
|
||||
### Environment Variables
|
||||
|
||||
All available environment variables can be found [here](../reference/static-configuration/env.md)
|
||||
|
||||
## Available Configuration Options
|
||||
|
||||
All the configuration options are documented in their related section.
|
||||
|
@@ -1,145 +0,0 @@
|
||||
# HTTPS & TLS
|
||||
|
||||
Traefik supports HTTPS & TLS, and is able to accept new certificates / updates over time (without being restarted).
|
||||
TLS is enabled at the [router](../routing/routers/index.md) level, but some options are configured in dedicated sections (`tlsOptions` & `tlsStores`) described in this section.
|
||||
|
||||
## Configuration Example
|
||||
|
||||
??? example "Configuring a Default Certificate"
|
||||
|
||||
```toml
|
||||
[tlsStores]
|
||||
[tlsStores.default]
|
||||
[tlsStores.default.defaultCertificate]
|
||||
certFile = "path/to/cert.crt"
|
||||
keyFile = "path/to/cert.key"
|
||||
```
|
||||
|
||||
??? example "Configuring a Minimum TLS Version"
|
||||
|
||||
```toml
|
||||
[tlsOptions]
|
||||
[tlsOptions.default]
|
||||
minVersion = "VersionTLS12"
|
||||
```
|
||||
|
||||
??? example "Defining Certificates"
|
||||
|
||||
```toml
|
||||
[[tls]]
|
||||
[tls.certificate]
|
||||
certFile = "/path/to/domain.cert"
|
||||
keyFile = "/path/to/domain.key"
|
||||
|
||||
[[tls]]
|
||||
[tls.certificate]
|
||||
certFile = "/path/to/other-domain.cert"
|
||||
keyFile = "/path/to/other-domain.key"
|
||||
```
|
||||
|
||||
!!! important "File Provider Only"
|
||||
|
||||
In the above example, we've used the [file provider](../providers/file.md) to handle the TLS configuration (tlsStores, tlsOptions, and TLS certificates).
|
||||
In its current alpha version, it is the only available method to configure these elements.
|
||||
Of course, these options are hot reloaded and can be updated at runtime (they belong to the [dynamic configuration](../getting-started/configuration-overview.md)).
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Dynamic Certificates
|
||||
|
||||
To add / remove TLS certificates while Traefik is running, the [file provider](../providers/file.md) supports Dynamic TLS certificates in its `[[tls]]` section.
|
||||
|
||||
!!! example "Defining Certificates"
|
||||
|
||||
```toml
|
||||
[[tls]]
|
||||
stores = ["default"]
|
||||
[tls.certificate]
|
||||
certFile = "/path/to/domain.cert"
|
||||
keyFile = "/path/to/domain.key"
|
||||
|
||||
[[tls]]
|
||||
stores = ["default"]
|
||||
[tls.certificate]
|
||||
certFile = "/path/to/other-domain.cert"
|
||||
keyFile = "/path/to/other-domain.key"
|
||||
```
|
||||
|
||||
??? note "Stores"
|
||||
|
||||
During the alpha version, the stores option will be ignored and be automatically set to ["default"].
|
||||
|
||||
### Mutual Authentication
|
||||
|
||||
Traefik supports both optional and non optional (defaut value) mutual authentication.
|
||||
|
||||
- When `optional = false`, Traefik accepts connections only from client presenting a certificate signed by a CA listed in `ClientCA.files`.
|
||||
- When `optional = true`, Traefik authorizes connections from client presenting a certificate signed by an unknown CA.
|
||||
|
||||
!!! example "Non Optional Mutual Authentication"
|
||||
|
||||
In the following example, both `snitest.com` and `snitest.org` will require client certificates.
|
||||
|
||||
```toml
|
||||
[tlsOptions]
|
||||
[tlsOptions.default]
|
||||
[tlsOptions.default.ClientCA]
|
||||
files = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||
optional = false
|
||||
```
|
||||
|
||||
??? note "ClientCA.files"
|
||||
|
||||
You can use a file per `CA:s`, or a single file containing multiple `CA:s` (in `PEM` format).
|
||||
|
||||
`ClientCA.files` is not optional: every client will have to present a valid certificate. (This requirement will apply to every server certificate declared in the entrypoint.)
|
||||
|
||||
### Minimum TLS Version
|
||||
|
||||
!!! example "Min TLS version & [cipherSuites](https://godoc.org/crypto/tls#pkg-constants)"
|
||||
|
||||
```toml
|
||||
[tlsOptions]
|
||||
[tlsOptions.default]
|
||||
minVersion = "VersionTLS12"
|
||||
cipherSuites = [
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384"
|
||||
]
|
||||
```
|
||||
|
||||
### Strict SNI Checking
|
||||
|
||||
With strict SNI checking, Traefik won't allow connections without a matching certificate.
|
||||
|
||||
!!! example "Strict SNI"
|
||||
|
||||
```toml
|
||||
[tlsOptions]
|
||||
[tlsOptions.default]
|
||||
sniStrict = true
|
||||
```
|
||||
|
||||
### Default Certificate
|
||||
|
||||
Traefik can use a default certificate for connections without a SNI, or without a matching domain.
|
||||
|
||||
If no default certificate is provided, Traefik generates and uses a self-signed certificate.
|
||||
|
||||
!!! example "Setting a Default Certificate"
|
||||
|
||||
```toml
|
||||
[tlsStores]
|
||||
[tlsStores.default]
|
||||
[tlsStores.default.defaultCertificate]
|
||||
certFile = "path/to/cert.crt"
|
||||
keyFile = "path/to/cert.key"
|
||||
```
|
||||
|
||||
??? note "Only One Default Certificate"
|
||||
|
||||
There can only be one `defaultCertificate` per tlsOptions.
|
||||
|
||||
??? note "Default TLS Store"
|
||||
|
||||
During the alpha version, there is only one globally available TLS Store (`default`).
|
@@ -1,4 +1,4 @@
|
||||
# ACME
|
||||
# Let's Encrypt
|
||||
|
||||
Automatic HTTPS
|
||||
{: .subtitle }
|
||||
@@ -54,7 +54,7 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
|
||||
There are many available options for ACME. For a quick glance at what's possible, browse the configuration reference:
|
||||
|
||||
```toml
|
||||
--8<-- "content/https-tls/ref-acme.toml"
|
||||
--8<-- "content/https/ref-acme.toml"
|
||||
```
|
||||
|
||||
## Automatic Renewals
|
16
docs/content/https/overview.md
Normal file
16
docs/content/https/overview.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# HTTPS & TLS
|
||||
|
||||
Overview
|
||||
{: .subtitle }
|
||||
|
||||
Traefik supports HTTPS & TLS, which concerns roughly two parts of the configuration:
|
||||
routers, and the TLS connection (and its underlying certificates).
|
||||
|
||||
When a router has to handle HTTPS traffic,
|
||||
it should be specified with a `tls` field of the router definition.
|
||||
See the TLS section of the [routers documentation](../routing/routers/index.md#tls).
|
||||
|
||||
The next sections of this documentation explain how to configure the TLS connection itself.
|
||||
That is to say, how to obtain [TLS certificates](./tls.md#certificates-definition):
|
||||
either through a definition in the dynamic configuration, or through [Let's Encrypt](./acme.md) (ACME).
|
||||
And how to configure [TLS options](./tls.md#tls-options), and [certificates stores](./tls.md#certificates-stores).
|
140
docs/content/https/tls.md
Normal file
140
docs/content/https/tls.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# TLS
|
||||
|
||||
Transport Layer Security
|
||||
{: .subtitle }
|
||||
|
||||
## Certificates Definition
|
||||
|
||||
### Automated
|
||||
|
||||
See the [Let's Encrypt](./acme.md) page.
|
||||
|
||||
### User defined
|
||||
|
||||
To add / remove TLS certificates, even when Traefik is already running, their definition can be added to the [dynamic configuration](../getting-started/configuration-overview.md), in the `[[tls]]` section:
|
||||
|
||||
```toml
|
||||
[[tls]]
|
||||
[tls.certificate]
|
||||
certFile = "/path/to/domain.cert"
|
||||
keyFile = "/path/to/domain.key"
|
||||
|
||||
[[tls]]
|
||||
[tls.certificate]
|
||||
certFile = "/path/to/other-domain.cert"
|
||||
keyFile = "/path/to/other-domain.key"
|
||||
```
|
||||
|
||||
!!! important "File Provider Only"
|
||||
|
||||
In the above example, we've used the [file provider](../providers/file.md) to handle these definitions.
|
||||
In its current alpha version, it is the only available method to configure the certificates (as well as the options and the stores).
|
||||
|
||||
## Certificates Stores
|
||||
|
||||
In Traefik, certificates are grouped together in certificates stores, which are defined as such:
|
||||
|
||||
```toml
|
||||
[tlsStores]
|
||||
[tlsStores.default]
|
||||
```
|
||||
|
||||
!!! important "Alpha restriction"
|
||||
|
||||
During the alpha version, any store definition other than the default one (named `default`) will be ignored,
|
||||
and there is thefore only one globally available TLS store.
|
||||
|
||||
In the `[[tls]]` section, a list of stores can then be specified to indicate where the certificates should be stored:
|
||||
|
||||
```toml
|
||||
[[tls]]
|
||||
stores = ["default"]
|
||||
[tls.certificate]
|
||||
certFile = "/path/to/domain.cert"
|
||||
keyFile = "/path/to/domain.key"
|
||||
|
||||
[[tls]]
|
||||
# Note that since no store is defined,
|
||||
# the certificate below will be stored in the `default` store.
|
||||
[tls.certificate]
|
||||
certFile = "/path/to/other-domain.cert"
|
||||
keyFile = "/path/to/other-domain.key"
|
||||
```
|
||||
|
||||
!!! important "Alpha restriction"
|
||||
|
||||
During the alpha version, the `stores` list will actually be ignored and automatically set to `["default"]`.
|
||||
|
||||
### Default Certificate
|
||||
|
||||
Traefik can use a default certificate for connections without a SNI, or without a matching domain.
|
||||
This default certificate should be defined in a TLS store:
|
||||
|
||||
```toml
|
||||
[tlsStores]
|
||||
[tlsStores.default]
|
||||
[tlsStores.default.defaultCertificate]
|
||||
certFile = "path/to/cert.crt"
|
||||
keyFile = "path/to/cert.key"
|
||||
```
|
||||
|
||||
If no default certificate is provided, Traefik generates and uses a self-signed certificate.
|
||||
|
||||
## TLS Options
|
||||
|
||||
The TLS options allow one to configure some parameters of the TLS connection.
|
||||
|
||||
### Minimum TLS Version
|
||||
|
||||
```toml
|
||||
[tlsOptions]
|
||||
|
||||
[tlsOptions.default]
|
||||
minVersion = "VersionTLS12"
|
||||
|
||||
[tlsOptions.mintls13]
|
||||
minVersion = "VersionTLS13"
|
||||
```
|
||||
|
||||
### Mutual Authentication
|
||||
|
||||
Traefik supports both optional and strict (which is the default) mutual authentication, though the `ClientCA.files` section.
|
||||
If present, connections from clients without a certificate will be rejected.
|
||||
|
||||
For clients with a certificate, the `optional` option governs the behaviour as follows:
|
||||
|
||||
- When `optional = false`, Traefik accepts connections only from clients presenting a certificate signed by a CA listed in `ClientCA.files`.
|
||||
- When `optional = true`, Traefik authorizes connections from clients presenting a certificate signed by an unknown CA.
|
||||
|
||||
```toml
|
||||
[tlsOptions]
|
||||
[tlsOptions.default]
|
||||
[tlsOptions.default.ClientCA]
|
||||
# in PEM format. each file can contain multiple CAs.
|
||||
files = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||
optional = false
|
||||
```
|
||||
|
||||
### Cipher Suites
|
||||
|
||||
See [cipherSuites](https://godoc.org/crypto/tls#pkg-constants) for more information.
|
||||
|
||||
```toml
|
||||
[tlsOptions]
|
||||
[tlsOptions.default]
|
||||
cipherSuites = [
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384"
|
||||
]
|
||||
```
|
||||
|
||||
### Strict SNI Checking
|
||||
|
||||
With strict SNI checking, Traefik won't allow connections from clients connections
|
||||
that do not specify a server_name extension.
|
||||
|
||||
```toml
|
||||
[tlsOptions]
|
||||
[tlsOptions.default]
|
||||
sniStrict = true
|
||||
```
|
@@ -15,6 +15,16 @@ labels:
|
||||
- "traefik.http.middlewares.test-compress.compress=true"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
# Enable gzip compression
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-compress
|
||||
spec:
|
||||
compress: {}
|
||||
```
|
||||
|
||||
```json tab="Marathon"
|
||||
"labels": {
|
||||
"traefik.http.middlewares.test-compress.compress": "true"
|
||||
|
@@ -19,10 +19,10 @@ labels:
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: addprefix
|
||||
name: test-maxconn
|
||||
spec:
|
||||
addPrefix:
|
||||
prefix: /bar
|
||||
maxConn:
|
||||
amount: 10
|
||||
```
|
||||
|
||||
```json tab="Marathon"
|
||||
|
@@ -71,6 +71,38 @@ labels:
|
||||
- "traefik.http.middlewares.foo-add-prefix.addprefix.prefix=/foo"
|
||||
```
|
||||
|
||||
```toml tab="File"
|
||||
[tlsOptions]
|
||||
[tlsOptions.default]
|
||||
minVersion = "VersionTLS12"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: tlsoptions.traefik.containo.us
|
||||
|
||||
spec:
|
||||
group: traefik.containo.us
|
||||
version: v1alpha1
|
||||
names:
|
||||
kind: TLSOption
|
||||
plural: tlsoptions
|
||||
singular: tlsoption
|
||||
scope: Namespaced
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TLSOption
|
||||
metadata:
|
||||
name: mytlsoption
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
minversion: VersionTLS12
|
||||
```
|
||||
|
||||
```toml tab="File"
|
||||
# As Toml Configuration File
|
||||
[providers]
|
||||
@@ -121,8 +153,8 @@ If you use multiple `providers` and wish to reference a middleware declared in a
|
||||
image: your-docker-image
|
||||
|
||||
labels:
|
||||
# Attach file.add-foo-prefix middleware (declared in file)
|
||||
- "traefik.http.routers.my-container.middlewares=file.add-foo-prefix"
|
||||
# Attach add-foo-prefix@file middleware (declared in file)
|
||||
- "traefik.http.routers.my-container.middlewares=add-foo-prefix@file"
|
||||
```
|
||||
|
||||
## Available Middlewares
|
||||
|
@@ -32,15 +32,16 @@ metadata:
|
||||
name: test-ratelimit
|
||||
spec:
|
||||
rateLimit:
|
||||
extractorfunc = "client.ip"
|
||||
rate0:
|
||||
period = "10s"
|
||||
average = 100
|
||||
burst = 200
|
||||
rate1:
|
||||
period = "3s"
|
||||
average = 5
|
||||
burst = 10
|
||||
extractorFunc: client.ip
|
||||
rateset:
|
||||
rate0:
|
||||
period: 10s
|
||||
average: 100
|
||||
burst: 200
|
||||
rate1:
|
||||
period: 3s
|
||||
average: 5
|
||||
burst: 10
|
||||
```
|
||||
|
||||
```json tab="Marathon"
|
||||
|
@@ -22,7 +22,7 @@ kind: Middleware
|
||||
metadata:
|
||||
name: test-stripprefix
|
||||
spec:
|
||||
StripPrefix:
|
||||
stripPrefix:
|
||||
prefixes:
|
||||
- /foobar
|
||||
- /fiibar
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# StripPrefix
|
||||
# StripPrefixRegex
|
||||
|
||||
Removing Prefixes From the Path Before Forwarding the Request (Using a Regex)
|
||||
{: .subtitle }
|
||||
@@ -22,7 +22,7 @@ kind: Middleware
|
||||
metadata:
|
||||
name: test-stripprefixregex
|
||||
spec:
|
||||
StripPrefixRegex:
|
||||
stripPrefixRegex:
|
||||
regex: "^/foo/(.*)"
|
||||
```
|
||||
|
||||
|
@@ -5,41 +5,57 @@ Reading What's Happening
|
||||
|
||||
By default, logs are written to stdout, in text format.
|
||||
|
||||
## Configuration Example
|
||||
|
||||
??? example "Writing Logs in a File"
|
||||
|
||||
```toml
|
||||
[log]
|
||||
filePath = "/path/to/traefik.log"
|
||||
```
|
||||
|
||||
??? example "Writing Logs in a File, in JSON"
|
||||
|
||||
```toml
|
||||
[log]
|
||||
filePath = "/path/to/log-file.log"
|
||||
format = "json"
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
## Configuration
|
||||
|
||||
### General
|
||||
|
||||
Traefik logs concern everything that happens to Traefik itself (startup, configuration, events, shutdown, and so on).
|
||||
|
||||
#### filePath
|
||||
#### `filePath`
|
||||
|
||||
By default, the logs are written to the standard output.
|
||||
You can configure a file path instead using the `filePath` option.
|
||||
|
||||
#### format
|
||||
```toml tab="File"
|
||||
# Writing Logs to a File
|
||||
[log]
|
||||
filePath = "/path/to/traefik.log"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# Writing Logs to a File
|
||||
--log.filePath="/path/to/traefik.log"
|
||||
```
|
||||
|
||||
#### `format`
|
||||
|
||||
By default, the logs use a text format (`common`), but you can also ask for the `json` format in the `format` option.
|
||||
|
||||
#### log level
|
||||
```toml tab="File"
|
||||
# Writing Logs to a File, in JSON
|
||||
[log]
|
||||
filePath = "/path/to/log-file.log"
|
||||
format = "json"
|
||||
```
|
||||
|
||||
By default, the `level` is set to `error`, but you can choose amongst `debug`, `panic`, `fatal`, `error`, `warn`, and `info`.
|
||||
```bash tab="CLI"
|
||||
# Writing Logs to a File, in JSON
|
||||
--log.filePath="/path/to/traefik.log"
|
||||
--log.format="json"
|
||||
```
|
||||
|
||||
#### `level`
|
||||
|
||||
By default, the `level` is set to `ERROR`. Alternative logging levels are `DEBUG`, `PANIC`, `FATAL`, `ERROR`, `WARN`, and `INFO`.
|
||||
|
||||
```toml tab="File"
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--log.level="DEBUG"
|
||||
```
|
||||
|
||||
## Log Rotation
|
||||
|
||||
|
168
docs/content/operations/api.md
Normal file
168
docs/content/operations/api.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# API
|
||||
|
||||
Traefik exposes a number of information through an API handler, such as the configuration of all routers, services, middlewares, etc.
|
||||
|
||||
As with all features of Traefik, this handler can be enabled with the [static configuration](../getting-started/configuration-overview.md#the-static-configuration).
|
||||
|
||||
## Security
|
||||
|
||||
Enabling the API in production is not recommended, because it will expose all configuration elements,
|
||||
including sensitive data.
|
||||
|
||||
In production, it should be at least secured by authentication and authorizations.
|
||||
|
||||
A good sane default (non exhaustive) set of recommendations
|
||||
would be to apply the following protection mechanisms:
|
||||
|
||||
* At the application level:
|
||||
securing with middlewares such as [basic authentication](../middlewares/basicauth.md) or [white listing](../middlewares/ipwhitelist.md).
|
||||
|
||||
* At the transport level:
|
||||
NOT publicly exposing the API's port,
|
||||
keeping it restricted to internal networks
|
||||
(as in the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege), applied to networks).
|
||||
|
||||
## Configuration
|
||||
|
||||
To enable the API handler:
|
||||
|
||||
```toml tab="File"
|
||||
[api]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--api
|
||||
```
|
||||
|
||||
### `dashboard`
|
||||
|
||||
_Optional, Default=true_
|
||||
|
||||
Enable the dashboard. More about the dashboard features [here](./dashboard.md).
|
||||
|
||||
```toml tab="File"
|
||||
[api]
|
||||
dashboard = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--api.dashboard
|
||||
```
|
||||
|
||||
### `entrypoint`
|
||||
|
||||
_Optional, Default="traefik"_
|
||||
|
||||
The entry point that the API handler will be bound to.
|
||||
The default ("traefik") is an internal entry point (which is always defined).
|
||||
|
||||
```toml tab="File"
|
||||
[api]
|
||||
entrypoint = "web"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--api.entrypoint="web"
|
||||
```
|
||||
|
||||
### `middlewares`
|
||||
|
||||
_Optional, Default=empty_
|
||||
|
||||
The list of [middlewares](../middlewares/overview.md) applied to the API handler.
|
||||
|
||||
```toml tab="File"
|
||||
[api]
|
||||
middlewares = ["api-auth", "api-prefix"]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--api.middlewares="api-auth,api-prefix"
|
||||
```
|
||||
|
||||
### `debug`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
Enable additional endpoints for debugging and profiling, served under `/debug/`.
|
||||
|
||||
```toml tab="File"
|
||||
[api]
|
||||
debug = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--api.debug=true
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
All the following endpoints must be accessed with a `GET` HTTP request.
|
||||
|
||||
| Path | Description |
|
||||
|--------------------------------|-------------------------------------------------------------------------------------------|
|
||||
| `/api/http/routers` | Lists all the HTTP routers information. |
|
||||
| `/api/http/routers/{name}` | Returns the information of the HTTP router specified by `name`. |
|
||||
| `/api/http/services` | Lists all the HTTP services information. |
|
||||
| `/api/http/services/{name}` | Returns the information of the HTTP service specified by `name`. |
|
||||
| `/api/http/middlewares` | Lists all the HTTP middlewares information. |
|
||||
| `/api/http/middlewares/{name}` | Returns the information of the HTTP middleware specified by `name`. |
|
||||
| `/api/tcp/routers` | Lists all the TCP routers information. |
|
||||
| `/api/tcp/routers/{name}` | Returns the information of the TCP router specified by `name`. |
|
||||
| `/api/tcp/services` | Lists all the TCP services information. |
|
||||
| `/api/tcp/services/{name}` | Returns the information of the TCP service specified by `name`. |
|
||||
| `/api/version` | Returns information about Traefik version. |
|
||||
| `/debug/vars` | See the [expvar](https://golang.org/pkg/expvar/) Go documentation. |
|
||||
| `/debug/pprof/` | See the [pprof Index](https://golang.org/pkg/net/http/pprof/#Index) Go documentation. |
|
||||
| `/debug/pprof/cmdline` | See the [pprof Cmdline](https://golang.org/pkg/net/http/pprof/#Cmdline) Go documentation. |
|
||||
| `/debug/pprof/profile` | See the [pprof Profile](https://golang.org/pkg/net/http/pprof/#Profile) Go documentation. |
|
||||
| `/debug/pprof/symbol` | See the [pprof Symbol](https://golang.org/pkg/net/http/pprof/#Symbol) Go documentation. |
|
||||
| `/debug/pprof/trace` | See the [pprof Trace](https://golang.org/pkg/net/http/pprof/#Trace) Go documentation. |
|
||||
|
||||
## Common Configuration Use Cases
|
||||
|
||||
### Address / Port
|
||||
|
||||
You can define a custom address/port like this:
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.foo]
|
||||
address = ":8082"
|
||||
|
||||
[entryPoints.bar]
|
||||
address = ":8083"
|
||||
|
||||
[ping]
|
||||
entryPoint = "foo"
|
||||
|
||||
[api]
|
||||
entryPoint = "bar"
|
||||
```
|
||||
|
||||
In the above example, you would access a service at /foo, an api endpoint, or the health-check as follows:
|
||||
|
||||
* Service: `http://hostname:80/foo`
|
||||
* API: `http://hostname:8083/api/http/routers`
|
||||
* Ping URL: `http://hostname:8082/ping`
|
||||
|
||||
### Authentication
|
||||
|
||||
To restrict access to the API handler, one can add authentication with the [basic auth middleware](../middlewares/basicauth.md).
|
||||
|
||||
```toml
|
||||
[api]
|
||||
middlewares=["api-auth"]
|
||||
```
|
||||
|
||||
```toml
|
||||
[http.middlewares]
|
||||
[http.middlewares.api-auth.basicauth]
|
||||
users = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
```
|
@@ -26,7 +26,7 @@ traefik [--flag=flag_argument] [-f [flag_argument]]
|
||||
traefik [--flag[=true|false| ]] [-f [true|false| ]]
|
||||
```
|
||||
|
||||
### healthcheck
|
||||
### `healthcheck`
|
||||
|
||||
Calls Traefik `/ping` to check the health of Traefik.
|
||||
Its exit status is `0` if Traefik is healthy and `1` otherwise.
|
||||
@@ -50,12 +50,12 @@ $ traefik healthcheck
|
||||
OK: http://:8082/ping
|
||||
```
|
||||
|
||||
### version
|
||||
### `version`
|
||||
|
||||
Shows the current Traefik version.
|
||||
|
||||
Usage:
|
||||
|
||||
```bash
|
||||
traefik version [command] [flags] [arguments]
|
||||
traefik version
|
||||
```
|
||||
|
@@ -1,15 +0,0 @@
|
||||
# The Debug Mode
|
||||
|
||||
Getting More Information (Not For Production)
|
||||
{: .subtitle }
|
||||
|
||||
The debug mode will make Traefik be _extremely_ verbose in its logs, and is NOT intended for production purposes.
|
||||
|
||||
## Configuration Example
|
||||
|
||||
??? example "TOML -- Enabling the Debug Mode"
|
||||
|
||||
```toml
|
||||
[Global]
|
||||
debug = true
|
||||
```
|
13
docs/content/providers/crd_tls_option.yml
Normal file
13
docs/content/providers/crd_tls_option.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: tlsoptions.traefik.containo.us
|
||||
|
||||
spec:
|
||||
group: traefik.containo.us
|
||||
version: v1alpha1
|
||||
names:
|
||||
kind: TLSOption
|
||||
plural: tlsoptions
|
||||
singular: tlsoption
|
||||
scope: Namespaced
|
@@ -37,7 +37,7 @@ Attach labels to your containers and let Traefik do the rest!
|
||||
Enabling the docker provider (Swarm Mode)
|
||||
|
||||
```toml
|
||||
[docker]
|
||||
[providers.docker]
|
||||
# swarm classic (1.12-)
|
||||
# endpoint = "tcp://127.0.0.1:2375"
|
||||
# docker swarm mode (1.12+)
|
||||
@@ -193,8 +193,8 @@ The container service name can be accessed as the `Name` identifier,
|
||||
and the template has access to all the labels defined on this container.
|
||||
|
||||
```toml tab="File"
|
||||
[docker]
|
||||
defaultRule = ""
|
||||
[providers.docker]
|
||||
defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -215,6 +215,48 @@ _Optional, Default=15_
|
||||
|
||||
Defines the polling interval (in seconds) in Swarm Mode.
|
||||
|
||||
### `constraints`
|
||||
|
||||
_Optional, Default=""_
|
||||
|
||||
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
|
||||
That is to say, if none of the container's labels match the expression, no route for the container is created.
|
||||
If the expression is empty, all detected containers are included.
|
||||
|
||||
The expression syntax is based on the `Label("key", "value")`, and `LabelRegexp("key", "value")` functions, as well as the usual boolean logic, as shown in examples below.
|
||||
|
||||
??? example "Constraints Expression Examples"
|
||||
|
||||
```toml
|
||||
# Includes only containers having a label with key `a.label.name` and value `foo`
|
||||
constraints = "Label(`a.label.name`, `foo`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# Excludes containers having any label with key `a.label.name` and value `foo`
|
||||
constraints = "!Label(`a.label.name`, `value`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# With logical AND.
|
||||
constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# With logical OR.
|
||||
constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# 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`))"
|
||||
```
|
||||
|
||||
```toml
|
||||
# Includes only containers having a label with key `a.label.name` and a value matching the `a.+` regular expression.
|
||||
constraints = "LabelRegexp(`a.label.name`, `a.+`)"
|
||||
```
|
||||
|
||||
## Routing Configuration Options
|
||||
|
||||
### General
|
||||
@@ -286,10 +328,6 @@ You can tell Traefik to consider (or not) the container by setting `traefik.enab
|
||||
|
||||
This option overrides the value of `exposedByDefault`.
|
||||
|
||||
#### `traefik.tags`
|
||||
|
||||
Sets the tags for [constraints filtering](./overview.md#constraints-configuration).
|
||||
|
||||
#### `traefik.docker.network`
|
||||
|
||||
Overrides the default docker network to use for connections to the container.
|
||||
|
@@ -230,6 +230,51 @@ spec:
|
||||
|
||||
More information about available middlewares in the dedicated [middlewares section](../middlewares/overview.md).
|
||||
|
||||
### Traefik TLS Option Definition
|
||||
|
||||
Additionally, to allow for the use of tls options in an IngressRoute, we defined the CRD below for the TLSOption kind.
|
||||
More information about TLS Options is available in the dedicated [TLS Configuration Options](../../https/tls/#tls-options).
|
||||
|
||||
```yaml
|
||||
--8<-- "content/providers/crd_tls_option.yml"
|
||||
```
|
||||
|
||||
Once the TLSOption kind has been registered with the Kubernetes cluster or defined in the File Provider, it can then be used in IngressRoute definitions, such as:
|
||||
|
||||
```yaml
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TLSOption
|
||||
metadata:
|
||||
name: mytlsoption
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
minversion: VersionTLS12
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: ingressroutebar
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`bar.com`) && PathPrefix(`/stripit`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: whoami
|
||||
port: 80
|
||||
tls:
|
||||
options:
|
||||
name: mytlsoption
|
||||
namespace: default
|
||||
```
|
||||
|
||||
!!! note "TLS Option reference and namespace"
|
||||
If the optional `namespace` attribute is not set, the configuration will be applied with the namespace of the IngressRoute.
|
||||
|
||||
### TLS
|
||||
|
||||
To allow for TLS, we made use of the `Secret` kind, as it was already defined, and it can be directly used in an `IngressRoute`:
|
||||
|
@@ -78,7 +78,7 @@ DCOSToken for DCOS environment.
|
||||
If set, it overrides the Authorization header.
|
||||
|
||||
```toml tab="File"
|
||||
[marathon]
|
||||
[providers.marathon]
|
||||
dcosToken = "xxxxxx"
|
||||
# ...
|
||||
```
|
||||
@@ -101,8 +101,8 @@ The app ID can be accessed as the Name identifier,
|
||||
and the template has access to all the labels defined on this Marathon application.
|
||||
|
||||
```toml tab="File"
|
||||
[marathon]
|
||||
defaultRule = ""
|
||||
[providers.marathon]
|
||||
defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -132,7 +132,7 @@ Marathon server endpoint.
|
||||
You can optionally specify multiple endpoints:
|
||||
|
||||
```toml tab="File"
|
||||
[marathon]
|
||||
[providers.marathon]
|
||||
endpoint = "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"
|
||||
# ...
|
||||
```
|
||||
@@ -150,16 +150,59 @@ Exposes Marathon applications by default through Traefik.
|
||||
|
||||
If set to false, applications that don't have a `traefik.enable=true` label will be ignored from the resulting routing configuration.
|
||||
|
||||
### `filterMarathonConstraints`
|
||||
### `constraints`
|
||||
|
||||
_Optional, Default=false_
|
||||
_Optional, Default=""_
|
||||
|
||||
Enables filtering using Marathon constraints.
|
||||
Constraints is an expression that Traefik matches against the application's labels to determine whether to create any route for that application.
|
||||
That is to say, if none of the application's labels match the expression, no route for the application is created.
|
||||
In addition, the expression also matched against the application's constraints, such as described in [Marathon constraints](https://mesosphere.github.io/marathon/docs/constraints.html).
|
||||
If the expression is empty, all detected applications are included.
|
||||
|
||||
If enabled, Traefik will take into account Marathon constraints, as defined in [Marathon constraints](https://mesosphere.github.io/marathon/docs/constraints.html).
|
||||
The expression syntax is based on the `Label("key", "value")`, and `LabelRegexp("key", "value")`, as well as the usual boolean logic.
|
||||
In addition, to match against marathon constraints, the function `MarathonConstraint("field:operator:value")` can be used, where the field, operator, and value parts are joined together in a single string with the `:` separator.
|
||||
|
||||
Each individual constraint will be treated as a verbatim compounded tag,
|
||||
e.g. "rack_id:CLUSTER:rack-1", with all constraint groups concatenated together using ":".
|
||||
??? example "Constraints Expression Examples"
|
||||
|
||||
```toml
|
||||
# Includes only applications having a label with key `a.label.name` and value `foo`
|
||||
constraints = "Label(`a.label.name`, `foo`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# Excludes applications having any label with key `a.label.name` and value `foo`
|
||||
constraints = "!Label(`a.label.name`, `value`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# With logical AND.
|
||||
constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# With logical OR.
|
||||
constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# 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`))"
|
||||
```
|
||||
|
||||
```toml
|
||||
# Includes only applications having a label with key `a.label.name` and a value matching the `a.+` regular expression.
|
||||
constraints = "LabelRegexp(`a.label.name`, `a.+`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# Includes only applications having a Marathon constraint with field `A`, operator `B`, and value `C`.
|
||||
constraints = "MarathonConstraint(`A:B:C`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# Uses both Marathon constraint and application label with logical operator.
|
||||
constraints = "MarathonConstraint(`A:B:C`) && Label(`a.label.name`, `value`)"
|
||||
```
|
||||
|
||||
### `forceTaskHostname`
|
||||
|
||||
@@ -318,10 +361,6 @@ You can declare TCP Routers and/or Services using labels.
|
||||
Setting this option controls whether Traefik exposes the application.
|
||||
It overrides the value of `exposedByDefault`.
|
||||
|
||||
#### `traefik.tags`
|
||||
|
||||
Sets the tags for [constraints filtering](./overview.md#constraints-configuration).
|
||||
|
||||
#### `traefik.marathon.ipadressidx`
|
||||
|
||||
If a task has several IP addresses, this option specifies which one, in the list of available addresses, to select.
|
||||
|
@@ -26,74 +26,46 @@ Even if each provider is different, we can categorize them in four groups:
|
||||
|
||||
Below is the list of the currently supported providers in Traefik.
|
||||
|
||||
| Provider | Type | Configuration Type |
|
||||
|---------------------------------|--------------|--------------------|
|
||||
| [Docker](./docker.md) | Orchestrator | Label |
|
||||
| [File](./file.md) | Orchestrator | Custom Annotation |
|
||||
| [Kubernetes](kubernetes-crd.md) | Orchestrator | Custom Resource |
|
||||
| [Marathon](marathon.md) | Orchestrator | Label |
|
||||
| Provider | Type | Configuration Type |
|
||||
|-----------------------------------|--------------|--------------------|
|
||||
| [Docker](./docker.md) | Orchestrator | Label |
|
||||
| [Kubernetes](./kubernetes-crd.md) | Orchestrator | Custom Resource |
|
||||
| [Marathon](./marathon.md) | Orchestrator | Label |
|
||||
| [Rancher](./rancher.md) | Orchestrator | Label |
|
||||
| [File](./file.md) | Manual | TOML format |
|
||||
|
||||
!!! note "More Providers"
|
||||
|
||||
The current version of Traefik is in development and doesn't support (yet) every provider. See the previous version (1.7) for more providers.
|
||||
The current version of Traefik is in development and doesn't support (yet) every provider.
|
||||
See the previous version (1.7) for more providers.
|
||||
|
||||
<!--
|
||||
TODO (document TCP VS HTTP dynamic configuration)
|
||||
-->
|
||||
|
||||
## Constraints Configuration
|
||||
TODO (document TCP VS HTTP dynamic configuration)
|
||||
-->
|
||||
|
||||
If you want to limit the scope of Traefik's service discovery, you can set constraints.
|
||||
Doing so, Traefik will create routes for containers that match these constraints only.
|
||||
## Restrict the Scope of Service Discovery
|
||||
|
||||
??? example "Containers with the api Tag"
|
||||
By default Traefik will create routes for all detected containers.
|
||||
|
||||
```toml
|
||||
constraints = ["tag==api"]
|
||||
```
|
||||
If you want to limit the scope of Traefik's service discovery,
|
||||
i.e. disallow route creation for some containers,
|
||||
you can do so in two different ways:
|
||||
either with the generic configuration option `exposedByDefault`,
|
||||
or with a finer granularity mechanism based on constraints.
|
||||
|
||||
??? example "Containers without the api Tag"
|
||||
### `exposedByDefault` and `traefik.enable`
|
||||
|
||||
```toml
|
||||
constraints = ["tag!=api"]
|
||||
```
|
||||
|
||||
??? example "Containers with tags starting with 'us-'"
|
||||
List of providers that support that feature:
|
||||
|
||||
```toml
|
||||
constraints = ["tag==us-*"]
|
||||
```
|
||||
- [Docker](./docker.md#exposedbydefault)
|
||||
- [Rancher](./rancher.md#exposedbydefault)
|
||||
- [Marathon](./marathon.md#exposedbydefault)
|
||||
|
||||
??? example "Multiple constraints"
|
||||
### Constraints
|
||||
|
||||
```toml
|
||||
# Multiple constraints
|
||||
# - "tag==" must match with at least one tag
|
||||
# - "tag!=" must match with none of tags
|
||||
constraints = ["tag!=us-*", "tag!=asia-*"]
|
||||
```
|
||||
List of providers that support constraints:
|
||||
|
||||
??? note "List of Providers that Support Constraints"
|
||||
|
||||
- Docker
|
||||
- Consul K/V
|
||||
- BoltDB
|
||||
- Zookeeper
|
||||
- ECS
|
||||
- Etcd
|
||||
- Consul Catalog
|
||||
- Rancher
|
||||
- Marathon
|
||||
- Kubernetes (using a provider-specific mechanism based on label selectors)
|
||||
|
||||
!!! note
|
||||
|
||||
The constraint option belongs to the provider configuration itself.
|
||||
|
||||
??? example "Setting the Constraint Options for Docker"
|
||||
|
||||
```toml
|
||||
[providers]
|
||||
[providers.docker]
|
||||
constraints = ["tag==api"]
|
||||
```
|
||||
- [Docker](./docker.md#constraints)
|
||||
- [Rancher](./rancher.md#constraints)
|
||||
- [Marathon](./marathon.md#constraints)
|
||||
- [Kubernetes CRD](./kubernetes-crd.md#labelselector)
|
||||
|
@@ -19,64 +19,23 @@ Attach labels to your services and let Traefik do the rest!
|
||||
Enabling the rancher provider
|
||||
|
||||
```toml
|
||||
[provider.rancher]
|
||||
[Providers.Rancher]
|
||||
```
|
||||
|
||||
Attaching labels to services
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- traefik.http.services.my-service.rule=Host(my-domain)
|
||||
- traefik.http.services.my-service.rule=Host(`my-domain`)
|
||||
```
|
||||
|
||||
## Provider Configuration Options
|
||||
|
||||
!!! tip "Browse the Reference"
|
||||
??? tip "Browse the Reference"
|
||||
If you're in a hurry, maybe you'd rather go through the configuration reference:
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Rancher Provider
|
||||
################################################################
|
||||
|
||||
# Enable Rancher Provider.
|
||||
[rancher]
|
||||
|
||||
# Expose Rancher services by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
ExposedByDefault = "true"
|
||||
|
||||
# Enable watch Rancher changes.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Filter services with unhealthy states and inactive states.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
EnableServiceHealthFilter = true
|
||||
|
||||
# Defines the polling interval (in seconds).
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
RefreshSeconds = true
|
||||
|
||||
# Poll the Rancher metadata service for changes every `rancher.refreshSeconds`, which is less accurate
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
IntervalPoll = false
|
||||
|
||||
# Prefix used for accessing the Rancher metadata service
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
Prefix = 15
|
||||
--8<-- "content/providers/rancher.toml"
|
||||
```
|
||||
|
||||
### `ExposedByDefault`
|
||||
@@ -99,8 +58,8 @@ The service name can be accessed as the `Name` identifier,
|
||||
and the template has access to all the labels defined on this container.
|
||||
|
||||
```toml tab="File"
|
||||
[rancher]
|
||||
defaultRule = ""
|
||||
[Providers.Rancher]
|
||||
defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -136,6 +95,50 @@ _Optional, Default=/latest_
|
||||
|
||||
Prefix used for accessing the Rancher metadata service
|
||||
|
||||
### `constraints`
|
||||
|
||||
_Optional, Default=""_
|
||||
|
||||
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
|
||||
That is to say, if none of the container's labels match the expression, no route for the container is created.
|
||||
If the expression is empty, all detected containers are included.
|
||||
|
||||
The expression syntax is based on the `Label("key", "value")`, and `LabelRegexp("key", "value")` functions, as well as the usual boolean logic, as shown in examples below.
|
||||
|
||||
??? example "Constraints Expression Examples"
|
||||
|
||||
```toml
|
||||
# Includes only containers having a label with key `a.label.name` and value `foo`
|
||||
constraints = "Label(`a.label.name`, `foo`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# Excludes containers having any label with key `a.label.name` and value `foo`
|
||||
constraints = "!Label(`a.label.name`, `value`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# With logical AND.
|
||||
constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# With logical OR.
|
||||
constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# 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`))"
|
||||
```
|
||||
|
||||
```toml
|
||||
# Includes only containers having a label with key `a.label.name` and a value matching the `a.+` regular expression.
|
||||
constraints = "LabelRegexp(`a.label.name`, `a.+`)"
|
||||
```
|
||||
|
||||
## Routing Configuration Options
|
||||
|
||||
### General
|
||||
|
||||
Traefik creates, for each rancher service, a corresponding [service](../routing/services/index.md) and [router](../routing/routers/index.md).
|
||||
@@ -185,10 +188,6 @@ You can tell Traefik to consider (or not) the container by setting `traefik.enab
|
||||
|
||||
This option overrides the value of `exposedByDefault`.
|
||||
|
||||
#### `traefik.tags`
|
||||
|
||||
Sets the tags for [constraints filtering](./overview.md#constraints-configuration).
|
||||
|
||||
#### Port Lookup
|
||||
|
||||
Traefik is now capable of detecting the port to use, by following the default rancher flow.
|
||||
|
20
docs/content/providers/rancher.toml
Normal file
20
docs/content/providers/rancher.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
# Enable Rancher Provider.
|
||||
[Providers.Rancher]
|
||||
|
||||
# Expose Rancher services by default in Traefik.
|
||||
ExposedByDefault = true
|
||||
|
||||
# Enable watch Rancher changes.
|
||||
Watch = true
|
||||
|
||||
# Filter services with unhealthy states and inactive states.
|
||||
EnableServiceHealthFilter = true
|
||||
|
||||
# Defines the polling interval (in seconds).
|
||||
RefreshSeconds = true
|
||||
|
||||
# Poll the Rancher metadata service for changes every `rancher.refreshSeconds`, which is less accurate
|
||||
IntervalPoll = false
|
||||
|
||||
# Prefix used for accessing the Rancher metadata service
|
||||
Prefix = "/latest"
|
@@ -26,6 +26,21 @@ spec:
|
||||
singular: middleware
|
||||
scope: Namespaced
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: tlsoptions.traefik.containo.us
|
||||
|
||||
spec:
|
||||
group: traefik.containo.us
|
||||
version: v1alpha1
|
||||
names:
|
||||
kind: TLSOption
|
||||
plural: tlsoptions
|
||||
singular: tlsoption
|
||||
scope: Namespaced
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
@@ -85,6 +100,9 @@ spec:
|
||||
# use an empty tls object for TLS with Let's Encrypt
|
||||
tls:
|
||||
secretName: supersecret
|
||||
options:
|
||||
name: myTLSOption
|
||||
namespace: default
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
@@ -104,3 +122,6 @@ spec:
|
||||
tls:
|
||||
secretName: foosecret
|
||||
passthrough: false
|
||||
options:
|
||||
name: myTLSOption
|
||||
namespace: default
|
||||
|
@@ -95,8 +95,11 @@
|
||||
--api.dashboard (Default: "true")
|
||||
Activate dashboard.
|
||||
|
||||
--api.debug (Default: "false")
|
||||
Enable additional endpoints for debugging and profiling.
|
||||
|
||||
--api.entrypoint (Default: "traefik")
|
||||
EntryPoint.
|
||||
The entry point that the API handler will be bound to.
|
||||
|
||||
--api.middlewares (Default: "")
|
||||
Middleware list.
|
||||
@@ -153,9 +156,6 @@
|
||||
--global.checknewversion (Default: "true")
|
||||
Periodically check if a new version has been released.
|
||||
|
||||
--global.debug (Default: "false")
|
||||
Enable debug mode.
|
||||
|
||||
--global.sendanonymoususage
|
||||
Periodically send anonymous usage statistics. If the option is not specified, it
|
||||
will be enabled by default.
|
||||
@@ -172,6 +172,9 @@
|
||||
--hostresolver.resolvdepth (Default: "5")
|
||||
The maximal depth of DNS recursive resolving
|
||||
|
||||
--log (Default: "false")
|
||||
Traefik log settings.
|
||||
|
||||
--log.filepath (Default: "")
|
||||
Traefik log file path. Stdout is used when omitted or empty.
|
||||
|
||||
@@ -248,17 +251,8 @@
|
||||
Enable Docker backend with default settings.
|
||||
|
||||
--providers.docker.constraints (Default: "")
|
||||
Filter services by constraint, matching with Traefik tags.
|
||||
|
||||
--providers.docker.constraints[n].key (Default: "")
|
||||
The provider label that will be matched against. In practice, it is always
|
||||
'tag'.
|
||||
|
||||
--providers.docker.constraints[n].mustmatch (Default: "false")
|
||||
Whether the matching operator is equals or not equals.
|
||||
|
||||
--providers.docker.constraints[n].value (Default: "")
|
||||
The value that will be matched against.
|
||||
Constraints is an expression that Traefik matches against the container's labels
|
||||
to determine whether to create any route for that container.
|
||||
|
||||
--providers.docker.defaultrule (Default: "Host(`{{ normalize .Name }}`)")
|
||||
Default rule.
|
||||
@@ -381,17 +375,8 @@
|
||||
Basic authentication Password.
|
||||
|
||||
--providers.marathon.constraints (Default: "")
|
||||
Filter services by constraint, matching with Traefik tags.
|
||||
|
||||
--providers.marathon.constraints[n].key (Default: "")
|
||||
The provider label that will be matched against. In practice, it is always
|
||||
'tag'.
|
||||
|
||||
--providers.marathon.constraints[n].mustmatch (Default: "false")
|
||||
Whether the matching operator is equals or not equals.
|
||||
|
||||
--providers.marathon.constraints[n].value (Default: "")
|
||||
The value that will be matched against.
|
||||
Constraints is an expression that Traefik matches against the application's
|
||||
labels to determine whether to create any route for that application.
|
||||
|
||||
--providers.marathon.dcostoken (Default: "")
|
||||
DCOSToken for DCOS environment, This will override the Authorization header.
|
||||
@@ -408,9 +393,6 @@
|
||||
--providers.marathon.exposedbydefault (Default: "true")
|
||||
Expose Marathon apps by default.
|
||||
|
||||
--providers.marathon.filtermarathonconstraints (Default: "false")
|
||||
Enable use of Marathon constraints in constraint filtering.
|
||||
|
||||
--providers.marathon.forcetaskhostname (Default: "false")
|
||||
Force to use the task's hostname.
|
||||
|
||||
@@ -456,17 +438,8 @@
|
||||
Enable Rancher backend with default settings.
|
||||
|
||||
--providers.rancher.constraints (Default: "")
|
||||
Filter services by constraint, matching with Traefik tags.
|
||||
|
||||
--providers.rancher.constraints[n].key (Default: "")
|
||||
The provider label that will be matched against. In practice, it is always
|
||||
'tag'.
|
||||
|
||||
--providers.rancher.constraints[n].mustmatch (Default: "false")
|
||||
Whether the matching operator is equals or not equals.
|
||||
|
||||
--providers.rancher.constraints[n].value (Default: "")
|
||||
The value that will be matched against.
|
||||
Constraints is an expression that Traefik matches against the container's labels
|
||||
to determine whether to create any route for that container.
|
||||
|
||||
--providers.rancher.defaultrule (Default: "Host(`{{ normalize .Name }}`)")
|
||||
Default rule.
|
||||
|
@@ -93,8 +93,11 @@ Enable api/dashboard. (Default: ```false```)
|
||||
`TRAEFIK_API_DASHBOARD`:
|
||||
Activate dashboard. (Default: ```true```)
|
||||
|
||||
`TRAEFIK_API_DEBUG`:
|
||||
Enable additional endpoints for debugging and profiling. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_API_ENTRYPOINT`:
|
||||
EntryPoint. (Default: ```traefik```)
|
||||
The entry point that the API handler will be bound to. (Default: ```traefik```)
|
||||
|
||||
`TRAEFIK_API_MIDDLEWARES`:
|
||||
Middleware list.
|
||||
@@ -147,9 +150,6 @@ WriteTimeout is the maximum duration before timing out writes of the response. I
|
||||
`TRAEFIK_GLOBAL_CHECKNEWVERSION`:
|
||||
Periodically check if a new version has been released. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_GLOBAL_DEBUG`:
|
||||
Enable debug mode. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_GLOBAL_SENDANONYMOUSUSAGE`:
|
||||
Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default.
|
||||
|
||||
@@ -165,6 +165,9 @@ resolv.conf used for DNS resolving (Default: ```/etc/resolv.conf```)
|
||||
`TRAEFIK_HOSTRESOLVER_RESOLVDEPTH`:
|
||||
The maximal depth of DNS recursive resolving (Default: ```5```)
|
||||
|
||||
`TRAEFIK_LOG`:
|
||||
Traefik log settings. (Default: "false")
|
||||
|
||||
`TRAEFIK_LOG_FILEPATH`:
|
||||
Traefik log file path. Stdout is used when omitted or empty.
|
||||
|
||||
@@ -241,16 +244,7 @@ Middleware list.
|
||||
Enable Docker backend with default settings. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS`:
|
||||
Filter services by constraint, matching with Traefik tags.
|
||||
|
||||
`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS[n]_KEY`:
|
||||
The provider label that will be matched against. In practice, it is always 'tag'.
|
||||
|
||||
`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS[n]_MUSTMATCH`:
|
||||
Whether the matching operator is equals or not equals. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS[n]_VALUE`:
|
||||
The value that will be matched against.
|
||||
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
|
||||
|
||||
`TRAEFIK_PROVIDERS_DOCKER_DEFAULTRULE`:
|
||||
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
|
||||
@@ -373,16 +367,7 @@ Basic authentication User.
|
||||
Basic authentication Password.
|
||||
|
||||
`TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS`:
|
||||
Filter services by constraint, matching with Traefik tags.
|
||||
|
||||
`TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS[n]_KEY`:
|
||||
The provider label that will be matched against. In practice, it is always 'tag'.
|
||||
|
||||
`TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS[n]_MUSTMATCH`:
|
||||
Whether the matching operator is equals or not equals. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS[n]_VALUE`:
|
||||
The value that will be matched against.
|
||||
Constraints is an expression that Traefik matches against the application's labels to determine whether to create any route for that application.
|
||||
|
||||
`TRAEFIK_PROVIDERS_MARATHON_DCOSTOKEN`:
|
||||
DCOSToken for DCOS environment, This will override the Authorization header.
|
||||
@@ -399,9 +384,6 @@ Marathon server endpoint. You can also specify multiple endpoint for Marathon. (
|
||||
`TRAEFIK_PROVIDERS_MARATHON_EXPOSEDBYDEFAULT`:
|
||||
Expose Marathon apps by default. (Default: ```true```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_MARATHON_FILTERMARATHONCONSTRAINTS`:
|
||||
Enable use of Marathon constraints in constraint filtering. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_MARATHON_FORCETASKHOSTNAME`:
|
||||
Force to use the task's hostname. (Default: ```false```)
|
||||
|
||||
@@ -445,16 +427,7 @@ Backends throttle duration: minimum duration between 2 events from providers bef
|
||||
Enable Rancher backend with default settings. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS`:
|
||||
Filter services by constraint, matching with Traefik tags.
|
||||
|
||||
`TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS[n]_KEY`:
|
||||
The provider label that will be matched against. In practice, it is always 'tag'.
|
||||
|
||||
`TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS[n]_MUSTMATCH`:
|
||||
Whether the matching operator is equals or not equals. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS[n]_VALUE`:
|
||||
The value that will be matched against.
|
||||
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
|
||||
|
||||
`TRAEFIK_PROVIDERS_RANCHER_DEFAULTRULE`:
|
||||
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
|
||||
|
@@ -1,5 +1,4 @@
|
||||
[Global]
|
||||
Debug = true
|
||||
CheckNewVersion = true
|
||||
SendAnonymousUsage = true
|
||||
|
||||
@@ -42,16 +41,7 @@
|
||||
SwarmMode = true
|
||||
Network = "foobar"
|
||||
SwarmModeRefreshSeconds = 42
|
||||
|
||||
[[Providers.Docker.Constraints]]
|
||||
Key = "foobar"
|
||||
MustMatch = true
|
||||
Regex = "foobar"
|
||||
|
||||
[[Providers.Docker.Constraints]]
|
||||
Key = "foobar"
|
||||
MustMatch = true
|
||||
Regex = "foobar"
|
||||
Constraints = "foobar"
|
||||
|
||||
[Providers.Docker.TLS]
|
||||
CA = "foobar"
|
||||
@@ -74,23 +64,13 @@
|
||||
DefaultRule = "foobar"
|
||||
ExposedByDefault = true
|
||||
DCOSToken = "foobar"
|
||||
FilterMarathonConstraints = true
|
||||
DialerTimeout = 42
|
||||
ResponseHeaderTimeout = 42
|
||||
TLSHandshakeTimeout = 42
|
||||
KeepAlive = 42
|
||||
ForceTaskHostname = true
|
||||
RespectReadinessChecks = true
|
||||
|
||||
[[Providers.Marathon.Constraints]]
|
||||
Key = "foobar"
|
||||
MustMatch = true
|
||||
Regex = "foobar"
|
||||
|
||||
[[Providers.Marathon.Constraints]]
|
||||
Key = "foobar"
|
||||
MustMatch = true
|
||||
Regex = "foobar"
|
||||
Constraints = "foobar"
|
||||
|
||||
[Providers.Marathon.TLS]
|
||||
CA = "foobar"
|
||||
@@ -135,16 +115,7 @@
|
||||
RefreshSeconds = 42
|
||||
IntervalPoll = true
|
||||
Prefix = "foobar"
|
||||
|
||||
[[Providers.Rancher.Constraints]]
|
||||
Key = "foobar"
|
||||
MustMatch = true
|
||||
Regex = "foobar"
|
||||
|
||||
[[Providers.Rancher.Constraints]]
|
||||
Key = "foobar"
|
||||
MustMatch = true
|
||||
Regex = "foobar"
|
||||
Constraints = "foobar"
|
||||
|
||||
[API]
|
||||
EntryPoint = "foobar"
|
||||
|
@@ -44,22 +44,22 @@ You can define them using a toml file, CLI arguments, or a key-value store.
|
||||
See the complete reference for the list of available options:
|
||||
|
||||
```toml tab="File"
|
||||
[EntryPoints]
|
||||
[entryPoints]
|
||||
|
||||
[EntryPoints.EntryPoint0]
|
||||
[entryPoints.EntryPoint0]
|
||||
Address = ":8888"
|
||||
[EntryPoints.EntryPoint0.Transport]
|
||||
[EntryPoints.EntryPoint0.Transport.LifeCycle]
|
||||
[entryPoints.EntryPoint0.Transport]
|
||||
[entryPoints.EntryPoint0.Transport.LifeCycle]
|
||||
RequestAcceptGraceTimeout = 42
|
||||
GraceTimeOut = 42
|
||||
[EntryPoints.EntryPoint0.Transport.RespondingTimeouts]
|
||||
[entryPoints.EntryPoint0.Transport.RespondingTimeouts]
|
||||
ReadTimeout = 42
|
||||
WriteTimeout = 42
|
||||
IdleTimeout = 42
|
||||
[EntryPoints.EntryPoint0.ProxyProtocol]
|
||||
[entryPoints.EntryPoint0.ProxyProtocol]
|
||||
Insecure = true
|
||||
TrustedIPs = ["foobar", "foobar"]
|
||||
[EntryPoints.EntryPoint0.ForwardedHeaders]
|
||||
[entryPoints.EntryPoint0.ForwardedHeaders]
|
||||
Insecure = true
|
||||
TrustedIPs = ["foobar", "foobar"]
|
||||
```
|
||||
|
@@ -36,8 +36,10 @@ In the process, routers may use pieces of [middleware](../../middlewares/overvie
|
||||
[entryPoints.mysql-default]
|
||||
address = ":80"
|
||||
[entryPoints.mysql-default]
|
||||
address = ":3306"
|
||||
|
||||
address = ":3306"
|
||||
```
|
||||
|
||||
```toml
|
||||
[tcp]
|
||||
[tcp.routers]
|
||||
[tcp.routers.to-database]
|
||||
@@ -50,8 +52,8 @@ In the process, routers may use pieces of [middleware](../../middlewares/overvie
|
||||
|
||||
### EntryPoints
|
||||
|
||||
If not specified, HTTP routers will accept requests from all defined entrypoints.
|
||||
If you want to limit the router scope to a set of entrypoints, set the entrypoints option.
|
||||
If not specified, HTTP routers will accept requests from all defined entry points.
|
||||
If you want to limit the router scope to a set of entry points, set the `entryPoints` option.
|
||||
|
||||
??? example "Listens to Every EntryPoint"
|
||||
|
||||
@@ -63,7 +65,9 @@ If you want to limit the router scope to a set of entrypoints, set the entrypoin
|
||||
# ...
|
||||
[entryPoints.other]
|
||||
# ...
|
||||
|
||||
```
|
||||
|
||||
```toml
|
||||
[http.routers]
|
||||
[http.routers.Router-1]
|
||||
# By default, routers listen to every entrypoints
|
||||
@@ -81,7 +85,9 @@ If you want to limit the router scope to a set of entrypoints, set the entrypoin
|
||||
# ...
|
||||
[entryPoints.other]
|
||||
# ...
|
||||
|
||||
```
|
||||
|
||||
```toml
|
||||
[http.routers]
|
||||
[http.routers.Router-1]
|
||||
entryPoints = ["web-secure", "other"] # won't listen to entrypoint web
|
||||
@@ -97,26 +103,27 @@ If the rule is verified, the router becomes active, calls middlewares, and then
|
||||
??? example "Host is traefik.io"
|
||||
|
||||
```toml
|
||||
rule = "Host(`traefik.io`)"
|
||||
rule = "Host(`traefik.io`)"
|
||||
```
|
||||
|
||||
??? example "Host is traefik.io OR Host is containo.us AND path is /traefik"
|
||||
|
||||
```toml
|
||||
rule = "Host(`traefik.io`) || (Host(`containo.us`) && Path(`/traefik`))"
|
||||
rule = "Host(`traefik.io`) || (Host(`containo.us`) && Path(`/traefik`))"
|
||||
```
|
||||
|
||||
The table below lists all the available matchers:
|
||||
|
||||
| Rule | Description |
|
||||
|--------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
|
||||
| ``Headers(`key`, `value`)`` | Check if there is a key `key`defined in the headers, with the value `value` |
|
||||
| ``HeadersRegexp(`key`, `regexp`)`` | Check if there is a key `key`defined in the headers, with a value that matches the regular expression `regexp` |
|
||||
| ``Host(`domain-1`, ...)`` | Check if the request domain targets one of the given `domains`. |
|
||||
| ``HostRegexp(`traefik.io`, `{subdomain:[a-z]+}.traefik.io`, ...)`` | Check if the request domain matches the given `regexp`. |
|
||||
| `Method(methods, ...)` | Check if the request method is one of the given `methods` (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) |
|
||||
| ``Path(`path`, `/articles/{category}/{id:[0-9]+}`, ...)`` | Match exact request path. It accepts a sequence of literal and regular expression paths. |
|
||||
| ``PathPrefix(`/products/`, `/articles/{category}/{id:[0-9]+}`)`` | Match request prefix path. It accepts a sequence of literal and regular expression prefix paths. |
|
||||
| ``Query(`foo=bar`, `bar=baz`)`` | Match` Query String parameters. It accepts a sequence of key=value pairs. |
|
||||
| Rule | Description |
|
||||
|----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
|
||||
| ```Headers(`key`, `value`)``` | Check if there is a key `key`defined in the headers, with the value `value` |
|
||||
| ```HeadersRegexp(`key`, `regexp`)``` | Check if there is a key `key`defined in the headers, with a value that matches the regular expression `regexp` |
|
||||
| ```Host(`domain-1`, ...)``` | Check if the request domain targets one of the given `domains`. |
|
||||
| ```HostRegexp(`traefik.io`, `{subdomain:[a-z]+}.traefik.io`, ...)``` | Check if the request domain matches the given `regexp`. |
|
||||
| `Method(methods, ...)` | Check if the request method is one of the given `methods` (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) |
|
||||
| ```Path(`path`, `/articles/{category}/{id:[0-9]+}`, ...)``` | Match exact request path. It accepts a sequence of literal and regular expression paths. |
|
||||
| ```PathPrefix(`/products/`, `/articles/{category}/{id:[0-9]+}`)``` | Match request prefix path. It accepts a sequence of literal and regular expression prefix paths. |
|
||||
| ```Query(`foo=bar`, `bar=baz`)``` | Match` Query String parameters. It accepts a sequence of key=value pairs. |
|
||||
|
||||
!!! important "Regexp Syntax"
|
||||
|
||||
@@ -126,7 +133,7 @@ The table below lists all the available matchers:
|
||||
|
||||
!!! tip "Combining Matchers Using Operators and Parenthesis"
|
||||
|
||||
You can combine multiple matchers using the AND (`&&`) and OR (`||) operators. You can also use parenthesis.
|
||||
You can combine multiple matchers using the AND (`&&`) and OR (`||`) operators. You can also use parenthesis.
|
||||
|
||||
!!! important "Rule, Middleware, and Services"
|
||||
|
||||
@@ -173,7 +180,7 @@ Traefik will terminate the SSL connections (meaning that it will send decrypted
|
||||
|
||||
!!! note "HTTPS & ACME"
|
||||
|
||||
In the current version, with [ACME](../../https-tls/acme.md) enabled, automatic certificate generation will apply to every router declaring a TLS section.
|
||||
In the current version, with [ACME](../../https/acme.md) enabled, automatic certificate generation will apply to every router declaring a TLS section.
|
||||
|
||||
!!! note "Passthrough"
|
||||
|
||||
@@ -200,7 +207,7 @@ Traefik will terminate the SSL connections (meaning that it will send decrypted
|
||||
#### `Options`
|
||||
|
||||
The `Options` field enables fine-grained control of the TLS parameters.
|
||||
It refers to a [tlsOptions](../../https-tls/overview/#configuration-options) and will be applied only if a `Host` rule is defined.
|
||||
It refers to a [tlsOptions](../../https/tls.md#tls-options) and will be applied only if a `Host` rule is defined.
|
||||
|
||||
??? example "Configuring the tls options"
|
||||
|
||||
@@ -212,7 +219,6 @@ It refers to a [tlsOptions](../../https-tls/overview/#configuration-options) and
|
||||
[http.routers.Router-1.tls] # will terminate the TLS request
|
||||
options = "foo"
|
||||
|
||||
|
||||
[tlsOptions]
|
||||
[tlsOptions.foo]
|
||||
minVersion = "VersionTLS12"
|
||||
@@ -226,15 +232,15 @@ It refers to a [tlsOptions](../../https-tls/overview/#configuration-options) and
|
||||
|
||||
### General
|
||||
|
||||
If both HTTP routers and TCP routers listen to the same entrypoints, the TCP routers will apply *before* the HTTP routers.
|
||||
If both HTTP routers and TCP routers listen to the same entry points, the TCP routers will apply *before* the HTTP routers.
|
||||
If no matching route is found for the TCP routers, then the HTTP routers will take over.
|
||||
|
||||
### EntryPoints
|
||||
|
||||
If not specified, TCP routers will accept requests from all defined entrypoints.
|
||||
If you want to limit the router scope to a set of entrypoints, set the entrypoints option.
|
||||
If not specified, TCP routers will accept requests from all defined entry points.
|
||||
If you want to limit the router scope to a set of entry points, set the entry points option.
|
||||
|
||||
??? example "Listens to Every EntryPoint"
|
||||
??? example "Listens to Every Entry Point"
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
@@ -244,7 +250,9 @@ If you want to limit the router scope to a set of entrypoints, set the entrypoin
|
||||
# ...
|
||||
[entryPoints.other]
|
||||
# ...
|
||||
|
||||
```
|
||||
|
||||
```toml
|
||||
[tcp.routers]
|
||||
[tcp.routers.Router-1]
|
||||
# By default, routers listen to every entrypoints
|
||||
@@ -253,7 +261,7 @@ If you want to limit the router scope to a set of entrypoints, set the entrypoin
|
||||
[tcp.routers.Router-1.tls] # will route TLS requests (and ignore non tls requests)
|
||||
```
|
||||
|
||||
??? example "Listens to Specific EntryPoints"
|
||||
??? example "Listens to Specific Entry Points"
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
@@ -263,7 +271,9 @@ If you want to limit the router scope to a set of entrypoints, set the entrypoin
|
||||
# ...
|
||||
[entryPoints.other]
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml
|
||||
[tcp.routers]
|
||||
[tcp.routers.Router-1]
|
||||
entryPoints = ["web-secure", "other"] # won't listen to entrypoint web
|
||||
@@ -274,9 +284,9 @@ If you want to limit the router scope to a set of entrypoints, set the entrypoin
|
||||
|
||||
### Rule
|
||||
|
||||
| Rule | Description |
|
||||
|------------------------------|-------------------------------------------------------------------------|
|
||||
| ``HostSNI(`domain-1`, ...)`` | Check if the Server Name Indication corresponds to the given `domains`. |
|
||||
| Rule | Description |
|
||||
|--------------------------------|-------------------------------------------------------------------------|
|
||||
| ```HostSNI(`domain-1`, ...)``` | Check if the Server Name Indication corresponds to the given `domains`. |
|
||||
|
||||
!!! important "HostSNI & TLS"
|
||||
|
||||
@@ -305,7 +315,7 @@ Services are the target for the router.
|
||||
```toml
|
||||
[tcp.routers]
|
||||
[tcp.routers.Router-1]
|
||||
rule = "Host(`foo-domain`)"
|
||||
rule = "HostSNI(`foo-domain`)"
|
||||
service = "service-id"
|
||||
[tcp.routers.Router-1.tls] # will terminate the TLS request by default
|
||||
```
|
||||
@@ -315,7 +325,7 @@ Services are the target for the router.
|
||||
```toml
|
||||
[tcp.routers]
|
||||
[tcp.routers.Router-1]
|
||||
rule = "Host(`foo-domain`)"
|
||||
rule = "HostSNI(`foo-domain`)"
|
||||
service = "service-id"
|
||||
[tcp.routers.Router-1.tls]
|
||||
passthrough=true
|
||||
@@ -323,24 +333,23 @@ Services are the target for the router.
|
||||
|
||||
!!! note "TLS & ACME"
|
||||
|
||||
In the current version, with [ACME](../../https-tls/acme.md) enabled, automatic certificate generation will apply to every router declaring a TLS section.
|
||||
In the current version, with [ACME](../../https/acme.md) enabled, automatic certificate generation will apply to every router declaring a TLS section.
|
||||
|
||||
#### `Options`
|
||||
|
||||
The `Options` field enables fine-grained control of the TLS parameters.
|
||||
It refers to a [tlsOptions](../../https-tls/overview/#configuration-options) and will be applied only if a `HostSNI` rule is defined.
|
||||
It refers to a [tlsOptions](../../https/tls.md#tls-options) and will be applied only if a `HostSNI` rule is defined.
|
||||
|
||||
??? example "Configuring the tls options"
|
||||
|
||||
```toml
|
||||
[tcp.routers]
|
||||
[tcp.routers.Router-1]
|
||||
rule = "Host(`foo-domain`) && Path(`/foo-path/`)"
|
||||
rule = "HostSNI(`foo-domain`)"
|
||||
service = "service-id"
|
||||
[tcp.routers.Router-1.tls] # will terminate the TLS request
|
||||
options = "foo"
|
||||
|
||||
|
||||
[tlsOptions]
|
||||
[tlsOptions.foo]
|
||||
minVersion = "VersionTLS12"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
server:
|
||||
image: rancher/k3s:v0.2.0
|
||||
image: rancher/k3s:v0.5.0
|
||||
command: server --disable-agent --no-deploy traefik
|
||||
environment:
|
||||
- K3S_CLUSTER_SECRET=somethingtotallyrandom
|
||||
@@ -17,7 +17,7 @@ server:
|
||||
- 6443:6443
|
||||
|
||||
node:
|
||||
image: rancher/k3s:v0.2.0
|
||||
image: rancher/k3s:v0.5.0
|
||||
privileged: true
|
||||
links:
|
||||
- server
|
||||
|
@@ -85,8 +85,9 @@ nav:
|
||||
- 'Routers': 'routing/routers/index.md'
|
||||
- 'Services': 'routing/services/index.md'
|
||||
- 'HTTPS & TLS':
|
||||
- 'Overview': 'https-tls/overview.md'
|
||||
- 'ACME': 'https-tls/acme.md'
|
||||
- 'Overview': 'https/overview.md'
|
||||
- 'TLS': 'https/tls.md'
|
||||
- 'Let''s Encrypt': 'https/acme.md'
|
||||
- 'Middlewares':
|
||||
- 'Overview': 'middlewares/overview.md'
|
||||
- 'AddPrefix': 'middlewares/addprefix.md'
|
||||
@@ -113,8 +114,8 @@ nav:
|
||||
- 'Operations':
|
||||
- 'CLI': 'operations/cli.md'
|
||||
- 'Dashboard' : 'operations/dashboard.md'
|
||||
- 'API': 'operations/api.md'
|
||||
- 'Ping': 'operations/ping.md'
|
||||
- 'Debug Mode': 'operations/debug-mode.md'
|
||||
- 'Observability':
|
||||
- 'Logs': 'observability/logs.md'
|
||||
- 'Access Logs': 'observability/access-logs.md'
|
||||
|
@@ -581,7 +581,7 @@ func CheckAccessLogFormat(c *check.C, line string, i int) {
|
||||
c.Assert(results, checker.HasLen, 14)
|
||||
c.Assert(results[accesslog.OriginStatus], checker.Matches, `^(-|\d{3})$`)
|
||||
c.Assert(results[accesslog.RequestCount], checker.Equals, fmt.Sprintf("%d", i+1))
|
||||
c.Assert(results[accesslog.RouterName], checker.HasPrefix, "\"docker.rt-")
|
||||
c.Assert(results[accesslog.RouterName], checker.Matches, `"rt-.+@docker"`)
|
||||
c.Assert(results[accesslog.ServiceURL], checker.HasPrefix, "\"http://")
|
||||
c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`)
|
||||
}
|
||||
@@ -596,7 +596,7 @@ func checkAccessLogExactValues(c *check.C, line string, i int, v accessLogValue)
|
||||
}
|
||||
c.Assert(results[accesslog.OriginStatus], checker.Equals, v.code)
|
||||
c.Assert(results[accesslog.RequestCount], checker.Equals, fmt.Sprintf("%d", i+1))
|
||||
c.Assert(results[accesslog.RouterName], checker.Matches, `^"?(docker\.)?`+v.routerName+`.*$`)
|
||||
c.Assert(results[accesslog.RouterName], checker.Matches, `^"?`+v.routerName+`.*(@docker)?$`)
|
||||
c.Assert(results[accesslog.ServiceURL], checker.Matches, `^"?`+v.serviceURL+`.*$`)
|
||||
c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`)
|
||||
}
|
||||
|
@@ -77,7 +77,7 @@ func (s *DockerComposeSuite) TestComposeScale(c *check.C) {
|
||||
services := rtconf.Services
|
||||
c.Assert(services, checker.HasLen, 1)
|
||||
for k, v := range services {
|
||||
c.Assert(k, checker.Equals, "docker."+composeService+"_integrationtest"+composeProject)
|
||||
c.Assert(k, checker.Equals, composeService+"_integrationtest"+composeProject+"@docker")
|
||||
c.Assert(v.LoadBalancer.Servers, checker.HasLen, serviceCount)
|
||||
// We could break here, but we don't just to keep us honest.
|
||||
}
|
||||
|
@@ -44,7 +44,6 @@ level = "DEBUG"
|
||||
[[http.services.service2.LoadBalancer.Servers]]
|
||||
URL = "http://127.0.0.1:9020"
|
||||
|
||||
|
||||
[[tls]]
|
||||
[tls.certificate]
|
||||
certFile = "fixtures/https/snitest.com.cert"
|
||||
|
@@ -41,3 +41,18 @@ spec:
|
||||
plural: ingressroutetcps
|
||||
singular: ingressroutetcp
|
||||
scope: Namespaced
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: tlsoptions.traefik.containo.us
|
||||
|
||||
spec:
|
||||
group: traefik.containo.us
|
||||
version: v1alpha1
|
||||
names:
|
||||
kind: TLSOption
|
||||
plural: tlsoptions
|
||||
singular: tlsoption
|
||||
scope: Namespaced
|
||||
|
@@ -15,3 +15,7 @@ spec:
|
||||
services:
|
||||
- name: whoami
|
||||
port: 80
|
||||
|
||||
tls:
|
||||
options:
|
||||
name: mytlsoption
|
||||
|
12
integration/fixtures/k8s/03-tlsoption.yml
Normal file
12
integration/fixtures/k8s/03-tlsoption.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TLSOption
|
||||
metadata:
|
||||
name: mytlsoption
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
minversion: VersionTLS12
|
||||
snistrict: true
|
||||
ciphersuites:
|
||||
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
@@ -12,3 +12,6 @@ spec:
|
||||
services:
|
||||
- name: whoamitcp
|
||||
port: 8080
|
||||
tls:
|
||||
options:
|
||||
name: mytlsoption
|
||||
|
@@ -13,7 +13,7 @@ level = "DEBUG"
|
||||
address = ":8001"
|
||||
|
||||
[api]
|
||||
middlewares = ["file.authentication"]
|
||||
middlewares = ["authentication@file"]
|
||||
|
||||
[ping]
|
||||
|
||||
|
@@ -191,7 +191,7 @@ func (s *HTTPSSuite) TestWithTLSOptions(c *check.C) {
|
||||
c.Assert(err.Error(), checker.Contains, "protocol version not supported")
|
||||
|
||||
// with unknown tls option
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("unknown TLS options: unknown"))
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("unknown TLS options: unknown@file"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
server:
|
||||
image: rancher/k3s:v0.2.0
|
||||
image: rancher/k3s:v0.5.0
|
||||
command: server --disable-agent --no-deploy traefik
|
||||
environment:
|
||||
- K3S_CLUSTER_SECRET=somethingtotallyrandom
|
||||
@@ -12,7 +12,7 @@ server:
|
||||
- 6443:6443
|
||||
|
||||
node:
|
||||
image: rancher/k3s:v0.2.0
|
||||
image: rancher/k3s:v0.5.0
|
||||
privileged: true
|
||||
links:
|
||||
- server
|
||||
|
@@ -161,7 +161,7 @@ func (s *SimpleSuite) TestApiOnSameEntryPoint(c *check.C) {
|
||||
s.createComposeProject(c, "base")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--api.entryPoint=http", "--global.debug", "--providers.docker")
|
||||
cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--api.entryPoint=http", "--log.level=DEBUG", "--providers.docker")
|
||||
defer output(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -241,7 +241,7 @@ func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) {
|
||||
s.createComposeProject(c, "base")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--global.debug", "--providers.docker", "--api")
|
||||
cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--log.level=DEBUG", "--providers.docker", "--api")
|
||||
defer output(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -259,7 +259,7 @@ func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) {
|
||||
s.createComposeProject(c, "base")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--global.debug", "--providers.docker", "--api")
|
||||
cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--log.level=DEBUG", "--providers.docker", "--api")
|
||||
defer output(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -277,7 +277,7 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntrypoint(c *check.C) {
|
||||
s.createComposeProject(c, "base")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--api", "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0", "--providers.docker", "--global.debug")
|
||||
cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--api", "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0", "--providers.docker", "--log.level=DEBUG")
|
||||
defer output(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -444,8 +444,8 @@ func (s *SimpleSuite) TestMultiprovider(c *check.C) {
|
||||
Routers: map[string]*config.Router{
|
||||
"router1": {
|
||||
EntryPoints: []string{"web"},
|
||||
Middlewares: []string{"file.customheader"},
|
||||
Service: "file.service",
|
||||
Middlewares: []string{"customheader@file"},
|
||||
Service: "service@file",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
},
|
||||
},
|
||||
|
53
integration/testdata/rawdata-crd.json
vendored
53
integration/testdata/rawdata-crd.json
vendored
@@ -1,14 +1,17 @@
|
||||
{
|
||||
"routers": {
|
||||
"kubernetescrd.default/test-crd-6b204d94623b3df4370c": {
|
||||
"default/test-crd-6b204d94623b3df4370c@kubernetescrd": {
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"service": "default/test-crd-6b204d94623b3df4370c",
|
||||
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/bar`)",
|
||||
"priority": 12
|
||||
"priority": 12,
|
||||
"tls": {
|
||||
"options": "default/mytlsoption"
|
||||
}
|
||||
},
|
||||
"kubernetescrd.default/test2-crd-23c7f4c450289ee29016": {
|
||||
"default/test2-crd-23c7f4c450289ee29016@kubernetescrd": {
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
@@ -20,82 +23,86 @@
|
||||
}
|
||||
},
|
||||
"middlewares": {
|
||||
"kubernetescrd.default/stripprefix": {
|
||||
"default/stripprefix@kubernetescrd": {
|
||||
"stripPrefix": {
|
||||
"prefixes": [
|
||||
"/tobestripped"
|
||||
]
|
||||
},
|
||||
"usedBy": [
|
||||
"kubernetescrd.default/test2-crd-23c7f4c450289ee29016"
|
||||
"default/test2-crd-23c7f4c450289ee29016@kubernetescrd"
|
||||
]
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"kubernetescrd.default/test-crd-6b204d94623b3df4370c": {
|
||||
"default/test-crd-6b204d94623b3df4370c@kubernetescrd": {
|
||||
"loadbalancer": {
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://10.42.0.4:80"
|
||||
"url": "http://10.42.0.2:80"
|
||||
},
|
||||
{
|
||||
"url": "http://10.42.0.5:80"
|
||||
"url": "http://10.42.0.6:80"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
},
|
||||
"usedBy": [
|
||||
"kubernetescrd.default/test-crd-6b204d94623b3df4370c"
|
||||
"default/test-crd-6b204d94623b3df4370c@kubernetescrd"
|
||||
],
|
||||
"serverStatus": {
|
||||
"http://10.42.0.4:80": "UP",
|
||||
"http://10.42.0.5:80": "UP"
|
||||
"http://10.42.0.2:80": "UP",
|
||||
"http://10.42.0.6:80": "UP"
|
||||
}
|
||||
},
|
||||
"kubernetescrd.default/test2-crd-23c7f4c450289ee29016": {
|
||||
"default/test2-crd-23c7f4c450289ee29016@kubernetescrd": {
|
||||
"loadbalancer": {
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://10.42.0.4:80"
|
||||
"url": "http://10.42.0.2:80"
|
||||
},
|
||||
{
|
||||
"url": "http://10.42.0.5:80"
|
||||
"url": "http://10.42.0.6:80"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
},
|
||||
"usedBy": [
|
||||
"kubernetescrd.default/test2-crd-23c7f4c450289ee29016"
|
||||
"default/test2-crd-23c7f4c450289ee29016@kubernetescrd"
|
||||
],
|
||||
"serverStatus": {
|
||||
"http://10.42.0.4:80": "UP",
|
||||
"http://10.42.0.5:80": "UP"
|
||||
"http://10.42.0.2:80": "UP",
|
||||
"http://10.42.0.6:80": "UP"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tcpRouters": {
|
||||
"kubernetescrd.default/test3-crd-673acf455cb2dab0b43a": {
|
||||
"default/test3-crd-673acf455cb2dab0b43a@kubernetescrd": {
|
||||
"entryPoints": [
|
||||
"footcp"
|
||||
],
|
||||
"service": "default/test3-crd-673acf455cb2dab0b43a",
|
||||
"rule": "HostSNI(`*`)"
|
||||
"rule": "HostSNI(`*`)",
|
||||
"tls": {
|
||||
"passthrough": false,
|
||||
"options": "default/mytlsoption"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tcpServices": {
|
||||
"kubernetescrd.default/test3-crd-673acf455cb2dab0b43a": {
|
||||
"default/test3-crd-673acf455cb2dab0b43a@kubernetescrd": {
|
||||
"loadbalancer": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "10.42.0.2:8080"
|
||||
"address": "10.42.0.3:8080"
|
||||
},
|
||||
{
|
||||
"address": "10.42.0.3:8080"
|
||||
"address": "10.42.0.4:8080"
|
||||
}
|
||||
]
|
||||
},
|
||||
"usedBy": [
|
||||
"kubernetescrd.default/test3-crd-673acf455cb2dab0b43a"
|
||||
"default/test3-crd-673acf455cb2dab0b43a@kubernetescrd"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
6
integration/testdata/rawdata-ingress.json
vendored
6
integration/testdata/rawdata-ingress.json
vendored
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"routers": {
|
||||
"kubernetes.whoami-test/whoami": {
|
||||
"whoami-test/whoami@kubernetes": {
|
||||
"entryPoints": null,
|
||||
"service": "default/whoami/http",
|
||||
"rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"kubernetes.default/whoami/http": {
|
||||
"default/whoami/http@kubernetes": {
|
||||
"loadbalancer": {
|
||||
"servers": [
|
||||
{
|
||||
@@ -20,7 +20,7 @@
|
||||
"passHostHeader": true
|
||||
},
|
||||
"usedBy": [
|
||||
"kubernetes.whoami-test/whoami"
|
||||
"whoami-test/whoami@kubernetes"
|
||||
],
|
||||
"serverStatus": {
|
||||
"http://10.42.0.2:80": "UP",
|
||||
|
@@ -89,7 +89,7 @@ func (s *TracingSuite) TestZipkinRateLimit(c *check.C) {
|
||||
err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://"+s.ZipkinIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("forward service1/file.router1", "file.ratelimit"))
|
||||
err = try.GetRequest("http://"+s.ZipkinIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("forward service1/router1@file", "ratelimit@file"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func (s *TracingSuite) TestZipkinRetry(c *check.C) {
|
||||
err = try.GetRequest("http://127.0.0.1:8000/retry", 500*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://"+s.ZipkinIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("forward service2/file.router2", "file.retry"))
|
||||
err = try.GetRequest("http://"+s.ZipkinIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("forward service2/router2@file", "retry@file"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -144,6 +144,6 @@ func (s *TracingSuite) TestZipkinAuth(c *check.C) {
|
||||
err = try.GetRequest("http://127.0.0.1:8000/auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://"+s.ZipkinIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("entrypoint web", "file.basic-auth"))
|
||||
err = try.GetRequest("http://"+s.ZipkinIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("entrypoint web", "basic-auth@file"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
@@ -49,7 +49,7 @@ func (s *WebsocketSuite) TestBase(c *check.C) {
|
||||
})
|
||||
|
||||
defer os.Remove(file)
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug")
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -99,7 +99,7 @@ func (s *WebsocketSuite) TestWrongOrigin(c *check.C) {
|
||||
})
|
||||
|
||||
defer os.Remove(file)
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug")
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -149,7 +149,7 @@ func (s *WebsocketSuite) TestOrigin(c *check.C) {
|
||||
})
|
||||
|
||||
defer os.Remove(file)
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug")
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -210,7 +210,7 @@ func (s *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) {
|
||||
})
|
||||
|
||||
defer os.Remove(file)
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug")
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -268,7 +268,7 @@ func (s *WebsocketSuite) TestSSLTermination(c *check.C) {
|
||||
})
|
||||
|
||||
defer os.Remove(file)
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug")
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -331,7 +331,7 @@ func (s *WebsocketSuite) TestBasicAuth(c *check.C) {
|
||||
})
|
||||
|
||||
defer os.Remove(file)
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug")
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -375,7 +375,7 @@ func (s *WebsocketSuite) TestSpecificResponseFromBackend(c *check.C) {
|
||||
})
|
||||
|
||||
defer os.Remove(file)
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug")
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -421,7 +421,7 @@ func (s *WebsocketSuite) TestURLWithURLEncodedChar(c *check.C) {
|
||||
})
|
||||
|
||||
defer os.Remove(file)
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug")
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -476,7 +476,7 @@ func (s *WebsocketSuite) TestSSLhttp2(c *check.C) {
|
||||
})
|
||||
|
||||
defer os.Remove(file)
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug", "--accesslog")
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG", "--accesslog")
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
@@ -535,7 +535,7 @@ func (s *WebsocketSuite) TestHeaderAreForwared(c *check.C) {
|
||||
})
|
||||
|
||||
defer os.Remove(file)
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--global.debug")
|
||||
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
|
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/pkg/ping"
|
||||
"github.com/containous/traefik/pkg/provider"
|
||||
"github.com/containous/traefik/pkg/provider/acme"
|
||||
acmeprovider "github.com/containous/traefik/pkg/provider/acme"
|
||||
"github.com/containous/traefik/pkg/provider/docker"
|
||||
@@ -28,7 +27,6 @@ func TestDo_globalConfiguration(t *testing.T) {
|
||||
|
||||
sendAnonymousUsage := true
|
||||
config.Global = &static.Global{
|
||||
Debug: true,
|
||||
CheckNewVersion: true,
|
||||
SendAnonymousUsage: &sendAnonymousUsage,
|
||||
}
|
||||
@@ -154,20 +152,7 @@ func TestDo_globalConfiguration(t *testing.T) {
|
||||
}
|
||||
|
||||
config.Providers.Docker = &docker.Provider{
|
||||
Constrainer: provider.Constrainer{
|
||||
Constraints: []*types.Constraint{
|
||||
{
|
||||
Key: "file Constraints Key 1",
|
||||
Value: "file Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "file Constraints Key 1",
|
||||
Value: "file Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Constraints: `Label("foo", "bar")`,
|
||||
Watch: true,
|
||||
Endpoint: "MyEndPoint",
|
||||
DefaultRule: "PathPrefix(`/`)",
|
||||
|
@@ -1,8 +1,12 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"io"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
@@ -11,14 +15,14 @@ import (
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
"github.com/containous/traefik/pkg/version"
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/unrolled/render"
|
||||
)
|
||||
|
||||
var templateRenderer jsonRenderer = render.New(render.Options{Directory: "nowhere"})
|
||||
const (
|
||||
defaultPerPage = 100
|
||||
defaultPage = 1
|
||||
)
|
||||
|
||||
type jsonRenderer interface {
|
||||
JSON(w io.Writer, status int, v interface{}) error
|
||||
}
|
||||
const nextPageHeader = "X-Next-Page"
|
||||
|
||||
type serviceInfoRepresentation struct {
|
||||
*config.ServiceInfo
|
||||
@@ -34,6 +38,43 @@ type RunTimeRepresentation struct {
|
||||
TCPServices map[string]*config.TCPServiceInfo `json:"tcpServices,omitempty"`
|
||||
}
|
||||
|
||||
type routerRepresentation struct {
|
||||
*config.RouterInfo
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type serviceRepresentation struct {
|
||||
*config.ServiceInfo
|
||||
ServerStatus map[string]string `json:"serverStatus,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type middlewareRepresentation struct {
|
||||
*config.MiddlewareInfo
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type tcpRouterRepresentation struct {
|
||||
*config.TCPRouterInfo
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type tcpServiceRepresentation struct {
|
||||
*config.TCPServiceInfo
|
||||
Name string `json:"name,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type pageInfo struct {
|
||||
startIndex int
|
||||
endIndex int
|
||||
nextPage int
|
||||
}
|
||||
|
||||
// Handler serves the configuration and status of Traefik on API endpoints.
|
||||
type Handler struct {
|
||||
dashboard bool
|
||||
@@ -59,7 +100,7 @@ func New(staticConfig static.Configuration, runtimeConfig *config.RuntimeConfigu
|
||||
statistics: staticConfig.API.Statistics,
|
||||
dashboardAssets: staticConfig.API.DashboardAssets,
|
||||
runtimeConfiguration: rConfig,
|
||||
debug: staticConfig.Global.Debug,
|
||||
debug: staticConfig.API.Debug,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,9 +112,21 @@ func (h Handler) Append(router *mux.Router) {
|
||||
|
||||
router.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRuntimeConfiguration)
|
||||
|
||||
router.Methods(http.MethodGet).Path("/api/http/routers").HandlerFunc(h.getRouters)
|
||||
router.Methods(http.MethodGet).Path("/api/http/routers/{routerID}").HandlerFunc(h.getRouter)
|
||||
router.Methods(http.MethodGet).Path("/api/http/services").HandlerFunc(h.getServices)
|
||||
router.Methods(http.MethodGet).Path("/api/http/services/{serviceID}").HandlerFunc(h.getService)
|
||||
router.Methods(http.MethodGet).Path("/api/http/middlewares").HandlerFunc(h.getMiddlewares)
|
||||
router.Methods(http.MethodGet).Path("/api/http/middlewares/{middlewareID}").HandlerFunc(h.getMiddleware)
|
||||
|
||||
router.Methods(http.MethodGet).Path("/api/tcp/routers").HandlerFunc(h.getTCPRouters)
|
||||
router.Methods(http.MethodGet).Path("/api/tcp/routers/{routerID}").HandlerFunc(h.getTCPRouter)
|
||||
router.Methods(http.MethodGet).Path("/api/tcp/services").HandlerFunc(h.getTCPServices)
|
||||
router.Methods(http.MethodGet).Path("/api/tcp/services/{serviceID}").HandlerFunc(h.getTCPService)
|
||||
|
||||
// FIXME stats
|
||||
// health route
|
||||
//router.Methods(http.MethodGet).Path("/health").HandlerFunc(p.getHealthHandler)
|
||||
// router.Methods(http.MethodGet).Path("/health").HandlerFunc(p.getHealthHandler)
|
||||
|
||||
version.Handler{}.Append(router)
|
||||
|
||||
@@ -82,6 +135,268 @@ func (h Handler) Append(router *mux.Router) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]routerRepresentation, 0, len(h.runtimeConfiguration.Routers))
|
||||
|
||||
for name, rt := range h.runtimeConfiguration.Routers {
|
||||
results = append(results, routerRepresentation{
|
||||
RouterInfo: rt,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) {
|
||||
routerID := mux.Vars(request)["routerID"]
|
||||
|
||||
router, ok := h.runtimeConfiguration.Routers[routerID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := routerRepresentation{
|
||||
RouterInfo: router,
|
||||
Name: routerID,
|
||||
Provider: getProviderName(routerID),
|
||||
}
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]serviceRepresentation, 0, len(h.runtimeConfiguration.Services))
|
||||
|
||||
for name, si := range h.runtimeConfiguration.Services {
|
||||
results = append(results, serviceRepresentation{
|
||||
ServiceInfo: si,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
ServerStatus: si.GetAllStatus(),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getService(rw http.ResponseWriter, request *http.Request) {
|
||||
serviceID := mux.Vars(request)["serviceID"]
|
||||
|
||||
service, ok := h.runtimeConfiguration.Services[serviceID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := serviceRepresentation{
|
||||
ServiceInfo: service,
|
||||
Name: serviceID,
|
||||
Provider: getProviderName(serviceID),
|
||||
ServerStatus: service.GetAllStatus(),
|
||||
}
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]middlewareRepresentation, 0, len(h.runtimeConfiguration.Middlewares))
|
||||
|
||||
for name, mi := range h.runtimeConfiguration.Middlewares {
|
||||
results = append(results, middlewareRepresentation{
|
||||
MiddlewareInfo: mi,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) {
|
||||
middlewareID := mux.Vars(request)["middlewareID"]
|
||||
|
||||
middleware, ok := h.runtimeConfiguration.Middlewares[middlewareID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := middlewareRepresentation{
|
||||
MiddlewareInfo: middleware,
|
||||
Name: middlewareID,
|
||||
Provider: getProviderName(middlewareID),
|
||||
}
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]tcpRouterRepresentation, 0, len(h.runtimeConfiguration.TCPRouters))
|
||||
|
||||
for name, rt := range h.runtimeConfiguration.TCPRouters {
|
||||
results = append(results, tcpRouterRepresentation{
|
||||
TCPRouterInfo: rt,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) {
|
||||
routerID := mux.Vars(request)["routerID"]
|
||||
|
||||
router, ok := h.runtimeConfiguration.TCPRouters[routerID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := tcpRouterRepresentation{
|
||||
TCPRouterInfo: router,
|
||||
Name: routerID,
|
||||
Provider: getProviderName(routerID),
|
||||
}
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) {
|
||||
results := make([]tcpServiceRepresentation, 0, len(h.runtimeConfiguration.TCPServices))
|
||||
|
||||
for name, si := range h.runtimeConfiguration.TCPServices {
|
||||
results = append(results, tcpServiceRepresentation{
|
||||
TCPServiceInfo: si,
|
||||
Name: name,
|
||||
Provider: getProviderName(name),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
pageInfo, err := pagination(request, len(results))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
|
||||
|
||||
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) {
|
||||
serviceID := mux.Vars(request)["serviceID"]
|
||||
|
||||
service, ok := h.runtimeConfiguration.TCPServices[serviceID]
|
||||
if !ok {
|
||||
http.NotFound(rw, request)
|
||||
return
|
||||
}
|
||||
|
||||
result := tcpServiceRepresentation{
|
||||
TCPServiceInfo: service,
|
||||
Name: serviceID,
|
||||
Provider: getProviderName(serviceID),
|
||||
}
|
||||
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.Request) {
|
||||
siRepr := make(map[string]*serviceInfoRepresentation, len(h.runtimeConfiguration.Services))
|
||||
for k, v := range h.runtimeConfiguration.Services {
|
||||
@@ -91,7 +406,7 @@ func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.R
|
||||
}
|
||||
}
|
||||
|
||||
rtRepr := RunTimeRepresentation{
|
||||
result := RunTimeRepresentation{
|
||||
Routers: h.runtimeConfiguration.Routers,
|
||||
Middlewares: h.runtimeConfiguration.Middlewares,
|
||||
Services: siRepr,
|
||||
@@ -99,9 +414,55 @@ func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.R
|
||||
TCPServices: h.runtimeConfiguration.TCPServices,
|
||||
}
|
||||
|
||||
err := templateRenderer.JSON(rw, http.StatusOK, rtRepr)
|
||||
err := json.NewEncoder(rw).Encode(result)
|
||||
if err != nil {
|
||||
log.FromContext(request.Context()).Error(err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func pagination(request *http.Request, max int) (pageInfo, error) {
|
||||
perPage, err := getIntParam(request, "per_page", defaultPerPage)
|
||||
if err != nil {
|
||||
return pageInfo{}, err
|
||||
}
|
||||
|
||||
page, err := getIntParam(request, "page", defaultPage)
|
||||
if err != nil {
|
||||
return pageInfo{}, err
|
||||
}
|
||||
|
||||
startIndex := (page - 1) * perPage
|
||||
if startIndex != 0 && startIndex >= max {
|
||||
return pageInfo{}, fmt.Errorf("invalid request: page: %d, per_page: %d", page, perPage)
|
||||
}
|
||||
|
||||
endIndex := startIndex + perPage
|
||||
if endIndex >= max {
|
||||
endIndex = max
|
||||
}
|
||||
|
||||
nextPage := 1
|
||||
if page*perPage < max {
|
||||
nextPage = page + 1
|
||||
}
|
||||
|
||||
return pageInfo{startIndex: startIndex, endIndex: endIndex, nextPage: nextPage}, nil
|
||||
}
|
||||
|
||||
func getIntParam(request *http.Request, key string, defaultValue int) (int, error) {
|
||||
raw := request.URL.Query().Get(key)
|
||||
if raw == "" {
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
value, err := strconv.Atoi(raw)
|
||||
if err != nil || value <= 0 {
|
||||
return 0, fmt.Errorf("invalid request: %s: %d", key, value)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func getProviderName(id string) string {
|
||||
return strings.SplitN(id, "@", 2)[1]
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
44
pkg/api/testdata/getrawdata.json
vendored
44
pkg/api/testdata/getrawdata.json
vendored
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"routers": {
|
||||
"myprovider.bar": {
|
||||
"bar@myprovider": {
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"middlewares": [
|
||||
"auth",
|
||||
"anotherprovider.addPrefixTest"
|
||||
"addPrefixTest@anotherprovider"
|
||||
],
|
||||
"service": "myprovider.foo-service",
|
||||
"service": "foo-service@myprovider",
|
||||
"rule": "Host(`foo.bar`)"
|
||||
},
|
||||
"myprovider.test": {
|
||||
"test@myprovider": {
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
@@ -19,41 +19,41 @@
|
||||
"addPrefixTest",
|
||||
"auth"
|
||||
],
|
||||
"service": "myprovider.foo-service",
|
||||
"service": "foo-service@myprovider",
|
||||
"rule": "Host(`foo.bar.other`)"
|
||||
}
|
||||
},
|
||||
"middlewares": {
|
||||
"anotherprovider.addPrefixTest": {
|
||||
"addPrefixTest@anotherprovider": {
|
||||
"addPrefix": {
|
||||
"prefix": "/toto"
|
||||
},
|
||||
"usedBy": [
|
||||
"myprovider.bar"
|
||||
"bar@myprovider"
|
||||
]
|
||||
},
|
||||
"myprovider.addPrefixTest": {
|
||||
"addPrefixTest@myprovider": {
|
||||
"addPrefix": {
|
||||
"prefix": "/titi"
|
||||
},
|
||||
"usedBy": [
|
||||
"myprovider.test"
|
||||
"test@myprovider"
|
||||
]
|
||||
},
|
||||
"myprovider.auth": {
|
||||
"auth@myprovider": {
|
||||
"basicAuth": {
|
||||
"users": [
|
||||
"admin:admin"
|
||||
]
|
||||
},
|
||||
"usedBy": [
|
||||
"myprovider.bar",
|
||||
"myprovider.test"
|
||||
"bar@myprovider",
|
||||
"test@myprovider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"myprovider.foo-service": {
|
||||
"foo-service@myprovider": {
|
||||
"loadbalancer": {
|
||||
"servers": [
|
||||
{
|
||||
@@ -63,29 +63,29 @@
|
||||
"passHostHeader": false
|
||||
},
|
||||
"usedBy": [
|
||||
"myprovider.bar",
|
||||
"myprovider.test"
|
||||
"bar@myprovider",
|
||||
"test@myprovider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tcpRouters": {
|
||||
"myprovider.tcpbar": {
|
||||
"tcpbar@myprovider": {
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"service": "myprovider.tcpfoo-service",
|
||||
"service": "tcpfoo-service@myprovider",
|
||||
"rule": "HostSNI(`foo.bar`)"
|
||||
},
|
||||
"myprovider.tcptest": {
|
||||
"tcptest@myprovider": {
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"service": "myprovider.tcpfoo-service",
|
||||
"service": "tcpfoo-service@myprovider",
|
||||
"rule": "HostSNI(`foo.bar.other`)"
|
||||
}
|
||||
},
|
||||
"tcpServices": {
|
||||
"myprovider.tcpfoo-service": {
|
||||
"tcpfoo-service@myprovider": {
|
||||
"loadbalancer": {
|
||||
"servers": [
|
||||
{
|
||||
@@ -94,8 +94,8 @@
|
||||
]
|
||||
},
|
||||
"usedBy": [
|
||||
"myprovider.tcpbar",
|
||||
"myprovider.tcptest"
|
||||
"tcpbar@myprovider",
|
||||
"tcptest@myprovider"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
13
pkg/api/testdata/middleware-auth.json
vendored
Normal file
13
pkg/api/testdata/middleware-auth.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"basicAuth": {
|
||||
"users": [
|
||||
"admin:admin"
|
||||
]
|
||||
},
|
||||
"name": "auth@myprovider",
|
||||
"provider": "myprovider",
|
||||
"usedBy": [
|
||||
"bar@myprovider",
|
||||
"test@myprovider"
|
||||
]
|
||||
}
|
1
pkg/api/testdata/middlewares-empty.json
vendored
Normal file
1
pkg/api/testdata/middlewares-empty.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
12
pkg/api/testdata/middlewares-page2.json
vendored
Normal file
12
pkg/api/testdata/middlewares-page2.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"addPrefix": {
|
||||
"prefix": "/titi"
|
||||
},
|
||||
"name": "addPrefixTest@myprovider",
|
||||
"provider": "myprovider",
|
||||
"usedBy": [
|
||||
"test@myprovider"
|
||||
]
|
||||
}
|
||||
]
|
35
pkg/api/testdata/middlewares.json
vendored
Normal file
35
pkg/api/testdata/middlewares.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
[
|
||||
{
|
||||
"addPrefix": {
|
||||
"prefix": "/toto"
|
||||
},
|
||||
"name": "addPrefixTest@anotherprovider",
|
||||
"provider": "anotherprovider",
|
||||
"usedBy": [
|
||||
"bar@myprovider"
|
||||
]
|
||||
},
|
||||
{
|
||||
"addPrefix": {
|
||||
"prefix": "/titi"
|
||||
},
|
||||
"name": "addPrefixTest@myprovider",
|
||||
"provider": "myprovider",
|
||||
"usedBy": [
|
||||
"test@myprovider"
|
||||
]
|
||||
},
|
||||
{
|
||||
"basicAuth": {
|
||||
"users": [
|
||||
"admin:admin"
|
||||
]
|
||||
},
|
||||
"name": "auth@myprovider",
|
||||
"provider": "myprovider",
|
||||
"usedBy": [
|
||||
"bar@myprovider",
|
||||
"test@myprovider"
|
||||
]
|
||||
}
|
||||
]
|
13
pkg/api/testdata/router-bar.json
vendored
Normal file
13
pkg/api/testdata/router-bar.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"middlewares": [
|
||||
"auth",
|
||||
"addPrefixTest@anotherprovider"
|
||||
],
|
||||
"name": "bar@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`foo.bar`)",
|
||||
"service": "foo-service@myprovider"
|
||||
}
|
1
pkg/api/testdata/routers-empty.json
vendored
Normal file
1
pkg/api/testdata/routers-empty.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
47
pkg/api/testdata/routers-many-lastpage.json
vendored
Normal file
47
pkg/api/testdata/routers-many-lastpage.json
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
[
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"name": "bar14@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`foo.bar14`)",
|
||||
"service": "foo-service@myprovider"
|
||||
},
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"name": "bar15@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`foo.bar15`)",
|
||||
"service": "foo-service@myprovider"
|
||||
},
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"name": "bar16@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`foo.bar16`)",
|
||||
"service": "foo-service@myprovider"
|
||||
},
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"name": "bar17@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`foo.bar17`)",
|
||||
"service": "foo-service@myprovider"
|
||||
},
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"name": "bar18@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`foo.bar18`)",
|
||||
"service": "foo-service@myprovider"
|
||||
}
|
||||
]
|
11
pkg/api/testdata/routers-page2.json
vendored
Normal file
11
pkg/api/testdata/routers-page2.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"name": "baz@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`toto.bar`)",
|
||||
"service": "foo-service@myprovider"
|
||||
}
|
||||
]
|
28
pkg/api/testdata/routers.json
vendored
Normal file
28
pkg/api/testdata/routers.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
[
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"middlewares": [
|
||||
"auth",
|
||||
"addPrefixTest@anotherprovider"
|
||||
],
|
||||
"name": "bar@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`foo.bar`)",
|
||||
"service": "foo-service@myprovider"
|
||||
},
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"middlewares": [
|
||||
"addPrefixTest",
|
||||
"auth"
|
||||
],
|
||||
"name": "test@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`foo.bar.other`)",
|
||||
"service": "foo-service@myprovider"
|
||||
}
|
||||
]
|
19
pkg/api/testdata/service-bar.json
vendored
Normal file
19
pkg/api/testdata/service-bar.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"loadbalancer": {
|
||||
"passHostHeader": false,
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://127.0.0.1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "bar@myprovider",
|
||||
"provider": "myprovider",
|
||||
"serverStatus": {
|
||||
"http://127.0.0.1": "UP"
|
||||
},
|
||||
"usedBy": [
|
||||
"foo@myprovider",
|
||||
"test@myprovider"
|
||||
]
|
||||
}
|
1
pkg/api/testdata/services-empty.json
vendored
Normal file
1
pkg/api/testdata/services-empty.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
20
pkg/api/testdata/services-page2.json
vendored
Normal file
20
pkg/api/testdata/services-page2.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"loadbalancer": {
|
||||
"passHostHeader": false,
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://127.0.0.2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "baz@myprovider",
|
||||
"provider": "myprovider",
|
||||
"serverStatus": {
|
||||
"http://127.0.0.2": "UP"
|
||||
},
|
||||
"usedBy": [
|
||||
"foo@myprovider"
|
||||
]
|
||||
}
|
||||
]
|
39
pkg/api/testdata/services.json
vendored
Normal file
39
pkg/api/testdata/services.json
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
[
|
||||
{
|
||||
"loadbalancer": {
|
||||
"passHostHeader": false,
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://127.0.0.1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "bar@myprovider",
|
||||
"provider": "myprovider",
|
||||
"serverStatus": {
|
||||
"http://127.0.0.1": "UP"
|
||||
},
|
||||
"usedBy": [
|
||||
"foo@myprovider",
|
||||
"test@myprovider"
|
||||
]
|
||||
},
|
||||
{
|
||||
"loadbalancer": {
|
||||
"passHostHeader": false,
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://127.0.0.2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "baz@myprovider",
|
||||
"provider": "myprovider",
|
||||
"serverStatus": {
|
||||
"http://127.0.0.2": "UP"
|
||||
},
|
||||
"usedBy": [
|
||||
"foo@myprovider"
|
||||
]
|
||||
}
|
||||
]
|
9
pkg/api/testdata/tcprouter-bar.json
vendored
Normal file
9
pkg/api/testdata/tcprouter-bar.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"name": "bar@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`foo.bar`)",
|
||||
"service": "foo-service@myprovider"
|
||||
}
|
1
pkg/api/testdata/tcprouters-empty.json
vendored
Normal file
1
pkg/api/testdata/tcprouters-empty.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
11
pkg/api/testdata/tcprouters-page2.json
vendored
Normal file
11
pkg/api/testdata/tcprouters-page2.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"name": "baz@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`toto.bar`)",
|
||||
"service": "foo-service@myprovider"
|
||||
}
|
||||
]
|
23
pkg/api/testdata/tcprouters.json
vendored
Normal file
23
pkg/api/testdata/tcprouters.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
[
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"name": "bar@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`foo.bar`)",
|
||||
"service": "foo-service@myprovider"
|
||||
},
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"name": "test@myprovider",
|
||||
"provider": "myprovider",
|
||||
"rule": "Host(`foo.bar.other`)",
|
||||
"service": "foo-service@myprovider",
|
||||
"tls": {
|
||||
"passthrough": false
|
||||
}
|
||||
}
|
||||
]
|
15
pkg/api/testdata/tcpservice-bar.json
vendored
Normal file
15
pkg/api/testdata/tcpservice-bar.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"loadbalancer": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "127.0.0.1:2345"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "bar@myprovider",
|
||||
"provider": "myprovider",
|
||||
"usedBy": [
|
||||
"foo@myprovider",
|
||||
"test@myprovider"
|
||||
]
|
||||
}
|
1
pkg/api/testdata/tcpservices-empty.json
vendored
Normal file
1
pkg/api/testdata/tcpservices-empty.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
16
pkg/api/testdata/tcpservices-page2.json
vendored
Normal file
16
pkg/api/testdata/tcpservices-page2.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"loadbalancer": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "127.0.0.2:2345"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "baz@myprovider",
|
||||
"provider": "myprovider",
|
||||
"usedBy": [
|
||||
"foo@myprovider"
|
||||
]
|
||||
}
|
||||
]
|
31
pkg/api/testdata/tcpservices.json
vendored
Normal file
31
pkg/api/testdata/tcpservices.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
[
|
||||
{
|
||||
"loadbalancer": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "127.0.0.1:2345"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "bar@myprovider",
|
||||
"provider": "myprovider",
|
||||
"usedBy": [
|
||||
"foo@myprovider",
|
||||
"test@myprovider"
|
||||
]
|
||||
},
|
||||
{
|
||||
"loadbalancer": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "127.0.0.2:2345"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "baz@myprovider",
|
||||
"provider": "myprovider",
|
||||
"usedBy": [
|
||||
"foo@myprovider"
|
||||
]
|
||||
}
|
||||
]
|
@@ -3,7 +3,6 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -17,7 +16,9 @@ type Command struct {
|
||||
Resources []ResourceLoader
|
||||
Run func([]string) error
|
||||
Hidden bool
|
||||
subCommands []*Command
|
||||
// AllowArg if not set, disallows any argument that is not a known command or a sub-command.
|
||||
AllowArg bool
|
||||
subCommands []*Command
|
||||
}
|
||||
|
||||
// AddCommand Adds a sub command.
|
||||
@@ -40,13 +41,17 @@ func Execute(cmd *Command) error {
|
||||
}
|
||||
|
||||
func execute(cmd *Command, args []string, root bool) error {
|
||||
// Calls command without args.
|
||||
if len(args) == 1 {
|
||||
if err := run(cmd, args); err != nil {
|
||||
if err := run(cmd, args[1:]); err != nil {
|
||||
return fmt.Errorf("command %s error: %v", args[0], err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Special case: if the command is the top level one,
|
||||
// and the first arg (`args[1]`) is not the command name or a known sub-command,
|
||||
// then we run the top level command itself.
|
||||
if root && cmd.Name != args[1] && !contains(cmd.subCommands, args[1]) {
|
||||
if err := run(cmd, args[1:]); err != nil {
|
||||
return fmt.Errorf("command %s error: %v", filepath.Base(args[0]), err)
|
||||
@@ -54,6 +59,7 @@ func execute(cmd *Command, args []string, root bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Calls command by its name.
|
||||
if len(args) >= 2 && cmd.Name == args[1] {
|
||||
if err := run(cmd, args[2:]); err != nil {
|
||||
return fmt.Errorf("command %s error: %v", cmd.Name, err)
|
||||
@@ -61,6 +67,7 @@ func execute(cmd *Command, args []string, root bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// No sub-command, calls the current command.
|
||||
if len(cmd.subCommands) == 0 {
|
||||
if err := run(cmd, args[1:]); err != nil {
|
||||
return fmt.Errorf("command %s error: %v", cmd.Name, err)
|
||||
@@ -68,6 +75,7 @@ func execute(cmd *Command, args []string, root bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Trying to find the sub-command.
|
||||
for _, subCmd := range cmd.subCommands {
|
||||
if len(args) >= 2 && subCmd.Name == args[1] {
|
||||
return execute(subCmd, args[1:], false)
|
||||
@@ -78,13 +86,18 @@ func execute(cmd *Command, args []string, root bool) error {
|
||||
}
|
||||
|
||||
func run(cmd *Command, args []string) error {
|
||||
if len(args) > 0 && !isFlag(args[0]) && !cmd.AllowArg {
|
||||
_ = PrintHelp(os.Stdout, cmd)
|
||||
return fmt.Errorf("command not found: %s", args[0])
|
||||
}
|
||||
|
||||
if isHelp(args) {
|
||||
return PrintHelp(os.Stdout, cmd)
|
||||
}
|
||||
|
||||
if cmd.Run == nil {
|
||||
_ = PrintHelp(os.Stdout, cmd)
|
||||
return errors.New("command not found")
|
||||
return fmt.Errorf("command %s is not runnable", cmd.Name)
|
||||
}
|
||||
|
||||
if cmd.Configuration == nil {
|
||||
@@ -113,3 +126,7 @@ func contains(cmds []*Command, name string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isFlag(arg string) bool {
|
||||
return len(arg) > 0 && arg[0] == '-'
|
||||
}
|
||||
|
@@ -86,6 +86,39 @@ func Test_execute(t *testing.T) {
|
||||
},
|
||||
expected: expected{result: "root"},
|
||||
},
|
||||
{
|
||||
desc: "root command, with argument, command not found",
|
||||
args: []string{"", "echo"},
|
||||
command: func() *Command {
|
||||
return &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called = "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "root command, call help, with argument, command not found",
|
||||
args: []string{"", "echo", "--help"},
|
||||
command: func() *Command {
|
||||
return &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called = "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "one sub command",
|
||||
args: []string{"", "sub1"},
|
||||
@@ -114,6 +147,34 @@ func Test_execute(t *testing.T) {
|
||||
},
|
||||
expected: expected{result: "sub1"},
|
||||
},
|
||||
{
|
||||
desc: "one sub command, with argument, command not found",
|
||||
args: []string{"", "sub1", "echo"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "test",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "sub1"
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "two sub commands",
|
||||
args: []string{"", "sub2"},
|
||||
@@ -376,6 +437,7 @@ func Test_execute(t *testing.T) {
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
AllowArg: true,
|
||||
Run: func(args []string) error {
|
||||
called += "sub1-" + strings.Join(args, "-")
|
||||
return nil
|
||||
@@ -394,6 +456,7 @@ func Test_execute(t *testing.T) {
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
AllowArg: true,
|
||||
Run: func(args []string) error {
|
||||
called += "root-" + strings.Join(args, "-")
|
||||
return nil
|
||||
|
@@ -3,7 +3,6 @@ package cli
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/env"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
@@ -14,23 +13,12 @@ type EnvLoader struct{}
|
||||
|
||||
// Load loads the command's configuration from the environment variables.
|
||||
func (e *EnvLoader) Load(_ []string, cmd *Command) (bool, error) {
|
||||
return e.load(os.Environ(), cmd)
|
||||
}
|
||||
|
||||
func (*EnvLoader) load(environ []string, cmd *Command) (bool, error) {
|
||||
var found bool
|
||||
for _, value := range environ {
|
||||
if strings.HasPrefix(value, "TRAEFIK_") {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
vars := env.FindPrefixedEnvVars(os.Environ(), env.DefaultNamePrefix, cmd.Configuration)
|
||||
if len(vars) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := env.Decode(environ, cmd.Configuration); err != nil {
|
||||
if err := env.Decode(vars, env.DefaultNamePrefix, cmd.Configuration); err != nil {
|
||||
return false, fmt.Errorf("failed to decode configuration from environment variables: %v", err)
|
||||
}
|
||||
|
||||
|
32
pkg/config/env/env.go
vendored
32
pkg/config/env/env.go
vendored
@@ -2,28 +2,38 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
// DefaultNamePrefix is the default prefix for environment variable names.
|
||||
const DefaultNamePrefix = "TRAEFIK_"
|
||||
|
||||
// Decode decodes the given environment variables into the given element.
|
||||
// The operation goes through four stages roughly summarized as:
|
||||
// env vars -> map
|
||||
// map -> tree of untyped nodes
|
||||
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
|
||||
// "typed" nodes -> typed element
|
||||
func Decode(environ []string, element interface{}) error {
|
||||
func Decode(environ []string, prefix string, element interface{}) error {
|
||||
if err := checkPrefix(prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vars := make(map[string]string)
|
||||
for _, evr := range environ {
|
||||
n := strings.SplitN(evr, "=", 2)
|
||||
if strings.HasPrefix(strings.ToUpper(n[0]), "TRAEFIK_") {
|
||||
if strings.HasPrefix(strings.ToUpper(n[0]), prefix) {
|
||||
key := strings.ReplaceAll(strings.ToLower(n[0]), "_", ".")
|
||||
vars[key] = n[1]
|
||||
}
|
||||
}
|
||||
|
||||
return parser.Decode(vars, element)
|
||||
rootName := strings.ToLower(prefix[:len(prefix)-1])
|
||||
return parser.Decode(vars, element, rootName)
|
||||
}
|
||||
|
||||
// Encode encodes the configuration in element into the environment variables represented in the returned Flats.
|
||||
@@ -36,7 +46,7 @@ func Encode(element interface{}) ([]parser.Flat, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
node, err := parser.EncodeToNode(element, false)
|
||||
node, err := parser.EncodeToNode(element, parser.DefaultRootName, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -48,3 +58,17 @@ func Encode(element interface{}) ([]parser.Flat, error) {
|
||||
|
||||
return parser.EncodeToFlat(element, node, parser.FlatOpts{Case: "upper", Separator: "_"})
|
||||
}
|
||||
|
||||
func checkPrefix(prefix string) error {
|
||||
prefixPattern := `[a-zA-Z0-9]+_`
|
||||
matched, err := regexp.MatchString(prefixPattern, prefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !matched {
|
||||
return fmt.Errorf("invalid prefix %q, the prefix pattern must match the following pattern: %s", prefix, prefixPattern)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
38
pkg/config/env/env_test.go
vendored
38
pkg/config/env/env_test.go
vendored
@@ -173,7 +173,7 @@ func TestDecode(t *testing.T) {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := Decode(test.environ, test.element)
|
||||
err := Decode(test.environ, DefaultNamePrefix, test.element)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, test.element)
|
||||
@@ -460,39 +460,3 @@ func TestEncode(t *testing.T) {
|
||||
|
||||
assert.Equal(t, expected, flats)
|
||||
}
|
||||
|
||||
type Ya struct {
|
||||
Foo *Yaa
|
||||
Field1 string
|
||||
Field2 bool
|
||||
Field3 int
|
||||
Field4 map[string]string
|
||||
Field5 map[string]int
|
||||
Field6 map[string]struct{ Field string }
|
||||
Field7 map[string]struct{ Field map[string]string }
|
||||
Field8 map[string]*struct{ Field string }
|
||||
Field9 map[string]*struct{ Field map[string]string }
|
||||
Field10 struct{ Field string }
|
||||
Field11 *struct{ Field string }
|
||||
Field12 *string
|
||||
Field13 *bool
|
||||
Field14 *int
|
||||
Field15 []int
|
||||
}
|
||||
|
||||
type Yaa struct {
|
||||
FieldIn1 string
|
||||
FieldIn2 bool
|
||||
FieldIn3 int
|
||||
FieldIn4 map[string]string
|
||||
FieldIn5 map[string]int
|
||||
FieldIn6 map[string]struct{ Field string }
|
||||
FieldIn7 map[string]struct{ Field map[string]string }
|
||||
FieldIn8 map[string]*struct{ Field string }
|
||||
FieldIn9 map[string]*struct{ Field map[string]string }
|
||||
FieldIn10 struct{ Field string }
|
||||
FieldIn11 *struct{ Field string }
|
||||
FieldIn12 *string
|
||||
FieldIn13 *bool
|
||||
FieldIn14 *int
|
||||
}
|
||||
|
64
pkg/config/env/filter.go
vendored
Normal file
64
pkg/config/env/filter.go
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
// FindPrefixedEnvVars finds prefixed environment variables.
|
||||
func FindPrefixedEnvVars(environ []string, prefix string, element interface{}) []string {
|
||||
prefixes := getRootPrefixes(element, prefix)
|
||||
|
||||
var values []string
|
||||
for _, px := range prefixes {
|
||||
for _, value := range environ {
|
||||
if strings.HasPrefix(value, px) {
|
||||
values = append(values, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
func getRootPrefixes(element interface{}, prefix string) []string {
|
||||
if element == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rootType := reflect.TypeOf(element)
|
||||
|
||||
return getPrefixes(prefix, rootType)
|
||||
}
|
||||
|
||||
func getPrefixes(prefix string, rootType reflect.Type) []string {
|
||||
var names []string
|
||||
|
||||
if rootType.Kind() == reflect.Ptr {
|
||||
rootType = rootType.Elem()
|
||||
}
|
||||
|
||||
if rootType.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < rootType.NumField(); i++ {
|
||||
field := rootType.Field(i)
|
||||
|
||||
if !parser.IsExported(field) {
|
||||
continue
|
||||
}
|
||||
|
||||
if field.Anonymous &&
|
||||
(field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct || field.Type.Kind() == reflect.Struct) {
|
||||
names = append(names, getPrefixes(prefix, field.Type)...)
|
||||
continue
|
||||
}
|
||||
|
||||
names = append(names, prefix+strings.ToUpper(field.Name))
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
87
pkg/config/env/filter_test.go
vendored
Normal file
87
pkg/config/env/filter_test.go
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFindPrefixedEnvVars(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
environ []string
|
||||
element interface{}
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "exact name",
|
||||
environ: []string{"TRAEFIK_FOO"},
|
||||
element: &Yo{},
|
||||
expected: []string{"TRAEFIK_FOO"},
|
||||
},
|
||||
{
|
||||
desc: "prefixed name",
|
||||
environ: []string{"TRAEFIK_FII01"},
|
||||
element: &Yo{},
|
||||
expected: []string{"TRAEFIK_FII01"},
|
||||
},
|
||||
{
|
||||
desc: "excluded env vars",
|
||||
environ: []string{"TRAEFIK_NOPE", "TRAEFIK_NO"},
|
||||
element: &Yo{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "filter",
|
||||
environ: []string{"TRAEFIK_NOPE", "TRAEFIK_NO", "TRAEFIK_FOO", "TRAEFIK_FII01"},
|
||||
element: &Yo{},
|
||||
expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII01"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
vars := FindPrefixedEnvVars(test.environ, DefaultNamePrefix, test.element)
|
||||
|
||||
assert.Equal(t, test.expected, vars)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getRootFieldNames(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
element interface{}
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "simple fields",
|
||||
element: &Yo{},
|
||||
expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII", "TRAEFIK_FUU", "TRAEFIK_YI", "TRAEFIK_YU"},
|
||||
},
|
||||
{
|
||||
desc: "embedded struct",
|
||||
element: &Yu{},
|
||||
expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII", "TRAEFIK_FUU"},
|
||||
},
|
||||
{
|
||||
desc: "embedded struct pointer",
|
||||
element: &Ye{},
|
||||
expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII", "TRAEFIK_FUU"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
names := getRootPrefixes(test.element, DefaultNamePrefix)
|
||||
|
||||
assert.Equal(t, test.expected, names)
|
||||
})
|
||||
}
|
||||
}
|
69
pkg/config/env/fixtures_test.go
vendored
Normal file
69
pkg/config/env/fixtures_test.go
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package env
|
||||
|
||||
type Ya struct {
|
||||
Foo *Yaa
|
||||
Field1 string
|
||||
Field2 bool
|
||||
Field3 int
|
||||
Field4 map[string]string
|
||||
Field5 map[string]int
|
||||
Field6 map[string]struct{ Field string }
|
||||
Field7 map[string]struct{ Field map[string]string }
|
||||
Field8 map[string]*struct{ Field string }
|
||||
Field9 map[string]*struct{ Field map[string]string }
|
||||
Field10 struct{ Field string }
|
||||
Field11 *struct{ Field string }
|
||||
Field12 *string
|
||||
Field13 *bool
|
||||
Field14 *int
|
||||
Field15 []int
|
||||
}
|
||||
|
||||
type Yaa struct {
|
||||
FieldIn1 string
|
||||
FieldIn2 bool
|
||||
FieldIn3 int
|
||||
FieldIn4 map[string]string
|
||||
FieldIn5 map[string]int
|
||||
FieldIn6 map[string]struct{ Field string }
|
||||
FieldIn7 map[string]struct{ Field map[string]string }
|
||||
FieldIn8 map[string]*struct{ Field string }
|
||||
FieldIn9 map[string]*struct{ Field map[string]string }
|
||||
FieldIn10 struct{ Field string }
|
||||
FieldIn11 *struct{ Field string }
|
||||
FieldIn12 *string
|
||||
FieldIn13 *bool
|
||||
FieldIn14 *int
|
||||
}
|
||||
|
||||
type Yo struct {
|
||||
Foo string `description:"Foo description"`
|
||||
Fii string `description:"Fii description"`
|
||||
Fuu string `description:"Fuu description"`
|
||||
Yi *Yi `label:"allowEmpty"`
|
||||
Yu *Yi
|
||||
}
|
||||
|
||||
func (y *Yo) SetDefaults() {
|
||||
y.Foo = "foo"
|
||||
y.Fii = "fii"
|
||||
}
|
||||
|
||||
type Yi struct {
|
||||
Foo string
|
||||
Fii string
|
||||
Fuu string
|
||||
}
|
||||
|
||||
func (y *Yi) SetDefaults() {
|
||||
y.Foo = "foo"
|
||||
y.Fii = "fii"
|
||||
}
|
||||
|
||||
type Yu struct {
|
||||
Yi
|
||||
}
|
||||
|
||||
type Ye struct {
|
||||
*Yi
|
||||
}
|
@@ -36,13 +36,13 @@ func decodeFileToNode(filePath string, filters ...string) (*parser.Node, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decodeRawToNode(data, filters...)
|
||||
return decodeRawToNode(data, parser.DefaultRootName, filters...)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported file extension: %s", filePath)
|
||||
}
|
||||
|
||||
return decodeRawToNode(data, filters...)
|
||||
return decodeRawToNode(data, parser.DefaultRootName, filters...)
|
||||
}
|
||||
|
||||
func getRootFieldNames(element interface{}) []string {
|
||||
|
@@ -248,7 +248,6 @@ func Test_decodeFileToNode_Toml(t *testing.T) {
|
||||
{Name: "DialerTimeout", Value: "42"},
|
||||
{Name: "Endpoint", Value: "foobar"},
|
||||
{Name: "ExposedByDefault", Value: "true"},
|
||||
{Name: "FilterMarathonConstraints", Value: "true"},
|
||||
{Name: "ForceTaskHostname", Value: "true"},
|
||||
{Name: "KeepAlive", Value: "42"},
|
||||
{Name: "RespectReadinessChecks", Value: "true"},
|
||||
@@ -518,7 +517,6 @@ func Test_decodeFileToNode_Yaml(t *testing.T) {
|
||||
{Name: "DialerTimeout", Value: "42"},
|
||||
{Name: "Endpoint", Value: "foobar"},
|
||||
{Name: "ExposedByDefault", Value: "true"},
|
||||
{Name: "FilterMarathonConstraints", Value: "true"},
|
||||
{Name: "ForceTaskHostname", Value: "true"},
|
||||
{Name: "KeepAlive", Value: "42"},
|
||||
{Name: "RespectReadinessChecks", Value: "true"},
|
||||
|
@@ -74,7 +74,6 @@
|
||||
DefaultRule = "foobar"
|
||||
ExposedByDefault = true
|
||||
DCOSToken = "foobar"
|
||||
FilterMarathonConstraints = true
|
||||
DialerTimeout = 42
|
||||
ResponseHeaderTimeout = 42
|
||||
TLSHandshakeTimeout = 42
|
||||
|
@@ -69,7 +69,6 @@ Providers:
|
||||
DefaultRule: foobar
|
||||
ExposedByDefault: true
|
||||
DCOSToken: foobar
|
||||
FilterMarathonConstraints: true
|
||||
DialerTimeout: 42
|
||||
ResponseHeaderTimeout: 42
|
||||
TLSHandshakeTimeout: 42
|
||||
|
@@ -9,9 +9,9 @@ import (
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
func decodeRawToNode(data map[string]interface{}, filters ...string) (*parser.Node, error) {
|
||||
func decodeRawToNode(data map[string]interface{}, rootName string, filters ...string) (*parser.Node, error) {
|
||||
root := &parser.Node{
|
||||
Name: "traefik",
|
||||
Name: rootName,
|
||||
}
|
||||
|
||||
vData := reflect.ValueOf(data)
|
||||
|
@@ -531,7 +531,7 @@ func Test_decodeRawToNode(t *testing.T) {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, err := decodeRawToNode(test.data)
|
||||
node, err := decodeRawToNode(test.data, parser.DefaultRootName)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, node)
|
||||
|
@@ -17,7 +17,7 @@ func Decode(args []string, element interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return parser.Decode(ref, element)
|
||||
return parser.Decode(ref, element, parser.DefaultRootName)
|
||||
}
|
||||
|
||||
// Encode encodes the configuration in element into the flags represented in the returned Flats.
|
||||
@@ -30,7 +30,7 @@ func Encode(element interface{}) ([]parser.Flat, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
node, err := parser.EncodeToNode(element, false)
|
||||
node, err := parser.EncodeToNode(element, parser.DefaultRootName, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
// Parse parses the command-line flag arguments into a map,
|
||||
@@ -96,7 +98,7 @@ func (f *flagSet) parseOne() (bool, error) {
|
||||
}
|
||||
|
||||
func (f *flagSet) setValue(name string, value string) {
|
||||
n := strings.ToLower("traefik." + name)
|
||||
n := strings.ToLower(parser.DefaultRootName + "." + name)
|
||||
v, ok := f.values[n]
|
||||
|
||||
if ok && f.flagTypes[name] == reflect.Slice {
|
||||
|
@@ -13,7 +13,7 @@ func DecodeConfiguration(labels map[string]string) (*config.Configuration, error
|
||||
TCP: &config.TCPConfiguration{},
|
||||
}
|
||||
|
||||
err := parser.Decode(labels, conf, "traefik.http", "traefik.tcp")
|
||||
err := parser.Decode(labels, conf, parser.DefaultRootName, "traefik.http", "traefik.tcp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -23,11 +23,11 @@ func DecodeConfiguration(labels map[string]string) (*config.Configuration, error
|
||||
|
||||
// EncodeConfiguration converts a configuration to labels.
|
||||
func EncodeConfiguration(conf *config.Configuration) (map[string]string, error) {
|
||||
return parser.Encode(conf)
|
||||
return parser.Encode(conf, parser.DefaultRootName)
|
||||
}
|
||||
|
||||
// Decode converts the labels to an element.
|
||||
// labels -> [ node -> node + metadata (type) ] -> element (node)
|
||||
func Decode(labels map[string]string, element interface{}, filters ...string) error {
|
||||
return parser.Decode(labels, element, filters...)
|
||||
return parser.Decode(labels, element, parser.DefaultRootName, filters...)
|
||||
}
|
||||
|
@@ -19,8 +19,8 @@ type Middleware struct {
|
||||
Headers *Headers `json:"headers,omitempty"`
|
||||
Errors *ErrorPage `json:"errors,omitempty"`
|
||||
RateLimit *RateLimit `json:"rateLimit,omitempty"`
|
||||
RedirectRegex *RedirectRegex `json:"redirectregex,omitempty"`
|
||||
RedirectScheme *RedirectScheme `json:"redirectscheme,omitempty"`
|
||||
RedirectRegex *RedirectRegex `json:"redirectRegex,omitempty"`
|
||||
RedirectScheme *RedirectScheme `json:"redirectScheme,omitempty"`
|
||||
BasicAuth *BasicAuth `json:"basicAuth,omitempty"`
|
||||
DigestAuth *DigestAuth `json:"digestAuth,omitempty"`
|
||||
ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty"`
|
||||
|
@@ -9,9 +9,9 @@ import (
|
||||
|
||||
// EncodeToNode converts an element to a node.
|
||||
// element -> nodes
|
||||
func EncodeToNode(element interface{}, omitEmpty bool) (*Node, error) {
|
||||
func EncodeToNode(element interface{}, rootName string, omitEmpty bool) (*Node, error) {
|
||||
rValue := reflect.ValueOf(element)
|
||||
node := &Node{Name: "traefik"}
|
||||
node := &Node{Name: rootName}
|
||||
|
||||
encoder := encoderToNode{omitEmpty: omitEmpty}
|
||||
|
||||
|
@@ -723,7 +723,7 @@ func TestEncodeToNode(t *testing.T) {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, err := EncodeToNode(test.element, true)
|
||||
node, err := EncodeToNode(test.element, DefaultRootName, true)
|
||||
|
||||
if test.expected.error {
|
||||
require.Error(t, err)
|
||||
|
@@ -6,18 +6,16 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const labelRoot = "traefik"
|
||||
|
||||
// DecodeToNode converts the labels to a tree of nodes.
|
||||
// If any filters are present, labels which do not match the filters are skipped.
|
||||
func DecodeToNode(labels map[string]string, filters ...string) (*Node, error) {
|
||||
func DecodeToNode(labels map[string]string, rootName string, filters ...string) (*Node, error) {
|
||||
sortedKeys := sortKeys(labels, filters)
|
||||
|
||||
var node *Node
|
||||
for i, key := range sortedKeys {
|
||||
split := strings.Split(key, ".")
|
||||
|
||||
if split[0] != labelRoot {
|
||||
if split[0] != rootName {
|
||||
return nil, fmt.Errorf("invalid label root %s", split[0])
|
||||
}
|
||||
|
||||
|
@@ -218,7 +218,7 @@ func TestDecodeToNode(t *testing.T) {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
out, err := DecodeToNode(test.in, test.filters...)
|
||||
out, err := DecodeToNode(test.in, DefaultRootName, test.filters...)
|
||||
|
||||
if test.expected.error {
|
||||
require.Error(t, err)
|
||||
|
@@ -2,6 +2,9 @@ package parser
|
||||
|
||||
import "reflect"
|
||||
|
||||
// DefaultRootName is the default name of the root node and the prefix of element name from the resources.
|
||||
const DefaultRootName = "traefik"
|
||||
|
||||
// MapNamePlaceholder is the placeholder for the map name.
|
||||
const MapNamePlaceholder = "<name>"
|
||||
|
||||
|
@@ -7,8 +7,8 @@ package parser
|
||||
// labels -> tree of untyped nodes
|
||||
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
|
||||
// "typed" nodes -> typed element
|
||||
func Decode(labels map[string]string, element interface{}, filters ...string) error {
|
||||
node, err := DecodeToNode(labels, filters...)
|
||||
func Decode(labels map[string]string, element interface{}, rootName string, filters ...string) error {
|
||||
node, err := DecodeToNode(labels, rootName, filters...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -28,8 +28,8 @@ func Decode(labels map[string]string, element interface{}, filters ...string) er
|
||||
|
||||
// Encode converts an element to labels.
|
||||
// element -> node (value) -> label (node)
|
||||
func Encode(element interface{}) (map[string]string, error) {
|
||||
node, err := EncodeToNode(element, true)
|
||||
func Encode(element interface{}, rootName string) (map[string]string, error) {
|
||||
node, err := EncodeToNode(element, rootName, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -263,17 +263,17 @@ type TCPServiceInfo struct {
|
||||
}
|
||||
|
||||
func getProviderName(elementName string) string {
|
||||
parts := strings.Split(elementName, ".")
|
||||
parts := strings.Split(elementName, "@")
|
||||
if len(parts) > 1 {
|
||||
return parts[0]
|
||||
return parts[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getQualifiedName(provider, elementName string) string {
|
||||
parts := strings.Split(elementName, ".")
|
||||
parts := strings.Split(elementName, "@")
|
||||
if len(parts) == 1 {
|
||||
return provider + "." + elementName
|
||||
return elementName + "@" + provider
|
||||
}
|
||||
return elementName
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user