Add KV store providers (dynamic configuration only)

Co-authored-by: Jean-Baptiste Doumenjou <jb.doumenjou@gmail.com>
This commit is contained in:
Ludovic Fernandez 2019-11-28 21:56:04 +01:00 committed by Traefiker Bot
parent 028683666d
commit 9b9f4be6a4
61 changed files with 5825 additions and 70 deletions

View file

@ -0,0 +1,216 @@
# Traefik & Consul
A Story of KV store & Containers
{: .subtitle }
Store your configuration in Consul and let Traefik do the rest!
## Routing Configuration
See the dedicated section in [routing](../routing/providers/kv.md).
## Provider Configuration
### `endpoints`
_Required, Default="127.0.0.1:8500"_
Defines how to access to Consul.
```toml tab="File (TOML)"
[providers.consul]
endpoints = ["127.0.0.1:8500"]
```
```yaml tab="File (YAML)"
providers:
consul:
endpoints:
- "127.0.0.1:8500"
```
```bash tab="CLI"
--providers.consul.endpoints=127.0.0.1:8500
```
### `rootKey`
Defines the root key of the configuration.
_Required, Default="traefik"_
```toml tab="File (TOML)"
[providers.consul]
rootKey = "traefik"
```
```yaml tab="File (YAML)"
providers:
consul:
rootKey: "traefik"
```
```bash tab="CLI"
--providers.consul.rootkey=traefik
```
### `username`
Defines a username to connect with Consul.
_Optional, Default=""_
```toml tab="File (TOML)"
[providers.consul]
# ...
username = "foo"
```
```yaml tab="File (YAML)"
providers:
consul:
# ...
usename: "foo"
```
```bash tab="CLI"
--providers.consul.username=foo
```
### `password`
_Optional, Default=""_
Defines a password to connect with Consul.
```toml tab="File (TOML)"
[providers.consul]
# ...
password = "bar"
```
```yaml tab="File (YAML)"
providers:
consul:
# ...
password: "bar"
```
```bash tab="CLI"
--providers.consul.password=foo
```
### `tls`
_Optional_
#### `tls.ca`
Certificate Authority used for the secured connection to Consul.
```toml tab="File (TOML)"
[providers.consul.tls]
ca = "path/to/ca.crt"
```
```yaml tab="File (YAML)"
providers:
consul:
tls:
ca: path/to/ca.crt
```
```bash tab="CLI"
--providers.consul.tls.ca=path/to/ca.crt
```
#### `tls.caOptional`
Policy followed for the secured connection with TLS Client Authentication to Consul.
Requires `tls.ca` to be defined.
- `true`: VerifyClientCertIfGiven
- `false`: RequireAndVerifyClientCert
- if `tls.ca` is undefined NoClientCert
```toml tab="File (TOML)"
[providers.consul.tls]
caOptional = true
```
```yaml tab="File (YAML)"
providers:
consul:
tls:
caOptional: true
```
```bash tab="CLI"
--providers.consul.tls.caOptional=true
```
#### `tls.cert`
Public certificate used for the secured connection to Consul.
```toml tab="File (TOML)"
[providers.consul.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```yaml tab="File (YAML)"
providers:
consul:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```bash tab="CLI"
--providers.consul.tls.cert=path/to/foo.cert
--providers.consul.tls.key=path/to/foo.key
```
#### `tls.key`
Private certificate used for the secured connection to Consul.
```toml tab="File (TOML)"
[providers.consul.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```yaml tab="File (YAML)"
providers:
consul:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```bash tab="CLI"
--providers.consul.tls.cert=path/to/foo.cert
--providers.consul.tls.key=path/to/foo.key
```
#### `tls.insecureSkipVerify`
If `insecureSkipVerify` is `true`, TLS for the connection to Consul accepts any certificate presented by the server and any host name in that certificate.
```toml tab="File (TOML)"
[providers.consul.tls]
insecureSkipVerify = true
```
```yaml tab="File (YAML)"
providers:
consul:
tls:
insecureSkipVerify: true
```
```bash tab="CLI"
--providers.consul.tls.insecureSkipVerify=true
```

View file

@ -0,0 +1,216 @@
# Traefik & Etcd
A Story of KV store & Containers
{: .subtitle }
Store your configuration in Etcd and let Traefik do the rest!
## Routing Configuration
See the dedicated section in [routing](../routing/providers/kv.md).
## Provider Configuration
### `endpoints`
_Required, Default="127.0.0.1:2379"_
Defines how to access to Etcd.
```toml tab="File (TOML)"
[providers.etcd]
endpoints = ["127.0.0.1:2379"]
```
```yaml tab="File (YAML)"
providers:
etcd:
endpoints:
- "127.0.0.1:2379"
```
```bash tab="CLI"
--providers.etcd.endpoints=127.0.0.1:2379
```
### `rootKey`
Defines the root key of the configuration.
_Required, Default="traefik"_
```toml tab="File (TOML)"
[providers.etcd]
rootKey = "traefik"
```
```yaml tab="File (YAML)"
providers:
etcd:
rootKey: "traefik"
```
```bash tab="CLI"
--providers.etcd.rootkey=traefik
```
### `username`
Defines a username to connect with Etcd.
_Optional, Default=""_
```toml tab="File (TOML)"
[providers.etcd]
# ...
username = "foo"
```
```yaml tab="File (YAML)"
providers:
etcd:
# ...
usename: "foo"
```
```bash tab="CLI"
--providers.etcd.username=foo
```
### `password`
_Optional, Default=""_
Defines a password to connect with Etcd.
```toml tab="File (TOML)"
[providers.etcd]
# ...
password = "bar"
```
```yaml tab="File (YAML)"
providers:
etcd:
# ...
password: "bar"
```
```bash tab="CLI"
--providers.etcd.password=foo
```
### `tls`
_Optional_
#### `tls.ca`
Certificate Authority used for the secured connection to Etcd.
```toml tab="File (TOML)"
[providers.etcd.tls]
ca = "path/to/ca.crt"
```
```yaml tab="File (YAML)"
providers:
etcd:
tls:
ca: path/to/ca.crt
```
```bash tab="CLI"
--providers.etcd.tls.ca=path/to/ca.crt
```
#### `tls.caOptional`
Policy followed for the secured connection with TLS Client Authentication to Etcd.
Requires `tls.ca` to be defined.
- `true`: VerifyClientCertIfGiven
- `false`: RequireAndVerifyClientCert
- if `tls.ca` is undefined NoClientCert
```toml tab="File (TOML)"
[providers.etcd.tls]
caOptional = true
```
```yaml tab="File (YAML)"
providers:
etcd:
tls:
caOptional: true
```
```bash tab="CLI"
--providers.etcd.tls.caOptional=true
```
#### `tls.cert`
Public certificate used for the secured connection to Etcd.
```toml tab="File (TOML)"
[providers.etcd.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```yaml tab="File (YAML)"
providers:
etcd:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```bash tab="CLI"
--providers.etcd.tls.cert=path/to/foo.cert
--providers.etcd.tls.key=path/to/foo.key
```
#### `tls.key`
Private certificate used for the secured connection to Etcd.
```toml tab="File (TOML)"
[providers.etcd.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```yaml tab="File (YAML)"
providers:
etcd:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```bash tab="CLI"
--providers.etcd.tls.cert=path/to/foo.cert
--providers.etcd.tls.key=path/to/foo.key
```
#### `tls.insecureSkipVerify`
If `insecureSkipVerify` is `true`, TLS for the connection to Etcd accepts any certificate presented by the server and any host name in that certificate.
```toml tab="File (TOML)"
[providers.etcd.tls]
insecureSkipVerify = true
```
```yaml tab="File (YAML)"
providers:
etcd:
tls:
insecureSkipVerify: true
```
```bash tab="CLI"
--providers.etcd.tls.insecureSkipVerify=true
```

View file

@ -0,0 +1,216 @@
# Traefik & Redis
A Story of KV store & Containers
{: .subtitle }
Store your configuration in Redis and let Traefik do the rest!
## Routing Configuration
See the dedicated section in [routing](../routing/providers/kv.md).
## Provider Configuration
### `endpoints`
_Required, Default="127.0.0.1:6379"_
Defines how to access to Redis.
```toml tab="File (TOML)"
[providers.redis]
endpoints = ["127.0.0.1:6379"]
```
```yaml tab="File (YAML)"
providers:
redis:
endpoints:
- "127.0.0.1:6379"
```
```bash tab="CLI"
--providers.redis.endpoints=127.0.0.1:6379
```
### `rootKey`
Defines the root key of the configuration.
_Required, Default="traefik"_
```toml tab="File (TOML)"
[providers.redis]
rootKey = "traefik"
```
```yaml tab="File (YAML)"
providers:
redis:
rootKey: "traefik"
```
```bash tab="CLI"
--providers.redis.rootkey=traefik
```
### `username`
Defines a username to connect with Redis.
_Optional, Default=""_
```toml tab="File (TOML)"
[providers.redis]
# ...
username = "foo"
```
```yaml tab="File (YAML)"
providers:
redis:
# ...
usename: "foo"
```
```bash tab="CLI"
--providers.redis.username=foo
```
### `password`
_Optional, Default=""_
Defines a password to connect with Redis.
```toml tab="File (TOML)"
[providers.redis]
# ...
password = "bar"
```
```yaml tab="File (YAML)"
providers:
redis:
# ...
password: "bar"
```
```bash tab="CLI"
--providers.redis.password=foo
```
### `tls`
_Optional_
#### `tls.ca`
Certificate Authority used for the secured connection to Redis.
```toml tab="File (TOML)"
[providers.redis.tls]
ca = "path/to/ca.crt"
```
```yaml tab="File (YAML)"
providers:
redis:
tls:
ca: path/to/ca.crt
```
```bash tab="CLI"
--providers.redis.tls.ca=path/to/ca.crt
```
#### `tls.caOptional`
Policy followed for the secured connection with TLS Client Authentication to Redis.
Requires `tls.ca` to be defined.
- `true`: VerifyClientCertIfGiven
- `false`: RequireAndVerifyClientCert
- if `tls.ca` is undefined NoClientCert
```toml tab="File (TOML)"
[providers.redis.tls]
caOptional = true
```
```yaml tab="File (YAML)"
providers:
redis:
tls:
caOptional: true
```
```bash tab="CLI"
--providers.redis.tls.caOptional=true
```
#### `tls.cert`
Public certificate used for the secured connection to Redis.
```toml tab="File (TOML)"
[providers.redis.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```yaml tab="File (YAML)"
providers:
redis:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```bash tab="CLI"
--providers.redis.tls.cert=path/to/foo.cert
--providers.redis.tls.key=path/to/foo.key
```
#### `tls.key`
Private certificate used for the secured connection to Redis.
```toml tab="File (TOML)"
[providers.redis.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```yaml tab="File (YAML)"
providers:
redis:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```bash tab="CLI"
--providers.redis.tls.cert=path/to/foo.cert
--providers.redis.tls.key=path/to/foo.key
```
#### `tls.insecureSkipVerify`
If `insecureSkipVerify` is `true`, TLS for the connection to Redis accepts any certificate presented by the server and any host name in that certificate.
```toml tab="File (TOML)"
[providers.redis.tls]
insecureSkipVerify = true
```
```yaml tab="File (YAML)"
providers:
redis:
tls:
insecureSkipVerify: true
```
```bash tab="CLI"
--providers.redis.tls.insecureSkipVerify=true
```

View file

@ -0,0 +1,216 @@
# Traefik & ZooKeeper
A Story of KV store & Containers
{: .subtitle }
Store your configuration in ZooKeeper and let Traefik do the rest!
## Routing Configuration
See the dedicated section in [routing](../routing/providers/kv.md).
## Provider Configuration
### `endpoints`
_Required, Default="127.0.0.1:2181"_
Defines how to access to ZooKeeper.
```toml tab="File (TOML)"
[providers.zooKeeper]
endpoints = ["127.0.0.1:2181"]
```
```yaml tab="File (YAML)"
providers:
zooKeeper:
endpoints:
- "127.0.0.1:2181"
```
```bash tab="CLI"
--providers.zookeeper.endpoints=127.0.0.1:2181
```
### `rootKey`
Defines the root key of the configuration.
_Required, Default="traefik"_
```toml tab="File (TOML)"
[providers.zooKeeper]
rootKey = "traefik"
```
```yaml tab="File (YAML)"
providers:
zooKeeper:
rootKey: "traefik"
```
```bash tab="CLI"
--providers.zookeeper.rootkey=traefik
```
### `username`
Defines a username to connect with ZooKeeper.
_Optional, Default=""_
```toml tab="File (TOML)"
[providers.zooKeeper]
# ...
username = "foo"
```
```yaml tab="File (YAML)"
providers:
zooKeeper:
# ...
usename: "foo"
```
```bash tab="CLI"
--providers.zookeeper.username=foo
```
### `password`
_Optional, Default=""_
Defines a password to connect with ZooKeeper.
```toml tab="File (TOML)"
[providers.zooKeeper]
# ...
password = "bar"
```
```yaml tab="File (YAML)"
providers:
zooKeeper:
# ...
password: "bar"
```
```bash tab="CLI"
--providers.zookeeper.password=foo
```
### `tls`
_Optional_
#### `tls.ca`
Certificate Authority used for the secured connection to ZooKeeper.
```toml tab="File (TOML)"
[providers.zooKeeper.tls]
ca = "path/to/ca.crt"
```
```yaml tab="File (YAML)"
providers:
zooKeeper:
tls:
ca: path/to/ca.crt
```
```bash tab="CLI"
--providers.zookeeper.tls.ca=path/to/ca.crt
```
#### `tls.caOptional`
Policy followed for the secured connection with TLS Client Authentication to ZooKeeper.
Requires `tls.ca` to be defined.
- `true`: VerifyClientCertIfGiven
- `false`: RequireAndVerifyClientCert
- if `tls.ca` is undefined NoClientCert
```toml tab="File (TOML)"
[providers.zooKeeper.tls]
caOptional = true
```
```yaml tab="File (YAML)"
providers:
zooKeeper:
tls:
caOptional: true
```
```bash tab="CLI"
--providers.zookeeper.tls.caOptional=true
```
#### `tls.cert`
Public certificate used for the secured connection to ZooKeeper.
```toml tab="File (TOML)"
[providers.zooKeeper.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```yaml tab="File (YAML)"
providers:
zooKeeper:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```bash tab="CLI"
--providers.zookeeper.tls.cert=path/to/foo.cert
--providers.zookeeper.tls.key=path/to/foo.key
```
#### `tls.key`
Private certificate used for the secured connection to ZooKeeper.
```toml tab="File (TOML)"
[providers.zooKeeper.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```yaml tab="File (YAML)"
providers:
zooKeeper:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```bash tab="CLI"
--providers.zookeeper.tls.cert=path/to/foo.cert
--providers.zookeeper.tls.key=path/to/foo.key
```
#### `tls.insecureSkipVerify`
If `insecureSkipVerify` is `true`, TLS for the connection to ZooKeeper accepts any certificate presented by the server and any host name in that certificate.
```toml tab="File (TOML)"
[providers.zooKeeper.tls]
insecureSkipVerify = true
```
```yaml tab="File (YAML)"
providers:
zooKeeper:
tls:
insecureSkipVerify: true
```
```bash tab="CLI"
--providers.zookeeper.tls.insecureSkipVerify=true
```

View file

@ -0,0 +1,246 @@
| `traefik/http/middlewares/Middleware00/addPrefix/prefix` | `foobar` |
| `traefik/http/middlewares/Middleware01/basicAuth/headerField` | `foobar` |
| `traefik/http/middlewares/Middleware01/basicAuth/realm` | `foobar` |
| `traefik/http/middlewares/Middleware01/basicAuth/removeHeader` | `true` |
| `traefik/http/middlewares/Middleware01/basicAuth/users/0` | `foobar` |
| `traefik/http/middlewares/Middleware01/basicAuth/users/1` | `foobar` |
| `traefik/http/middlewares/Middleware01/basicAuth/usersFile` | `foobar` |
| `traefik/http/middlewares/Middleware02/buffering/maxRequestBodyBytes` | `42` |
| `traefik/http/middlewares/Middleware02/buffering/maxResponseBodyBytes` | `42` |
| `traefik/http/middlewares/Middleware02/buffering/memRequestBodyBytes` | `42` |
| `traefik/http/middlewares/Middleware02/buffering/memResponseBodyBytes` | `42` |
| `traefik/http/middlewares/Middleware02/buffering/retryExpression` | `foobar` |
| `traefik/http/middlewares/Middleware03/chain/middlewares/0` | `foobar` |
| `traefik/http/middlewares/Middleware03/chain/middlewares/1` | `foobar` |
| `traefik/http/middlewares/Middleware04/circuitBreaker/expression` | `foobar` |
| `traefik/http/middlewares/Middleware05/compress` | `` |
| `traefik/http/middlewares/Middleware06/digestAuth/headerField` | `foobar` |
| `traefik/http/middlewares/Middleware06/digestAuth/realm` | `foobar` |
| `traefik/http/middlewares/Middleware06/digestAuth/removeHeader` | `true` |
| `traefik/http/middlewares/Middleware06/digestAuth/users/0` | `foobar` |
| `traefik/http/middlewares/Middleware06/digestAuth/users/1` | `foobar` |
| `traefik/http/middlewares/Middleware06/digestAuth/usersFile` | `foobar` |
| `traefik/http/middlewares/Middleware07/errors/query` | `foobar` |
| `traefik/http/middlewares/Middleware07/errors/service` | `foobar` |
| `traefik/http/middlewares/Middleware07/errors/status/0` | `foobar` |
| `traefik/http/middlewares/Middleware07/errors/status/1` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/address` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/authResponseHeaders/0` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/authResponseHeaders/1` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/tls/ca` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/tls/caOptional` | `true` |
| `traefik/http/middlewares/Middleware08/forwardAuth/tls/cert` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/tls/insecureSkipVerify` | `true` |
| `traefik/http/middlewares/Middleware08/forwardAuth/tls/key` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/trustForwardHeader` | `true` |
| `traefik/http/middlewares/Middleware09/headers/accessControlAllowCredentials` | `true` |
| `traefik/http/middlewares/Middleware09/headers/accessControlAllowHeaders/0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlAllowHeaders/1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlAllowMethods/0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlAllowMethods/1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlAllowOrigin` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlMaxAge` | `42` |
| `traefik/http/middlewares/Middleware09/headers/addVaryHeader` | `true` |
| `traefik/http/middlewares/Middleware09/headers/allowedHosts/0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/allowedHosts/1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/browserXssFilter` | `true` |
| `traefik/http/middlewares/Middleware09/headers/contentSecurityPolicy` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/contentTypeNosniff` | `true` |
| `traefik/http/middlewares/Middleware09/headers/customBrowserXSSValue` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/customFrameOptionsValue` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/customResponseHeaders/name0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/customResponseHeaders/name1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/featurePolicy` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/forceSTSHeader` | `true` |
| `traefik/http/middlewares/Middleware09/headers/frameDeny` | `true` |
| `traefik/http/middlewares/Middleware09/headers/hostsProxyHeaders/0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/hostsProxyHeaders/1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/isDevelopment` | `true` |
| `traefik/http/middlewares/Middleware09/headers/publicKey` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/referrerPolicy` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/sslForceHost` | `true` |
| `traefik/http/middlewares/Middleware09/headers/sslHost` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/sslProxyHeaders/name0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/sslProxyHeaders/name1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/sslRedirect` | `true` |
| `traefik/http/middlewares/Middleware09/headers/sslTemporaryRedirect` | `true` |
| `traefik/http/middlewares/Middleware09/headers/stsIncludeSubdomains` | `true` |
| `traefik/http/middlewares/Middleware09/headers/stsPreload` | `true` |
| `traefik/http/middlewares/Middleware09/headers/stsSeconds` | `42` |
| `traefik/http/middlewares/Middleware10/ipWhiteList/ipStrategy/depth` | `42` |
| `traefik/http/middlewares/Middleware10/ipWhiteList/ipStrategy/excludedIPs/0` | `foobar` |
| `traefik/http/middlewares/Middleware10/ipWhiteList/ipStrategy/excludedIPs/1` | `foobar` |
| `traefik/http/middlewares/Middleware10/ipWhiteList/sourceRange/0` | `foobar` |
| `traefik/http/middlewares/Middleware10/ipWhiteList/sourceRange/1` | `foobar` |
| `traefik/http/middlewares/Middleware11/inFlightReq/amount` | `42` |
| `traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/ipStrategy/depth` | `42` |
| `traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` |
| `traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` |
| `traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/requestHeaderName` | `foobar` |
| `traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/requestHost` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/commonName` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/country` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/domainComponent` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/locality` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/organization` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/province` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/serialNumber` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/notAfter` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/notBefore` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/sans` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/commonName` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/country` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/domainComponent` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/locality` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/organization` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/province` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/serialNumber` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/pem` | `true` |
| `traefik/http/middlewares/Middleware13/rateLimit/average` | `42` |
| `traefik/http/middlewares/Middleware13/rateLimit/burst` | `42` |
| `traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/depth` | `42` |
| `traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` |
| `traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` |
| `traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/requestHeaderName` | `foobar` |
| `traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/requestHost` | `true` |
| `traefik/http/middlewares/Middleware14/redirectRegex/permanent` | `true` |
| `traefik/http/middlewares/Middleware14/redirectRegex/regex` | `foobar` |
| `traefik/http/middlewares/Middleware14/redirectRegex/replacement` | `foobar` |
| `traefik/http/middlewares/Middleware15/redirectScheme/permanent` | `true` |
| `traefik/http/middlewares/Middleware15/redirectScheme/port` | `foobar` |
| `traefik/http/middlewares/Middleware15/redirectScheme/scheme` | `foobar` |
| `traefik/http/middlewares/Middleware16/replacePath/path` | `foobar` |
| `traefik/http/middlewares/Middleware17/replacePathRegex/regex` | `foobar` |
| `traefik/http/middlewares/Middleware17/replacePathRegex/replacement` | `foobar` |
| `traefik/http/middlewares/Middleware18/retry/attempts` | `42` |
| `traefik/http/middlewares/Middleware19/stripPrefix/forceSlash` | `true` |
| `traefik/http/middlewares/Middleware19/stripPrefix/prefixes/0` | `foobar` |
| `traefik/http/middlewares/Middleware19/stripPrefix/prefixes/1` | `foobar` |
| `traefik/http/middlewares/Middleware20/stripPrefixRegex/regex/0` | `foobar` |
| `traefik/http/middlewares/Middleware20/stripPrefixRegex/regex/1` | `foobar` |
| `traefik/http/routers/Router0/entryPoints/0` | `foobar` |
| `traefik/http/routers/Router0/entryPoints/1` | `foobar` |
| `traefik/http/routers/Router0/middlewares/0` | `foobar` |
| `traefik/http/routers/Router0/middlewares/1` | `foobar` |
| `traefik/http/routers/Router0/priority` | `42` |
| `traefik/http/routers/Router0/rule` | `foobar` |
| `traefik/http/routers/Router0/service` | `foobar` |
| `traefik/http/routers/Router0/tls/certResolver` | `foobar` |
| `traefik/http/routers/Router0/tls/domains/0/main` | `foobar` |
| `traefik/http/routers/Router0/tls/domains/0/sans/0` | `foobar` |
| `traefik/http/routers/Router0/tls/domains/0/sans/1` | `foobar` |
| `traefik/http/routers/Router0/tls/domains/1/main` | `foobar` |
| `traefik/http/routers/Router0/tls/domains/1/sans/0` | `foobar` |
| `traefik/http/routers/Router0/tls/domains/1/sans/1` | `foobar` |
| `traefik/http/routers/Router0/tls/options` | `foobar` |
| `traefik/http/routers/Router1/entryPoints/0` | `foobar` |
| `traefik/http/routers/Router1/entryPoints/1` | `foobar` |
| `traefik/http/routers/Router1/middlewares/0` | `foobar` |
| `traefik/http/routers/Router1/middlewares/1` | `foobar` |
| `traefik/http/routers/Router1/priority` | `42` |
| `traefik/http/routers/Router1/rule` | `foobar` |
| `traefik/http/routers/Router1/service` | `foobar` |
| `traefik/http/routers/Router1/tls/certResolver` | `foobar` |
| `traefik/http/routers/Router1/tls/domains/0/main` | `foobar` |
| `traefik/http/routers/Router1/tls/domains/0/sans/0` | `foobar` |
| `traefik/http/routers/Router1/tls/domains/0/sans/1` | `foobar` |
| `traefik/http/routers/Router1/tls/domains/1/main` | `foobar` |
| `traefik/http/routers/Router1/tls/domains/1/sans/0` | `foobar` |
| `traefik/http/routers/Router1/tls/domains/1/sans/1` | `foobar` |
| `traefik/http/routers/Router1/tls/options` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name1` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/healthCheck/hostname` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/healthCheck/interval` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/healthCheck/path` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/healthCheck/port` | `42` |
| `traefik/http/services/Service01/loadBalancer/healthCheck/scheme` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/healthCheck/timeout` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/passHostHeader` | `true` |
| `traefik/http/services/Service01/loadBalancer/responseForwarding/flushInterval` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/servers/0/url` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/servers/1/url` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/sticky/cookie/httpOnly` | `true` |
| `traefik/http/services/Service01/loadBalancer/sticky/cookie/name` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/sticky/cookie/secure` | `true` |
| `traefik/http/services/Service02/mirroring/mirrors/0/name` | `foobar` |
| `traefik/http/services/Service02/mirroring/mirrors/0/percent` | `42` |
| `traefik/http/services/Service02/mirroring/mirrors/1/name` | `foobar` |
| `traefik/http/services/Service02/mirroring/mirrors/1/percent` | `42` |
| `traefik/http/services/Service02/mirroring/service` | `foobar` |
| `traefik/http/services/Service03/weighted/services/0/name` | `foobar` |
| `traefik/http/services/Service03/weighted/services/0/weight` | `42` |
| `traefik/http/services/Service03/weighted/services/1/name` | `foobar` |
| `traefik/http/services/Service03/weighted/services/1/weight` | `42` |
| `traefik/http/services/Service03/weighted/sticky/cookie/httpOnly` | `true` |
| `traefik/http/services/Service03/weighted/sticky/cookie/name` | `foobar` |
| `traefik/http/services/Service03/weighted/sticky/cookie/secure` | `true` |
| `traefik/tcp/routers/TCPRouter0/entryPoints/0` | `foobar` |
| `traefik/tcp/routers/TCPRouter0/entryPoints/1` | `foobar` |
| `traefik/tcp/routers/TCPRouter0/rule` | `foobar` |
| `traefik/tcp/routers/TCPRouter0/service` | `foobar` |
| `traefik/tcp/routers/TCPRouter0/tls/certResolver` | `foobar` |
| `traefik/tcp/routers/TCPRouter0/tls/domains/0/main` | `foobar` |
| `traefik/tcp/routers/TCPRouter0/tls/domains/0/sans/0` | `foobar` |
| `traefik/tcp/routers/TCPRouter0/tls/domains/0/sans/1` | `foobar` |
| `traefik/tcp/routers/TCPRouter0/tls/domains/1/main` | `foobar` |
| `traefik/tcp/routers/TCPRouter0/tls/domains/1/sans/0` | `foobar` |
| `traefik/tcp/routers/TCPRouter0/tls/domains/1/sans/1` | `foobar` |
| `traefik/tcp/routers/TCPRouter0/tls/options` | `foobar` |
| `traefik/tcp/routers/TCPRouter0/tls/passthrough` | `true` |
| `traefik/tcp/routers/TCPRouter1/entryPoints/0` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/entryPoints/1` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/rule` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/service` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/tls/certResolver` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/tls/domains/0/main` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/tls/domains/0/sans/0` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/tls/domains/0/sans/1` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/tls/domains/1/main` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/tls/domains/1/sans/0` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/tls/domains/1/sans/1` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/tls/options` | `foobar` |
| `traefik/tcp/routers/TCPRouter1/tls/passthrough` | `true` |
| `traefik/tcp/services/TCPService01/loadBalancer/servers/0/address` | `foobar` |
| `traefik/tcp/services/TCPService01/loadBalancer/servers/1/address` | `foobar` |
| `traefik/tcp/services/TCPService01/loadBalancer/terminationDelay` | `42` |
| `traefik/tcp/services/TCPService02/weighted/services/0/name` | `foobar` |
| `traefik/tcp/services/TCPService02/weighted/services/0/weight` | `42` |
| `traefik/tcp/services/TCPService02/weighted/services/1/name` | `foobar` |
| `traefik/tcp/services/TCPService02/weighted/services/1/weight` | `42` |
| `traefik/tls/certificates/0/certFile` | `foobar` |
| `traefik/tls/certificates/0/keyFile` | `foobar` |
| `traefik/tls/certificates/0/stores/0` | `foobar` |
| `traefik/tls/certificates/0/stores/1` | `foobar` |
| `traefik/tls/certificates/1/certFile` | `foobar` |
| `traefik/tls/certificates/1/keyFile` | `foobar` |
| `traefik/tls/certificates/1/stores/0` | `foobar` |
| `traefik/tls/certificates/1/stores/1` | `foobar` |
| `traefik/tls/options/Options0/cipherSuites/0` | `foobar` |
| `traefik/tls/options/Options0/cipherSuites/1` | `foobar` |
| `traefik/tls/options/Options0/clientAuth/caFiles/0` | `foobar` |
| `traefik/tls/options/Options0/clientAuth/caFiles/1` | `foobar` |
| `traefik/tls/options/Options0/clientAuth/clientAuthType` | `foobar` |
| `traefik/tls/options/Options0/curvePreferences/0` | `foobar` |
| `traefik/tls/options/Options0/curvePreferences/1` | `foobar` |
| `traefik/tls/options/Options0/maxVersion` | `foobar` |
| `traefik/tls/options/Options0/minVersion` | `foobar` |
| `traefik/tls/options/Options0/sniStrict` | `true` |
| `traefik/tls/options/Options1/cipherSuites/0` | `foobar` |
| `traefik/tls/options/Options1/cipherSuites/1` | `foobar` |
| `traefik/tls/options/Options1/clientAuth/caFiles/0` | `foobar` |
| `traefik/tls/options/Options1/clientAuth/caFiles/1` | `foobar` |
| `traefik/tls/options/Options1/clientAuth/clientAuthType` | `foobar` |
| `traefik/tls/options/Options1/curvePreferences/0` | `foobar` |
| `traefik/tls/options/Options1/curvePreferences/1` | `foobar` |
| `traefik/tls/options/Options1/maxVersion` | `foobar` |
| `traefik/tls/options/Options1/minVersion` | `foobar` |
| `traefik/tls/options/Options1/sniStrict` | `true` |
| `traefik/tls/stores/Store0/defaultCertificate/certFile` | `foobar` |
| `traefik/tls/stores/Store0/defaultCertificate/keyFile` | `foobar` |
| `traefik/tls/stores/Store1/defaultCertificate/certFile` | `foobar` |
| `traefik/tls/stores/Store1/defaultCertificate/keyFile` | `foobar` |

View file

@ -0,0 +1,8 @@
# KV Configuration Reference
Dynamic configuration with KV stores.
{: .subtitle }
| Key (Path) | Value |
|----------------------------------------------------------------------------------------------|-------------|
--8<-- "content/reference/dynamic-configuration/kv-ref.md"

View file

@ -243,6 +243,36 @@ EntryPoint (Default: ```traefik```)
`--ping.manualrouting`:
Manual routing (Default: ```false```)
`--providers.consul`:
Enable Consul backend with default settings. (Default: ```false```)
`--providers.consul.endpoints`:
KV store endpoints (Default: ```127.0.0.1:8500```)
`--providers.consul.password`:
KV Password
`--providers.consul.rootkey`:
Root key used for KV store (Default: ```traefik```)
`--providers.consul.tls.ca`:
TLS CA
`--providers.consul.tls.caoptional`:
TLS CA.Optional (Default: ```false```)
`--providers.consul.tls.cert`:
TLS cert
`--providers.consul.tls.insecureskipverify`:
TLS insecure skip verify (Default: ```false```)
`--providers.consul.tls.key`:
TLS key
`--providers.consul.username`:
KV Username
`--providers.consulcatalog.cache`:
Use local agent caching for catalog reads. (Default: ```false```)
@ -348,6 +378,36 @@ Use the ip address from the bound port, rather than from the inner network. (Def
`--providers.docker.watch`:
Watch provider. (Default: ```true```)
`--providers.etcd`:
Enable Etcd backend with default settings. (Default: ```false```)
`--providers.etcd.endpoints`:
KV store endpoints (Default: ```127.0.0.1:2379```)
`--providers.etcd.password`:
KV Password
`--providers.etcd.rootkey`:
Root key used for KV store (Default: ```traefik```)
`--providers.etcd.tls.ca`:
TLS CA
`--providers.etcd.tls.caoptional`:
TLS CA.Optional (Default: ```false```)
`--providers.etcd.tls.cert`:
TLS cert
`--providers.etcd.tls.insecureskipverify`:
TLS insecure skip verify (Default: ```false```)
`--providers.etcd.tls.key`:
TLS key
`--providers.etcd.username`:
KV Username
`--providers.file.debugloggeneratedtemplate`:
Enable debug logging of generated configuration template. (Default: ```false```)
@ -516,12 +576,72 @@ Defines the polling interval in seconds. (Default: ```15```)
`--providers.rancher.watch`:
Watch provider. (Default: ```true```)
`--providers.redis`:
Enable Redis backend with default settings. (Default: ```false```)
`--providers.redis.endpoints`:
KV store endpoints (Default: ```127.0.0.1:6379```)
`--providers.redis.password`:
KV Password
`--providers.redis.rootkey`:
Root key used for KV store (Default: ```traefik```)
`--providers.redis.tls.ca`:
TLS CA
`--providers.redis.tls.caoptional`:
TLS CA.Optional (Default: ```false```)
`--providers.redis.tls.cert`:
TLS cert
`--providers.redis.tls.insecureskipverify`:
TLS insecure skip verify (Default: ```false```)
`--providers.redis.tls.key`:
TLS key
`--providers.redis.username`:
KV Username
`--providers.rest`:
Enable Rest backend with default settings. (Default: ```false```)
`--providers.rest.insecure`:
Activate REST Provider directly on the entryPoint named traefik. (Default: ```false```)
`--providers.zookeeper`:
Enable ZooKeeper backend with default settings. (Default: ```false```)
`--providers.zookeeper.endpoints`:
KV store endpoints (Default: ```127.0.0.1:2181```)
`--providers.zookeeper.password`:
KV Password
`--providers.zookeeper.rootkey`:
Root key used for KV store (Default: ```traefik```)
`--providers.zookeeper.tls.ca`:
TLS CA
`--providers.zookeeper.tls.caoptional`:
TLS CA.Optional (Default: ```false```)
`--providers.zookeeper.tls.cert`:
TLS cert
`--providers.zookeeper.tls.insecureskipverify`:
TLS insecure skip verify (Default: ```false```)
`--providers.zookeeper.tls.key`:
TLS key
`--providers.zookeeper.username`:
KV Username
`--serverstransport.forwardingtimeouts.dialtimeout`:
The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists. (Default: ```30```)

View file

@ -243,6 +243,9 @@ EntryPoint (Default: ```traefik```)
`TRAEFIK_PING_MANUALROUTING`:
Manual routing (Default: ```false```)
`TRAEFIK_PROVIDERS_CONSUL`:
Enable Consul backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_CONSULCATALOG_CACHE`:
Use local agent caching for catalog reads. (Default: ```false```)
@ -303,6 +306,33 @@ Forces the read to be fully consistent. (Default: ```false```)
`TRAEFIK_PROVIDERS_CONSULCATALOG_STALE`:
Use stale consistency for catalog reads. (Default: ```false```)
`TRAEFIK_PROVIDERS_CONSUL_ENDPOINTS`:
KV store endpoints (Default: ```127.0.0.1:8500```)
`TRAEFIK_PROVIDERS_CONSUL_PASSWORD`:
KV Password
`TRAEFIK_PROVIDERS_CONSUL_ROOTKEY`:
Root key used for KV store (Default: ```traefik```)
`TRAEFIK_PROVIDERS_CONSUL_TLS_CA`:
TLS CA
`TRAEFIK_PROVIDERS_CONSUL_TLS_CAOPTIONAL`:
TLS CA.Optional (Default: ```false```)
`TRAEFIK_PROVIDERS_CONSUL_TLS_CERT`:
TLS cert
`TRAEFIK_PROVIDERS_CONSUL_TLS_INSECURESKIPVERIFY`:
TLS insecure skip verify (Default: ```false```)
`TRAEFIK_PROVIDERS_CONSUL_TLS_KEY`:
TLS key
`TRAEFIK_PROVIDERS_CONSUL_USERNAME`:
KV Username
`TRAEFIK_PROVIDERS_DOCKER`:
Enable Docker backend with default settings. (Default: ```false```)
@ -348,6 +378,36 @@ Use the ip address from the bound port, rather than from the inner network. (Def
`TRAEFIK_PROVIDERS_DOCKER_WATCH`:
Watch provider. (Default: ```true```)
`TRAEFIK_PROVIDERS_ETCD`:
Enable Etcd backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_ETCD_ENDPOINTS`:
KV store endpoints (Default: ```127.0.0.1:2379```)
`TRAEFIK_PROVIDERS_ETCD_PASSWORD`:
KV Password
`TRAEFIK_PROVIDERS_ETCD_ROOTKEY`:
Root key used for KV store (Default: ```traefik```)
`TRAEFIK_PROVIDERS_ETCD_TLS_CA`:
TLS CA
`TRAEFIK_PROVIDERS_ETCD_TLS_CAOPTIONAL`:
TLS CA.Optional (Default: ```false```)
`TRAEFIK_PROVIDERS_ETCD_TLS_CERT`:
TLS cert
`TRAEFIK_PROVIDERS_ETCD_TLS_INSECURESKIPVERIFY`:
TLS insecure skip verify (Default: ```false```)
`TRAEFIK_PROVIDERS_ETCD_TLS_KEY`:
TLS key
`TRAEFIK_PROVIDERS_ETCD_USERNAME`:
KV Username
`TRAEFIK_PROVIDERS_FILE_DEBUGLOGGENERATEDTEMPLATE`:
Enable debug logging of generated configuration template. (Default: ```false```)
@ -516,12 +576,72 @@ Defines the polling interval in seconds. (Default: ```15```)
`TRAEFIK_PROVIDERS_RANCHER_WATCH`:
Watch provider. (Default: ```true```)
`TRAEFIK_PROVIDERS_REDIS`:
Enable Redis backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_REDIS_ENDPOINTS`:
KV store endpoints (Default: ```127.0.0.1:6379```)
`TRAEFIK_PROVIDERS_REDIS_PASSWORD`:
KV Password
`TRAEFIK_PROVIDERS_REDIS_ROOTKEY`:
Root key used for KV store (Default: ```traefik```)
`TRAEFIK_PROVIDERS_REDIS_TLS_CA`:
TLS CA
`TRAEFIK_PROVIDERS_REDIS_TLS_CAOPTIONAL`:
TLS CA.Optional (Default: ```false```)
`TRAEFIK_PROVIDERS_REDIS_TLS_CERT`:
TLS cert
`TRAEFIK_PROVIDERS_REDIS_TLS_INSECURESKIPVERIFY`:
TLS insecure skip verify (Default: ```false```)
`TRAEFIK_PROVIDERS_REDIS_TLS_KEY`:
TLS key
`TRAEFIK_PROVIDERS_REDIS_USERNAME`:
KV Username
`TRAEFIK_PROVIDERS_REST`:
Enable Rest backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_REST_INSECURE`:
Activate REST Provider directly on the entryPoint named traefik. (Default: ```false```)
`TRAEFIK_PROVIDERS_ZOOKEEPER`:
Enable ZooKeeper backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_ZOOKEEPER_ENDPOINTS`:
KV store endpoints (Default: ```127.0.0.1:2181```)
`TRAEFIK_PROVIDERS_ZOOKEEPER_PASSWORD`:
KV Password
`TRAEFIK_PROVIDERS_ZOOKEEPER_ROOTKEY`:
Root key used for KV store (Default: ```traefik```)
`TRAEFIK_PROVIDERS_ZOOKEEPER_TLS_CA`:
TLS CA
`TRAEFIK_PROVIDERS_ZOOKEEPER_TLS_CAOPTIONAL`:
TLS CA.Optional (Default: ```false```)
`TRAEFIK_PROVIDERS_ZOOKEEPER_TLS_CERT`:
TLS cert
`TRAEFIK_PROVIDERS_ZOOKEEPER_TLS_INSECURESKIPVERIFY`:
TLS insecure skip verify (Default: ```false```)
`TRAEFIK_PROVIDERS_ZOOKEEPER_TLS_KEY`:
TLS key
`TRAEFIK_PROVIDERS_ZOOKEEPER_USERNAME`:
KV Username
`TRAEFIK_SERVERSTRANSPORT_FORWARDINGTIMEOUTS_DIALTIMEOUT`:
The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists. (Default: ```30```)

View file

@ -129,6 +129,50 @@
cert = "foobar"
key = "foobar"
insecureSkipVerify = true
[providers.consul]
rootKey = "traefik"
endpoints = ["foobar", "foobar"]
username = "foobar"
password = "foobar"
[providers.consul.tls]
ca = "foobar"
caOptional = true
cert = "foobar"
key = "foobar"
insecureSkipVerify = true
[providers.etcd]
rootKey = "traefik"
endpoints = ["foobar", "foobar"]
username = "foobar"
password = "foobar"
[providers.etcd.tls]
ca = "foobar"
caOptional = true
cert = "foobar"
key = "foobar"
insecureSkipVerify = true
[providers.zooKeeper]
rootKey = "traefik"
endpoints = ["foobar", "foobar"]
username = "foobar"
password = "foobar"
[providers.zooKeeper.tls]
ca = "foobar"
caOptional = true
cert = "foobar"
key = "foobar"
insecureSkipVerify = true
[providers.redis]
rootKey = "traefik"
endpoints = ["foobar", "foobar"]
username = "foobar"
password = "foobar"
[providers.redis.tls]
ca = "foobar"
caOptional = true
cert = "foobar"
key = "foobar"
insecureSkipVerify = true
[api]
insecure = true

View file

@ -136,6 +136,58 @@ providers:
cert: foobar
key: foobar
insecureSkipVerify: true
consul:
rootKey: traefik
endpoints:
- foobar
- foobar
username: foobar
password: foobar
tls:
ca: foobar
caOptional: true
cert: foobar
key: foobar
insecureSkipVerify: true
etcd:
rootKey: traefik
endpoints:
- foobar
- foobar
username: foobar
password: foobar
tls:
ca: foobar
caOptional: true
cert: foobar
key: foobar
insecureSkipVerify: true
zooKeeper:
rootKey: traefik
endpoints:
- foobar
- foobar
username: foobar
password: foobar
tls:
ca: foobar
caOptional: true
cert: foobar
key: foobar
insecureSkipVerify: true
redis:
rootKey: traefik
endpoints:
- foobar
- foobar
username: foobar
password: foobar
tls:
ca: foobar
caOptional: true
cert: foobar
key: foobar
insecureSkipVerify: true
api:
insecure: true
dashboard: true

View file

@ -0,0 +1,392 @@
# Traefik & KV Stores
A Story of key & values
{: .subtitle }
## Routing Configuration
!!! info "Keys"
- Keys are case insensitive.
- The complete list of keys can be found in [the reference page](../../reference/dynamic-configuration/kv.md).
### Routers
!!! warning "The character `@` is not authorized in the router name `<router_name>`."
??? info "`traefik/http/routers/<router_name>/rule`"
See [rule](../routers/index.md#rule) for more information.
| Key (Path) | Value |
|--------------------------------------|----------------------------|
| `traefik/http/routers/myrouter/rule` | ```Host(`mydomain.com`)``` |
??? info "`traefik/http/routers/<router_name>/entrypoints`"
See [entry points](../routers/index.md#entrypoints) for more information.
| Key (Path) | Value |
|-----------------------------------------------|-------------|
| `traefik.http.routers.myrouter.entrypoints/0` | `web` |
| `traefik.http.routers.myrouter.entrypoints/1` | `websecure` |
??? info "`traefik/http/routers/<router_name>/middlewares`"
See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information.
| Key (Path) | Value |
|-----------------------------------------------|-------------|
| `traefik/http/routers/myrouter/middlewares/0` | `auth` |
| `traefik/http/routers/myrouter/middlewares/1` | `prefix` |
| `traefik/http/routers/myrouter/middlewares/2` | `cb` |
??? info "`traefik/http/routers/<router_name>/service`"
See [rule](../routers/index.md#service) for more information.
| Key (Path) | Value |
|-----------------------------------------|-------------|
| `traefik/http/routers/myrouter/service` | `myservice` |
??? info "`traefik/http/routers/<router_name>/tls`"
See [tls](../routers/index.md#tls) for more information.
| Key (Path) | Value |
|-------------------------------------|--------|
| `traefik/http/routers/myrouter/tls` | `true` |
??? info "`traefik/http/routers/<router_name>/tls/certresolver`"
See [certResolver](../routers/index.md#certresolver) for more information.
| Key (Path) | Value |
|--------------------------------------------------|--------------|
| `traefik/http/routers/myrouter/tls/certresolver` | `myresolver` |
??? info "`traefik/http/routers/<router_name>/tls/domains/<n>/main`"
See [domains](../routers/index.md#domains) for more information.
| Key (Path) | Value |
|----------------------------------------------------|--------------|
| `traefik/http/routers/myrouter/tls/domains/0/main` | `foobar.com` |
??? info "`traefik/http/routers/<router_name>/tls/domains/<n>/sans/<n>`"
See [domains](../routers/index.md#domains) for more information.
| Key (Path) | Value |
|------------------------------------------------------|-------------------|
| `traefik/http/routers/myrouter/tls/domains/0/sans/0` | `test.foobar.com` |
| `traefik/http/routers/myrouter/tls/domains/0/sans/1` | `dev.foobar.com` |
??? info "`traefik/http/routers/<router_name>/tls/options`"
See [options](../routers/index.md#options) for more information.
| Key (Path) | Value |
|---------------------------------------------|----------|
| `traefik/http/routers/myrouter/tls/options` | `foobar` |
??? info "`traefik/http/routers/<router_name>/priority`"
See [priority](../routers/index.md#priority) for more information.
| Key (Path) | Value |
|------------------------------------------|-------|
| `traefik/http/routers/myrouter/priority` | `42` |
### Services
!!! warning "The character `@` is not authorized in the service name `<service_name>`."
??? info "`traefik/http/services/<service_name>/loadbalancer/servers/<n>/url`"
See [servers](../services/index.md#servers) for more information.
| Key (Path) | Value |
|-----------------------------------------------------------------|--------|
| `traefik/http/services/myservice/loadbalancer/servers/0/scheme` | `http` |
??? info "`traefik/http/services/<service_name>/loadbalancer/servers/<n>/scheme`"
Overrides the default scheme.
| Key (Path) | Value |
|-----------------------------------------------------------------|--------|
| `traefik/http/services/myservice/loadbalancer/servers/0/scheme` | `http` |
??? info "`traefik/http/services/<service_name>/loadbalancer/passhostheader`"
See [pass Host header](../services/index.md#pass-host-header) for more information.
| Key (Path) | Value |
|-----------------------------------------------------------------|--------|
| `traefik/http/services/myservice/loadbalancer/passhostheader` | `true` |
??? info "`traefik/http/services/<service_name>/loadbalancer/healthcheck/headers/<header_name>`"
See [health check](../services/index.md#health-check) for more information.
| Key (Path) | Value |
|--------------------------------------------------------------------------|----------|
| `traefik/http/services/myservice/loadbalancer/healthcheck/headers/X-Foo` | `foobar` |
??? info "`traefik/http/services/<service_name>/loadbalancer/healthcheck/hostname`"
See [health check](../services/index.md#health-check) for more information.
| Key (Path) | Value |
|---------------------------------------------------------------------|--------------|
| `traefik/http/services/myservice/loadbalancer/healthcheck/hostname` | `foobar.com` |
??? info "`traefik/http/services/<service_name>/loadbalancer/healthcheck/interval`"
See [health check](../services/index.md#health-check) for more information.
| Key (Path) | Value |
|---------------------------------------------------------------------|-------|
| `traefik/http/services/myservice/loadbalancer/healthcheck/interval` | `10` |
??? info "`traefik/http/services/<service_name>/loadbalancer/healthcheck/path`"
See [health check](../services/index.md#health-check) for more information.
| Key (Path) | Value |
|-----------------------------------------------------------------|--------|
| `traefik/http/services/myservice/loadbalancer/healthcheck/path` | `/foo` |
??? info "`traefik/http/services/<service_name>/loadbalancer/healthcheck/port`"
See [health check](../services/index.md#health-check) for more information.
| Key (Path) | Value |
|-----------------------------------------------------------------|-------|
| `traefik/http/services/myservice/loadbalancer/healthcheck/port` | `42` |
??? info "`traefik/http/services/<service_name>/loadbalancer/healthcheck/scheme`"
See [health check](../services/index.md#health-check) for more information.
| Key (Path) | Value |
|-------------------------------------------------------------------|--------|
| `traefik/http/services/myservice/loadbalancer/healthcheck/scheme` | `http` |
??? info "`traefik/http/services/<service_name>/loadbalancer/healthcheck/timeout`"
See [health check](../services/index.md#health-check) for more information.
| Key (Path) | Value |
|--------------------------------------------------------------------|-------|
| `traefik/http/services/myservice/loadbalancer/healthcheck/timeout` | `10` |
??? info "`traefik/http/services/<service_name>/loadbalancer/sticky`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
| Key (Path) | Value |
|-------------------------------------------------------|--------|
| `traefik/http/services/myservice/loadbalancer/sticky` | `true` |
??? info "`traefik/http/services/<service_name>/loadbalancer/sticky/cookie/httponly`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
| Key (Path) | Value |
|-----------------------------------------------------------------------|--------|
| `traefik/http/services/myservice/loadbalancer/sticky/cookie/httponly` | `true` |
??? info "`traefik/http/services/<service_name>/loadbalancer/sticky/cookie/name`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
| Key (Path) | Value |
|-------------------------------------------------------------------|----------|
| `traefik/http/services/myservice/loadbalancer/sticky/cookie/name` | `foobar` |
??? info "`traefik/http/services/<service_name>/loadbalancer/sticky/cookie/secure`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
| Key (Path) | Value |
|---------------------------------------------------------------------|--------|
| `traefik/http/services/myservice/loadbalancer/sticky/cookie/secure` | `true` |
??? info "`traefik/http/services/<service_name>/loadbalancer/responseforwarding/flushinterval`"
See [response forwarding](../services/index.md#response-forwarding) for more information.
| Key (Path) | Value |
|---------------------------------------------------------------------------------|-------|
| `traefik/http/services/myservice/loadbalancer/responseforwarding/flushinterval` | `10` |
??? info "`traefik/http/services/<service_name>/mirroring/service`"
| Key (Path) | Value |
|----------------------------------------------------------|----------|
| `traefik/http/services/<service_name>/mirroring/service` | `foobar` |
??? info "`traefik/http/services/<service_name>/mirroring/mirrors/<n>/name`"
| Key (Path) | Value |
|-------------------------------------------------------------------|----------|
| `traefik/http/services/<service_name>/mirroring/mirrors/<n>/name` | `foobar` |
??? info "`traefik/http/services/<service_name>/mirroring/mirrors/<n>/percent`"
| Key (Path) | Value |
|----------------------------------------------------------------------|-------|
| `traefik/http/services/<service_name>/mirroring/mirrors/<n>/percent` | `42` |
??? info "`traefik/http/services/<service_name>/weighted/services/<n>/name`"
| Key (Path) | Value |
|-------------------------------------------------------------------|----------|
| `traefik/http/services/<service_name>/weighted/services/<n>/name` | `foobar` |
??? info "`traefik/http/services/<service_name>/weighted/services/<n>/weight`"
| Key (Path) | Value |
|---------------------------------------------------------------------|-------|
| `traefik/http/services/<service_name>/weighted/services/<n>/weight` | `42` |
??? info "`traefik/http/services/<service_name>/weighted/sticky/cookie/name`"
| Key (Path) | Value |
|--------------------------------------------------------------------|----------|
| `traefik/http/services/<service_name>/weighted/sticky/cookie/name` | `foobar` |
??? info "`traefik/http/services/<service_name>/weighted/sticky/cookie/secure`"
| Key (Path) | Value |
|----------------------------------------------------------------------|--------|
| `traefik/http/services/<service_name>/weighted/sticky/cookie/secure` | `true` |
??? info "`traefik/http/services/<service_name>/weighted/sticky/cookie/httpOnly`"
| Key (Path) | Value |
|------------------------------------------------------------------------|--------|
| `traefik/http/services/<service_name>/weighted/sticky/cookie/httpOnly` | `true` |
### Middleware
More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md).
!!! warning "The character `@` is not authorized in the middleware name."
!!! warning "Conflicts in Declaration"
If you declare multiple middleware with the same name but with different parameters, the middleware fails to be declared.
### TCP
You can declare TCP Routers and/or Services using KV.
#### TCP Routers
??? info "`traefik/tcp/routers/<router_name>/entrypoints`"
See [entry points](../routers/index.md#entrypoints_1) for more information.
| Key (Path) | Value |
|-------------------------------------------------|-------|
| `traefik/tcp/routers/mytcprouter/entrypoints/0` | `ep1` |
| `traefik/tcp/routers/mytcprouter/entrypoints/1` | `ep2` |
??? info "`traefik/tcp/routers/<router_name>/rule`"
See [rule](../routers/index.md#rule_1) for more information.
| Key (Path) | Value |
|--------------------------------------|------------------------------|
| `traefik/tcp/routers/my-router/rule` | ```HostSNI(`my-host.com`)``` |
??? info "`traefik/tcp/routers/<router_name>/service`"
See [service](../routers/index.md#services) for more information.
| Key (Path) | Value |
|-------------------------------------------|-------------|
| `traefik/tcp/routers/mytcprouter/service` | `myservice` |
??? info "`traefik/tcp/routers/<router_name>/tls`"
See [TLS](../routers/index.md#tls_1) for more information.
| Key (Path) | Value |
|---------------------------------------|--------|
| `traefik/tcp/routers/mytcprouter/tls` | `true` |
??? info "`traefik/tcp/routers/<router_name>/tls/certresolver`"
See [certResolver](../routers/index.md#certresolver_1) for more information.
| Key (Path) | Value |
|----------------------------------------------------|--------------|
| `traefik/tcp/routers/mytcprouter/tls/certresolver` | `myresolver` |
??? info "`traefik/tcp/routers/<router_name>/tls/domains/<n>/main`"
See [domains](../routers/index.md#domains_1) for more information.
| Key (Path) | Value |
|------------------------------------------------------|--------------|
| `traefik/tcp/routers/mytcprouter/tls/domains/0/main` | `foobar.com` |
??? info "`traefik/tcp/routers/<router_name>/tls/domains/<n>/sans`"
See [domains](../routers/index.md#domains_1) for more information.
| Key (Path) | Value |
|--------------------------------------------------------|-------------------|
| `traefik/tcp/routers/mytcprouter/tls/domains/0/sans/0` | `test.foobar.com` |
| `traefik/tcp/routers/mytcprouter/tls/domains/0/sans/1` | `dev.foobar.com` |
??? info "`traefik/tcp/routers/<router_name>/tls/options`"
See [options](../routers/index.md#options_1) for more information.
| Key (Path) | Value |
|-----------------------------------------------|----------|
| `traefik/tcp/routers/mytcprouter/tls/options` | `foobar` |
??? info "`traefik/tcp/routers/<router_name>/tls/passthrough`"
See [TLS](../routers/index.md#tls_1) for more information.
| Key (Path) | Value |
|---------------------------------------------------|--------|
| `traefik/tcp/routers/mytcprouter/tls/passthrough` | `true` |
#### TCP Services
??? info "`traefik/tcp/services/<service_name>/loadbalancer/servers/<n>/url`"
See [servers](../services/index.md#servers) for more information.
| Key (Path) | Value |
|-------------------------------------------------------------------|--------|
| `traefik/tcp/services/mytcpservice/loadbalancer/servers/0/scheme` | `http` |
??? info "`traefik/tcp/services/<service_name>/loadbalancer/terminationdelay`"
See [termination delay](../services/index.md#termination-delay) for more information.
| Key (Path) | Value |
|-------------------------------------------------------------------|-------|
| `traefik/tcp/services/mytcpservice/loadbalancer/terminationdelay` | `100` |
??? info "`traefik/tcp/services/<service_name>/weighted/services/<n>/name`"
| Key (Path) | Value |
|---------------------------------------------------------------------|----------|
| `traefik/tcp/services/<service_name>/weighted/services/0/name` | `foobar` |
??? info "`traefik/tcp/services/<service_name>/weighted/services/<n>/weight`"
| Key (Path) | Value |
|------------------------------------------------------------------|-------|
| `traefik/tcp/services/<service_name>/weighted/services/0/weight` | `42` |

View file

@ -84,6 +84,10 @@ nav:
- 'Marathon': 'providers/marathon.md'
- 'Rancher': 'providers/rancher.md'
- 'File': 'providers/file.md'
- 'Consul': 'providers/consul.md'
- 'Etcd': 'providers/etcd.md'
- 'ZooKeeper': 'providers/zookeeper.md'
- 'Redis': 'providers/redis.md'
- 'Routing & Load Balancing':
- 'Overview': 'routing/overview.md'
- 'EntryPoints': 'routing/entrypoints.md'
@ -95,6 +99,7 @@ nav:
- 'Consul Catalog': 'routing/providers/consul-catalog.md'
- 'Marathon': 'routing/providers/marathon.md'
- 'Rancher': 'routing/providers/rancher.md'
- 'KV': 'routing/providers/kv.md'
- 'HTTPS & TLS':
- 'Overview': 'https/overview.md'
- 'TLS': 'https/tls.md'
@ -180,3 +185,4 @@ nav:
- 'Consul Catalog': 'reference/dynamic-configuration/consul-catalog.md'
- 'Marathon': 'reference/dynamic-configuration/marathon.md'
- 'Rancher': 'reference/dynamic-configuration/rancher.md'
- 'KV': 'reference/dynamic-configuration/kv.md'

16
go.sum
View file

@ -120,11 +120,15 @@ github.com/containous/multibuf v0.0.0-20190809014333-8b6c9a7e6bba h1:PhR03pep+5e
github.com/containous/multibuf v0.0.0-20190809014333-8b6c9a7e6bba/go.mod h1:zkWcASFUJEst6QwCrxLdkuw1gvaKqmflEipm+iecV5M=
github.com/containous/mux v0.0.0-20181024131434-c33f32e26898 h1:1srn9voikJGofblBhWy3WuZWqo14Ou7NaswNG/I2yWc=
github.com/containous/mux v0.0.0-20181024131434-c33f32e26898/go.mod h1:z8WW7n06n8/1xF9Jl9WmuDeZuHAhfL+bwarNjsciwwg=
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpu/goacmedns v0.0.1 h1:GeIU5chKys9zmHgOAgP+bstRaLqcGQ6HJh/hLw9hrus=
github.com/cpu/goacmedns v0.0.1/go.mod h1:sesf/pNnCYwUevQEQfEwY0Y3DydlQWSGZbaMElOWxok=
@ -198,6 +202,7 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2 h1:df6OFl8WNXk82xxP3R9ZPZ5seOA8XZkwLdbEzZF1/xI=
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2/go.mod h1:GLyXJD41gBO/NPKVPGQbhyyC06eugGy15QEZyUkE2/s=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-acme/lego/v3 v3.2.0 h1:z0zvNlL1niv/1qA06V5X1BRC5PeLoGKAlVaWthXQz9c=
github.com/go-acme/lego/v3 v3.2.0/go.mod h1:074uqt+JS6plx+c9Xaiz6+L+GBb+7itGtzfcDM2AhEE=
@ -279,7 +284,9 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
github.com/gravitational/trace v0.0.0-20190726142706-a535a178675f h1:68WxnfBzJRYktZ30fmIjGQ74RsXYLoeH2/NITPktTMY=
github.com/gravitational/trace v0.0.0-20190726142706-a535a178675f/go.mod h1:RvdOUHE4SHqR3oXlFFKnGzms8a5dugHygGw1bqDstYI=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
@ -527,6 +534,7 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sacloud/libsacloud v1.26.1 h1:td3Kd7lvpSAxxHEVpnaZ9goHmmhi0D/RfP0Rqqf/kek=
github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ=
github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY=
github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
@ -543,6 +551,7 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@ -561,6 +570,7 @@ github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7 h1:CpHxIaZzVy26G
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
github.com/tinylib/msgp v1.0.2 h1:DfdQrzQa7Yh2es9SuLkixqxuXS2SxsdYn0KbdrOGWD8=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY=
github.com/transip/gotransip v5.8.2+incompatible h1:aNJhw/w/3QBqFcHAIPz1ytoK5FexeMzbUCGrrhWr3H0=
@ -593,6 +603,7 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg=
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.elastic.co/apm v1.6.0 h1:RzyNj9Qx2iXh4A8DIg/aMUdtwGFNw3R8sKO3/Hf4pqk=
go.elastic.co/apm v1.6.0/go.mod h1:/VByR6FBtuNu1YnPHz7Gri7YiIqAoFBGVp+2xkSE8tI=
@ -602,7 +613,9 @@ go.elastic.co/apm/module/apmot v1.6.0 h1:3Tjwu25SfB2Bq+UsO8JOKYOuMzUu4s8/fS8BGkl
go.elastic.co/apm/module/apmot v1.6.0/go.mod h1:JIGFfrixcGjp8oD22PTTCvdNMfp+O+lOb5Hw4MtqxQ4=
go.elastic.co/fastjson v1.0.0 h1:ooXV/ABvf+tBul26jcVViPT3sBir0PvXgibYB1IQQzg=
go.elastic.co/fastjson v1.0.0/go.mod h1:PmeUOMMtLHQr9ZS9J9owrAVg0FkaZDRZJEFTTGHtchs=
go.etcd.io/bbolt v1.3.1-etcd.8 h1:6J7QAKqfFBGnU80KRnuQxfjjeE5xAGE/qB810I3FQHQ=
go.etcd.io/bbolt v1.3.1-etcd.8/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v3.3.13+incompatible h1:jCejD5EMnlGxFvcGRyEV4VGlENZc7oPQX6o0t7n3xbw=
go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@ -612,9 +625,11 @@ go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 h1:d9qaMM+ODpCq+9We41//fu/sHsTnXcrqd1en3x+GKy4=
go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -769,6 +784,7 @@ gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc h1:GAcf+t0o8gdJAdSFYdE9wChu4bIyguMVqz0RHiFL5VY=
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw=
gopkg.in/redis.v5 v5.2.9 h1:MNZYOLPomQzZMfpN3ZtD1uyJ2IDonTTlxYiV/pEApiw=
gopkg.in/redis.v5 v5.2.9/go.mod h1:6gtv0/+A4iM08kdRfocWYB3bLX2tebpNtfKlFT6H4mY=
gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=

159
integration/consul_test.go Normal file
View file

@ -0,0 +1,159 @@
package integration
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"time"
"github.com/abronan/valkeyrie"
"github.com/abronan/valkeyrie/store"
"github.com/abronan/valkeyrie/store/consul"
"github.com/containous/traefik/v2/integration/try"
"github.com/containous/traefik/v2/pkg/api"
"github.com/go-check/check"
"github.com/pmezard/go-difflib/difflib"
checker "github.com/vdemeester/shakers"
)
// Consul test suites (using libcompose)
type ConsulSuite struct {
BaseSuite
kvClient store.Store
}
func (s *ConsulSuite) setupStore(c *check.C) {
s.createComposeProject(c, "consul")
s.composeProject.Start(c)
consul.Register()
kv, err := valkeyrie.NewStore(
store.CONSUL,
[]string{s.composeProject.Container(c, "consul").NetworkSettings.IPAddress + ":8500"},
&store.Config{
ConnectionTimeout: 10 * time.Second,
},
)
if err != nil {
c.Fatal("Cannot create store consul")
}
s.kvClient = kv
// wait for consul
err = try.Do(60*time.Second, try.KVExists(kv, "test"))
c.Assert(err, checker.IsNil)
}
func (s *ConsulSuite) TearDownTest(c *check.C) {
// shutdown and delete compose project
if s.composeProject != nil {
s.composeProject.Stop(c)
}
}
func (s *ConsulSuite) TearDownSuite(c *check.C) {}
func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
s.setupStore(c)
address := "http://" + s.composeProject.Container(c, "consul").NetworkSettings.IPAddress + ":8500"
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulAddress string }{address})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
data := map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "web",
"traefik/http/routers/Router0/middlewares/0": "compressor",
"traefik/http/routers/Router0/middlewares/1": "striper",
"traefik/http/routers/Router0/service": "simplesvc",
"traefik/http/routers/Router0/rule": "Host(`kv1.localhost`)",
"traefik/http/routers/Router0/priority": "42",
"traefik/http/routers/Router0/tls": "",
"traefik/http/routers/Router1/rule": "Host(`kv2.localhost`)",
"traefik/http/routers/Router1/priority": "42",
"traefik/http/routers/Router1/tls/domains/0/main": "aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/0": "aaa.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/1": "bbb.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/1/main": "bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/0": "aaa.bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/1": "bbb.bbb.localhost",
"traefik/http/routers/Router1/entryPoints/0": "web",
"traefik/http/routers/Router1/service": "simplesvc",
"traefik/http/services/simplesvc/loadBalancer/servers/0/url": "http://10.0.1.1:8888",
"traefik/http/services/simplesvc/loadBalancer/servers/1/url": "http://10.0.1.1:8889",
"traefik/http/services/srvcA/loadBalancer/servers/0/url": "http://10.0.1.2:8888",
"traefik/http/services/srvcA/loadBalancer/servers/1/url": "http://10.0.1.2:8889",
"traefik/http/services/srvcB/loadBalancer/servers/0/url": "http://10.0.1.3:8888",
"traefik/http/services/srvcB/loadBalancer/servers/1/url": "http://10.0.1.3:8889",
"traefik/http/services/mirror/mirroring/service": "simplesvc",
"traefik/http/services/mirror/mirroring/mirrors/0/name": "srvcA",
"traefik/http/services/mirror/mirroring/mirrors/0/percent": "42",
"traefik/http/services/mirror/mirroring/mirrors/1/name": "srvcB",
"traefik/http/services/mirror/mirroring/mirrors/1/percent": "42",
"traefik/http/services/Service03/weighted/services/0/name": "srvcA",
"traefik/http/services/Service03/weighted/services/0/weight": "42",
"traefik/http/services/Service03/weighted/services/1/name": "srvcB",
"traefik/http/services/Service03/weighted/services/1/weight": "42",
"traefik/http/middlewares/compressor/compress": "",
"traefik/http/middlewares/striper/stripPrefix/prefixes/0": "foo",
"traefik/http/middlewares/striper/stripPrefix/prefixes/1": "bar",
"traefik/http/middlewares/striper/stripPrefix/forceSlash": "true",
}
for k, v := range data {
err := s.kvClient.Put(k, []byte(v), nil)
c.Assert(err, checker.IsNil)
}
// wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("@consul"))
c.Assert(err, checker.IsNil)
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
c.Assert(err, checker.IsNil)
var obtained api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&obtained)
c.Assert(err, checker.IsNil)
got, err := json.MarshalIndent(obtained, "", " ")
c.Assert(err, checker.IsNil)
expectedJSON := filepath.FromSlash("testdata/rawdata-consul.json")
if *updateExpected {
err = ioutil.WriteFile(expectedJSON, got, 0666)
c.Assert(err, checker.IsNil)
}
expected, err := ioutil.ReadFile(expectedJSON)
c.Assert(err, checker.IsNil)
if !bytes.Equal(expected, got) {
diff := difflib.UnifiedDiff{
FromFile: "Expected",
A: difflib.SplitLines(string(expected)),
ToFile: "Got",
B: difflib.SplitLines(string(got)),
Context: 3,
}
text, err := difflib.GetUnifiedDiffString(diff)
c.Assert(err, checker.IsNil)
c.Error(text)
}
}

159
integration/etcd_test.go Normal file
View file

@ -0,0 +1,159 @@
package integration
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"time"
"github.com/abronan/valkeyrie"
"github.com/abronan/valkeyrie/store"
etcdv3 "github.com/abronan/valkeyrie/store/etcd/v3"
"github.com/containous/traefik/v2/integration/try"
"github.com/containous/traefik/v2/pkg/api"
"github.com/go-check/check"
"github.com/pmezard/go-difflib/difflib"
checker "github.com/vdemeester/shakers"
)
// etcd test suites (using libcompose)
type EtcdSuite struct {
BaseSuite
kvClient store.Store
}
func (s *EtcdSuite) setupStore(c *check.C) {
s.createComposeProject(c, "etcd")
s.composeProject.Start(c)
etcdv3.Register()
kv, err := valkeyrie.NewStore(
store.ETCDV3,
[]string{s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress + ":2379"},
&store.Config{
ConnectionTimeout: 10 * time.Second,
},
)
if err != nil {
c.Fatal("Cannot create store etcd")
}
s.kvClient = kv
// wait for etcd
err = try.Do(60*time.Second, try.KVExists(kv, "test"))
c.Assert(err, checker.IsNil)
}
func (s *EtcdSuite) TearDownTest(c *check.C) {
// shutdown and delete compose project
if s.composeProject != nil {
s.composeProject.Stop(c)
}
}
func (s *EtcdSuite) TearDownSuite(c *check.C) {}
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
s.setupStore(c)
address := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress + ":2379"
file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdAddress string }{address})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
data := map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "web",
"traefik/http/routers/Router0/middlewares/0": "compressor",
"traefik/http/routers/Router0/middlewares/1": "striper",
"traefik/http/routers/Router0/service": "simplesvc",
"traefik/http/routers/Router0/rule": "Host(`kv1.localhost`)",
"traefik/http/routers/Router0/priority": "42",
"traefik/http/routers/Router0/tls": "",
"traefik/http/routers/Router1/rule": "Host(`kv2.localhost`)",
"traefik/http/routers/Router1/priority": "42",
"traefik/http/routers/Router1/tls/domains/0/main": "aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/0": "aaa.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/1": "bbb.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/1/main": "bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/0": "aaa.bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/1": "bbb.bbb.localhost",
"traefik/http/routers/Router1/entryPoints/0": "web",
"traefik/http/routers/Router1/service": "simplesvc",
"traefik/http/services/simplesvc/loadBalancer/servers/0/url": "http://10.0.1.1:8888",
"traefik/http/services/simplesvc/loadBalancer/servers/1/url": "http://10.0.1.1:8889",
"traefik/http/services/srvcA/loadBalancer/servers/0/url": "http://10.0.1.2:8888",
"traefik/http/services/srvcA/loadBalancer/servers/1/url": "http://10.0.1.2:8889",
"traefik/http/services/srvcB/loadBalancer/servers/0/url": "http://10.0.1.3:8888",
"traefik/http/services/srvcB/loadBalancer/servers/1/url": "http://10.0.1.3:8889",
"traefik/http/services/mirror/mirroring/service": "simplesvc",
"traefik/http/services/mirror/mirroring/mirrors/0/name": "srvcA",
"traefik/http/services/mirror/mirroring/mirrors/0/percent": "42",
"traefik/http/services/mirror/mirroring/mirrors/1/name": "srvcB",
"traefik/http/services/mirror/mirroring/mirrors/1/percent": "42",
"traefik/http/services/Service03/weighted/services/0/name": "srvcA",
"traefik/http/services/Service03/weighted/services/0/weight": "42",
"traefik/http/services/Service03/weighted/services/1/name": "srvcB",
"traefik/http/services/Service03/weighted/services/1/weight": "42",
"traefik/http/middlewares/compressor/compress": "",
"traefik/http/middlewares/striper/stripPrefix/prefixes/0": "foo",
"traefik/http/middlewares/striper/stripPrefix/prefixes/1": "bar",
"traefik/http/middlewares/striper/stripPrefix/forceSlash": "true",
}
for k, v := range data {
err := s.kvClient.Put(k, []byte(v), nil)
c.Assert(err, checker.IsNil)
}
// wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("@etcd"))
c.Assert(err, checker.IsNil)
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
c.Assert(err, checker.IsNil)
var obtained api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&obtained)
c.Assert(err, checker.IsNil)
got, err := json.MarshalIndent(obtained, "", " ")
c.Assert(err, checker.IsNil)
expectedJSON := filepath.FromSlash("testdata/rawdata-etcd.json")
if *updateExpected {
err = ioutil.WriteFile(expectedJSON, got, 0666)
c.Assert(err, checker.IsNil)
}
expected, err := ioutil.ReadFile(expectedJSON)
c.Assert(err, checker.IsNil)
if !bytes.Equal(expected, got) {
diff := difflib.UnifiedDiff{
FromFile: "Expected",
A: difflib.SplitLines(string(expected)),
ToFile: "Got",
B: difflib.SplitLines(string(got)),
Context: 3,
}
text, err := difflib.GetUnifiedDiffString(diff)
c.Assert(err, checker.IsNil)
c.Error(text)
}
}

View file

@ -0,0 +1,16 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints.web]
address = ":8000"
[api]
insecure = true
[providers.consul]
rootKey = "traefik"
endpoints = ["{{ .ConsulAddress }}"]

View file

@ -0,0 +1,16 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints.web]
address = ":8000"
[api]
insecure = true
[providers.etcd]
rootKey = "traefik"
endpoints = ["{{ .EtcdAddress }}"]

View file

@ -0,0 +1,16 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints.web]
address = ":8000"
[api]
insecure = true
[providers.redis]
rootKey = "traefik"
endpoints = ["{{ .RedisAddress }}"]

View file

@ -0,0 +1,16 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints.web]
address = ":8000"
[api]
insecure = true
[providers.zookeeper]
rootKey = "traefik"
endpoints = ["{{ .ZkAddress }}"]

View file

@ -36,6 +36,8 @@ func Test(t *testing.T) {
// tests launched from a container
check.Suite(&AccessLogSuite{})
check.Suite(&AcmeSuite{})
check.Suite(&EtcdSuite{})
check.Suite(&ConsulSuite{})
check.Suite(&ConsulCatalogSuite{})
check.Suite(&DockerComposeSuite{})
check.Suite(&DockerSuite{})
@ -51,6 +53,7 @@ func Test(t *testing.T) {
check.Suite(&MarathonSuite{})
check.Suite(&MarathonSuite15{})
check.Suite(&RateLimitSuite{})
check.Suite(&RedisSuite{})
check.Suite(&RestSuite{})
check.Suite(&RetrySuite{})
check.Suite(&SimpleSuite{})
@ -58,6 +61,7 @@ func Test(t *testing.T) {
check.Suite(&TLSClientHeadersSuite{})
check.Suite(&TracingSuite{})
check.Suite(&WebsocketSuite{})
check.Suite(&ZookeeperSuite{})
}
if *host {
// tests launched from the host

159
integration/redis_test.go Normal file
View file

@ -0,0 +1,159 @@
package integration
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"time"
"github.com/abronan/valkeyrie"
"github.com/abronan/valkeyrie/store"
"github.com/abronan/valkeyrie/store/redis"
"github.com/containous/traefik/v2/integration/try"
"github.com/containous/traefik/v2/pkg/api"
"github.com/go-check/check"
"github.com/pmezard/go-difflib/difflib"
checker "github.com/vdemeester/shakers"
)
// Redis test suites (using libcompose)
type RedisSuite struct {
BaseSuite
kvClient store.Store
}
func (s *RedisSuite) setupStore(c *check.C) {
s.createComposeProject(c, "redis")
s.composeProject.Start(c)
redis.Register()
kv, err := valkeyrie.NewStore(
store.REDIS,
[]string{s.composeProject.Container(c, "redis").NetworkSettings.IPAddress + ":6379"},
&store.Config{
ConnectionTimeout: 10 * time.Second,
},
)
if err != nil {
c.Fatal("Cannot create store redis")
}
s.kvClient = kv
// wait for redis
err = try.Do(60*time.Second, try.KVExists(kv, "test"))
c.Assert(err, checker.IsNil)
}
func (s *RedisSuite) TearDownTest(c *check.C) {
// shutdown and delete compose project
if s.composeProject != nil {
s.composeProject.Stop(c)
}
}
func (s *RedisSuite) TearDownSuite(c *check.C) {}
func (s *RedisSuite) TestSimpleConfiguration(c *check.C) {
s.setupStore(c)
address := s.composeProject.Container(c, "redis").NetworkSettings.IPAddress + ":6379"
file := s.adaptFile(c, "fixtures/redis/simple.toml", struct{ RedisAddress string }{address})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
data := map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "web",
"traefik/http/routers/Router0/middlewares/0": "compressor",
"traefik/http/routers/Router0/middlewares/1": "striper",
"traefik/http/routers/Router0/service": "simplesvc",
"traefik/http/routers/Router0/rule": "Host(`kv1.localhost`)",
"traefik/http/routers/Router0/priority": "42",
"traefik/http/routers/Router0/tls": "",
"traefik/http/routers/Router1/rule": "Host(`kv2.localhost`)",
"traefik/http/routers/Router1/priority": "42",
"traefik/http/routers/Router1/tls/domains/0/main": "aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/0": "aaa.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/1": "bbb.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/1/main": "bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/0": "aaa.bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/1": "bbb.bbb.localhost",
"traefik/http/routers/Router1/entryPoints/0": "web",
"traefik/http/routers/Router1/service": "simplesvc",
"traefik/http/services/simplesvc/loadBalancer/servers/0/url": "http://10.0.1.1:8888",
"traefik/http/services/simplesvc/loadBalancer/servers/1/url": "http://10.0.1.1:8889",
"traefik/http/services/srvcA/loadBalancer/servers/0/url": "http://10.0.1.2:8888",
"traefik/http/services/srvcA/loadBalancer/servers/1/url": "http://10.0.1.2:8889",
"traefik/http/services/srvcB/loadBalancer/servers/0/url": "http://10.0.1.3:8888",
"traefik/http/services/srvcB/loadBalancer/servers/1/url": "http://10.0.1.3:8889",
"traefik/http/services/mirror/mirroring/service": "simplesvc",
"traefik/http/services/mirror/mirroring/mirrors/0/name": "srvcA",
"traefik/http/services/mirror/mirroring/mirrors/0/percent": "42",
"traefik/http/services/mirror/mirroring/mirrors/1/name": "srvcB",
"traefik/http/services/mirror/mirroring/mirrors/1/percent": "42",
"traefik/http/services/Service03/weighted/services/0/name": "srvcA",
"traefik/http/services/Service03/weighted/services/0/weight": "42",
"traefik/http/services/Service03/weighted/services/1/name": "srvcB",
"traefik/http/services/Service03/weighted/services/1/weight": "42",
"traefik/http/middlewares/compressor/compress": "",
"traefik/http/middlewares/striper/stripPrefix/prefixes/0": "foo",
"traefik/http/middlewares/striper/stripPrefix/prefixes/1": "bar",
"traefik/http/middlewares/striper/stripPrefix/forceSlash": "true",
}
for k, v := range data {
err := s.kvClient.Put(k, []byte(v), nil)
c.Assert(err, checker.IsNil)
}
// wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("@redis"))
c.Assert(err, checker.IsNil)
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
c.Assert(err, checker.IsNil)
var obtained api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&obtained)
c.Assert(err, checker.IsNil)
got, err := json.MarshalIndent(obtained, "", " ")
c.Assert(err, checker.IsNil)
expectedJSON := filepath.FromSlash("testdata/rawdata-redis.json")
if *updateExpected {
err = ioutil.WriteFile(expectedJSON, got, 0666)
c.Assert(err, checker.IsNil)
}
expected, err := ioutil.ReadFile(expectedJSON)
c.Assert(err, checker.IsNil)
if !bytes.Equal(expected, got) {
diff := difflib.UnifiedDiff{
FromFile: "Expected",
A: difflib.SplitLines(string(expected)),
ToFile: "Got",
B: difflib.SplitLines(string(got)),
Context: 3,
}
text, err := difflib.GetUnifiedDiffString(diff)
c.Assert(err, checker.IsNil)
c.Error(text)
}
}

View file

@ -0,0 +1,4 @@
consul:
image: consul:1.6
ports:
- "8500:8500"

View file

@ -0,0 +1,5 @@
etcd:
image: quay.io/coreos/etcd:v3.3.18
command: etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2380
ports:
- "2379:2379"

View file

@ -0,0 +1,4 @@
redis:
image: redis:5.0
ports:
- "6379:6379"

View file

@ -0,0 +1,4 @@
zookeeper:
image: zookeeper:3.5
ports:
- "2181:2181"

219
integration/testdata/rawdata-consul.json vendored Normal file
View file

@ -0,0 +1,219 @@
{
"routers": {
"Router0@consul": {
"entryPoints": [
"web"
],
"middlewares": [
"compressor@consul",
"striper@consul"
],
"service": "simplesvc",
"rule": "Host(`kv1.localhost`)",
"priority": 42,
"tls": {},
"status": "enabled",
"using": [
"web"
]
},
"Router1@consul": {
"entryPoints": [
"web"
],
"service": "simplesvc",
"rule": "Host(`kv2.localhost`)",
"priority": 42,
"tls": {
"domains": [
{
"main": "aaa.localhost",
"sans": [
"aaa.aaa.localhost",
"bbb.aaa.localhost"
]
},
{
"main": "bbb.localhost",
"sans": [
"aaa.bbb.localhost",
"bbb.bbb.localhost"
]
}
]
},
"status": "enabled",
"using": [
"web"
]
},
"api@internal": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 2147483646,
"status": "enabled",
"using": [
"traefik"
]
},
"dashboard@internal": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 2147483645,
"status": "enabled",
"using": [
"traefik"
]
}
},
"middlewares": {
"compressor@consul": {
"compress": {},
"status": "enabled",
"usedBy": [
"Router0@consul"
]
},
"dashboard_redirect@internal": {
"redirectRegex": {
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
"replacement": "${1}/dashboard/",
"permanent": true
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"dashboard_stripprefix@internal": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"striper@consul": {
"stripPrefix": {
"prefixes": [
"foo",
"bar"
],
"forceSlash": true
},
"status": "enabled",
"usedBy": [
"Router0@consul"
]
}
},
"services": {
"Service03@consul": {
"weighted": {
"services": [
{
"name": "srvcA",
"weight": 42
},
{
"name": "srvcB",
"weight": 42
}
]
},
"status": "enabled"
},
"api@internal": {
"status": "enabled",
"usedBy": [
"api@internal"
]
},
"dashboard@internal": {
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"mirror@consul": {
"mirroring": {
"service": "simplesvc",
"mirrors": [
{
"name": "srvcA",
"percent": 42
},
{
"name": "srvcB",
"percent": 42
}
]
},
"status": "enabled"
},
"simplesvc@consul": {
"loadBalancer": {
"servers": [
{
"url": "http://10.0.1.1:8888"
},
{
"url": "http://10.0.1.1:8889"
}
],
"passHostHeader": true
},
"status": "enabled",
"usedBy": [
"Router0@consul",
"Router1@consul"
],
"serverStatus": {
"http://10.0.1.1:8888": "UP",
"http://10.0.1.1:8889": "UP"
}
},
"srvcA@consul": {
"loadBalancer": {
"servers": [
{
"url": "http://10.0.1.2:8888"
},
{
"url": "http://10.0.1.2:8889"
}
],
"passHostHeader": true
},
"status": "enabled"
},
"srvcB@consul": {
"loadBalancer": {
"servers": [
{
"url": "http://10.0.1.3:8888"
},
{
"url": "http://10.0.1.3:8889"
}
],
"passHostHeader": true
},
"status": "enabled"
}
}
}

219
integration/testdata/rawdata-etcd.json vendored Normal file
View file

@ -0,0 +1,219 @@
{
"routers": {
"Router0@etcd": {
"entryPoints": [
"web"
],
"middlewares": [
"compressor@etcd",
"striper@etcd"
],
"service": "simplesvc",
"rule": "Host(`kv1.localhost`)",
"priority": 42,
"tls": {},
"status": "enabled",
"using": [
"web"
]
},
"Router1@etcd": {
"entryPoints": [
"web"
],
"service": "simplesvc",
"rule": "Host(`kv2.localhost`)",
"priority": 42,
"tls": {
"domains": [
{
"main": "aaa.localhost",
"sans": [
"aaa.aaa.localhost",
"bbb.aaa.localhost"
]
},
{
"main": "bbb.localhost",
"sans": [
"aaa.bbb.localhost",
"bbb.bbb.localhost"
]
}
]
},
"status": "enabled",
"using": [
"web"
]
},
"api@internal": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 2147483646,
"status": "enabled",
"using": [
"traefik"
]
},
"dashboard@internal": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 2147483645,
"status": "enabled",
"using": [
"traefik"
]
}
},
"middlewares": {
"compressor@etcd": {
"compress": {},
"status": "enabled",
"usedBy": [
"Router0@etcd"
]
},
"dashboard_redirect@internal": {
"redirectRegex": {
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
"replacement": "${1}/dashboard/",
"permanent": true
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"dashboard_stripprefix@internal": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"striper@etcd": {
"stripPrefix": {
"prefixes": [
"foo",
"bar"
],
"forceSlash": true
},
"status": "enabled",
"usedBy": [
"Router0@etcd"
]
}
},
"services": {
"Service03@etcd": {
"weighted": {
"services": [
{
"name": "srvcA",
"weight": 42
},
{
"name": "srvcB",
"weight": 42
}
]
},
"status": "enabled"
},
"api@internal": {
"status": "enabled",
"usedBy": [
"api@internal"
]
},
"dashboard@internal": {
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"mirror@etcd": {
"mirroring": {
"service": "simplesvc",
"mirrors": [
{
"name": "srvcA",
"percent": 42
},
{
"name": "srvcB",
"percent": 42
}
]
},
"status": "enabled"
},
"simplesvc@etcd": {
"loadBalancer": {
"servers": [
{
"url": "http://10.0.1.1:8888"
},
{
"url": "http://10.0.1.1:8889"
}
],
"passHostHeader": true
},
"status": "enabled",
"usedBy": [
"Router0@etcd",
"Router1@etcd"
],
"serverStatus": {
"http://10.0.1.1:8888": "UP",
"http://10.0.1.1:8889": "UP"
}
},
"srvcA@etcd": {
"loadBalancer": {
"servers": [
{
"url": "http://10.0.1.2:8888"
},
{
"url": "http://10.0.1.2:8889"
}
],
"passHostHeader": true
},
"status": "enabled"
},
"srvcB@etcd": {
"loadBalancer": {
"servers": [
{
"url": "http://10.0.1.3:8888"
},
{
"url": "http://10.0.1.3:8889"
}
],
"passHostHeader": true
},
"status": "enabled"
}
}
}

219
integration/testdata/rawdata-redis.json vendored Normal file
View file

@ -0,0 +1,219 @@
{
"routers": {
"Router0@redis": {
"entryPoints": [
"web"
],
"middlewares": [
"compressor@redis",
"striper@redis"
],
"service": "simplesvc",
"rule": "Host(`kv1.localhost`)",
"priority": 42,
"tls": {},
"status": "enabled",
"using": [
"web"
]
},
"Router1@redis": {
"entryPoints": [
"web"
],
"service": "simplesvc",
"rule": "Host(`kv2.localhost`)",
"priority": 42,
"tls": {
"domains": [
{
"main": "aaa.localhost",
"sans": [
"aaa.aaa.localhost",
"bbb.aaa.localhost"
]
},
{
"main": "bbb.localhost",
"sans": [
"aaa.bbb.localhost",
"bbb.bbb.localhost"
]
}
]
},
"status": "enabled",
"using": [
"web"
]
},
"api@internal": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 2147483646,
"status": "enabled",
"using": [
"traefik"
]
},
"dashboard@internal": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 2147483645,
"status": "enabled",
"using": [
"traefik"
]
}
},
"middlewares": {
"compressor@redis": {
"compress": {},
"status": "enabled",
"usedBy": [
"Router0@redis"
]
},
"dashboard_redirect@internal": {
"redirectRegex": {
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
"replacement": "${1}/dashboard/",
"permanent": true
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"dashboard_stripprefix@internal": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"striper@redis": {
"stripPrefix": {
"prefixes": [
"foo",
"bar"
],
"forceSlash": true
},
"status": "enabled",
"usedBy": [
"Router0@redis"
]
}
},
"services": {
"Service03@redis": {
"weighted": {
"services": [
{
"name": "srvcA",
"weight": 42
},
{
"name": "srvcB",
"weight": 42
}
]
},
"status": "enabled"
},
"api@internal": {
"status": "enabled",
"usedBy": [
"api@internal"
]
},
"dashboard@internal": {
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"mirror@redis": {
"mirroring": {
"service": "simplesvc",
"mirrors": [
{
"name": "srvcA",
"percent": 42
},
{
"name": "srvcB",
"percent": 42
}
]
},
"status": "enabled"
},
"simplesvc@redis": {
"loadBalancer": {
"servers": [
{
"url": "http://10.0.1.1:8888"
},
{
"url": "http://10.0.1.1:8889"
}
],
"passHostHeader": true
},
"status": "enabled",
"usedBy": [
"Router0@redis",
"Router1@redis"
],
"serverStatus": {
"http://10.0.1.1:8888": "UP",
"http://10.0.1.1:8889": "UP"
}
},
"srvcA@redis": {
"loadBalancer": {
"servers": [
{
"url": "http://10.0.1.2:8888"
},
{
"url": "http://10.0.1.2:8889"
}
],
"passHostHeader": true
},
"status": "enabled"
},
"srvcB@redis": {
"loadBalancer": {
"servers": [
{
"url": "http://10.0.1.3:8888"
},
{
"url": "http://10.0.1.3:8889"
}
],
"passHostHeader": true
},
"status": "enabled"
}
}
}

219
integration/testdata/rawdata-zk.json vendored Normal file
View file

@ -0,0 +1,219 @@
{
"routers": {
"Router0@zookeeper": {
"entryPoints": [
"web"
],
"middlewares": [
"compressor@zookeeper",
"striper@zookeeper"
],
"service": "simplesvc",
"rule": "Host(`kv1.localhost`)",
"priority": 42,
"tls": {},
"status": "enabled",
"using": [
"web"
]
},
"Router1@zookeeper": {
"entryPoints": [
"web"
],
"service": "simplesvc",
"rule": "Host(`kv2.localhost`)",
"priority": 42,
"tls": {
"domains": [
{
"main": "aaa.localhost",
"sans": [
"aaa.aaa.localhost",
"bbb.aaa.localhost"
]
},
{
"main": "bbb.localhost",
"sans": [
"aaa.bbb.localhost",
"bbb.bbb.localhost"
]
}
]
},
"status": "enabled",
"using": [
"web"
]
},
"api@internal": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 2147483646,
"status": "enabled",
"using": [
"traefik"
]
},
"dashboard@internal": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 2147483645,
"status": "enabled",
"using": [
"traefik"
]
}
},
"middlewares": {
"compressor@zookeeper": {
"compress": {},
"status": "enabled",
"usedBy": [
"Router0@zookeeper"
]
},
"dashboard_redirect@internal": {
"redirectRegex": {
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
"replacement": "${1}/dashboard/",
"permanent": true
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"dashboard_stripprefix@internal": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"striper@zookeeper": {
"stripPrefix": {
"prefixes": [
"foo",
"bar"
],
"forceSlash": true
},
"status": "enabled",
"usedBy": [
"Router0@zookeeper"
]
}
},
"services": {
"Service03@zookeeper": {
"weighted": {
"services": [
{
"name": "srvcA",
"weight": 42
},
{
"name": "srvcB",
"weight": 42
}
]
},
"status": "enabled"
},
"api@internal": {
"status": "enabled",
"usedBy": [
"api@internal"
]
},
"dashboard@internal": {
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"mirror@zookeeper": {
"mirroring": {
"service": "simplesvc",
"mirrors": [
{
"name": "srvcA",
"percent": 42
},
{
"name": "srvcB",
"percent": 42
}
]
},
"status": "enabled"
},
"simplesvc@zookeeper": {
"loadBalancer": {
"servers": [
{
"url": "http://10.0.1.1:8888"
},
{
"url": "http://10.0.1.1:8889"
}
],
"passHostHeader": true
},
"status": "enabled",
"usedBy": [
"Router0@zookeeper",
"Router1@zookeeper"
],
"serverStatus": {
"http://10.0.1.1:8888": "UP",
"http://10.0.1.1:8889": "UP"
}
},
"srvcA@zookeeper": {
"loadBalancer": {
"servers": [
{
"url": "http://10.0.1.2:8888"
},
{
"url": "http://10.0.1.2:8889"
}
],
"passHostHeader": true
},
"status": "enabled"
},
"srvcB@zookeeper": {
"loadBalancer": {
"servers": [
{
"url": "http://10.0.1.3:8888"
},
{
"url": "http://10.0.1.3:8889"
}
],
"passHostHeader": true
},
"status": "enabled"
}
}
}

159
integration/zk_test.go Normal file
View file

@ -0,0 +1,159 @@
package integration
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"time"
"github.com/abronan/valkeyrie"
"github.com/abronan/valkeyrie/store"
"github.com/abronan/valkeyrie/store/zookeeper"
"github.com/containous/traefik/v2/integration/try"
"github.com/containous/traefik/v2/pkg/api"
"github.com/go-check/check"
"github.com/pmezard/go-difflib/difflib"
checker "github.com/vdemeester/shakers"
)
// Zk test suites (using libcompose)
type ZookeeperSuite struct {
BaseSuite
kvClient store.Store
}
func (s *ZookeeperSuite) setupStore(c *check.C) {
s.createComposeProject(c, "zookeeper")
s.composeProject.Start(c)
zookeeper.Register()
kv, err := valkeyrie.NewStore(
store.ZK,
[]string{s.composeProject.Container(c, "zookeeper").NetworkSettings.IPAddress + ":2181"},
&store.Config{
ConnectionTimeout: 10 * time.Second,
},
)
if err != nil {
c.Fatal("Cannot create store zookeeper")
}
s.kvClient = kv
// wait for zk
err = try.Do(60*time.Second, try.KVExists(kv, "test"))
c.Assert(err, checker.IsNil)
}
func (s *ZookeeperSuite) TearDownTest(c *check.C) {
// shutdown and delete compose project
if s.composeProject != nil {
s.composeProject.Stop(c)
}
}
func (s *ZookeeperSuite) TearDownSuite(c *check.C) {}
func (s *ZookeeperSuite) TestSimpleConfiguration(c *check.C) {
s.setupStore(c)
address := s.composeProject.Container(c, "zookeeper").NetworkSettings.IPAddress + ":2181"
file := s.adaptFile(c, "fixtures/zookeeper/simple.toml", struct{ ZkAddress string }{address})
defer os.Remove(file)
data := map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "web",
"traefik/http/routers/Router0/middlewares/0": "compressor",
"traefik/http/routers/Router0/middlewares/1": "striper",
"traefik/http/routers/Router0/service": "simplesvc",
"traefik/http/routers/Router0/rule": "Host(`kv1.localhost`)",
"traefik/http/routers/Router0/priority": "42",
"traefik/http/routers/Router0/tls": "",
"traefik/http/routers/Router1/rule": "Host(`kv2.localhost`)",
"traefik/http/routers/Router1/priority": "42",
"traefik/http/routers/Router1/tls/domains/0/main": "aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/0": "aaa.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/1": "bbb.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/1/main": "bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/0": "aaa.bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/1": "bbb.bbb.localhost",
"traefik/http/routers/Router1/entryPoints/0": "web",
"traefik/http/routers/Router1/service": "simplesvc",
"traefik/http/services/simplesvc/loadBalancer/servers/0/url": "http://10.0.1.1:8888",
"traefik/http/services/simplesvc/loadBalancer/servers/1/url": "http://10.0.1.1:8889",
"traefik/http/services/srvcA/loadBalancer/servers/0/url": "http://10.0.1.2:8888",
"traefik/http/services/srvcA/loadBalancer/servers/1/url": "http://10.0.1.2:8889",
"traefik/http/services/srvcB/loadBalancer/servers/0/url": "http://10.0.1.3:8888",
"traefik/http/services/srvcB/loadBalancer/servers/1/url": "http://10.0.1.3:8889",
"traefik/http/services/mirror/mirroring/service": "simplesvc",
"traefik/http/services/mirror/mirroring/mirrors/0/name": "srvcA",
"traefik/http/services/mirror/mirroring/mirrors/0/percent": "42",
"traefik/http/services/mirror/mirroring/mirrors/1/name": "srvcB",
"traefik/http/services/mirror/mirroring/mirrors/1/percent": "42",
"traefik/http/services/Service03/weighted/services/0/name": "srvcA",
"traefik/http/services/Service03/weighted/services/0/weight": "42",
"traefik/http/services/Service03/weighted/services/1/name": "srvcB",
"traefik/http/services/Service03/weighted/services/1/weight": "42",
"traefik/http/middlewares/compressor/compress": "",
"traefik/http/middlewares/striper/stripPrefix/prefixes/0": "foo",
"traefik/http/middlewares/striper/stripPrefix/prefixes/1": "bar",
"traefik/http/middlewares/striper/stripPrefix/forceSlash": "true",
}
for k, v := range data {
err := s.kvClient.Put(k, []byte(v), nil)
c.Assert(err, checker.IsNil)
}
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("@zookeeper"))
c.Assert(err, checker.IsNil)
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
c.Assert(err, checker.IsNil)
var obtained api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&obtained)
c.Assert(err, checker.IsNil)
got, err := json.MarshalIndent(obtained, "", " ")
c.Assert(err, checker.IsNil)
expectedJSON := filepath.FromSlash("testdata/rawdata-zk.json")
if *updateExpected {
err = ioutil.WriteFile(expectedJSON, got, 0666)
c.Assert(err, checker.IsNil)
}
expected, err := ioutil.ReadFile(expectedJSON)
c.Assert(err, checker.IsNil)
if !bytes.Equal(expected, got) {
diff := difflib.UnifiedDiff{
FromFile: "Expected",
A: difflib.SplitLines(string(expected)),
ToFile: "Got",
B: difflib.SplitLines(string(got)),
Context: 3,
}
text, err := difflib.GetUnifiedDiffString(diff)
c.Assert(err, checker.IsNil)
c.Error(text)
}
}

View file

@ -4,8 +4,12 @@ import (
"fmt"
"io"
"os"
"path"
"sort"
"strconv"
"strings"
"github.com/BurntSushi/toml"
"github.com/containous/traefik/v2/pkg/config/env"
"github.com/containous/traefik/v2/pkg/config/flag"
"github.com/containous/traefik/v2/pkg/config/generator"
@ -17,6 +21,7 @@ import (
func main() {
genStaticConfDoc("./docs/content/reference/static-configuration/env-ref.md", "", env.Encode)
genStaticConfDoc("./docs/content/reference/static-configuration/cli-ref.md", "--", flag.Encode)
genKVDynConfDoc("./docs/content/reference/dynamic-configuration/kv-ref.md")
}
func genStaticConfDoc(outputFile string, prefix string, encodeFn func(interface{}) ([]parser.Flat, error)) {
@ -81,3 +86,94 @@ func (ew *errWriter) writeln(a ...interface{}) {
_, ew.err = fmt.Fprintln(ew.w, a...)
}
func genKVDynConfDoc(outputFile string) {
dynConfPath := "./docs/content/reference/dynamic-configuration/file.toml"
conf := map[string]interface{}{}
_, err := toml.DecodeFile(dynConfPath, &conf)
if err != nil {
log.Fatal(err)
}
file, err := os.Create(outputFile)
if err != nil {
log.Fatal(err)
}
store := storeWriter{data: map[string]string{}}
c := client{store: store}
err = c.load("traefik", conf)
if err != nil {
log.Fatal(err)
}
var keys []string
for k := range store.data {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
_, _ = fmt.Fprintf(file, "| `%s` | `%s` |\n", k, store.data[k])
}
}
type storeWriter struct {
data map[string]string
}
func (f storeWriter) Put(key string, value []byte, options []string) error {
f.data[key] = string(value)
return nil
}
type client struct {
store storeWriter
}
func (c client) load(parentKey string, conf map[string]interface{}) error {
for k, v := range conf {
switch entry := v.(type) {
case map[string]interface{}:
key := path.Join(parentKey, k)
if len(entry) == 0 {
err := c.store.Put(key, nil, nil)
if err != nil {
return err
}
} else {
err := c.load(key, entry)
if err != nil {
return err
}
}
case []map[string]interface{}:
for i, o := range entry {
key := path.Join(parentKey, k, strconv.Itoa(i))
if err := c.load(key, o); err != nil {
return err
}
}
case []interface{}:
for i, o := range entry {
key := path.Join(parentKey, k, strconv.Itoa(i))
err := c.store.Put(key, []byte(fmt.Sprintf("%v", o)), nil)
if err != nil {
return err
}
}
default:
key := path.Join(parentKey, k)
err := c.store.Put(key, []byte(fmt.Sprintf("%v", v)), nil)
if err != nil {
return err
}
}
}
return nil
}

View file

@ -46,17 +46,20 @@ func Encode(element interface{}) ([]parser.Flat, error) {
return nil, nil
}
node, err := parser.EncodeToNode(element, parser.DefaultRootName, false)
etnOpts := parser.EncoderToNodeOpts{OmitEmpty: false, TagName: parser.TagLabel, AllowSliceAsStruct: true}
node, err := parser.EncodeToNode(element, parser.DefaultRootName, etnOpts)
if err != nil {
return nil, err
}
err = parser.AddMetadata(element, node)
metaOpts := parser.MetadataOpts{TagName: parser.TagLabel, AllowSliceAsStruct: true}
err = parser.AddMetadata(element, node, metaOpts)
if err != nil {
return nil, err
}
return parser.EncodeToFlat(element, node, parser.FlatOpts{Case: "upper", Separator: "_"})
flatOpts := parser.FlatOpts{Case: "upper", Separator: "_", TagName: parser.TagLabel}
return parser.EncodeToFlat(element, node, flatOpts)
}
func checkPrefix(prefix string) error {

View file

@ -22,10 +22,11 @@ func Decode(filePath string, element interface{}) error {
return err
}
err = parser.AddMetadata(element, root)
metaOpts := parser.MetadataOpts{TagName: parser.TagLabel, AllowSliceAsStruct: true}
err = parser.AddMetadata(element, root, metaOpts)
if err != nil {
return err
}
return parser.Fill(element, root)
return parser.Fill(element, root, parser.FillerOpts{AllowSliceAsStruct: true})
}

View file

@ -30,15 +30,18 @@ func Encode(element interface{}) ([]parser.Flat, error) {
return nil, nil
}
node, err := parser.EncodeToNode(element, parser.DefaultRootName, false)
etnOpts := parser.EncoderToNodeOpts{OmitEmpty: false, TagName: parser.TagLabel, AllowSliceAsStruct: true}
node, err := parser.EncodeToNode(element, parser.DefaultRootName, etnOpts)
if err != nil {
return nil, err
}
err = parser.AddMetadata(element, node)
metaOpts := parser.MetadataOpts{TagName: parser.TagLabel, AllowSliceAsStruct: true}
err = parser.AddMetadata(element, node, metaOpts)
if err != nil {
return nil, err
}
return parser.EncodeToFlat(element, node, parser.FlatOpts{Separator: ".", SkipRoot: true})
flatOpts := parser.FlatOpts{Separator: ".", SkipRoot: true, TagName: parser.TagLabel}
return parser.EncodeToFlat(element, node, flatOpts)
}

75
pkg/config/kv/kv.go Normal file
View file

@ -0,0 +1,75 @@
package kv
import (
"path"
"reflect"
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/config/parser"
)
// Decode decodes the given KV pairs into the given element.
// The operation goes through three stages roughly summarized as:
// KV pairs -> tree of untyped nodes
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
// "typed" nodes -> typed element
func Decode(pairs []*store.KVPair, element interface{}, rootName string) error {
if element == nil {
return nil
}
filters := getRootFieldNames(rootName, element)
node, err := DecodeToNode(pairs, rootName, filters...)
if err != nil {
return err
}
metaOpts := parser.MetadataOpts{TagName: parser.TagLabel, AllowSliceAsStruct: false}
err = parser.AddMetadata(element, node, metaOpts)
if err != nil {
return err
}
return parser.Fill(element, node, parser.FillerOpts{AllowSliceAsStruct: false})
}
func getRootFieldNames(rootName string, element interface{}) []string {
if element == nil {
return nil
}
rootType := reflect.TypeOf(element)
return getFieldNames(rootName, rootType)
}
func getFieldNames(rootName 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, getFieldNames(rootName, field.Type)...)
continue
}
names = append(names, path.Join(rootName, field.Name))
}
return names
}

128
pkg/config/kv/kv_node.go Normal file
View file

@ -0,0 +1,128 @@
package kv
import (
"fmt"
"regexp"
"sort"
"strings"
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/config/parser"
)
// 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(pairs []*store.KVPair, rootName string, filters ...string) (*parser.Node, error) {
sortedPairs := filterPairs(pairs, filters)
exp := regexp.MustCompile(`^\d+$`)
var node *parser.Node
for i, pair := range sortedPairs {
split := strings.FieldsFunc(pair.Key, func(c rune) bool { return c == '/' })
if split[0] != rootName {
return nil, fmt.Errorf("invalid label root %s", split[0])
}
var parts []string
for _, fragment := range split {
if exp.MatchString(fragment) {
parts = append(parts, "["+fragment+"]")
} else {
parts = append(parts, fragment)
}
}
if i == 0 {
node = &parser.Node{}
}
decodeToNode(node, parts, string(pair.Value))
}
return node, nil
}
func decodeToNode(root *parser.Node, path []string, value string) {
if len(root.Name) == 0 {
root.Name = path[0]
}
// it's a leaf or not -> children
if len(path) > 1 {
if n := containsNode(root.Children, path[1]); n != nil {
// the child already exists
decodeToNode(n, path[1:], value)
} else {
// new child
child := &parser.Node{Name: path[1]}
decodeToNode(child, path[1:], value)
root.Children = append(root.Children, child)
}
} else {
root.Value = value
}
}
func containsNode(nodes []*parser.Node, name string) *parser.Node {
for _, n := range nodes {
if strings.EqualFold(name, n.Name) {
return n
}
}
return nil
}
func filterPairs(pairs []*store.KVPair, filters []string) []*store.KVPair {
exp := regexp.MustCompile(`^(.+)/\d+$`)
sort.Slice(pairs, func(i, j int) bool {
return pairs[i].Key < pairs[j].Key
})
var simplePairs = map[string]*store.KVPair{}
var slicePairs = map[string][]string{}
for _, pair := range pairs {
if len(filters) == 0 {
// Slice of simple type
if exp.MatchString(pair.Key) {
sanitizedKey := exp.FindStringSubmatch(pair.Key)[1]
slicePairs[sanitizedKey] = append(slicePairs[sanitizedKey], string(pair.Value))
} else {
simplePairs[pair.Key] = pair
}
continue
}
for _, filter := range filters {
if len(pair.Key) >= len(filter) && strings.EqualFold(pair.Key[:len(filter)], filter) {
// Slice of simple type
if exp.MatchString(pair.Key) {
sanitizedKey := exp.FindStringSubmatch(pair.Key)[1]
slicePairs[sanitizedKey] = append(slicePairs[sanitizedKey], string(pair.Value))
} else {
simplePairs[pair.Key] = pair
}
continue
}
}
}
var sortedPairs []*store.KVPair
for k, v := range slicePairs {
delete(simplePairs, k)
sortedPairs = append(sortedPairs, &store.KVPair{Key: k, Value: []byte(strings.Join(v, ","))})
}
for _, v := range simplePairs {
sortedPairs = append(sortedPairs, v)
}
sort.Slice(sortedPairs, func(i, j int) bool {
return sortedPairs[i].Key < sortedPairs[j].Key
})
return sortedPairs
}

View file

@ -0,0 +1,274 @@
package kv
import (
"encoding/json"
"fmt"
"testing"
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/config/parser"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDecodeToNode(t *testing.T) {
type expected struct {
error bool
node *parser.Node
}
testCases := []struct {
desc string
in map[string]string
filters []string
expected expected
}{
{
desc: "no label",
in: map[string]string{},
expected: expected{node: nil},
},
{
desc: "level 1",
in: map[string]string{
"traefik/foo": "bar",
},
expected: expected{node: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "bar"},
},
}},
},
{
desc: "level 1 empty value",
in: map[string]string{
"traefik/foo": "",
},
expected: expected{node: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: ""},
},
}},
},
{
desc: "level 2",
in: map[string]string{
"traefik/foo/bar": "bar",
},
expected: expected{node: &parser.Node{
Name: "traefik",
Children: []*parser.Node{{
Name: "foo",
Children: []*parser.Node{
{Name: "bar", Value: "bar"},
},
}},
}},
},
{
desc: "several entries, level 0",
in: map[string]string{
"traefik": "bar",
"traefic": "bur",
},
expected: expected{error: true},
},
{
desc: "several entries, prefix filter",
in: map[string]string{
"traefik/foo": "bar",
"traefik/fii": "bir",
},
filters: []string{"traefik/Foo"},
expected: expected{node: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "bar"},
},
}},
},
{
desc: "several entries, level 1",
in: map[string]string{
"traefik/foo": "bar",
"traefik/fii": "bur",
},
expected: expected{node: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "fii", Value: "bur"},
{Name: "foo", Value: "bar"},
},
}},
},
{
desc: "several entries, level 2",
in: map[string]string{
"traefik/foo/aaa": "bar",
"traefik/foo/bbb": "bur",
},
expected: expected{node: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Children: []*parser.Node{
{Name: "aaa", Value: "bar"},
{Name: "bbb", Value: "bur"},
}},
},
}},
},
{
desc: "several entries, level 2, case insensitive",
in: map[string]string{
"traefik/foo/aaa": "bar",
"traefik/Foo/bbb": "bur",
},
expected: expected{node: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "Foo", Children: []*parser.Node{
{Name: "bbb", Value: "bur"},
{Name: "aaa", Value: "bar"},
}},
},
}},
},
{
desc: "several entries, level 2, 3 children",
in: map[string]string{
"traefik/foo/aaa": "bar",
"traefik/foo/bbb": "bur",
"traefik/foo/ccc": "bir",
},
expected: expected{node: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Children: []*parser.Node{
{Name: "aaa", Value: "bar"},
{Name: "bbb", Value: "bur"},
{Name: "ccc", Value: "bir"},
}},
},
}},
},
{
desc: "several entries, level 3",
in: map[string]string{
"traefik/foo/bar/aaa": "bar",
"traefik/foo/bar/bbb": "bur",
},
expected: expected{node: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Children: []*parser.Node{
{Name: "bar", Children: []*parser.Node{
{Name: "aaa", Value: "bar"},
{Name: "bbb", Value: "bur"},
}},
}},
},
}},
},
{
desc: "several entries, level 3, 2 children level 1",
in: map[string]string{
"traefik/foo/bar/aaa": "bar",
"traefik/foo/bar/bbb": "bur",
"traefik/bar/foo/bbb": "bir",
},
expected: expected{node: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "bar", Children: []*parser.Node{
{Name: "foo", Children: []*parser.Node{
{Name: "bbb", Value: "bir"},
}},
}},
{Name: "foo", Children: []*parser.Node{
{Name: "bar", Children: []*parser.Node{
{Name: "aaa", Value: "bar"},
{Name: "bbb", Value: "bur"},
}},
}},
},
}},
},
{
desc: "several entries, slice syntax",
in: map[string]string{
"traefik/foo/0/aaa": "bar0",
"traefik/foo/0/bbb": "bur0",
"traefik/foo/1/aaa": "bar1",
"traefik/foo/1/bbb": "bur1",
},
expected: expected{node: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Children: []*parser.Node{
{Name: "[0]", Children: []*parser.Node{
{Name: "aaa", Value: "bar0"},
{Name: "bbb", Value: "bur0"},
}},
{Name: "[1]", Children: []*parser.Node{
{Name: "aaa", Value: "bar1"},
{Name: "bbb", Value: "bur1"},
}},
}},
},
}},
},
{
desc: "several entries, slice in slice of struct",
in: map[string]string{
"traefik/foo/0/aaa/0": "bar0",
"traefik/foo/0/aaa/1": "bar1",
"traefik/foo/1/aaa/0": "bar2",
"traefik/foo/1/aaa/1": "bar3",
},
expected: expected{node: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Children: []*parser.Node{
{Name: "[0]", Children: []*parser.Node{
{Name: "aaa", Value: "bar0,bar1"},
}},
{Name: "[1]", Children: []*parser.Node{
{Name: "aaa", Value: "bar2,bar3"},
}},
}},
},
}},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
out, err := DecodeToNode(mapToPairs(test.in), "traefik", test.filters...)
if test.expected.error {
require.Error(t, err)
} else {
require.NoError(t, err)
if !assert.Equal(t, test.expected.node, out) {
bytes, err := json.MarshalIndent(out, "", " ")
require.NoError(t, err)
fmt.Println(string(bytes))
}
}
})
}
}
func mapToPairs(in map[string]string) []*store.KVPair {
var out []*store.KVPair
for k, v := range in {
out = append(out, &store.KVPair{Key: k, Value: []byte(v)})
}
return out
}

63
pkg/config/kv/kv_test.go Normal file
View file

@ -0,0 +1,63 @@
package kv
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDecode(t *testing.T) {
pairs := mapToPairs(map[string]string{
"traefik/fielda": "bar",
"traefik/fieldb": "1",
"traefik/fieldc": "true",
"traefik/fieldd/0": "one",
"traefik/fieldd/1": "two",
"traefik/fielde": "",
"traefik/fieldf/Test1": "A",
"traefik/fieldf/Test2": "B",
"traefik/fieldg/0/name": "A",
"traefik/fieldg/1/name": "B",
})
element := &sample{}
err := Decode(pairs, element, "traefik")
require.NoError(t, err)
expected := &sample{
FieldA: "bar",
FieldB: 1,
FieldC: true,
FieldD: []string{"one", "two"},
FieldE: &struct {
Name string
}{},
FieldF: map[string]string{
"Test1": "A",
"Test2": "B",
},
FieldG: []sub{
{Name: "A"},
{Name: "B"},
},
}
assert.Equal(t, expected, element)
}
type sample struct {
FieldA string
FieldB int
FieldC bool
FieldD []string
FieldE *struct {
Name string
} `label:"allowEmpty"`
FieldF map[string]string
FieldG []sub
}
type sub struct {
Name string
}

View file

@ -14,8 +14,22 @@ type initializer interface {
SetDefaults()
}
// FillerOpts Options for the filler.
type FillerOpts struct {
AllowSliceAsStruct bool
}
// Fill populates the fields of the element using the information in node.
func Fill(element interface{}, node *Node) error {
func Fill(element interface{}, node *Node, opts FillerOpts) error {
return filler{FillerOpts: opts}.Fill(element, node)
}
type filler struct {
FillerOpts
}
// Fill populates the fields of the element using the information in node.
func (f filler) Fill(element interface{}, node *Node) error {
if element == nil || node == nil {
return nil
}
@ -29,10 +43,10 @@ func Fill(element interface{}, node *Node) error {
return fmt.Errorf("struct are not supported, use pointer instead")
}
return fill(root.Elem(), node)
return f.fill(root.Elem(), node)
}
func fill(field reflect.Value, node *Node) error {
func (f filler) fill(field reflect.Value, node *Node) error {
// related to allow-empty tag
if node.Disabled {
return nil
@ -70,19 +84,19 @@ func fill(field reflect.Value, node *Node) error {
case reflect.Float64:
return setFloat(field, node.Value, 64)
case reflect.Struct:
return setStruct(field, node)
return f.setStruct(field, node)
case reflect.Ptr:
return setPtr(field, node)
return f.setPtr(field, node)
case reflect.Map:
return setMap(field, node)
return f.setMap(field, node)
case reflect.Slice:
return setSlice(field, node)
return f.setSlice(field, node)
default:
return nil
}
}
func setPtr(field reflect.Value, node *Node) error {
func (f filler) setPtr(field reflect.Value, node *Node) error {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
@ -94,10 +108,10 @@ func setPtr(field reflect.Value, node *Node) error {
}
}
return fill(field.Elem(), node)
return f.fill(field.Elem(), node)
}
func setStruct(field reflect.Value, node *Node) error {
func (f filler) setStruct(field reflect.Value, node *Node) error {
for _, child := range node.Children {
fd := field.FieldByName(child.FieldName)
@ -106,7 +120,7 @@ func setStruct(field reflect.Value, node *Node) error {
return fmt.Errorf("field not found, node: %s (%s)", child.Name, child.FieldName)
}
err := fill(fd, child)
err := f.fill(fd, child)
if err != nil {
return err
}
@ -115,10 +129,10 @@ func setStruct(field reflect.Value, node *Node) error {
return nil
}
func setSlice(field reflect.Value, node *Node) error {
func (f filler) setSlice(field reflect.Value, node *Node) error {
if field.Type().Elem().Kind() == reflect.Struct ||
field.Type().Elem().Kind() == reflect.Ptr && field.Type().Elem().Elem().Kind() == reflect.Struct {
return setSliceStruct(field, node)
return f.setSliceStruct(field, node)
}
if len(node.Value) == 0 {
@ -211,9 +225,9 @@ func setSlice(field reflect.Value, node *Node) error {
return nil
}
func setSliceStruct(field reflect.Value, node *Node) error {
if node.Tag.Get(TagLabelSliceAsStruct) != "" {
return setSliceAsStruct(field, node)
func (f filler) setSliceStruct(field reflect.Value, node *Node) error {
if f.AllowSliceAsStruct && node.Tag.Get(TagLabelSliceAsStruct) != "" {
return f.setSliceAsStruct(field, node)
}
field.Set(reflect.MakeSlice(field.Type(), len(node.Children), len(node.Children)))
@ -221,7 +235,7 @@ func setSliceStruct(field reflect.Value, node *Node) error {
for i, child := range node.Children {
// use Ptr to allow "SetDefaults"
value := reflect.New(reflect.PtrTo(field.Type().Elem()))
err := setPtr(value, child)
err := f.setPtr(value, child)
if err != nil {
return err
}
@ -232,14 +246,14 @@ func setSliceStruct(field reflect.Value, node *Node) error {
return nil
}
func setSliceAsStruct(field reflect.Value, node *Node) error {
func (f filler) setSliceAsStruct(field reflect.Value, node *Node) error {
if len(node.Children) == 0 {
return fmt.Errorf("invalid slice: node %s", node.Name)
}
// use Ptr to allow "SetDefaults"
value := reflect.New(reflect.PtrTo(field.Type().Elem()))
err := setPtr(value, node)
err := f.setPtr(value, node)
if err != nil {
return err
}
@ -252,7 +266,7 @@ func setSliceAsStruct(field reflect.Value, node *Node) error {
return nil
}
func setMap(field reflect.Value, node *Node) error {
func (f filler) setMap(field reflect.Value, node *Node) error {
if field.IsNil() {
field.Set(reflect.MakeMap(field.Type()))
}
@ -260,7 +274,7 @@ func setMap(field reflect.Value, node *Node) error {
for _, child := range node.Children {
ptrValue := reflect.New(reflect.PtrTo(field.Type().Elem()))
err := fill(ptrValue, child)
err := f.fill(ptrValue, child)
if err != nil {
return err
}

View file

@ -1390,7 +1390,7 @@ func TestFill(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
err := Fill(test.element, test.node)
err := filler{FillerOpts: FillerOpts{AllowSliceAsStruct: true}}.Fill(test.element, test.node)
if test.expected.error {
require.Error(t, err)
} else {

View file

@ -7,13 +7,20 @@ import (
"strings"
)
// EncoderToNodeOpts Options for the encoderToNode.
type EncoderToNodeOpts struct {
OmitEmpty bool
TagName string
AllowSliceAsStruct bool
}
// EncodeToNode converts an element to a node.
// element -> nodes
func EncodeToNode(element interface{}, rootName string, omitEmpty bool) (*Node, error) {
func EncodeToNode(element interface{}, rootName string, opts EncoderToNodeOpts) (*Node, error) {
rValue := reflect.ValueOf(element)
node := &Node{Name: rootName}
encoder := encoderToNode{omitEmpty: omitEmpty}
encoder := encoderToNode{EncoderToNodeOpts: opts}
err := encoder.setNodeValue(node, rValue)
if err != nil {
@ -24,7 +31,7 @@ func EncodeToNode(element interface{}, rootName string, omitEmpty bool) (*Node,
}
type encoderToNode struct {
omitEmpty bool
EncoderToNodeOpts
}
func (e encoderToNode) setNodeValue(node *Node, rValue reflect.Value) error {
@ -65,7 +72,7 @@ func (e encoderToNode) setStructValue(node *Node, rValue reflect.Value) error {
continue
}
if field.Tag.Get(TagLabel) == "-" {
if field.Tag.Get(e.TagName) == "-" {
continue
}
@ -78,7 +85,7 @@ func (e encoderToNode) setStructValue(node *Node, rValue reflect.Value) error {
}
nodeName := field.Name
if field.Type.Kind() == reflect.Slice && len(field.Tag.Get(TagLabelSliceAsStruct)) != 0 {
if e.AllowSliceAsStruct && field.Type.Kind() == reflect.Slice && len(field.Tag.Get(TagLabelSliceAsStruct)) != 0 {
nodeName = field.Tag.Get(TagLabelSliceAsStruct)
}
@ -101,7 +108,7 @@ func (e encoderToNode) setStructValue(node *Node, rValue reflect.Value) error {
}
if field.Type.Elem().Kind() == reflect.Struct && len(child.Children) == 0 {
if field.Tag.Get(TagLabel) != TagLabelAllowEmpty {
if field.Tag.Get(e.TagName) != TagLabelAllowEmpty {
continue
}
@ -181,7 +188,7 @@ func (e encoderToNode) setSliceValue(node *Node, rValue reflect.Value) error {
}
func (e encoderToNode) isSkippedField(field reflect.StructField, fieldValue reflect.Value) bool {
if e.omitEmpty && field.Type.Kind() == reflect.String && fieldValue.Len() == 0 {
if e.OmitEmpty && field.Type.Kind() == reflect.String && fieldValue.Len() == 0 {
return true
}
@ -189,7 +196,7 @@ func (e encoderToNode) isSkippedField(field reflect.StructField, fieldValue refl
return true
}
if e.omitEmpty && (field.Type.Kind() == reflect.Slice) &&
if e.OmitEmpty && (field.Type.Kind() == reflect.Slice) &&
(fieldValue.IsNil() || fieldValue.Len() == 0) {
return true
}

View file

@ -723,7 +723,8 @@ func TestEncodeToNode(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
node, err := EncodeToNode(test.element, DefaultRootName, true)
etnOpts := EncoderToNodeOpts{OmitEmpty: true, TagName: TagLabel, AllowSliceAsStruct: true}
node, err := EncodeToNode(test.element, DefaultRootName, etnOpts)
if test.expected.error {
require.Error(t, err)

View file

@ -18,6 +18,7 @@ type FlatOpts struct {
Case string // "lower" or "upper", defaults to "lower".
Separator string
SkipRoot bool
TagName string
}
// Flat is a configuration item representation.
@ -69,7 +70,7 @@ func (e encoderToFlat) createFlat(field reflect.Value, name string, node *Node)
var entries []Flat
if node.Kind != reflect.Map && node.Description != "-" {
if !(node.Kind == reflect.Ptr && len(node.Children) > 0) ||
(node.Kind == reflect.Ptr && node.Tag.Get("label") == TagLabelAllowEmpty) {
(node.Kind == reflect.Ptr && node.Tag.Get(e.TagName) == TagLabelAllowEmpty) {
if node.Name[0] != '[' {
entries = append(entries, Flat{
Name: e.getName(name),

View file

@ -156,6 +156,7 @@ func TestEncodeToFlat(t *testing.T) {
Case: "upper",
Separator: "_",
SkipRoot: false,
TagName: TagLabel,
},
expected: []Flat{{
Name: "TRAEFIK_FIELD",
@ -1236,7 +1237,7 @@ func TestEncodeToFlat(t *testing.T) {
var opts FlatOpts
if test.opts == nil {
opts = FlatOpts{Separator: ".", SkipRoot: true}
opts = FlatOpts{Separator: ".", SkipRoot: true, TagName: TagLabel}
} else {
opts = *test.opts
}

View file

@ -7,8 +7,23 @@ import (
"strings"
)
// MetadataOpts Options for the metadata.
type MetadataOpts struct {
TagName string
AllowSliceAsStruct bool
}
// AddMetadata adds metadata such as type, inferred from element, to a node.
func AddMetadata(element interface{}, node *Node) error {
func AddMetadata(element interface{}, node *Node, opts MetadataOpts) error {
return metadata{MetadataOpts: opts}.Add(element, node)
}
type metadata struct {
MetadataOpts
}
// Add adds metadata such as type, inferred from element, to a node.
func (m metadata) Add(element interface{}, node *Node) error {
if node == nil {
return nil
}
@ -24,25 +39,25 @@ func AddMetadata(element interface{}, node *Node) error {
rootType := reflect.TypeOf(element)
node.Kind = rootType.Kind()
return browseChildren(rootType, node)
return m.browseChildren(rootType, node)
}
func browseChildren(fType reflect.Type, node *Node) error {
func (m metadata) browseChildren(fType reflect.Type, node *Node) error {
for _, child := range node.Children {
if err := addMetadata(fType, child); err != nil {
if err := m.add(fType, child); err != nil {
return err
}
}
return nil
}
func addMetadata(rootType reflect.Type, node *Node) error {
func (m metadata) add(rootType reflect.Type, node *Node) error {
rType := rootType
if rootType.Kind() == reflect.Ptr {
rType = rootType.Elem()
}
field, err := findTypedField(rType, node)
field, err := m.findTypedField(rType, node)
if err != nil {
return err
}
@ -57,11 +72,11 @@ func addMetadata(rootType reflect.Type, node *Node) error {
if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Ptr && fType.Elem().Kind() == reflect.Struct ||
fType.Kind() == reflect.Map {
if len(node.Children) == 0 && field.Tag.Get(TagLabel) != TagLabelAllowEmpty {
if len(node.Children) == 0 && field.Tag.Get(m.TagName) != TagLabelAllowEmpty {
return fmt.Errorf("%s cannot be a standalone element (type %s)", node.Name, fType)
}
node.Disabled = len(node.Value) > 0 && !strings.EqualFold(node.Value, "true") && field.Tag.Get(TagLabel) == TagLabelAllowEmpty
node.Disabled = len(node.Value) > 0 && !strings.EqualFold(node.Value, "true") && field.Tag.Get(m.TagName) == TagLabelAllowEmpty
}
if len(node.Children) == 0 {
@ -69,7 +84,7 @@ func addMetadata(rootType reflect.Type, node *Node) error {
}
if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Ptr && fType.Elem().Kind() == reflect.Struct {
return browseChildren(fType, node)
return m.browseChildren(fType, node)
}
if fType.Kind() == reflect.Map {
@ -80,7 +95,7 @@ func addMetadata(rootType reflect.Type, node *Node) error {
if elem.Kind() == reflect.Map || elem.Kind() == reflect.Struct ||
(elem.Kind() == reflect.Ptr && elem.Elem().Kind() == reflect.Struct) {
if err = browseChildren(elem, child); err != nil {
if err = m.browseChildren(elem, child); err != nil {
return err
}
}
@ -89,13 +104,13 @@ func addMetadata(rootType reflect.Type, node *Node) error {
}
if fType.Kind() == reflect.Slice {
if field.Tag.Get(TagLabelSliceAsStruct) != "" {
return browseChildren(fType.Elem(), node)
if m.AllowSliceAsStruct && field.Tag.Get(TagLabelSliceAsStruct) != "" {
return m.browseChildren(fType.Elem(), node)
}
for _, ch := range node.Children {
ch.Kind = fType.Elem().Kind()
if err = browseChildren(fType.Elem(), ch); err != nil {
if err = m.browseChildren(fType.Elem(), ch); err != nil {
return err
}
}
@ -105,19 +120,19 @@ func addMetadata(rootType reflect.Type, node *Node) error {
return fmt.Errorf("invalid node %s: %v", node.Name, fType.Kind())
}
func findTypedField(rType reflect.Type, node *Node) (reflect.StructField, error) {
func (m metadata) findTypedField(rType reflect.Type, node *Node) (reflect.StructField, error) {
for i := 0; i < rType.NumField(); i++ {
cField := rType.Field(i)
fieldName := cField.Tag.Get(TagLabelSliceAsStruct)
if len(fieldName) == 0 {
if !m.AllowSliceAsStruct || len(fieldName) == 0 {
fieldName = cField.Name
}
if IsExported(cField) {
if cField.Anonymous {
if cField.Type.Kind() == reflect.Struct {
structField, err := findTypedField(cField.Type, node)
structField, err := m.findTypedField(cField.Type, node)
if err != nil {
continue
}

View file

@ -991,7 +991,7 @@ func TestAddMetadata(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
err := AddMetadata(test.structure, test.tree)
err := metadata{MetadataOpts{TagName: TagLabel, AllowSliceAsStruct: true}}.Add(test.structure, test.tree)
if test.expected.error {
assert.Error(t, err)

View file

@ -13,12 +13,13 @@ func Decode(labels map[string]string, element interface{}, rootName string, filt
return err
}
err = AddMetadata(element, node)
metaOpts := MetadataOpts{TagName: TagLabel, AllowSliceAsStruct: true}
err = AddMetadata(element, node, metaOpts)
if err != nil {
return err
}
err = Fill(element, node)
err = Fill(element, node, FillerOpts{AllowSliceAsStruct: true})
if err != nil {
return err
}
@ -29,7 +30,8 @@ func Decode(labels map[string]string, element interface{}, rootName string, filt
// Encode converts an element to labels.
// element -> node (value) -> label (node)
func Encode(element interface{}, rootName string) (map[string]string, error) {
node, err := EncodeToNode(element, rootName, true)
etnOpts := EncoderToNodeOpts{OmitEmpty: true, TagName: TagLabel, AllowSliceAsStruct: true}
node, err := EncodeToNode(element, rootName, etnOpts)
if err != nil {
return nil, err
}

View file

@ -14,6 +14,10 @@ import (
"github.com/containous/traefik/v2/pkg/provider/file"
"github.com/containous/traefik/v2/pkg/provider/kubernetes/crd"
"github.com/containous/traefik/v2/pkg/provider/kubernetes/ingress"
"github.com/containous/traefik/v2/pkg/provider/kv/consul"
"github.com/containous/traefik/v2/pkg/provider/kv/etcd"
"github.com/containous/traefik/v2/pkg/provider/kv/redis"
"github.com/containous/traefik/v2/pkg/provider/kv/zk"
"github.com/containous/traefik/v2/pkg/provider/marathon"
"github.com/containous/traefik/v2/pkg/provider/rancher"
"github.com/containous/traefik/v2/pkg/provider/rest"
@ -156,15 +160,21 @@ func (t *Tracing) SetDefaults() {
// Providers contains providers configuration
type Providers struct {
ProvidersThrottleDuration types.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." json:"providersThrottleDuration,omitempty" toml:"providersThrottleDuration,omitempty" yaml:"providersThrottleDuration,omitempty" export:"true"`
Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" export:"true" label:"allowEmpty"`
File *file.Provider `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"`
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings." json:"marathon,omitempty" toml:"marathon,omitempty" yaml:"marathon,omitempty" export:"true" label:"allowEmpty"`
KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" export:"true" label:"allowEmpty"`
KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" export:"true" label:"allowEmpty"`
Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" export:"true" label:"allowEmpty"`
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings." json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" export:"true" label:"allowEmpty"`
ConsulCatalog *consulcatalog.Provider `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty"`
ProvidersThrottleDuration types.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." json:"providersThrottleDuration,omitempty" toml:"providersThrottleDuration,omitempty" yaml:"providersThrottleDuration,omitempty" export:"true"`
Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" export:"true" label:"allowEmpty"`
File *file.Provider `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"`
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings." json:"marathon,omitempty" toml:"marathon,omitempty" yaml:"marathon,omitempty" export:"true" label:"allowEmpty"`
KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" export:"true" label:"allowEmpty"`
KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" export:"true" label:"allowEmpty"`
Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" export:"true" label:"allowEmpty"`
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings." json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" export:"true" label:"allowEmpty"`
ConsulCatalog *consulcatalog.Provider `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty"`
Consul *consul.Provider `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" export:"true" label:"allowEmpty"`
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" export:"true" label:"allowEmpty"`
ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" export:"true" label:"allowEmpty"`
Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" export:"true" label:"allowEmpty"`
}
// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones.

View file

@ -53,6 +53,22 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator {
p.quietAddProvider(conf.ConsulCatalog)
}
if conf.Consul != nil {
p.quietAddProvider(conf.Consul)
}
if conf.Etcd != nil {
p.quietAddProvider(conf.Etcd)
}
if conf.ZooKeeper != nil {
p.quietAddProvider(conf.ZooKeeper)
}
if conf.Redis != nil {
p.quietAddProvider(conf.Redis)
}
return p
}

View file

@ -0,0 +1,25 @@
package consul
import (
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/provider/kv"
)
var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
kv.Provider
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Provider.SetDefaults()
p.Endpoints = []string{"127.0.0.1:8500"}
}
// Init the provider
func (p *Provider) Init() error {
return p.Provider.Init(store.CONSUL, "consul")
}

View file

@ -0,0 +1,25 @@
package etcd
import (
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/provider/kv"
)
var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
kv.Provider
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Provider.SetDefaults()
p.Endpoints = []string{"127.0.0.1:2379"}
}
// Init the provider
func (p *Provider) Init() error {
return p.Provider.Init(store.ETCDV3, "etcd")
}

191
pkg/provider/kv/kv.go Normal file
View file

@ -0,0 +1,191 @@
package kv
import (
"context"
"errors"
"fmt"
"path"
"time"
"github.com/abronan/valkeyrie"
"github.com/abronan/valkeyrie/store"
"github.com/abronan/valkeyrie/store/consul"
etcdv3 "github.com/abronan/valkeyrie/store/etcd/v3"
"github.com/abronan/valkeyrie/store/redis"
"github.com/abronan/valkeyrie/store/zookeeper"
"github.com/cenkalti/backoff/v3"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/kv"
"github.com/containous/traefik/v2/pkg/job"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/safe"
"github.com/containous/traefik/v2/pkg/types"
)
// Provider holds configurations of the provider.
type Provider struct {
RootKey string `description:"Root key used for KV store" export:"true" json:"rootKey,omitempty" toml:"rootKey,omitempty" yaml:"rootKey,omitempty"`
Endpoints []string `description:"KV store endpoints" json:"endpoints,omitempty" toml:"endpoints,omitempty" yaml:"endpoints,omitempty"`
Username string `description:"KV Username" json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty"`
Password string `description:"KV Password" json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty"`
TLS *types.ClientTLS `description:"Enable TLS support" export:"true" json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty"`
storeType store.Backend
kvClient store.Store
name string
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.RootKey = "traefik"
}
// Init the provider
func (p *Provider) Init(storeType store.Backend, name string) error {
ctx := log.With(context.Background(), log.Str(log.ProviderName, string(storeType)))
p.storeType = storeType
p.name = name
kvClient, err := p.createKVClient(ctx)
if err != nil {
return fmt.Errorf("failed to Connect to KV store: %w", err)
}
p.kvClient = kvClient
return nil
}
// Provide allows the docker provider to provide configurations to traefik using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
ctx := log.With(context.Background(), log.Str(log.ProviderName, string(p.storeType)))
logger := log.FromContext(ctx)
operation := func() error {
if _, err := p.kvClient.Exists(path.Join(p.RootKey, "qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"), nil); err != nil {
return fmt.Errorf("KV store connection error: %w", err)
}
return nil
}
notify := func(err error, time time.Duration) {
logger.Errorf("KV connection error: %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
return fmt.Errorf("cannot connect to KV server: %w", err)
}
configuration, err := p.buildConfiguration()
if err != nil {
logger.Errorf("Cannot build the configuration: %v", err)
} else {
configurationChan <- dynamic.Message{
ProviderName: p.name,
Configuration: configuration,
}
}
pool.Go(func(stop chan bool) {
err := p.watchKv(ctx, configurationChan, p.RootKey, stop)
if err != nil {
logger.Errorf("Cannot watch KV store: %v", err)
}
})
return nil
}
func (p *Provider) watchKv(ctx context.Context, configurationChan chan<- dynamic.Message, prefix string, stop chan bool) error {
operation := func() error {
events, err := p.kvClient.WatchTree(p.RootKey, make(chan struct{}), nil)
if err != nil {
return fmt.Errorf("failed to watch KV: %w", err)
}
for {
select {
case <-stop:
return nil
case _, ok := <-events:
if !ok {
return errors.New("the WatchTree channel is closed")
}
configuration, errC := p.buildConfiguration()
if errC != nil {
return errC
}
if configuration != nil {
configurationChan <- dynamic.Message{
ProviderName: string(p.storeType),
Configuration: configuration,
}
}
}
}
}
notify := func(err error, time time.Duration) {
log.FromContext(ctx).Errorf("KV connection error: %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
return fmt.Errorf("cannot connect to KV server: %w", err)
}
return nil
}
func (p *Provider) buildConfiguration() (*dynamic.Configuration, error) {
pairs, err := p.kvClient.List(p.RootKey, nil)
if err != nil {
return nil, err
}
cfg := &dynamic.Configuration{}
err = kv.Decode(pairs, cfg, p.RootKey)
if err != nil {
return nil, err
}
return cfg, nil
}
func (p *Provider) createKVClient(ctx context.Context) (store.Store, error) {
storeConfig := &store.Config{
ConnectionTimeout: 3 * time.Second,
Bucket: "traefik",
Username: p.Username,
Password: p.Password,
}
if p.TLS != nil {
var err error
storeConfig.TLS, err = p.TLS.CreateTLSConfig(ctx)
if err != nil {
return nil, err
}
}
switch p.storeType {
case store.CONSUL:
consul.Register()
case store.ETCDV3:
etcdv3.Register()
case store.ZK:
zookeeper.Register()
case store.REDIS:
redis.Register()
}
kvStore, err := valkeyrie.NewStore(p.storeType, p.Endpoints, storeConfig)
if err != nil {
return nil, err
}
return &storeWrapper{Store: kvStore}, nil
}

View file

@ -0,0 +1,122 @@
package kv
import (
"errors"
"strings"
"github.com/abronan/valkeyrie/store"
)
func newProviderMock(kvPairs []*store.KVPair) *Provider {
return &Provider{
RootKey: "traefik",
kvClient: newKvClientMock(kvPairs, nil),
}
}
// Override Get/List to return a error
type KvError struct {
Get error
List error
}
// Extremely limited mock store so we can test initialization
type Mock struct {
Error KvError
KVPairs []*store.KVPair
WatchTreeMethod func() <-chan []*store.KVPair
}
func newKvClientMock(kvPairs []*store.KVPair, err error) *Mock {
mock := &Mock{
KVPairs: kvPairs,
}
if err != nil {
mock.Error = KvError{
Get: err,
List: err,
}
}
return mock
}
func (s *Mock) Put(key string, value []byte, opts *store.WriteOptions) error {
return errors.New("method Put not supported")
}
func (s *Mock) Get(key string, options *store.ReadOptions) (*store.KVPair, error) {
if err := s.Error.Get; err != nil {
return nil, err
}
for _, kvPair := range s.KVPairs {
if kvPair.Key == key {
return kvPair, nil
}
}
return nil, store.ErrKeyNotFound
}
func (s *Mock) Delete(key string) error {
return errors.New("method Delete not supported")
}
// Exists mock
func (s *Mock) Exists(key string, options *store.ReadOptions) (bool, error) {
if err := s.Error.Get; err != nil {
return false, err
}
for _, kvPair := range s.KVPairs {
if strings.HasPrefix(kvPair.Key, key) {
return true, nil
}
}
return false, store.ErrKeyNotFound
}
// Watch mock
func (s *Mock) Watch(key string, stopCh <-chan struct{}, options *store.ReadOptions) (<-chan *store.KVPair, error) {
return nil, errors.New("method Watch not supported")
}
// WatchTree mock
func (s *Mock) WatchTree(prefix string, stopCh <-chan struct{}, options *store.ReadOptions) (<-chan []*store.KVPair, error) {
return s.WatchTreeMethod(), nil
}
// NewLock mock
func (s *Mock) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
return nil, errors.New("method NewLock not supported")
}
// List mock
func (s *Mock) List(prefix string, options *store.ReadOptions) ([]*store.KVPair, error) {
if err := s.Error.List; err != nil {
return nil, err
}
var kv []*store.KVPair
for _, kvPair := range s.KVPairs {
if strings.HasPrefix(kvPair.Key, prefix) { // FIXME && !strings.ContainsAny(strings.TrimPrefix(kvPair.Key, prefix), "/") {
kv = append(kv, kvPair)
}
}
return kv, nil
}
// DeleteTree mock
func (s *Mock) DeleteTree(prefix string) error {
return errors.New("method DeleteTree not supported")
}
// AtomicPut mock
func (s *Mock) AtomicPut(key string, value []byte, previous *store.KVPair, opts *store.WriteOptions) (bool, *store.KVPair, error) {
return false, nil, errors.New("method AtomicPut not supported")
}
// AtomicDelete mock
func (s *Mock) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
return false, errors.New("method AtomicDelete not supported")
}
// Close mock
func (s *Mock) Close() {}

892
pkg/provider/kv/kv_test.go Normal file
View file

@ -0,0 +1,892 @@
package kv
import (
"context"
"errors"
"testing"
"time"
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/tls"
"github.com/containous/traefik/v2/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_buildConfiguration(t *testing.T) {
provider := newProviderMock(mapToPairs(map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "foobar",
"traefik/http/routers/Router0/entryPoints/1": "foobar",
"traefik/http/routers/Router0/middlewares/0": "foobar",
"traefik/http/routers/Router0/middlewares/1": "foobar",
"traefik/http/routers/Router0/service": "foobar",
"traefik/http/routers/Router0/rule": "foobar",
"traefik/http/routers/Router0/priority": "42",
"traefik/http/routers/Router0/tls": "",
"traefik/http/routers/Router1/rule": "foobar",
"traefik/http/routers/Router1/priority": "42",
"traefik/http/routers/Router1/tls/domains/0/main": "foobar",
"traefik/http/routers/Router1/tls/domains/0/sans/0": "foobar",
"traefik/http/routers/Router1/tls/domains/0/sans/1": "foobar",
"traefik/http/routers/Router1/tls/domains/1/main": "foobar",
"traefik/http/routers/Router1/tls/domains/1/sans/0": "foobar",
"traefik/http/routers/Router1/tls/domains/1/sans/1": "foobar",
"traefik/http/routers/Router1/tls/options": "foobar",
"traefik/http/routers/Router1/tls/certResolver": "foobar",
"traefik/http/routers/Router1/entryPoints/0": "foobar",
"traefik/http/routers/Router1/entryPoints/1": "foobar",
"traefik/http/routers/Router1/middlewares/0": "foobar",
"traefik/http/routers/Router1/middlewares/1": "foobar",
"traefik/http/routers/Router1/service": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/path": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/port": "42",
"traefik/http/services/Service01/loadBalancer/healthCheck/interval": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/timeout": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/hostname": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/headers/name1": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/scheme": "foobar",
"traefik/http/services/Service01/loadBalancer/responseForwarding/flushInterval": "foobar",
"traefik/http/services/Service01/loadBalancer/passHostHeader": "true",
"traefik/http/services/Service01/loadBalancer/sticky/cookie/name": "foobar",
"traefik/http/services/Service01/loadBalancer/sticky/cookie/secure": "true",
"traefik/http/services/Service01/loadBalancer/sticky/cookie/httpOnly": "true",
"traefik/http/services/Service01/loadBalancer/servers/0/url": "foobar",
"traefik/http/services/Service01/loadBalancer/servers/1/url": "foobar",
"traefik/http/services/Service02/mirroring/service": "foobar",
"traefik/http/services/Service02/mirroring/mirrors/0/name": "foobar",
"traefik/http/services/Service02/mirroring/mirrors/0/percent": "42",
"traefik/http/services/Service02/mirroring/mirrors/1/name": "foobar",
"traefik/http/services/Service02/mirroring/mirrors/1/percent": "42",
"traefik/http/services/Service03/weighted/sticky/cookie/name": "foobar",
"traefik/http/services/Service03/weighted/sticky/cookie/secure": "true",
"traefik/http/services/Service03/weighted/sticky/cookie/httpOnly": "true",
"traefik/http/services/Service03/weighted/services/0/name": "foobar",
"traefik/http/services/Service03/weighted/services/0/weight": "42",
"traefik/http/services/Service03/weighted/services/1/name": "foobar",
"traefik/http/services/Service03/weighted/services/1/weight": "42",
"traefik/http/middlewares/Middleware08/forwardAuth/authResponseHeaders/0": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/authResponseHeaders/1": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/key": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/insecureSkipVerify": "true",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/ca": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/caOptional": "true",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/cert": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/address": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/trustForwardHeader": "true",
"traefik/http/middlewares/Middleware15/redirectScheme/scheme": "foobar",
"traefik/http/middlewares/Middleware15/redirectScheme/port": "foobar",
"traefik/http/middlewares/Middleware15/redirectScheme/permanent": "true",
"traefik/http/middlewares/Middleware17/replacePathRegex/regex": "foobar",
"traefik/http/middlewares/Middleware17/replacePathRegex/replacement": "foobar",
"traefik/http/middlewares/Middleware14/redirectRegex/regex": "foobar",
"traefik/http/middlewares/Middleware14/redirectRegex/replacement": "foobar",
"traefik/http/middlewares/Middleware14/redirectRegex/permanent": "true",
"traefik/http/middlewares/Middleware16/replacePath/path": "foobar",
"traefik/http/middlewares/Middleware06/digestAuth/removeHeader": "true",
"traefik/http/middlewares/Middleware06/digestAuth/realm": "foobar",
"traefik/http/middlewares/Middleware06/digestAuth/headerField": "foobar",
"traefik/http/middlewares/Middleware06/digestAuth/users/0": "foobar",
"traefik/http/middlewares/Middleware06/digestAuth/users/1": "foobar",
"traefik/http/middlewares/Middleware06/digestAuth/usersFile": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlAllowHeaders/0": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlAllowHeaders/1": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlAllowOrigin": "foobar",
"traefik/http/middlewares/Middleware09/headers/contentTypeNosniff": "true",
"traefik/http/middlewares/Middleware09/headers/accessControlAllowCredentials": "true",
"traefik/http/middlewares/Middleware09/headers/featurePolicy": "foobar",
"traefik/http/middlewares/Middleware09/headers/forceSTSHeader": "true",
"traefik/http/middlewares/Middleware09/headers/sslRedirect": "true",
"traefik/http/middlewares/Middleware09/headers/sslHost": "foobar",
"traefik/http/middlewares/Middleware09/headers/sslForceHost": "true",
"traefik/http/middlewares/Middleware09/headers/sslProxyHeaders/name1": "foobar",
"traefik/http/middlewares/Middleware09/headers/sslProxyHeaders/name0": "foobar",
"traefik/http/middlewares/Middleware09/headers/allowedHosts/0": "foobar",
"traefik/http/middlewares/Middleware09/headers/allowedHosts/1": "foobar",
"traefik/http/middlewares/Middleware09/headers/stsPreload": "true",
"traefik/http/middlewares/Middleware09/headers/frameDeny": "true",
"traefik/http/middlewares/Middleware09/headers/isDevelopment": "true",
"traefik/http/middlewares/Middleware09/headers/customResponseHeaders/name1": "foobar",
"traefik/http/middlewares/Middleware09/headers/customResponseHeaders/name0": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlAllowMethods/0": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlAllowMethods/1": "foobar",
"traefik/http/middlewares/Middleware09/headers/stsSeconds": "42",
"traefik/http/middlewares/Middleware09/headers/stsIncludeSubdomains": "true",
"traefik/http/middlewares/Middleware09/headers/customFrameOptionsValue": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlMaxAge": "42",
"traefik/http/middlewares/Middleware09/headers/addVaryHeader": "true",
"traefik/http/middlewares/Middleware09/headers/hostsProxyHeaders/0": "foobar",
"traefik/http/middlewares/Middleware09/headers/hostsProxyHeaders/1": "foobar",
"traefik/http/middlewares/Middleware09/headers/sslTemporaryRedirect": "true",
"traefik/http/middlewares/Middleware09/headers/customBrowserXSSValue": "foobar",
"traefik/http/middlewares/Middleware09/headers/referrerPolicy": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/0": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/1": "foobar",
"traefik/http/middlewares/Middleware09/headers/contentSecurityPolicy": "foobar",
"traefik/http/middlewares/Middleware09/headers/publicKey": "foobar",
"traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name0": "foobar",
"traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name1": "foobar",
"traefik/http/middlewares/Middleware09/headers/browserXssFilter": "true",
"traefik/http/middlewares/Middleware10/ipWhiteList/sourceRange/0": "foobar",
"traefik/http/middlewares/Middleware10/ipWhiteList/sourceRange/1": "foobar",
"traefik/http/middlewares/Middleware10/ipWhiteList/ipStrategy/excludedIPs/0": "foobar",
"traefik/http/middlewares/Middleware10/ipWhiteList/ipStrategy/excludedIPs/1": "foobar",
"traefik/http/middlewares/Middleware10/ipWhiteList/ipStrategy/depth": "42",
"traefik/http/middlewares/Middleware11/inFlightReq/amount": "42",
"traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/requestHost": "true",
"traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/ipStrategy/depth": "42",
"traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/0": "foobar",
"traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/1": "foobar",
"traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/requestHeaderName": "foobar",
"traefik/http/middlewares/Middleware12/passTLSClientCert/pem": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/notAfter": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/notBefore": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/sans": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/country": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/province": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/locality": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/organization": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/commonName": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/serialNumber": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/domainComponent": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/country": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/province": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/locality": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/organization": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/commonName": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/serialNumber": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/domainComponent": "true",
"traefik/http/middlewares/Middleware00/addPrefix/prefix": "foobar",
"traefik/http/middlewares/Middleware03/chain/middlewares/0": "foobar",
"traefik/http/middlewares/Middleware03/chain/middlewares/1": "foobar",
"traefik/http/middlewares/Middleware04/circuitBreaker/expression": "foobar",
"traefik/http/middlewares/Middleware07/errors/status/0": "foobar",
"traefik/http/middlewares/Middleware07/errors/status/1": "foobar",
"traefik/http/middlewares/Middleware07/errors/service": "foobar",
"traefik/http/middlewares/Middleware07/errors/query": "foobar",
"traefik/http/middlewares/Middleware13/rateLimit/average": "42",
"traefik/http/middlewares/Middleware13/rateLimit/burst": "42",
"traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/requestHeaderName": "foobar",
"traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/requestHost": "true",
"traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/depth": "42",
"traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0": "foobar",
"traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/excludedIPs/1": "foobar",
"traefik/http/middlewares/Middleware20/stripPrefixRegex/regex/0": "foobar",
"traefik/http/middlewares/Middleware20/stripPrefixRegex/regex/1": "foobar",
"traefik/http/middlewares/Middleware01/basicAuth/users/0": "foobar",
"traefik/http/middlewares/Middleware01/basicAuth/users/1": "foobar",
"traefik/http/middlewares/Middleware01/basicAuth/usersFile": "foobar",
"traefik/http/middlewares/Middleware01/basicAuth/realm": "foobar",
"traefik/http/middlewares/Middleware01/basicAuth/removeHeader": "true",
"traefik/http/middlewares/Middleware01/basicAuth/headerField": "foobar",
"traefik/http/middlewares/Middleware02/buffering/maxResponseBodyBytes": "42",
"traefik/http/middlewares/Middleware02/buffering/memResponseBodyBytes": "42",
"traefik/http/middlewares/Middleware02/buffering/retryExpression": "foobar",
"traefik/http/middlewares/Middleware02/buffering/maxRequestBodyBytes": "42",
"traefik/http/middlewares/Middleware02/buffering/memRequestBodyBytes": "42",
"traefik/http/middlewares/Middleware05/compress": "",
"traefik/http/middlewares/Middleware18/retry/attempts": "42",
"traefik/http/middlewares/Middleware19/stripPrefix/prefixes/0": "foobar",
"traefik/http/middlewares/Middleware19/stripPrefix/prefixes/1": "foobar",
"traefik/http/middlewares/Middleware19/stripPrefix/forceSlash": "true",
"traefik/tcp/routers/TCPRouter0/entryPoints/0": "foobar",
"traefik/tcp/routers/TCPRouter0/entryPoints/1": "foobar",
"traefik/tcp/routers/TCPRouter0/service": "foobar",
"traefik/tcp/routers/TCPRouter0/rule": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/options": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/certResolver": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/domains/0/main": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/domains/0/sans/0": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/domains/0/sans/1": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/domains/1/main": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/domains/1/sans/0": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/domains/1/sans/1": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/passthrough": "true",
"traefik/tcp/routers/TCPRouter1/entryPoints/0": "foobar",
"traefik/tcp/routers/TCPRouter1/entryPoints/1": "foobar",
"traefik/tcp/routers/TCPRouter1/service": "foobar",
"traefik/tcp/routers/TCPRouter1/rule": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/domains/0/main": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/domains/0/sans/0": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/domains/0/sans/1": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/domains/1/main": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/domains/1/sans/0": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/domains/1/sans/1": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/passthrough": "true",
"traefik/tcp/routers/TCPRouter1/tls/options": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/certResolver": "foobar",
"traefik/tcp/services/TCPService01/loadBalancer/terminationDelay": "42",
"traefik/tcp/services/TCPService01/loadBalancer/servers/0/address": "foobar",
"traefik/tcp/services/TCPService01/loadBalancer/servers/1/address": "foobar",
"traefik/tcp/services/TCPService02/weighted/services/0/name": "foobar",
"traefik/tcp/services/TCPService02/weighted/services/0/weight": "42",
"traefik/tcp/services/TCPService02/weighted/services/1/name": "foobar",
"traefik/tcp/services/TCPService02/weighted/services/1/weight": "43",
"traefik/tls/options/Options0/minVersion": "foobar",
"traefik/tls/options/Options0/maxVersion": "foobar",
"traefik/tls/options/Options0/cipherSuites/0": "foobar",
"traefik/tls/options/Options0/cipherSuites/1": "foobar",
"traefik/tls/options/Options0/sniStrict": "true",
"traefik/tls/options/Options0/curvePreferences/0": "foobar",
"traefik/tls/options/Options0/curvePreferences/1": "foobar",
"traefik/tls/options/Options0/clientAuth/caFiles/0": "foobar",
"traefik/tls/options/Options0/clientAuth/caFiles/1": "foobar",
"traefik/tls/options/Options0/clientAuth/clientAuthType": "foobar",
"traefik/tls/options/Options1/sniStrict": "true",
"traefik/tls/options/Options1/curvePreferences/0": "foobar",
"traefik/tls/options/Options1/curvePreferences/1": "foobar",
"traefik/tls/options/Options1/clientAuth/caFiles/0": "foobar",
"traefik/tls/options/Options1/clientAuth/caFiles/1": "foobar",
"traefik/tls/options/Options1/clientAuth/clientAuthType": "foobar",
"traefik/tls/options/Options1/minVersion": "foobar",
"traefik/tls/options/Options1/maxVersion": "foobar",
"traefik/tls/options/Options1/cipherSuites/0": "foobar",
"traefik/tls/options/Options1/cipherSuites/1": "foobar",
"traefik/tls/stores/Store0/defaultCertificate/certFile": "foobar",
"traefik/tls/stores/Store0/defaultCertificate/keyFile": "foobar",
"traefik/tls/stores/Store1/defaultCertificate/certFile": "foobar",
"traefik/tls/stores/Store1/defaultCertificate/keyFile": "foobar",
"traefik/tls/certificates/0/certFile": "foobar",
"traefik/tls/certificates/0/keyFile": "foobar",
"traefik/tls/certificates/0/stores/0": "foobar",
"traefik/tls/certificates/0/stores/1": "foobar",
"traefik/tls/certificates/1/certFile": "foobar",
"traefik/tls/certificates/1/keyFile": "foobar",
"traefik/tls/certificates/1/stores/0": "foobar",
"traefik/tls/certificates/1/stores/1": "foobar",
}))
cfg, err := provider.buildConfiguration()
require.NoError(t, err)
expected := &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
EntryPoints: []string{
"foobar",
"foobar",
},
Middlewares: []string{
"foobar",
"foobar",
},
Service: "foobar",
Rule: "foobar",
Priority: 42,
TLS: &dynamic.RouterTLSConfig{
Options: "foobar",
CertResolver: "foobar",
Domains: []types.Domain{
{
Main: "foobar",
SANs: []string{
"foobar",
"foobar",
},
},
{
Main: "foobar",
SANs: []string{
"foobar",
"foobar",
},
},
},
},
},
"Router0": {
EntryPoints: []string{
"foobar",
"foobar",
},
Middlewares: []string{
"foobar",
"foobar",
},
Service: "foobar",
Rule: "foobar",
Priority: 42,
TLS: &dynamic.RouterTLSConfig{},
},
},
Middlewares: map[string]*dynamic.Middleware{
"Middleware10": {
IPWhiteList: &dynamic.IPWhiteList{
SourceRange: []string{
"foobar",
"foobar",
},
IPStrategy: &dynamic.IPStrategy{
Depth: 42,
ExcludedIPs: []string{
"foobar",
"foobar",
},
},
},
},
"Middleware13": {
RateLimit: &dynamic.RateLimit{
Average: 42,
Burst: 42,
SourceCriterion: &dynamic.SourceCriterion{
IPStrategy: &dynamic.IPStrategy{
Depth: 42,
ExcludedIPs: []string{
"foobar",
"foobar",
},
},
RequestHeaderName: "foobar",
RequestHost: true,
},
},
},
"Middleware19": {
StripPrefix: &dynamic.StripPrefix{
Prefixes: []string{
"foobar",
"foobar",
},
ForceSlash: true,
},
},
"Middleware00": {
AddPrefix: &dynamic.AddPrefix{
Prefix: "foobar",
},
},
"Middleware02": {
Buffering: &dynamic.Buffering{
MaxRequestBodyBytes: 42,
MemRequestBodyBytes: 42,
MaxResponseBodyBytes: 42,
MemResponseBodyBytes: 42,
RetryExpression: "foobar",
},
},
"Middleware04": {
CircuitBreaker: &dynamic.CircuitBreaker{
Expression: "foobar",
},
},
"Middleware05": {
Compress: &dynamic.Compress{},
},
"Middleware08": {
ForwardAuth: &dynamic.ForwardAuth{
Address: "foobar",
TLS: &dynamic.ClientTLS{
CA: "foobar",
CAOptional: true,
Cert: "foobar",
Key: "foobar",
InsecureSkipVerify: true,
},
TrustForwardHeader: true,
AuthResponseHeaders: []string{
"foobar",
"foobar",
},
},
},
"Middleware06": {
DigestAuth: &dynamic.DigestAuth{
Users: dynamic.Users{
"foobar",
"foobar",
},
UsersFile: "foobar",
RemoveHeader: true,
Realm: "foobar",
HeaderField: "foobar",
},
},
"Middleware18": {
Retry: &dynamic.Retry{
Attempts: 42,
},
},
"Middleware16": {
ReplacePath: &dynamic.ReplacePath{
Path: "foobar",
},
},
"Middleware20": {
StripPrefixRegex: &dynamic.StripPrefixRegex{
Regex: []string{
"foobar",
"foobar",
},
},
},
"Middleware03": {
Chain: &dynamic.Chain{
Middlewares: []string{
"foobar",
"foobar",
},
},
},
"Middleware11": {
InFlightReq: &dynamic.InFlightReq{
Amount: 42,
SourceCriterion: &dynamic.SourceCriterion{
IPStrategy: &dynamic.IPStrategy{
Depth: 42,
ExcludedIPs: []string{
"foobar",
"foobar",
},
},
RequestHeaderName: "foobar",
RequestHost: true,
},
},
},
"Middleware12": {
PassTLSClientCert: &dynamic.PassTLSClientCert{
PEM: true,
Info: &dynamic.TLSClientCertificateInfo{
NotAfter: true,
NotBefore: true,
Sans: true,
Subject: &dynamic.TLSCLientCertificateDNInfo{
Country: true,
Province: true,
Locality: true,
Organization: true,
CommonName: true,
SerialNumber: true,
DomainComponent: true,
},
Issuer: &dynamic.TLSCLientCertificateDNInfo{
Country: true,
Province: true,
Locality: true,
Organization: true,
CommonName: true,
SerialNumber: true,
DomainComponent: true,
},
},
},
},
"Middleware14": {
RedirectRegex: &dynamic.RedirectRegex{
Regex: "foobar",
Replacement: "foobar",
Permanent: true,
},
},
"Middleware15": {
RedirectScheme: &dynamic.RedirectScheme{
Scheme: "foobar",
Port: "foobar",
Permanent: true,
},
},
"Middleware01": {
BasicAuth: &dynamic.BasicAuth{
Users: dynamic.Users{
"foobar",
"foobar",
},
UsersFile: "foobar",
Realm: "foobar",
RemoveHeader: true,
HeaderField: "foobar",
},
},
"Middleware07": {
Errors: &dynamic.ErrorPage{
Status: []string{
"foobar",
"foobar",
},
Service: "foobar",
Query: "foobar",
},
},
"Middleware09": {
Headers: &dynamic.Headers{
CustomRequestHeaders: map[string]string{
"name0": "foobar",
"name1": "foobar",
},
CustomResponseHeaders: map[string]string{
"name0": "foobar",
"name1": "foobar",
},
AccessControlAllowCredentials: true,
AccessControlAllowHeaders: []string{
"foobar",
"foobar",
},
AccessControlAllowMethods: []string{
"foobar",
"foobar",
},
AccessControlAllowOrigin: "foobar",
AccessControlExposeHeaders: []string{
"foobar",
"foobar",
},
AccessControlMaxAge: 42,
AddVaryHeader: true,
AllowedHosts: []string{
"foobar",
"foobar",
},
HostsProxyHeaders: []string{
"foobar",
"foobar",
},
SSLRedirect: true,
SSLTemporaryRedirect: true,
SSLHost: "foobar",
SSLProxyHeaders: map[string]string{
"name1": "foobar",
"name0": "foobar",
},
SSLForceHost: true,
STSSeconds: 42,
STSIncludeSubdomains: true,
STSPreload: true,
ForceSTSHeader: true,
FrameDeny: true,
CustomFrameOptionsValue: "foobar",
ContentTypeNosniff: true,
BrowserXSSFilter: true,
CustomBrowserXSSValue: "foobar",
ContentSecurityPolicy: "foobar",
PublicKey: "foobar",
ReferrerPolicy: "foobar",
FeaturePolicy: "foobar",
IsDevelopment: true,
},
},
"Middleware17": {
ReplacePathRegex: &dynamic.ReplacePathRegex{
Regex: "foobar",
Replacement: "foobar",
},
},
},
Services: map[string]*dynamic.Service{
"Service01": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Sticky: &dynamic.Sticky{
Cookie: &dynamic.Cookie{
Name: "foobar",
Secure: true,
HTTPOnly: true,
},
},
Servers: []dynamic.Server{
{
URL: "foobar",
Scheme: "http",
},
{
URL: "foobar",
Scheme: "http",
},
},
HealthCheck: &dynamic.HealthCheck{
Scheme: "foobar",
Path: "foobar",
Port: 42,
Interval: "foobar",
Timeout: "foobar",
Hostname: "foobar",
Headers: map[string]string{
"name0": "foobar",
"name1": "foobar",
},
},
PassHostHeader: func(v bool) *bool { return &v }(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: "foobar",
},
},
},
"Service02": {
Mirroring: &dynamic.Mirroring{
Service: "foobar",
Mirrors: []dynamic.MirrorService{
{
Name: "foobar",
Percent: 42,
},
{
Name: "foobar",
Percent: 42,
},
},
},
},
"Service03": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "foobar",
Weight: func(v int) *int { return &v }(42),
},
{
Name: "foobar",
Weight: func(v int) *int { return &v }(42),
},
},
Sticky: &dynamic.Sticky{
Cookie: &dynamic.Cookie{
Name: "foobar",
Secure: true,
HTTPOnly: true,
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"TCPRouter0": {
EntryPoints: []string{
"foobar",
"foobar",
},
Service: "foobar",
Rule: "foobar",
TLS: &dynamic.RouterTCPTLSConfig{
Passthrough: true,
Options: "foobar",
CertResolver: "foobar",
Domains: []types.Domain{
{
Main: "foobar",
SANs: []string{
"foobar",
"foobar",
},
},
{
Main: "foobar",
SANs: []string{
"foobar",
"foobar",
},
},
},
},
},
"TCPRouter1": {
EntryPoints: []string{
"foobar",
"foobar",
},
Service: "foobar",
Rule: "foobar",
TLS: &dynamic.RouterTCPTLSConfig{
Passthrough: true,
Options: "foobar",
CertResolver: "foobar",
Domains: []types.Domain{
{
Main: "foobar",
SANs: []string{
"foobar",
"foobar",
},
},
{
Main: "foobar",
SANs: []string{
"foobar",
"foobar",
},
},
},
},
},
},
Services: map[string]*dynamic.TCPService{
"TCPService01": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
TerminationDelay: func(v int) *int { return &v }(42),
Servers: []dynamic.TCPServer{
{Address: "foobar"},
{Address: "foobar"},
},
},
},
"TCPService02": {
Weighted: &dynamic.TCPWeightedRoundRobin{
Services: []dynamic.TCPWRRService{
{
Name: "foobar",
Weight: func(v int) *int { return &v }(42),
},
{
Name: "foobar",
Weight: func(v int) *int { return &v }(43),
},
},
},
},
},
},
TLS: &dynamic.TLSConfiguration{
Certificates: []*tls.CertAndStores{
{
Certificate: tls.Certificate{
CertFile: tls.FileOrContent("foobar"),
KeyFile: tls.FileOrContent("foobar"),
},
Stores: []string{
"foobar",
"foobar",
},
},
{
Certificate: tls.Certificate{
CertFile: tls.FileOrContent("foobar"),
KeyFile: tls.FileOrContent("foobar"),
},
Stores: []string{
"foobar",
"foobar",
},
},
},
Options: map[string]tls.Options{
"Options0": {
MinVersion: "foobar",
MaxVersion: "foobar",
CipherSuites: []string{
"foobar",
"foobar",
},
CurvePreferences: []string{
"foobar",
"foobar",
},
ClientAuth: tls.ClientAuth{
CAFiles: []tls.FileOrContent{
tls.FileOrContent("foobar"),
tls.FileOrContent("foobar"),
},
ClientAuthType: "foobar",
},
SniStrict: true,
},
"Options1": {
MinVersion: "foobar",
MaxVersion: "foobar",
CipherSuites: []string{
"foobar",
"foobar",
},
CurvePreferences: []string{
"foobar",
"foobar",
},
ClientAuth: tls.ClientAuth{
CAFiles: []tls.FileOrContent{
tls.FileOrContent("foobar"),
tls.FileOrContent("foobar"),
},
ClientAuthType: "foobar",
},
SniStrict: true,
},
},
Stores: map[string]tls.Store{
"Store0": {
DefaultCertificate: &tls.Certificate{
CertFile: tls.FileOrContent("foobar"),
KeyFile: tls.FileOrContent("foobar"),
},
},
"Store1": {
DefaultCertificate: &tls.Certificate{
CertFile: tls.FileOrContent("foobar"),
KeyFile: tls.FileOrContent("foobar"),
},
},
},
},
}
assert.Equal(t, expected, cfg)
}
func Test_buildConfiguration_KV_error(t *testing.T) {
provider := &Provider{
RootKey: "traefik",
kvClient: &Mock{
Error: KvError{
List: errors.New("OOPS"),
},
KVPairs: mapToPairs(map[string]string{
"traefik/foo": "bar",
}),
},
}
cfg, err := provider.buildConfiguration()
require.Error(t, err)
assert.Nil(t, cfg)
}
func TestKvWatchTree(t *testing.T) {
returnedChans := make(chan chan []*store.KVPair)
provider := Provider{
kvClient: &Mock{
WatchTreeMethod: func() <-chan []*store.KVPair {
c := make(chan []*store.KVPair, 10)
returnedChans <- c
return c
},
},
}
configChan := make(chan dynamic.Message)
go func() {
err := provider.watchKv(context.Background(), configChan, "prefix", make(chan bool, 1))
require.NoError(t, err)
}()
select {
case c1 := <-returnedChans:
c1 <- []*store.KVPair{}
<-configChan
close(c1) // WatchTree chans can close due to error
case <-time.After(1 * time.Second):
t.Fatalf("Failed to create a new WatchTree chan")
}
select {
case c2 := <-returnedChans:
c2 <- []*store.KVPair{}
<-configChan
case <-time.After(1 * time.Second):
t.Fatalf("Failed to create a new WatchTree chan")
}
select {
case <-configChan:
t.Fatalf("configChan should be empty")
default:
}
}
func mapToPairs(in map[string]string) []*store.KVPair {
var out []*store.KVPair
for k, v := range in {
out = append(out, &store.KVPair{Key: k, Value: []byte(v)})
}
return out
}

View file

@ -0,0 +1,25 @@
package redis
import (
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/provider/kv"
)
var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
kv.Provider
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Provider.SetDefaults()
p.Endpoints = []string{"127.0.0.1:6379"}
}
// Init the provider
func (p *Provider) Init() error {
return p.Provider.Init(store.REDIS, "redis")
}

View file

@ -0,0 +1,118 @@
package kv
import (
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/log"
)
type storeWrapper struct {
store.Store
}
func (s *storeWrapper) Put(key string, value []byte, options *store.WriteOptions) error {
log.WithoutContext().Debugf("Put: %s", key, string(value))
if s.Store == nil {
return nil
}
return s.Store.Put(key, value, options)
}
func (s *storeWrapper) Get(key string, options *store.ReadOptions) (*store.KVPair, error) {
log.WithoutContext().Debugf("Get: %s", key)
if s.Store == nil {
return nil, nil
}
return s.Store.Get(key, options)
}
func (s *storeWrapper) Delete(key string) error {
log.WithoutContext().Debugf("Delete: %s", key)
if s.Store == nil {
return nil
}
return s.Store.Delete(key)
}
func (s *storeWrapper) Exists(key string, options *store.ReadOptions) (bool, error) {
log.WithoutContext().Debugf("Exists: %s", key)
if s.Store == nil {
return true, nil
}
return s.Store.Exists(key, options)
}
func (s *storeWrapper) Watch(key string, stopCh <-chan struct{}, options *store.ReadOptions) (<-chan *store.KVPair, error) {
log.WithoutContext().Debugf("Watch: %s", key)
if s.Store == nil {
return nil, nil
}
return s.Store.Watch(key, stopCh, options)
}
func (s *storeWrapper) WatchTree(directory string, stopCh <-chan struct{}, options *store.ReadOptions) (<-chan []*store.KVPair, error) {
log.WithoutContext().Debugf("WatchTree: %s", directory)
if s.Store == nil {
return nil, nil
}
return s.Store.WatchTree(directory, stopCh, options)
}
func (s *storeWrapper) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
log.WithoutContext().Debugf("NewLock: %s", key)
if s.Store == nil {
return nil, nil
}
return s.Store.NewLock(key, options)
}
func (s *storeWrapper) List(directory string, options *store.ReadOptions) ([]*store.KVPair, error) {
log.WithoutContext().Debugf("List: %s", directory)
if s.Store == nil {
return nil, nil
}
return s.Store.List(directory, options)
}
func (s *storeWrapper) DeleteTree(directory string) error {
log.WithoutContext().Debugf("DeleteTree: %s", directory)
if s.Store == nil {
return nil
}
return s.Store.DeleteTree(directory)
}
func (s *storeWrapper) AtomicPut(key string, value []byte, previous *store.KVPair, options *store.WriteOptions) (bool, *store.KVPair, error) {
log.WithoutContext().Debugf("AtomicPut: %s", key, string(value), previous)
if s.Store == nil {
return true, nil, nil
}
return s.Store.AtomicPut(key, value, previous, options)
}
func (s *storeWrapper) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
log.WithoutContext().Debugf("AtomicDelete: %s", key, previous)
if s.Store == nil {
return true, nil
}
return s.Store.AtomicDelete(key, previous)
}
func (s *storeWrapper) Close() {
log.WithoutContext().Debugf("Close")
if s.Store == nil {
return
}
s.Store.Close()
}

25
pkg/provider/kv/zk/zk.go Normal file
View file

@ -0,0 +1,25 @@
package zk
import (
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/provider/kv"
)
var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
kv.Provider
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Provider.SetDefaults()
p.Endpoints = []string{"127.0.0.1:2181"}
}
// Init the provider
func (p *Provider) Init() error {
return p.Provider.Init(store.ZK, "zookeeper")
}

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<g fill="none">
<circle cx="16" cy="16" r="16" fill="#CA2170"/>
<path fill="#FFF" d="M11.4584691,13.9059027 C10.1039643,13.9073677 9.00383358,12.8130959 9.00001,11.4597195 C8.9961853,10.1066373 10.0901428,9.00589931 11.4449417,9.00002359 C12.7994476,8.99415233 13.9028137,10.0851921 13.9110468,11.4382755 C13.9169293,12.0913154 13.6604964,12.719091 13.1999766,13.1832409 C12.7391627,13.6465085 12.1119026,13.9067839 11.4584691,13.9059027 Z M16.1321856,12.6563312 C15.5069844,12.6563312 15,12.1498792 15,11.5253345 C15,10.9007897 15.5069844,10.3943388 16.1321856,10.3943388 C16.7573868,10.3943388 17.26437,10.9007897 17.26437,11.5253345 C17.2652647,11.8255635 17.1461532,12.1137469 16.9335378,12.3258457 C16.7212154,12.5382374 16.4327288,12.6572125 16.1321856,12.6563312 Z M20.2756886,13.7226995 C20.0998328,14.2855536 19.5052147,14.6048759 18.9382407,14.440955 C18.3712656,14.2770341 18.0392557,13.6900911 18.1912921,13.1201859 C18.3436226,12.5502819 18.9235368,12.2065765 19.4969804,12.3455272 C20.0710134,12.4856533 20.4271367,13.0579081 20.299215,13.6336879 C20.299215,13.6630643 20.299215,13.689504 20.2756886,13.7218171 L20.2756886,13.7226995 Z M19.4758081,10.8076661 C19.0288146,10.919297 18.5588851,10.7503816 18.2859836,10.3796506 C18.0130833,10.0095067 17.9913217,9.51098701 18.2306977,9.11822256 C18.4703689,8.72516511 18.923831,8.51600424 19.3787636,8.58797574 C19.8334021,8.6608296 20.1992304,9.00042286 20.3050963,9.44812099 C20.3333275,9.59500296 20.3333275,9.74482391 20.3050963,9.89170588 C20.2433408,10.3470422 19.9022154,10.7151306 19.4522817,10.811191 L19.4758081,10.8076661 Z M23.5046227,13.5990241 C23.3964026,14.2138739 22.8094317,14.6248516 22.1939343,14.5170397 C21.5781427,14.4089349 21.1667334,13.8231661 21.2743652,13.2080221 C21.3819959,12.5922898 21.9683797,12.1810192 22.5841702,12.2885369 C23.1684963,12.3813665 23.5840222,12.9060317 23.5399117,13.4959125 C23.5263843,13.5273444 23.5184442,13.5611282 23.5163853,13.5957921 L23.5046227,13.5990241 Z M22.5812301,10.7586081 C21.9660268,10.8605442 21.3843478,10.4448662 21.2825984,9.83030935 C21.1805549,9.21633958 21.5960819,8.63527231 22.2112841,8.53304206 C22.8258991,8.43081192 23.4081664,8.84590155 23.510504,9.46016544 C23.5222666,9.55769534 23.5222666,9.65640061 23.510504,9.7539305 C23.4646288,10.2603814 23.0832144,10.6728285 22.5812301,10.7586081 Z M21.7931111,17.5152078 C21.5078599,18.0263591 20.8823635,18.2408086 20.3433266,18.0122592 C19.8034061,17.7831215 19.523448,17.1850164 19.6934225,16.624512 C19.8639852,16.0643016 20.430078,15.7226528 21.0058745,15.8322272 C21.5816722,15.9426828 21.9822012,16.4682293 21.9342672,17.0513532 C21.9254436,17.2158612 21.8769224,17.3744946 21.7931111,17.515502 L21.7931111,17.5152078 Z M21.3843478,7.12208774 C20.8379589,7.42936619 20.1462963,7.23606866 19.8386952,6.69025284 C19.5310941,6.14473081 19.7245938,5.45350117 20.2709838,5.14622272 C20.8167855,4.83894427 21.5090353,5.03165434 21.8166375,5.57717637 C21.9333848,5.77488035 21.9830836,6.00460481 21.9577925,6.23227287 C21.9233858,6.60182954 21.7101822,6.93084661 21.387289,7.1135685 L21.3843478,7.12208774 Z M11.5666684,22.7418333 C7.45668969,22.8079313 3.63019739,20.6552191 1.55579888,17.1103546 C-0.518599627,13.5651948 -0.518599627,9.17810524 1.55579888,5.63294615 C3.63019739,2.08837486 7.45668969,-0.0643368804 11.5666684,0.00146655576 C14.0674744,-0.00264555789 16.4982905,0.824009284 18.4774093,2.35158849 L17.086439,4.16412007 C14.3397875,2.0636986 10.6379827,1.70236727 7.53638328,3.23229666 C4.43478381,4.76193226 2.47095732,7.91785222 2.47095732,11.3731195 C2.47272205,14.8286795 4.4359603,17.9843057 7.5366774,19.5162911 C10.6373945,21.0468082 14.3397875,20.689883 17.0893791,18.5938681 L18.4774093,20.4093377 C16.4959386,21.929867 14.0651214,22.7500586 11.5666684,22.7418333 Z" transform="translate(4 5)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<g fill="none">
<circle cx="16" cy="16" r="16" fill="#419EDA"/>
<path fill="#FFF" d="M15.2941176,14.587756 C15.2941176,15.4465307 14.5982287,16.1411765 13.7405374,16.1411765 C12.8822069,16.1411765 12.1882353,15.4466372 12.1882353,14.587756 C12.1882353,13.7326026 12.8823134,13.0352941 13.7405374,13.0352941 C14.5982287,13.0352941 15.2941176,13.7326026 15.2941176,14.587756 Z M17.5529412,14.5879158 C17.5529412,13.7325308 18.2488173,13.0352941 19.1063616,13.0352941 C19.9636929,13.0352941 20.6588235,13.7325308 20.6588235,14.5879158 C20.6588235,15.4467087 19.9636929,16.1411765 19.1063616,16.1411765 C18.2488173,16.1411765 17.5529412,15.4466022 17.5529412,14.5879158 Z M28.4963286,16.4275595 L28.8470588,16.4005593 L28.8136083,16.7562292 C28.6413549,18.5561348 28.0815868,20.2707062 27.1497512,21.8521653 L26.9690518,22.1596126 L26.6964468,21.9286105 C26.0607761,21.3912721 25.2930814,21.0362689 24.4788227,20.8941564 C23.9428367,21.9894999 23.3080549,23.0179539 22.5879244,23.9729627 C21.4441616,24.3731886 20.2525012,24.6721914 19.0221671,24.8506375 C18.9037011,25.667534 18.9980516,26.5133196 19.3194432,27.2944379 L19.4556901,27.6261076 L19.1058489,27.7029972 L16.4248074,28 C15.5407583,28 14.637928,27.8997768 13.742988,27.7029972 L13.3934802,27.6261076 L13.5293938,27.2951046 C13.8516744,26.5139862 13.9460248,25.6692006 13.8275589,24.8524153 C12.592335,24.6740803 11.3962294,24.3744109 10.2482435,23.9729627 C9.52877981,23.0187317 8.89455375,21.9909444 8.35945677,20.8970453 C7.5477541,21.0400467 6.78439353,21.3948277 6.15261236,21.9306105 L5.8800074,22.1619459 L5.69841893,21.8537209 C4.76825035,20.2742618 4.2083711,18.5595793 4.03345051,16.7567848 L4,16.4004482 L4.35695358,16.4276706 C4.4715299,16.4372263 4.58944016,16.4417819 4.715463,16.4417819 C5.43337089,16.4417819 6.13060837,16.2742248 6.76005579,15.9667775 C6.54846188,14.7422106 6.46344647,13.5164214 6.49011797,12.3045213 C7.17535328,11.31729 7.95827295,10.3845036 8.8403217,9.52571783 C8.45714114,8.81004454 7.89126081,8.19459439 7.18513283,7.75725701 L6.88129998,7.56925527 L7.11900973,7.30147501 C8.32011631,5.94712914 9.81760994,4.85545237 11.4491279,4.14466801 L11.7774096,4.00200002 L11.8616471,4.35011435 C12.0579049,5.15934407 12.4687571,5.88968416 13.0318592,6.47646737 C14.1181671,5.89979537 15.2514837,5.43434661 16.4194731,5.08156557 C17.5902409,5.4351244 18.7255577,5.9019065 19.8123103,6.4794674 C20.3777461,5.89190641 20.7895985,5.15978852 20.9866342,4.34822545 L21.070316,4 L21.3998202,4.14333466 C23.0521197,4.86456356 24.5094949,5.92746229 25.7306051,7.3022528 L25.9676481,7.5694775 L25.6634818,7.75747924 C24.9551312,8.1964833 24.3876951,8.81560014 24.0038477,9.53516236 C24.8885636,10.3972815 25.6723723,11.3330679 26.3581633,12.3248549 C26.381612,13.5373105 26.2920402,14.7542107 26.0814465,15.9621108 C26.7140056,16.272447 27.4151326,16.441893 28.1378192,16.441893 C28.2623973,16.441893 28.3796408,16.4373374 28.4963286,16.4275595 Z M20.2563907,20.7843776 C20.9539616,19.7022565 21.5063951,18.539468 21.9016889,17.3193455 C22.2969828,16.1066677 22.5306918,14.8414337 22.6009268,13.5385328 C21.7771108,12.5187455 20.8407189,11.6240706 19.8066426,10.8727303 C18.7623422,10.1147233 17.6256917,9.49716201 16.4194731,9.03182437 C15.2112541,9.4970509 14.0724922,10.1155011 13.0257469,10.8767303 C11.9956714,11.6240706 11.0633913,12.5118566 10.243576,13.5253104 C10.3105882,14.8356559 10.5415189,16.1082232 10.936146,17.3229011 C11.33244,18.5432458 11.8830954,19.7023676 12.579555,20.7843776 C13.8490073,21.1293808 15.1355738,21.3033824 16.4194731,21.3033824 C17.6999274,21.3033824 18.9880498,21.1292697 20.2563907,20.7843776 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32">
<g fill="none">
<circle cx="16" cy="16" r="16" fill="#000"/>
<path fill="#A41E11" d="M26.04 21.21c-1.214.634-7.504 3.22-8.843 3.918-1.34.698-2.083.691-3.141.186-1.058-.506-7.752-3.21-8.959-3.787-.603-.288-.919-.53-.919-.76v-2.3s8.715-1.897 10.123-2.402c1.407-.505 1.894-.523 3.092-.084 1.198.438 8.357 1.73 9.54 2.163v2.269c0 .227-.273.482-.891.798h-.001l-.001-.001z"/>
<path fill="#D82C20" d="M26.04 18.909c-1.214.633-7.504 3.22-8.843 3.918-1.34.698-2.083.69-3.141.185-1.058-.505-7.752-3.209-8.959-3.786-1.206-.576-1.23-.973-.046-1.437l9.249-3.58c1.407-.505 1.894-.524 3.092-.085 1.198.439 7.45 2.927 8.633 3.367 1.182.44 1.229.79.014 1.424l.002-.006h-.001z"/>
<path fill="#A41E11" d="M26.04 17.46c-1.214.633-7.504 3.219-8.843 3.917-1.34.698-2.083.691-3.141.186-1.058-.506-7.752-3.21-8.959-3.787-.603-.288-.919-.53-.919-.76v-2.3s8.715-1.897 10.123-2.402c1.407-.505 1.894-.523 3.092-.084 1.198.438 8.357 1.73 9.54 2.163v2.269c0 .227-.273.482-.891.798h-.002z"/>
<path fill="#D82C20" d="M26.04 15.158c-1.214.634-7.504 3.22-8.843 3.918-1.34.698-2.083.691-3.141.186-1.058-.506-7.752-3.21-8.959-3.786-1.206-.577-1.23-.974-.046-1.437l9.249-3.58c1.407-.506 1.894-.524 3.092-.085 1.198.438 7.45 2.927 8.634 3.36 1.184.432 1.229.79.015 1.424h-.001z"/>
<path fill="#A41E11" d="M26.04 13.569c-1.214.633-7.504 3.22-8.843 3.918-1.34.699-2.083.692-3.141.186-1.058-.506-7.752-3.21-8.959-3.786-.603-.288-.919-.53-.919-.76v-2.302s8.715-1.897 10.123-2.402c1.407-.505 1.894-.523 3.092-.084 1.198.438 8.357 1.73 9.54 2.164v2.268c0 .227-.273.483-.891.799l-.001-.001h-.001z"/>
<path fill="#D82C20" d="M26.04 11.268c-1.214.634-7.504 3.22-8.843 3.918-1.34.698-2.083.691-3.141.186-1.058-.506-7.752-3.21-8.959-3.786-1.206-.577-1.23-.974-.046-1.437l9.249-3.58c1.407-.506 1.894-.524 3.092-.085 1.198.438 7.45 2.927 8.634 3.36 1.184.432 1.229.79.015 1.424h-.001z"/>
<g fill="#FFF" transform="translate(8 7)">
<path d="M8.503 3.446L7.79 2.26l-2.276-.205 1.699-.613-.51-.94 1.59.62 1.5-.49-.406.972 1.529.573-1.972.205-.44 1.064h-.001zm-3.798 2.36l5.273-.81-1.593 2.336-3.68-1.526z"/>
<ellipse cx="3.284" cy="3.862" rx="2.818" ry="1.092"/>
</g>
<path fill="#7A0C00" d="M23.987 10.577l-3.119 1.232-.002-2.466z"/>
<path fill="#AD2115" d="M20.869 11.809l-.338.132-3.116-1.231 3.452-1.366z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 864 KiB