New constraints management.

Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
This commit is contained in:
Ludovic Fernandez 2019-06-21 09:24:04 +02:00 committed by Traefiker Bot
parent e9792b446f
commit fe68e9e243
40 changed files with 658 additions and 630 deletions

15
Gopkg.lock generated
View file

@ -1415,14 +1415,6 @@
pruneopts = "NUT"
revision = "1f30fe9094a513ce4c700b9a54458bbb0c96996c"
[[projects]]
branch = "master"
digest = "1:09d61699d553a4e6ec998ad29816177b1f3d3ed0c18fe923d2c174ec065c99c8"
name = "github.com/ryanuber/go-glob"
packages = ["."]
pruneopts = "NUT"
revision = "256dc444b735e061061cf46c809487313d5b0065"
[[projects]]
digest = "1:253f275bd72c42f8d234712d1574c8b222fe9b72838bfaca11b21ace9c0e3d0a"
name = "github.com/sacloud/libsacloud"
@ -1598,12 +1590,12 @@
revision = "3d629cff40b7040e0519628e7774ed11a95d9aff"
[[projects]]
digest = "1:ca6bac407fedc14fbeeba861dd33a821ba3a1624c10126ec6003b0a28d4139c5"
digest = "1:b9d8cc221fb40078c7eb78d73b1702b5b548511b3d62bbd56b2f8180089c79af"
name = "github.com/vulcand/predicate"
packages = ["."]
pruneopts = "NUT"
revision = "939c094524d124c55fa8afe0e077701db4a865e2"
version = "v1.0.0"
revision = "8fbfb3ab0e94276b6b58bec378600829adc7a203"
version = "v1.1.0"
[[projects]]
branch = "master"
@ -2289,7 +2281,6 @@
"github.com/prometheus/client_golang/prometheus/promhttp",
"github.com/prometheus/client_model/go",
"github.com/rancher/go-rancher-metadata/metadata",
"github.com/ryanuber/go-glob",
"github.com/sirupsen/logrus",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/mock",

View file

@ -146,10 +146,6 @@ required = [
name = "github.com/rancher/go-rancher-metadata"
source = "github.com/containous/go-rancher-metadata"
[[constraint]]
branch = "master"
name = "github.com/ryanuber/go-glob"
[[constraint]]
name = "github.com/Masterminds/sprig"
version = "2.19.0"

View file

@ -10,10 +10,10 @@ Configuration in Traefik can refer to two different things:
- The fully dynamic routing configuration (referred to as the _dynamic configuration_)
- The startup configuration (referred to as the _static configuration_)
Elements in the _static configuration_ set up connections to [providers](../../providers/overview/) and define the [entrypoints](../../routing/entrypoints/) Traefik will listen to (these elements don't change often).
Elements in the _static configuration_ set up connections to [providers](../providers/overview.md) and define the [entrypoints](../routing/entrypoints.md) Traefik will listen to (these elements don't change often).
The _dynamic configuration_ contains everything that defines how the requests are handled by your system.
This configuration can change and is seamlessly hot-reloaded, without any request interuption or connection loss.
This configuration can change and is seamlessly hot-reloaded, without any request interruption or connection loss.
!!! warning "Incompatible Configuration"
Please be aware that the old configurations for Traefik v1.X are NOT compatible with the v2.X config as of now.

View file

@ -26,7 +26,7 @@ traefik [--flag=flag_argument] [-f [flag_argument]]
traefik [--flag[=true|false| ]] [-f [true|false| ]]
```
### healthcheck
### `healthcheck`
Calls Traefik `/ping` to check the health of Traefik.
Its exit status is `0` if Traefik is healthy and `1` otherwise.
@ -50,12 +50,12 @@ $ traefik healthcheck
OK: http://:8082/ping
```
### version
### `version`
Shows the current Traefik version.
Usage:
```bash
traefik version [command] [flags] [arguments]
traefik version
```

View file

@ -37,7 +37,7 @@ Attach labels to your containers and let Traefik do the rest!
Enabling the docker provider (Swarm Mode)
```toml
[docker]
[providers.docker]
# swarm classic (1.12-)
# endpoint = "tcp://127.0.0.1:2375"
# docker swarm mode (1.12+)
@ -193,8 +193,8 @@ The container service name can be accessed as the `Name` identifier,
and the template has access to all the labels defined on this container.
```toml tab="File"
[docker]
defaultRule = ""
[providers.docker]
defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
# ...
```
@ -215,6 +215,48 @@ _Optional, Default=15_
Defines the polling interval (in seconds) in Swarm Mode.
### `constraints`
_Optional, Default=""_
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
That is to say, if none of the container's labels match the expression, no route for the container is created.
If the expression is empty, all detected containers are included.
The expression syntax is based on the `Label("key", "value")`, and `LabelRegexp("key", "value")` functions, as well as the usual boolean logic, as shown in examples below.
??? example "Constraints Expression Examples"
```toml
# Includes only containers having a label with key `a.label.name` and value `foo`
constraints = "Label(`a.label.name`, `foo`)"
```
```toml
# Excludes containers having any label with key `a.label.name` and value `foo`
constraints = "!Label(`a.label.name`, `value`)"
```
```toml
# With logical AND.
constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)"
```
```toml
# With logical OR.
constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)"
```
```toml
# With logical AND and OR, with precedence set by parentheses.
constraints = "Label(`a.label.name`, `valueA`) && (Label(`another.label.name`, `valueB`) || Label(`yet.another.label.name`, `valueC`))"
```
```toml
# Includes only containers having a label with key `a.label.name` and a value matching the `a.+` regular expression.
constraints = "LabelRegexp(`a.label.name`, `a.+`)"
```
## Routing Configuration Options
### General
@ -286,10 +328,6 @@ You can tell Traefik to consider (or not) the container by setting `traefik.enab
This option overrides the value of `exposedByDefault`.
#### `traefik.tags`
Sets the tags for [constraints filtering](./overview.md#constraints-configuration).
#### `traefik.docker.network`
Overrides the default docker network to use for connections to the container.

View file

@ -78,7 +78,7 @@ DCOSToken for DCOS environment.
If set, it overrides the Authorization header.
```toml tab="File"
[marathon]
[providers.marathon]
dcosToken = "xxxxxx"
# ...
```
@ -101,8 +101,8 @@ The app ID can be accessed as the Name identifier,
and the template has access to all the labels defined on this Marathon application.
```toml tab="File"
[marathon]
defaultRule = ""
[providers.marathon]
defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
# ...
```
@ -132,7 +132,7 @@ Marathon server endpoint.
You can optionally specify multiple endpoints:
```toml tab="File"
[marathon]
[providers.marathon]
endpoint = "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"
# ...
```
@ -150,16 +150,59 @@ Exposes Marathon applications by default through Traefik.
If set to false, applications that don't have a `traefik.enable=true` label will be ignored from the resulting routing configuration.
### `filterMarathonConstraints`
### `constraints`
_Optional, Default=false_
_Optional, Default=""_
Enables filtering using Marathon constraints.
Constraints is an expression that Traefik matches against the application's labels to determine whether to create any route for that application.
That is to say, if none of the application's labels match the expression, no route for the application is created.
In addition, the expression also matched against the application's constraints, such as described in [Marathon constraints](https://mesosphere.github.io/marathon/docs/constraints.html).
If the expression is empty, all detected applications are included.
If enabled, Traefik will take into account Marathon constraints, as defined in [Marathon constraints](https://mesosphere.github.io/marathon/docs/constraints.html).
The expression syntax is based on the `Label("key", "value")`, and `LabelRegexp("key", "value")`, as well as the usual boolean logic.
In addition, to match against marathon constraints, the function `MarathonConstraint("field:operator:value")` can be used, where the field, operator, and value parts are joined together in a single string with the `:` separator.
Each individual constraint will be treated as a verbatim compounded tag,
e.g. "rack_id:CLUSTER:rack-1", with all constraint groups concatenated together using ":".
??? example "Constraints Expression Examples"
```toml
# Includes only applications having a label with key `a.label.name` and value `foo`
constraints = "Label(`a.label.name`, `foo`)"
```
```toml
# Excludes applications having any label with key `a.label.name` and value `foo`
constraints = "!Label(`a.label.name`, `value`)"
```
```toml
# With logical AND.
constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)"
```
```toml
# With logical OR.
constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)"
```
```toml
# With logical AND and OR, with precedence set by parentheses.
constraints = "Label(`a.label.name`, `valueA`) && (Label(`another.label.name`, `valueB`) || Label(`yet.another.label.name`, `valueC`))"
```
```toml
# Includes only applications having a label with key `a.label.name` and a value matching the `a.+` regular expression.
constraints = "LabelRegexp(`a.label.name`, `a.+`)"
```
```toml
# Includes only applications having a Marathon constraint with field `A`, operator `B`, and value `C`.
constraints = "MarathonConstraint(`A:B:C`)"
```
```toml
# Uses both Marathon constraint and application label with logical operator.
constraints = "MarathonConstraint(`A:B:C`) && Label(`a.label.name`, `value`)"
```
### `forceTaskHostname`
@ -318,10 +361,6 @@ You can declare TCP Routers and/or Services using labels.
Setting this option controls whether Traefik exposes the application.
It overrides the value of `exposedByDefault`.
#### `traefik.tags`
Sets the tags for [constraints filtering](./overview.md#constraints-configuration).
#### `traefik.marathon.ipadressidx`
If a task has several IP addresses, this option specifies which one, in the list of available addresses, to select.

View file

@ -26,74 +26,46 @@ Even if each provider is different, we can categorize them in four groups:
Below is the list of the currently supported providers in Traefik.
| Provider | Type | Configuration Type |
|---------------------------------|--------------|--------------------|
| [Docker](./docker.md) | Orchestrator | Label |
| [File](./file.md) | Orchestrator | Custom Annotation |
| [Kubernetes](kubernetes-crd.md) | Orchestrator | Custom Resource |
| [Marathon](marathon.md) | Orchestrator | Label |
| Provider | Type | Configuration Type |
|-----------------------------------|--------------|--------------------|
| [Docker](./docker.md) | Orchestrator | Label |
| [Kubernetes](./kubernetes-crd.md) | Orchestrator | Custom Resource |
| [Marathon](./marathon.md) | Orchestrator | Label |
| [Rancher](./rancher.md) | Orchestrator | Label |
| [File](./file.md) | Manual | TOML format |
!!! note "More Providers"
The current version of Traefik is in development and doesn't support (yet) every provider. See the previous version (1.7) for more providers.
The current version of Traefik is in development and doesn't support (yet) every provider.
See the previous version (1.7) for more providers.
<!--
TODO (document TCP VS HTTP dynamic configuration)
-->
## Constraints Configuration
TODO (document TCP VS HTTP dynamic configuration)
-->
If you want to limit the scope of Traefik's service discovery, you can set constraints.
Doing so, Traefik will create routes for containers that match these constraints only.
## Restrict the Scope of Service Discovery
??? example "Containers with the api Tag"
By default Traefik will create routes for all detected containers.
```toml
constraints = ["tag==api"]
```
If you want to limit the scope of Traefik's service discovery,
i.e. disallow route creation for some containers,
you can do so in two different ways:
either with the generic configuration option `exposedByDefault`,
or with a finer granularity mechanism based on constraints.
??? example "Containers without the api Tag"
### `exposedByDefault` and `traefik.enable`
```toml
constraints = ["tag!=api"]
```
??? example "Containers with tags starting with 'us-'"
List of providers that support that feature:
```toml
constraints = ["tag==us-*"]
```
- [Docker](./docker.md#exposedbydefault)
- [Rancher](./rancher.md#exposedbydefault)
- [Marathon](./marathon.md#exposedbydefault)
??? example "Multiple constraints"
### Constraints
```toml
# Multiple constraints
# - "tag==" must match with at least one tag
# - "tag!=" must match with none of tags
constraints = ["tag!=us-*", "tag!=asia-*"]
```
List of providers that support constraints:
??? note "List of Providers that Support Constraints"
- Docker
- Consul K/V
- BoltDB
- Zookeeper
- ECS
- Etcd
- Consul Catalog
- Rancher
- Marathon
- Kubernetes (using a provider-specific mechanism based on label selectors)
!!! note
The constraint option belongs to the provider configuration itself.
??? example "Setting the Constraint Options for Docker"
```toml
[providers]
[providers.docker]
constraints = ["tag==api"]
```
- [Docker](./docker.md#constraints)
- [Rancher](./rancher.md#constraints)
- [Marathon](./marathon.md#constraints)
- [Kubernetes CRD](./kubernetes-crd.md#labelselector)

View file

@ -19,64 +19,23 @@ Attach labels to your services and let Traefik do the rest!
Enabling the rancher provider
```toml
[provider.rancher]
[Providers.Rancher]
```
Attaching labels to services
```yaml
labels:
- traefik.http.services.my-service.rule=Host(my-domain)
- traefik.http.services.my-service.rule=Host(`my-domain`)
```
## Provider Configuration Options
!!! tip "Browse the Reference"
??? tip "Browse the Reference"
If you're in a hurry, maybe you'd rather go through the configuration reference:
```toml
################################################################
# Rancher Provider
################################################################
# Enable Rancher Provider.
[rancher]
# Expose Rancher services by default in Traefik.
#
# Optional
#
ExposedByDefault = "true"
# Enable watch Rancher changes.
#
# Optional
#
watch = true
# Filter services with unhealthy states and inactive states.
#
# Optional
#
EnableServiceHealthFilter = true
# Defines the polling interval (in seconds).
#
# Optional
#
RefreshSeconds = true
# Poll the Rancher metadata service for changes every `rancher.refreshSeconds`, which is less accurate
#
# Optional
#
IntervalPoll = false
# Prefix used for accessing the Rancher metadata service
#
# Optional
#
Prefix = 15
--8<-- "content/providers/rancher.toml"
```
### `ExposedByDefault`
@ -99,8 +58,8 @@ The service name can be accessed as the `Name` identifier,
and the template has access to all the labels defined on this container.
```toml tab="File"
[rancher]
defaultRule = ""
[Providers.Rancher]
defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
# ...
```
@ -136,6 +95,50 @@ _Optional, Default=/latest_
Prefix used for accessing the Rancher metadata service
### `constraints`
_Optional, Default=""_
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
That is to say, if none of the container's labels match the expression, no route for the container is created.
If the expression is empty, all detected containers are included.
The expression syntax is based on the `Label("key", "value")`, and `LabelRegexp("key", "value")` functions, as well as the usual boolean logic, as shown in examples below.
??? example "Constraints Expression Examples"
```toml
# Includes only containers having a label with key `a.label.name` and value `foo`
constraints = "Label(`a.label.name`, `foo`)"
```
```toml
# Excludes containers having any label with key `a.label.name` and value `foo`
constraints = "!Label(`a.label.name`, `value`)"
```
```toml
# With logical AND.
constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)"
```
```toml
# With logical OR.
constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)"
```
```toml
# With logical AND and OR, with precedence set by parentheses.
constraints = "Label(`a.label.name`, `valueA`) && (Label(`another.label.name`, `valueB`) || Label(`yet.another.label.name`, `valueC`))"
```
```toml
# Includes only containers having a label with key `a.label.name` and a value matching the `a.+` regular expression.
constraints = "LabelRegexp(`a.label.name`, `a.+`)"
```
## Routing Configuration Options
### General
Traefik creates, for each rancher service, a corresponding [service](../routing/services/index.md) and [router](../routing/routers/index.md).
@ -185,10 +188,6 @@ You can tell Traefik to consider (or not) the container by setting `traefik.enab
This option overrides the value of `exposedByDefault`.
#### `traefik.tags`
Sets the tags for [constraints filtering](./overview.md#constraints-configuration).
#### Port Lookup
Traefik is now capable of detecting the port to use, by following the default rancher flow.

View file

@ -0,0 +1,20 @@
# Enable Rancher Provider.
[Providers.Rancher]
# Expose Rancher services by default in Traefik.
ExposedByDefault = true
# Enable watch Rancher changes.
Watch = true
# Filter services with unhealthy states and inactive states.
EnableServiceHealthFilter = true
# Defines the polling interval (in seconds).
RefreshSeconds = true
# Poll the Rancher metadata service for changes every `rancher.refreshSeconds`, which is less accurate
IntervalPoll = false
# Prefix used for accessing the Rancher metadata service
Prefix = "/latest"

View file

@ -249,17 +249,8 @@
Enable Docker backend with default settings.
--providers.docker.constraints (Default: "")
Filter services by constraint, matching with Traefik tags.
--providers.docker.constraints[n].key (Default: "")
The provider label that will be matched against. In practice, it is always
'tag'.
--providers.docker.constraints[n].mustmatch (Default: "false")
Whether the matching operator is equals or not equals.
--providers.docker.constraints[n].value (Default: "")
The value that will be matched against.
Constraints is an expression that Traefik matches against the container's labels
to determine whether to create any route for that container.
--providers.docker.defaultrule (Default: "Host(`{{ normalize .Name }}`)")
Default rule.
@ -382,17 +373,8 @@
Basic authentication Password.
--providers.marathon.constraints (Default: "")
Filter services by constraint, matching with Traefik tags.
--providers.marathon.constraints[n].key (Default: "")
The provider label that will be matched against. In practice, it is always
'tag'.
--providers.marathon.constraints[n].mustmatch (Default: "false")
Whether the matching operator is equals or not equals.
--providers.marathon.constraints[n].value (Default: "")
The value that will be matched against.
Constraints is an expression that Traefik matches against the application's
labels to determine whether to create any route for that application.
--providers.marathon.dcostoken (Default: "")
DCOSToken for DCOS environment, This will override the Authorization header.
@ -409,9 +391,6 @@
--providers.marathon.exposedbydefault (Default: "true")
Expose Marathon apps by default.
--providers.marathon.filtermarathonconstraints (Default: "false")
Enable use of Marathon constraints in constraint filtering.
--providers.marathon.forcetaskhostname (Default: "false")
Force to use the task's hostname.
@ -457,17 +436,8 @@
Enable Rancher backend with default settings.
--providers.rancher.constraints (Default: "")
Filter services by constraint, matching with Traefik tags.
--providers.rancher.constraints[n].key (Default: "")
The provider label that will be matched against. In practice, it is always
'tag'.
--providers.rancher.constraints[n].mustmatch (Default: "false")
Whether the matching operator is equals or not equals.
--providers.rancher.constraints[n].value (Default: "")
The value that will be matched against.
Constraints is an expression that Traefik matches against the container's labels
to determine whether to create any route for that container.
--providers.rancher.defaultrule (Default: "Host(`{{ normalize .Name }}`)")
Default rule.

View file

@ -241,16 +241,7 @@ Middleware list.
Enable Docker backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS`:
Filter services by constraint, matching with Traefik tags.
`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS[n]_KEY`:
The provider label that will be matched against. In practice, it is always 'tag'.
`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS[n]_MUSTMATCH`:
Whether the matching operator is equals or not equals. (Default: ```false```)
`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS[n]_VALUE`:
The value that will be matched against.
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
`TRAEFIK_PROVIDERS_DOCKER_DEFAULTRULE`:
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
@ -373,16 +364,7 @@ Basic authentication User.
Basic authentication Password.
`TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS`:
Filter services by constraint, matching with Traefik tags.
`TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS[n]_KEY`:
The provider label that will be matched against. In practice, it is always 'tag'.
`TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS[n]_MUSTMATCH`:
Whether the matching operator is equals or not equals. (Default: ```false```)
`TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS[n]_VALUE`:
The value that will be matched against.
Constraints is an expression that Traefik matches against the application's labels to determine whether to create any route for that application.
`TRAEFIK_PROVIDERS_MARATHON_DCOSTOKEN`:
DCOSToken for DCOS environment, This will override the Authorization header.
@ -399,9 +381,6 @@ Marathon server endpoint. You can also specify multiple endpoint for Marathon. (
`TRAEFIK_PROVIDERS_MARATHON_EXPOSEDBYDEFAULT`:
Expose Marathon apps by default. (Default: ```true```)
`TRAEFIK_PROVIDERS_MARATHON_FILTERMARATHONCONSTRAINTS`:
Enable use of Marathon constraints in constraint filtering. (Default: ```false```)
`TRAEFIK_PROVIDERS_MARATHON_FORCETASKHOSTNAME`:
Force to use the task's hostname. (Default: ```false```)
@ -445,16 +424,7 @@ Backends throttle duration: minimum duration between 2 events from providers bef
Enable Rancher backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS`:
Filter services by constraint, matching with Traefik tags.
`TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS[n]_KEY`:
The provider label that will be matched against. In practice, it is always 'tag'.
`TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS[n]_MUSTMATCH`:
Whether the matching operator is equals or not equals. (Default: ```false```)
`TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS[n]_VALUE`:
The value that will be matched against.
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
`TRAEFIK_PROVIDERS_RANCHER_DEFAULTRULE`:
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)

View file

@ -41,16 +41,7 @@
SwarmMode = true
Network = "foobar"
SwarmModeRefreshSeconds = 42
[[Providers.Docker.Constraints]]
Key = "foobar"
MustMatch = true
Regex = "foobar"
[[Providers.Docker.Constraints]]
Key = "foobar"
MustMatch = true
Regex = "foobar"
Constraints = "foobar"
[Providers.Docker.TLS]
CA = "foobar"
@ -73,23 +64,13 @@
DefaultRule = "foobar"
ExposedByDefault = true
DCOSToken = "foobar"
FilterMarathonConstraints = true
DialerTimeout = 42
ResponseHeaderTimeout = 42
TLSHandshakeTimeout = 42
KeepAlive = 42
ForceTaskHostname = true
RespectReadinessChecks = true
[[Providers.Marathon.Constraints]]
Key = "foobar"
MustMatch = true
Regex = "foobar"
[[Providers.Marathon.Constraints]]
Key = "foobar"
MustMatch = true
Regex = "foobar"
Constraints = "foobar"
[Providers.Marathon.TLS]
CA = "foobar"
@ -134,16 +115,7 @@
RefreshSeconds = 42
IntervalPoll = true
Prefix = "foobar"
[[Providers.Rancher.Constraints]]
Key = "foobar"
MustMatch = true
Regex = "foobar"
[[Providers.Rancher.Constraints]]
Key = "foobar"
MustMatch = true
Regex = "foobar"
Constraints = "foobar"
[API]
EntryPoint = "foobar"

View file

@ -7,7 +7,6 @@ import (
"github.com/containous/traefik/pkg/config/static"
"github.com/containous/traefik/pkg/ping"
"github.com/containous/traefik/pkg/provider"
"github.com/containous/traefik/pkg/provider/acme"
acmeprovider "github.com/containous/traefik/pkg/provider/acme"
"github.com/containous/traefik/pkg/provider/docker"
@ -153,20 +152,7 @@ func TestDo_globalConfiguration(t *testing.T) {
}
config.Providers.Docker = &docker.Provider{
Constrainer: provider.Constrainer{
Constraints: []*types.Constraint{
{
Key: "file Constraints Key 1",
Value: "file Constraints Regex 2",
MustMatch: true,
},
{
Key: "file Constraints Key 1",
Value: "file Constraints Regex 2",
MustMatch: true,
},
},
},
Constraints: `Label("foo", "bar")`,
Watch: true,
Endpoint: "MyEndPoint",
DefaultRule: "PathPrefix(`/`)",

View file

@ -248,7 +248,6 @@ func Test_decodeFileToNode_Toml(t *testing.T) {
{Name: "DialerTimeout", Value: "42"},
{Name: "Endpoint", Value: "foobar"},
{Name: "ExposedByDefault", Value: "true"},
{Name: "FilterMarathonConstraints", Value: "true"},
{Name: "ForceTaskHostname", Value: "true"},
{Name: "KeepAlive", Value: "42"},
{Name: "RespectReadinessChecks", Value: "true"},
@ -518,7 +517,6 @@ func Test_decodeFileToNode_Yaml(t *testing.T) {
{Name: "DialerTimeout", Value: "42"},
{Name: "Endpoint", Value: "foobar"},
{Name: "ExposedByDefault", Value: "true"},
{Name: "FilterMarathonConstraints", Value: "true"},
{Name: "ForceTaskHostname", Value: "true"},
{Name: "KeepAlive", Value: "42"},
{Name: "RespectReadinessChecks", Value: "true"},

View file

@ -74,7 +74,6 @@
DefaultRule = "foobar"
ExposedByDefault = true
DCOSToken = "foobar"
FilterMarathonConstraints = true
DialerTimeout = 42
ResponseHeaderTimeout = 42
TLSHandshakeTimeout = 42

View file

@ -69,7 +69,6 @@ Providers:
DefaultRule: foobar
ExposedByDefault: true
DCOSToken: foobar
FilterMarathonConstraints: true
DialerTimeout: 42
ResponseHeaderTimeout: 42
TLSHandshakeTimeout: 42

View file

@ -1,27 +0,0 @@
package provider
import "github.com/containous/traefik/pkg/types"
// Constrainer Filter services by constraint, matching with Traefik tags.
type Constrainer struct {
Constraints []*types.Constraint `description:"Filter services by constraint, matching with Traefik tags." export:"true"`
}
// MatchConstraints must match with EVERY single constraint
// returns first constraint that do not match or nil.
func (c *Constrainer) MatchConstraints(tags []string) (bool, *types.Constraint) {
// if there is no tags and no constraints, filtering is disabled
if len(tags) == 0 && len(c.Constraints) == 0 {
return true, nil
}
for _, constraint := range c.Constraints {
// xor: if ok and constraint.MustMatch are equal, then no tag is currently matching with the constraint
if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); ok != constraint.MustMatch {
return false, constraint
}
}
// If no constraint or every constraints matching
return true, nil
}

View file

@ -0,0 +1,100 @@
package constraints
import (
"errors"
"regexp"
"strings"
"github.com/vulcand/predicate"
)
// MarathonConstraintPrefix is the prefix for each label's key created from a Marathon application constraint.
// It is used in order to create a specific and unique pattern for these labels.
const MarathonConstraintPrefix = "Traefik-Marathon-505F9E15-BDC7-45E7-828D-C06C7BAB8091"
type constraintFunc func(map[string]string) bool
// Match reports whether the expression matches with the given labels.
// The expression must match any logical boolean combination of:
// - `Label(labelName, labelValue)`
// - `LabelRegex(labelName, regexValue)`
// - `MarathonConstraint(field:operator:value)`
func Match(labels map[string]string, expr string) (bool, error) {
if expr == "" {
return true, nil
}
p, err := predicate.NewParser(predicate.Def{
Operators: predicate.Operators{
AND: andFunc,
NOT: notFunc,
OR: orFunc,
},
Functions: map[string]interface{}{
"Label": labelFn,
"LabelRegex": labelRegexFn,
"MarathonConstraint": marathonFn,
},
})
if err != nil {
return false, err
}
parse, err := p.Parse(expr)
if err != nil {
return false, err
}
fn, ok := parse.(constraintFunc)
if !ok {
return false, errors.New("not a constraintFunc")
}
return fn(labels), nil
}
func labelFn(name, value string) constraintFunc {
return func(labels map[string]string) bool {
return labels[name] == value
}
}
func labelRegexFn(name, expr string) constraintFunc {
return func(labels map[string]string) bool {
matched, err := regexp.MatchString(expr, labels[name])
if err != nil {
return false
}
return matched
}
}
func marathonFn(value string) constraintFunc {
return func(labels map[string]string) bool {
for k, v := range labels {
if strings.HasPrefix(k, MarathonConstraintPrefix) {
if v == value {
return true
}
}
}
return false
}
}
func andFunc(a, b constraintFunc) constraintFunc {
return func(labels map[string]string) bool {
return a(labels) && b(labels)
}
}
func orFunc(a, b constraintFunc) constraintFunc {
return func(labels map[string]string) bool {
return a(labels) || b(labels)
}
}
func notFunc(a constraintFunc) constraintFunc {
return func(labels map[string]string) bool {
return !a(labels)
}
}

View file

@ -0,0 +1,204 @@
package constraints
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMatch(t *testing.T) {
testCases := []struct {
expr string
labels map[string]string
expected bool
expectedErr bool
}{
{
expr: `Label("hello", "world")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: true,
},
{
expr: `Label("hello", "worlds")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `Label("hi", "world")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `!Label("hello", "world")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `Label("hello", "world") && Label("foo", "bar")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: true,
},
{
expr: `Label("hello", "worlds") && Label("foo", "bar")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `Label("hello", "world") && !Label("foo", "bar")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `Label("hello", "world") || Label("foo", "bar")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: true,
},
{
expr: `Label("hello", "worlds") || Label("foo", "bar")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: true,
},
{
expr: `Label("hello", "world") || !Label("foo", "bar")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: true,
},
{
expr: `Label("hello")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expectedErr: true,
},
{
expr: `Foo("hello")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expectedErr: true,
},
{
expr: `Label("hello", "bar")`,
expected: false,
},
{
expr: ``,
expected: true,
},
{
expr: `MarathonConstraint("bar")`,
labels: map[string]string{
"hello": "world",
MarathonConstraintPrefix + "-1": "bar",
MarathonConstraintPrefix + "-2": "foo",
},
expected: true,
},
{
expr: `MarathonConstraint("bur")`,
labels: map[string]string{
"hello": "world",
MarathonConstraintPrefix + "-1": "bar",
MarathonConstraintPrefix + "-2": "foo",
},
expected: false,
},
{
expr: `Label("hello", "world") && MarathonConstraint("bar")`,
labels: map[string]string{
"hello": "world",
MarathonConstraintPrefix + "-1": "bar",
MarathonConstraintPrefix + "-2": "foo",
},
expected: true,
},
{
expr: `LabelRegex("hello", "w\\w+")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: true,
},
{
expr: `LabelRegex("hello", "w\\w+s")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `LabelRegex("hi", "w\\w+")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `!LabelRegex("hello", "w\\w+")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `LabelRegex("hello", "w(\\w+")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.expr, func(t *testing.T) {
t.Parallel()
matches, err := Match(test.labels, test.expr)
if test.expectedErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, test.expected, matches)
})
}
}

View file

@ -11,6 +11,7 @@ import (
"github.com/containous/traefik/pkg/config/label"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/provider"
"github.com/containous/traefik/pkg/provider/constraints"
"github.com/docker/go-connections/nat"
)
@ -123,10 +124,13 @@ func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool
return false
}
if ok, failingConstraint := p.MatchConstraints(container.ExtraConf.Tags); !ok {
if failingConstraint != nil {
logger.Debugf("Container pruned by %q constraint", failingConstraint.String())
}
matches, err := constraints.Match(container.Labels, p.Constraints)
if err != nil {
logger.Error("Error matching constraints expression: %v", err)
return false
}
if !matches {
logger.Debugf("Container pruned by constraint expression: %q", p.Constraints)
return false
}

View file

@ -6,7 +6,6 @@ import (
"testing"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/types"
docker "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/go-connections/nat"
@ -339,7 +338,7 @@ func Test_buildConfiguration(t *testing.T) {
testCases := []struct {
desc string
containers []dockerData
constraints []*types.Constraint
constraints string
expected *config.Configuration
}{
{
@ -1924,13 +1923,7 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Value: "bar",
},
},
constraints: `Label("traefik.tags", "bar")`,
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
@ -1965,13 +1958,7 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Value: "foo",
},
},
constraints: `Label("traefik.tags", "foo")`,
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},

View file

@ -45,7 +45,7 @@ var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
provider.Constrainer `description:"List of constraints used to filter out some containers." export:"true"`
Constraints string `description:"Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container." export:"true"`
Watch bool `description:"Watch provider." export:"true"`
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint."`
DefaultRule string `description:"Default rule."`

View file

@ -14,7 +14,6 @@ const (
// configuration Contains information from the labels that are globals (not related to the dynamic configuration) or specific to the provider.
type configuration struct {
Enable bool
Tags []string
Docker specificConfiguration
}
@ -31,7 +30,7 @@ func (p *Provider) getConfiguration(container dockerData) (configuration, error)
},
}
err := label.Decode(container.Labels, &conf, "traefik.docker.", "traefik.enable", "traefik.tags")
err := label.Decode(container.Labels, &conf, "traefik.docker.", "traefik.enable")
if err != nil {
return configuration{}, err
}

View file

@ -13,6 +13,7 @@ import (
"github.com/containous/traefik/pkg/config/label"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/provider"
"github.com/containous/traefik/pkg/provider/constraints"
"github.com/gambol99/go-marathon"
)
@ -29,11 +30,20 @@ func (p *Provider) buildConfiguration(ctx context.Context, applications *maratho
continue
}
if !p.keepApplication(ctxApp, extraConf) {
labels := stringValueMap(app.Labels)
if app.Constraints != nil {
for i, constraintParts := range *app.Constraints {
key := constraints.MarathonConstraintPrefix + "-" + strconv.Itoa(i)
labels[key] = strings.Join(constraintParts, ":")
}
}
if !p.keepApplication(ctxApp, extraConf, labels) {
continue
}
confFromLabel, err := label.DecodeConfiguration(stringValueMap(app.Labels))
confFromLabel, err := label.DecodeConfiguration(labels)
if err != nil {
logger.Error(err)
continue
@ -65,7 +75,7 @@ func (p *Provider) buildConfiguration(ctx context.Context, applications *maratho
Labels map[string]string
}{
Name: app.ID,
Labels: stringValueMap(app.Labels),
Labels: labels,
}
serviceName := getServiceName(app)
@ -164,7 +174,7 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, app maratho
return nil
}
func (p *Provider) keepApplication(ctx context.Context, extraConf configuration) bool {
func (p *Provider) keepApplication(ctx context.Context, extraConf configuration, labels map[string]string) bool {
logger := log.FromContext(ctx)
// Filter disabled application.
@ -174,10 +184,13 @@ func (p *Provider) keepApplication(ctx context.Context, extraConf configuration)
}
// Filter by constraints.
if ok, failingConstraint := p.MatchConstraints(extraConf.Tags); !ok {
if failingConstraint != nil {
logger.Debugf("Filtering Marathon application, pruned by %q constraint", failingConstraint.String())
}
matches, err := constraints.Match(labels, p.Constraints)
if err != nil {
logger.Error("Error matching constraints expression: %v", err)
return false
}
if !matches {
logger.Debugf("Marathon application filtered by constraint expression: %q", p.Constraints)
return false
}

View file

@ -6,7 +6,6 @@ import (
"testing"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/types"
"github.com/gambol99/go-marathon"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -29,12 +28,11 @@ func TestGetConfigurationAPIErrors(t *testing.T) {
func TestBuildConfiguration(t *testing.T) {
testCases := []struct {
desc string
applications *marathon.Applications
constraints []*types.Constraint
filterMarathonConstraints bool
defaultRule string
expected *config.Configuration
desc string
applications *marathon.Applications
constraints string
defaultRule string
expected *config.Configuration
}{
{
desc: "simple application",
@ -1065,13 +1063,7 @@ func TestBuildConfiguration(t *testing.T) {
withTasks(localhostTask(taskPorts(80, 81))),
withLabel("traefik.tags", "foo"),
)),
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Value: "bar",
},
},
constraints: `Label("traefik.tags", "bar")`,
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
@ -1093,14 +1085,7 @@ func TestBuildConfiguration(t *testing.T) {
withTasks(localhostTask(taskPorts(80, 81))),
constraint("rack_id:CLUSTER:rack-1"),
)),
filterMarathonConstraints: true,
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Value: "rack_id:CLUSTER:rack-2",
},
},
constraints: `MarathonConstraint("rack_id:CLUSTER:rack-2")`,
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
@ -1122,14 +1107,7 @@ func TestBuildConfiguration(t *testing.T) {
withTasks(localhostTask(taskPorts(80, 81))),
constraint("rack_id:CLUSTER:rack-1"),
)),
filterMarathonConstraints: true,
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Value: "rack_id:CLUSTER:rack-1",
},
},
constraints: `MarathonConstraint("rack_id:CLUSTER:rack-1")`,
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
@ -1167,14 +1145,7 @@ func TestBuildConfiguration(t *testing.T) {
withTasks(localhostTask(taskPorts(80, 81))),
withLabel("traefik.tags", "bar"),
)),
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Value: "bar",
},
},
constraints: `Label("traefik.tags", "bar")`,
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
@ -1417,9 +1388,8 @@ func TestBuildConfiguration(t *testing.T) {
}
p := &Provider{
DefaultRule: defaultRule,
ExposedByDefault: true,
FilterMarathonConstraints: test.filterMarathonConstraints,
DefaultRule: defaultRule,
ExposedByDefault: true,
}
p.Constraints = test.constraints
@ -1473,7 +1443,7 @@ func TestApplicationFilterEnabled(t *testing.T) {
extraConf, err := provider.getConfiguration(app)
require.NoError(t, err)
if provider.keepApplication(context.Background(), extraConf) != test.expected {
if provider.keepApplication(context.Background(), extraConf, stringValueMap(app.Labels)) != test.expected {
t.Errorf("got unexpected filtering = %t", !test.expected)
}
})

View file

@ -2,7 +2,6 @@ package marathon
import (
"math"
"strings"
"github.com/containous/traefik/pkg/config/label"
"github.com/gambol99/go-marathon"
@ -10,7 +9,6 @@ import (
type configuration struct {
Enable bool
Tags []string
Marathon specificConfiguration
}
@ -23,23 +21,16 @@ func (p *Provider) getConfiguration(app marathon.Application) (configuration, er
conf := configuration{
Enable: p.ExposedByDefault,
Tags: nil,
Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32,
},
}
err := label.Decode(labels, &conf, "traefik.marathon.", "traefik.enable", "traefik.tags")
err := label.Decode(labels, &conf, "traefik.marathon.", "traefik.enable")
if err != nil {
return configuration{}, err
}
if p.FilterMarathonConstraints && app.Constraints != nil {
for _, constraintParts := range *app.Constraints {
conf.Tags = append(conf.Tags, strings.Join(constraintParts, ":"))
}
}
return conf, nil
}

View file

@ -23,12 +23,10 @@ func TestGetConfiguration(t *testing.T) {
Labels: &map[string]string{},
},
p: Provider{
ExposedByDefault: false,
FilterMarathonConstraints: false,
ExposedByDefault: false,
},
expected: configuration{
Enable: false,
Tags: nil,
Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32,
},
@ -43,12 +41,10 @@ func TestGetConfiguration(t *testing.T) {
},
},
p: Provider{
ExposedByDefault: false,
FilterMarathonConstraints: false,
ExposedByDefault: false,
},
expected: configuration{
Enable: true,
Tags: nil,
Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32,
},
@ -63,12 +59,10 @@ func TestGetConfiguration(t *testing.T) {
},
},
p: Provider{
ExposedByDefault: false,
FilterMarathonConstraints: false,
ExposedByDefault: false,
},
expected: configuration{
Enable: false,
Tags: nil,
Marathon: specificConfiguration{
IPAddressIdx: 4,
},
@ -83,14 +77,10 @@ func TestGetConfiguration(t *testing.T) {
Labels: &map[string]string{},
},
p: Provider{
ExposedByDefault: false,
FilterMarathonConstraints: true,
ExposedByDefault: false,
},
expected: configuration{
Enable: false,
Tags: []string{
"key:value",
},
Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32,
},
@ -103,12 +93,10 @@ func TestGetConfiguration(t *testing.T) {
Labels: &map[string]string{},
},
p: Provider{
ExposedByDefault: true,
FilterMarathonConstraints: false,
ExposedByDefault: true,
},
expected: configuration{
Enable: true,
Tags: nil,
Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32,
},
@ -123,32 +111,10 @@ func TestGetConfiguration(t *testing.T) {
},
},
p: Provider{
ExposedByDefault: true,
FilterMarathonConstraints: false,
ExposedByDefault: true,
},
expected: configuration{
Enable: false,
Tags: nil,
Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32,
},
},
},
{
desc: "Tags in label",
app: marathon.Application{
Constraints: &[][]string{},
Labels: &map[string]string{
"traefik.tags": "mytags",
},
},
p: Provider{
ExposedByDefault: true,
FilterMarathonConstraints: false,
},
expected: configuration{
Enable: true,
Tags: []string{"mytags"},
Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32,
},

View file

@ -45,26 +45,24 @@ var _ provider.Provider = (*Provider)(nil)
// Provider holds configuration of the provider.
type Provider struct {
provider.Constrainer `description:"List of constraints used to filter out some containers." export:"true"`
Trace bool `description:"Display additional provider logs." export:"true"`
Watch bool `description:"Watch provider." export:"true"`
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon." export:"true"`
DefaultRule string `description:"Default rule."`
ExposedByDefault bool `description:"Expose Marathon apps by default." export:"true"`
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header." export:"true"`
FilterMarathonConstraints bool `description:"Enable use of Marathon constraints in constraint filtering." export:"true"`
TLS *types.ClientTLS `description:"Enable TLS support." export:"true"`
DialerTimeout types.Duration `description:"Set a dialer timeout for Marathon." export:"true"`
ResponseHeaderTimeout types.Duration `description:"Set a response header timeout for Marathon." export:"true"`
TLSHandshakeTimeout types.Duration `description:"Set a TLS handshake timeout for Marathon." export:"true"`
KeepAlive types.Duration `description:"Set a TCP Keep Alive time." export:"true"`
ForceTaskHostname bool `description:"Force to use the task's hostname." export:"true"`
Basic *Basic `description:"Enable basic authentication." export:"true"`
RespectReadinessChecks bool `description:"Filter out tasks with non-successful readiness checks during deployments." export:"true"`
readyChecker *readinessChecker
marathonClient marathon.Marathon
defaultRuleTpl *template.Template
Constraints string `description:"Constraints is an expression that Traefik matches against the application's labels to determine whether to create any route for that application." export:"true"`
Trace bool `description:"Display additional provider logs." export:"true"`
Watch bool `description:"Watch provider." export:"true"`
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon." export:"true"`
DefaultRule string `description:"Default rule."`
ExposedByDefault bool `description:"Expose Marathon apps by default." export:"true"`
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header." export:"true"`
TLS *types.ClientTLS `description:"Enable TLS support." export:"true"`
DialerTimeout types.Duration `description:"Set a dialer timeout for Marathon." export:"true"`
ResponseHeaderTimeout types.Duration `description:"Set a response header timeout for Marathon." export:"true"`
TLSHandshakeTimeout types.Duration `description:"Set a TLS handshake timeout for Marathon." export:"true"`
KeepAlive types.Duration `description:"Set a TCP Keep Alive time." export:"true"`
ForceTaskHostname bool `description:"Force to use the task's hostname." export:"true"`
Basic *Basic `description:"Enable basic authentication." export:"true"`
RespectReadinessChecks bool `description:"Filter out tasks with non-successful readiness checks during deployments." export:"true"`
readyChecker *readinessChecker
marathonClient marathon.Marathon
defaultRuleTpl *template.Template
}
// SetDefaults sets the default values.

View file

@ -11,6 +11,7 @@ import (
"github.com/containous/traefik/pkg/config/label"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/provider"
"github.com/containous/traefik/pkg/provider/constraints"
)
func (p *Provider) buildConfiguration(ctx context.Context, services []rancherData) *config.Configuration {
@ -120,10 +121,13 @@ func (p *Provider) keepService(ctx context.Context, service rancherData) bool {
return false
}
if ok, failingConstraint := p.MatchConstraints(service.ExtraConf.Tags); !ok {
if failingConstraint != nil {
logger.Debugf("service pruned by %q constraint", failingConstraint.String())
}
matches, err := constraints.Match(service.Labels, p.Constraints)
if err != nil {
logger.Error("Error matching constraints expression: %v", err)
return false
}
if !matches {
logger.Debugf("Service pruned by constraint expression: %q", p.Constraints)
return false
}

View file

@ -5,7 +5,6 @@ import (
"testing"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -14,7 +13,7 @@ func Test_buildConfiguration(t *testing.T) {
testCases := []struct {
desc string
containers []rancherData
constraints []*types.Constraint
constraints string
expected *config.Configuration
}{
{
@ -330,13 +329,7 @@ func Test_buildConfiguration(t *testing.T) {
State: "",
},
},
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Value: "bar",
},
},
constraints: `Label("traefik.tags", "bar")`,
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
@ -363,13 +356,7 @@ func Test_buildConfiguration(t *testing.T) {
State: "",
},
},
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Value: "foo",
},
},
constraints: `Label("traefik.tags", "foo")`,
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},

View file

@ -6,7 +6,6 @@ import (
type configuration struct {
Enable bool
Tags []string
}
func (p *Provider) getConfiguration(service rancherData) (configuration, error) {
@ -14,7 +13,7 @@ func (p *Provider) getConfiguration(service rancherData) (configuration, error)
Enable: p.ExposedByDefault,
}
err := label.Decode(service.Labels, &conf, "traefik.rancher.", "traefik.enable", "traefik.tags")
err := label.Decode(service.Labels, &conf, "traefik.rancher.", "traefik.enable")
if err != nil {
return configuration{}, err
}

View file

@ -40,8 +40,7 @@ var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
provider.Constrainer `description:"List of constraints used to filter out some containers." export:"true"`
Constraints string `description:"Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container." export:"true"`
Watch bool `description:"Watch provider." export:"true"`
DefaultRule string `description:"Default rule."`
ExposedByDefault bool `description:"Expose containers by default." export:"true"`

View file

@ -25,7 +25,6 @@ type Provider struct {
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.EntryPoint = "traefik"
// FIXME p.EntryPoint = static.DefaultInternalEntryPointName
}
var templatesRenderer = render.New(render.Options{Directory: "nowhere"})

View file

@ -1,88 +0,0 @@
package types
import (
"encoding"
"errors"
"fmt"
"strings"
"github.com/ryanuber/go-glob"
)
// Constraint holds a parsed constraint expression.
// FIXME replace by a string.
type Constraint struct {
Key string `description:"The provider label that will be matched against. In practice, it is always 'tag'." export:"true"`
// MustMatch is true if operator is "==" or false if operator is "!="
MustMatch bool `description:"Whether the matching operator is equals or not equals." export:"true"`
Value string `description:"The value that will be matched against." export:"true"` // TODO: support regex
}
// NewConstraint receives a string and return a *Constraint, after checking syntax and parsing the constraint expression.
func NewConstraint(exp string) (*Constraint, error) {
sep := ""
constraint := &Constraint{}
switch {
case strings.Contains(exp, "=="):
sep = "=="
constraint.MustMatch = true
case strings.Contains(exp, "!="):
sep = "!="
constraint.MustMatch = false
default:
return nil, errors.New("constraint expression missing valid operator: '==' or '!='")
}
kv := strings.SplitN(exp, sep, 2)
if len(kv) == 2 {
// At the moment, it only supports tags
if kv[0] != "tag" {
return nil, errors.New("constraint must be tag-based. Syntax: tag==us-*")
}
constraint.Key = kv[0]
constraint.Value = kv[1]
return constraint, nil
}
return nil, fmt.Errorf("incorrect constraint expression: %s", exp)
}
func (c *Constraint) String() string {
if c.MustMatch {
return c.Key + "==" + c.Value
}
return c.Key + "!=" + c.Value
}
var _ encoding.TextUnmarshaler = (*Constraint)(nil)
// UnmarshalText defines how unmarshal in TOML parsing
func (c *Constraint) UnmarshalText(text []byte) error {
constraint, err := NewConstraint(string(text))
if err != nil {
return err
}
c.Key = constraint.Key
c.MustMatch = constraint.MustMatch
c.Value = constraint.Value
return nil
}
var _ encoding.TextMarshaler = (*Constraint)(nil)
// MarshalText encodes the receiver into UTF-8-encoded text and returns the result.
func (c *Constraint) MarshalText() (text []byte, err error) {
return []byte(c.String()), nil
}
// MatchConstraintWithAtLeastOneTag tests a constraint for one single service.
func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool {
for _, tag := range tags {
if glob.Glob(c.Value, tag) {
return true
}
}
return false
}

View file

@ -23,7 +23,6 @@ type Prometheus struct {
func (p *Prometheus) SetDefaults() {
p.Buckets = []float64{0.1, 0.3, 1.2, 5}
p.EntryPoint = "traefik"
// FIXME p.EntryPoint = static.DefaultInternalEntryPointName
}
// Datadog contains address and metrics pushing interval configuration

View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Ryan Uber
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,56 +0,0 @@
package glob
import "strings"
// The character which is treated like a glob
const GLOB = "*"
// Glob will test a string pattern, potentially containing globs, against a
// subject string. The result is a simple true/false, determining whether or
// not the glob pattern matched the subject text.
func Glob(pattern, subj string) bool {
// Empty pattern can only match empty subject
if pattern == "" {
return subj == pattern
}
// If the pattern _is_ a glob, it matches everything
if pattern == GLOB {
return true
}
parts := strings.Split(pattern, GLOB)
if len(parts) == 1 {
// No globs in pattern, so test for equality
return subj == pattern
}
leadingGlob := strings.HasPrefix(pattern, GLOB)
trailingGlob := strings.HasSuffix(pattern, GLOB)
end := len(parts) - 1
// Go over the leading parts and ensure they match.
for i := 0; i < end; i++ {
idx := strings.Index(subj, parts[i])
switch i {
case 0:
// Check the first section. Requires special handling.
if !leadingGlob && idx != 0 {
return false
}
default:
// Check that the middle parts match.
if idx < 0 {
return false
}
}
// Trim evaluated text from subj as we loop over the pattern.
subj = subj[idx+len(parts[i]):]
}
// Reached the last section. Requires special handling.
return trailingGlob || strings.HasSuffix(subj, parts[end])
}

View file

@ -119,6 +119,14 @@ func Or(a, b BoolPredicate) BoolPredicate {
}
}
// Not is a boolean predicate that calls a boolean predicate
// and returns negated result
func Not(a BoolPredicate) BoolPredicate {
return func() bool {
return !a()
}
}
// GetFieldByTag returns a field from the object based on the tag
func GetFieldByTag(ival interface{}, tagName string, fieldNames []string) (interface{}, error) {
if len(fieldNames) == 0 {

View file

@ -59,6 +59,16 @@ func (p *predicateParser) parseNode(node ast.Node) (interface{}, error) {
return callFunction(fn, arguments)
case *ast.ParenExpr:
return p.parseNode(n.X)
case *ast.UnaryExpr:
joinFn, err := p.getJoinFunction(n.Op)
if err != nil {
return nil, err
}
node, err := p.parseNode(n.X)
if err != nil {
return nil, err
}
return callFunction(joinFn, []interface{}{node})
}
return nil, trace.BadParameter("unsupported %T", node)
}
@ -122,6 +132,20 @@ func (p *predicateParser) evaluateExpr(n ast.Expr) (interface{}, error) {
return nil, trace.Wrap(err)
}
return val, nil
case *ast.CallExpr:
name, err := getIdentifier(l.Fun)
if err != nil {
return nil, err
}
fn, err := p.getFunction(name)
if err != nil {
return nil, err
}
arguments, err := p.evaluateArguments(l.Args)
if err != nil {
return nil, err
}
return callFunction(fn, arguments)
default:
return nil, trace.BadParameter("%T is not supported", n)
}
@ -161,6 +185,8 @@ func (p *predicateParser) joinPredicates(op token.Token, a, b interface{}) (inte
func (p *predicateParser) getJoinFunction(op token.Token) (interface{}, error) {
var fn interface{}
switch op {
case token.NOT:
fn = p.d.Operators.NOT
case token.LAND:
fn = p.d.Operators.AND
case token.LOR:

View file

@ -1,3 +1,20 @@
/*
Copyright 2014-2018 Vulcand Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Predicate package used to create interpreted mini languages with Go syntax - mostly to define
various predicates for configuration, e.g. Latency() > 40 || ErrorRate() > 0.5.
@ -76,6 +93,7 @@ type Operators struct {
OR interface{}
AND interface{}
NOT interface{}
}
// Parser takes the string with expression and calls the operators and functions.