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

View file

@ -146,10 +146,6 @@ required = [
name = "github.com/rancher/go-rancher-metadata" name = "github.com/rancher/go-rancher-metadata"
source = "github.com/containous/go-rancher-metadata" source = "github.com/containous/go-rancher-metadata"
[[constraint]]
branch = "master"
name = "github.com/ryanuber/go-glob"
[[constraint]] [[constraint]]
name = "github.com/Masterminds/sprig" name = "github.com/Masterminds/sprig"
version = "2.19.0" 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 fully dynamic routing configuration (referred to as the _dynamic configuration_)
- The startup configuration (referred to as the _static 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. 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" !!! 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. 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| ]] traefik [--flag[=true|false| ]] [-f [true|false| ]]
``` ```
### healthcheck ### `healthcheck`
Calls Traefik `/ping` to check the health of Traefik. Calls Traefik `/ping` to check the health of Traefik.
Its exit status is `0` if Traefik is healthy and `1` otherwise. Its exit status is `0` if Traefik is healthy and `1` otherwise.
@ -50,12 +50,12 @@ $ traefik healthcheck
OK: http://:8082/ping OK: http://:8082/ping
``` ```
### version ### `version`
Shows the current Traefik version. Shows the current Traefik version.
Usage: Usage:
```bash ```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) Enabling the docker provider (Swarm Mode)
```toml ```toml
[docker] [providers.docker]
# swarm classic (1.12-) # swarm classic (1.12-)
# endpoint = "tcp://127.0.0.1:2375" # endpoint = "tcp://127.0.0.1:2375"
# docker swarm mode (1.12+) # 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. and the template has access to all the labels defined on this container.
```toml tab="File" ```toml tab="File"
[docker] [providers.docker]
defaultRule = "" defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
# ... # ...
``` ```
@ -215,6 +215,48 @@ _Optional, Default=15_
Defines the polling interval (in seconds) in Swarm Mode. 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 ## Routing Configuration Options
### General ### 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`. This option overrides the value of `exposedByDefault`.
#### `traefik.tags`
Sets the tags for [constraints filtering](./overview.md#constraints-configuration).
#### `traefik.docker.network` #### `traefik.docker.network`
Overrides the default docker network to use for connections to the container. 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. If set, it overrides the Authorization header.
```toml tab="File" ```toml tab="File"
[marathon] [providers.marathon]
dcosToken = "xxxxxx" 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. and the template has access to all the labels defined on this Marathon application.
```toml tab="File" ```toml tab="File"
[marathon] [providers.marathon]
defaultRule = "" defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
# ... # ...
``` ```
@ -132,7 +132,7 @@ Marathon server endpoint.
You can optionally specify multiple endpoints: You can optionally specify multiple endpoints:
```toml tab="File" ```toml tab="File"
[marathon] [providers.marathon]
endpoint = "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080" 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. 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, ??? example "Constraints Expression Examples"
e.g. "rack_id:CLUSTER:rack-1", with all constraint groups concatenated together using ":".
```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` ### `forceTaskHostname`
@ -318,10 +361,6 @@ You can declare TCP Routers and/or Services using labels.
Setting this option controls whether Traefik exposes the application. Setting this option controls whether Traefik exposes the application.
It overrides the value of `exposedByDefault`. It overrides the value of `exposedByDefault`.
#### `traefik.tags`
Sets the tags for [constraints filtering](./overview.md#constraints-configuration).
#### `traefik.marathon.ipadressidx` #### `traefik.marathon.ipadressidx`
If a task has several IP addresses, this option specifies which one, in the list of available addresses, to select. 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. Below is the list of the currently supported providers in Traefik.
| Provider | Type | Configuration Type | | Provider | Type | Configuration Type |
|---------------------------------|--------------|--------------------| |-----------------------------------|--------------|--------------------|
| [Docker](./docker.md) | Orchestrator | Label | | [Docker](./docker.md) | Orchestrator | Label |
| [File](./file.md) | Orchestrator | Custom Annotation | | [Kubernetes](./kubernetes-crd.md) | Orchestrator | Custom Resource |
| [Kubernetes](kubernetes-crd.md) | Orchestrator | Custom Resource | | [Marathon](./marathon.md) | Orchestrator | Label |
| [Marathon](marathon.md) | Orchestrator | Label | | [Rancher](./rancher.md) | Orchestrator | Label |
| [File](./file.md) | Manual | TOML format |
!!! note "More Providers" !!! 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) TODO (document TCP VS HTTP dynamic configuration)
--> -->
## Constraints Configuration
If you want to limit the scope of Traefik's service discovery, you can set constraints. ## Restrict the Scope of Service Discovery
Doing so, Traefik will create routes for containers that match these constraints only.
??? example "Containers with the api Tag" By default Traefik will create routes for all detected containers.
```toml If you want to limit the scope of Traefik's service discovery,
constraints = ["tag==api"] 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 List of providers that support that feature:
constraints = ["tag!=api"]
```
??? example "Containers with tags starting with 'us-'"
```toml - [Docker](./docker.md#exposedbydefault)
constraints = ["tag==us-*"] - [Rancher](./rancher.md#exposedbydefault)
``` - [Marathon](./marathon.md#exposedbydefault)
??? example "Multiple constraints" ### Constraints
```toml List of providers that support constraints:
# Multiple constraints
# - "tag==" must match with at least one tag
# - "tag!=" must match with none of tags
constraints = ["tag!=us-*", "tag!=asia-*"]
```
??? note "List of Providers that Support Constraints" - [Docker](./docker.md#constraints)
- [Rancher](./rancher.md#constraints)
- Docker - [Marathon](./marathon.md#constraints)
- Consul K/V - [Kubernetes CRD](./kubernetes-crd.md#labelselector)
- 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"]
```

View file

@ -19,64 +19,23 @@ Attach labels to your services and let Traefik do the rest!
Enabling the rancher provider Enabling the rancher provider
```toml ```toml
[provider.rancher] [Providers.Rancher]
``` ```
Attaching labels to services Attaching labels to services
```yaml ```yaml
labels: labels:
- traefik.http.services.my-service.rule=Host(my-domain) - traefik.http.services.my-service.rule=Host(`my-domain`)
``` ```
## Provider Configuration Options ## 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: If you're in a hurry, maybe you'd rather go through the configuration reference:
```toml ```toml
################################################################ --8<-- "content/providers/rancher.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
``` ```
### `ExposedByDefault` ### `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. and the template has access to all the labels defined on this container.
```toml tab="File" ```toml tab="File"
[rancher] [Providers.Rancher]
defaultRule = "" defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
# ... # ...
``` ```
@ -136,6 +95,50 @@ _Optional, Default=/latest_
Prefix used for accessing the Rancher metadata service 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 ### General
Traefik creates, for each rancher service, a corresponding [service](../routing/services/index.md) and [router](../routing/routers/index.md). 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`. This option overrides the value of `exposedByDefault`.
#### `traefik.tags`
Sets the tags for [constraints filtering](./overview.md#constraints-configuration).
#### Port Lookup #### Port Lookup
Traefik is now capable of detecting the port to use, by following the default rancher flow. 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. Enable Docker backend with default settings.
--providers.docker.constraints (Default: "") --providers.docker.constraints (Default: "")
Filter services by constraint, matching with Traefik tags. Constraints is an expression that Traefik matches against the container's labels
to determine whether to create any route for that container.
--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.
--providers.docker.defaultrule (Default: "Host(`{{ normalize .Name }}`)") --providers.docker.defaultrule (Default: "Host(`{{ normalize .Name }}`)")
Default rule. Default rule.
@ -382,17 +373,8 @@
Basic authentication Password. Basic authentication Password.
--providers.marathon.constraints (Default: "") --providers.marathon.constraints (Default: "")
Filter services by constraint, matching with Traefik tags. Constraints is an expression that Traefik matches against the application's
labels to determine whether to create any route for that application.
--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.
--providers.marathon.dcostoken (Default: "") --providers.marathon.dcostoken (Default: "")
DCOSToken for DCOS environment, This will override the Authorization header. DCOSToken for DCOS environment, This will override the Authorization header.
@ -409,9 +391,6 @@
--providers.marathon.exposedbydefault (Default: "true") --providers.marathon.exposedbydefault (Default: "true")
Expose Marathon apps by default. Expose Marathon apps by default.
--providers.marathon.filtermarathonconstraints (Default: "false")
Enable use of Marathon constraints in constraint filtering.
--providers.marathon.forcetaskhostname (Default: "false") --providers.marathon.forcetaskhostname (Default: "false")
Force to use the task's hostname. Force to use the task's hostname.
@ -457,17 +436,8 @@
Enable Rancher backend with default settings. Enable Rancher backend with default settings.
--providers.rancher.constraints (Default: "") --providers.rancher.constraints (Default: "")
Filter services by constraint, matching with Traefik tags. Constraints is an expression that Traefik matches against the container's labels
to determine whether to create any route for that container.
--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.
--providers.rancher.defaultrule (Default: "Host(`{{ normalize .Name }}`)") --providers.rancher.defaultrule (Default: "Host(`{{ normalize .Name }}`)")
Default rule. Default rule.

View file

@ -241,16 +241,7 @@ Middleware list.
Enable Docker backend with default settings. (Default: ```false```) Enable Docker backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS`: `TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS`:
Filter services by constraint, matching with Traefik tags. 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_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.
`TRAEFIK_PROVIDERS_DOCKER_DEFAULTRULE`: `TRAEFIK_PROVIDERS_DOCKER_DEFAULTRULE`:
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```) Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
@ -373,16 +364,7 @@ Basic authentication User.
Basic authentication Password. Basic authentication Password.
`TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS`: `TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS`:
Filter services by constraint, matching with Traefik tags. 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_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.
`TRAEFIK_PROVIDERS_MARATHON_DCOSTOKEN`: `TRAEFIK_PROVIDERS_MARATHON_DCOSTOKEN`:
DCOSToken for DCOS environment, This will override the Authorization header. 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`: `TRAEFIK_PROVIDERS_MARATHON_EXPOSEDBYDEFAULT`:
Expose Marathon apps by default. (Default: ```true```) 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`: `TRAEFIK_PROVIDERS_MARATHON_FORCETASKHOSTNAME`:
Force to use the task's hostname. (Default: ```false```) 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```) Enable Rancher backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS`: `TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS`:
Filter services by constraint, matching with Traefik tags. 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_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.
`TRAEFIK_PROVIDERS_RANCHER_DEFAULTRULE`: `TRAEFIK_PROVIDERS_RANCHER_DEFAULTRULE`:
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```) Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)

View file

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

View file

@ -7,7 +7,6 @@ import (
"github.com/containous/traefik/pkg/config/static" "github.com/containous/traefik/pkg/config/static"
"github.com/containous/traefik/pkg/ping" "github.com/containous/traefik/pkg/ping"
"github.com/containous/traefik/pkg/provider"
"github.com/containous/traefik/pkg/provider/acme" "github.com/containous/traefik/pkg/provider/acme"
acmeprovider "github.com/containous/traefik/pkg/provider/acme" acmeprovider "github.com/containous/traefik/pkg/provider/acme"
"github.com/containous/traefik/pkg/provider/docker" "github.com/containous/traefik/pkg/provider/docker"
@ -153,20 +152,7 @@ func TestDo_globalConfiguration(t *testing.T) {
} }
config.Providers.Docker = &docker.Provider{ config.Providers.Docker = &docker.Provider{
Constrainer: provider.Constrainer{ Constraints: `Label("foo", "bar")`,
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,
},
},
},
Watch: true, Watch: true,
Endpoint: "MyEndPoint", Endpoint: "MyEndPoint",
DefaultRule: "PathPrefix(`/`)", DefaultRule: "PathPrefix(`/`)",

View file

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

View file

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

View file

@ -69,7 +69,6 @@ Providers:
DefaultRule: foobar DefaultRule: foobar
ExposedByDefault: true ExposedByDefault: true
DCOSToken: foobar DCOSToken: foobar
FilterMarathonConstraints: true
DialerTimeout: 42 DialerTimeout: 42
ResponseHeaderTimeout: 42 ResponseHeaderTimeout: 42
TLSHandshakeTimeout: 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/config/label"
"github.com/containous/traefik/pkg/log" "github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/provider" "github.com/containous/traefik/pkg/provider"
"github.com/containous/traefik/pkg/provider/constraints"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
) )
@ -123,10 +124,13 @@ func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool
return false return false
} }
if ok, failingConstraint := p.MatchConstraints(container.ExtraConf.Tags); !ok { matches, err := constraints.Match(container.Labels, p.Constraints)
if failingConstraint != nil { if err != nil {
logger.Debugf("Container pruned by %q constraint", failingConstraint.String()) logger.Error("Error matching constraints expression: %v", err)
} return false
}
if !matches {
logger.Debugf("Container pruned by constraint expression: %q", p.Constraints)
return false return false
} }

View file

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

View file

@ -45,7 +45,7 @@ var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider. // Provider holds configurations of the provider.
type Provider struct { 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"` Watch bool `description:"Watch provider." export:"true"`
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint."` Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint."`
DefaultRule string `description:"Default rule."` 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. // configuration Contains information from the labels that are globals (not related to the dynamic configuration) or specific to the provider.
type configuration struct { type configuration struct {
Enable bool Enable bool
Tags []string
Docker specificConfiguration 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 { if err != nil {
return configuration{}, err return configuration{}, err
} }

View file

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

View file

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

View file

@ -2,7 +2,6 @@ package marathon
import ( import (
"math" "math"
"strings"
"github.com/containous/traefik/pkg/config/label" "github.com/containous/traefik/pkg/config/label"
"github.com/gambol99/go-marathon" "github.com/gambol99/go-marathon"
@ -10,7 +9,6 @@ import (
type configuration struct { type configuration struct {
Enable bool Enable bool
Tags []string
Marathon specificConfiguration Marathon specificConfiguration
} }
@ -23,23 +21,16 @@ func (p *Provider) getConfiguration(app marathon.Application) (configuration, er
conf := configuration{ conf := configuration{
Enable: p.ExposedByDefault, Enable: p.ExposedByDefault,
Tags: nil,
Marathon: specificConfiguration{ Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32, 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 { if err != nil {
return configuration{}, err 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 return conf, nil
} }

View file

@ -23,12 +23,10 @@ func TestGetConfiguration(t *testing.T) {
Labels: &map[string]string{}, Labels: &map[string]string{},
}, },
p: Provider{ p: Provider{
ExposedByDefault: false, ExposedByDefault: false,
FilterMarathonConstraints: false,
}, },
expected: configuration{ expected: configuration{
Enable: false, Enable: false,
Tags: nil,
Marathon: specificConfiguration{ Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32, IPAddressIdx: math.MinInt32,
}, },
@ -43,12 +41,10 @@ func TestGetConfiguration(t *testing.T) {
}, },
}, },
p: Provider{ p: Provider{
ExposedByDefault: false, ExposedByDefault: false,
FilterMarathonConstraints: false,
}, },
expected: configuration{ expected: configuration{
Enable: true, Enable: true,
Tags: nil,
Marathon: specificConfiguration{ Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32, IPAddressIdx: math.MinInt32,
}, },
@ -63,12 +59,10 @@ func TestGetConfiguration(t *testing.T) {
}, },
}, },
p: Provider{ p: Provider{
ExposedByDefault: false, ExposedByDefault: false,
FilterMarathonConstraints: false,
}, },
expected: configuration{ expected: configuration{
Enable: false, Enable: false,
Tags: nil,
Marathon: specificConfiguration{ Marathon: specificConfiguration{
IPAddressIdx: 4, IPAddressIdx: 4,
}, },
@ -83,14 +77,10 @@ func TestGetConfiguration(t *testing.T) {
Labels: &map[string]string{}, Labels: &map[string]string{},
}, },
p: Provider{ p: Provider{
ExposedByDefault: false, ExposedByDefault: false,
FilterMarathonConstraints: true,
}, },
expected: configuration{ expected: configuration{
Enable: false, Enable: false,
Tags: []string{
"key:value",
},
Marathon: specificConfiguration{ Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32, IPAddressIdx: math.MinInt32,
}, },
@ -103,12 +93,10 @@ func TestGetConfiguration(t *testing.T) {
Labels: &map[string]string{}, Labels: &map[string]string{},
}, },
p: Provider{ p: Provider{
ExposedByDefault: true, ExposedByDefault: true,
FilterMarathonConstraints: false,
}, },
expected: configuration{ expected: configuration{
Enable: true, Enable: true,
Tags: nil,
Marathon: specificConfiguration{ Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32, IPAddressIdx: math.MinInt32,
}, },
@ -123,32 +111,10 @@ func TestGetConfiguration(t *testing.T) {
}, },
}, },
p: Provider{ p: Provider{
ExposedByDefault: true, ExposedByDefault: true,
FilterMarathonConstraints: false,
}, },
expected: configuration{ expected: configuration{
Enable: false, 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{ Marathon: specificConfiguration{
IPAddressIdx: math.MinInt32, IPAddressIdx: math.MinInt32,
}, },

View file

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

View file

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

View file

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

View file

@ -6,7 +6,6 @@ import (
type configuration struct { type configuration struct {
Enable bool Enable bool
Tags []string
} }
func (p *Provider) getConfiguration(service rancherData) (configuration, error) { func (p *Provider) getConfiguration(service rancherData) (configuration, error) {
@ -14,7 +13,7 @@ func (p *Provider) getConfiguration(service rancherData) (configuration, error)
Enable: p.ExposedByDefault, 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 { if err != nil {
return configuration{}, err return configuration{}, err
} }

View file

@ -40,8 +40,7 @@ var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider. // Provider holds configurations of the provider.
type Provider struct { 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"` Watch bool `description:"Watch provider." export:"true"`
DefaultRule string `description:"Default rule."` DefaultRule string `description:"Default rule."`
ExposedByDefault bool `description:"Expose containers by default." export:"true"` ExposedByDefault bool `description:"Expose containers by default." export:"true"`

View file

@ -25,7 +25,6 @@ type Provider struct {
// SetDefaults sets the default values. // SetDefaults sets the default values.
func (p *Provider) SetDefaults() { func (p *Provider) SetDefaults() {
p.EntryPoint = "traefik" p.EntryPoint = "traefik"
// FIXME p.EntryPoint = static.DefaultInternalEntryPointName
} }
var templatesRenderer = render.New(render.Options{Directory: "nowhere"}) 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() { func (p *Prometheus) SetDefaults() {
p.Buckets = []float64{0.1, 0.3, 1.2, 5} p.Buckets = []float64{0.1, 0.3, 1.2, 5}
p.EntryPoint = "traefik" p.EntryPoint = "traefik"
// FIXME p.EntryPoint = static.DefaultInternalEntryPointName
} }
// Datadog contains address and metrics pushing interval configuration // 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 // GetFieldByTag returns a field from the object based on the tag
func GetFieldByTag(ival interface{}, tagName string, fieldNames []string) (interface{}, error) { func GetFieldByTag(ival interface{}, tagName string, fieldNames []string) (interface{}, error) {
if len(fieldNames) == 0 { if len(fieldNames) == 0 {

View file

@ -59,6 +59,16 @@ func (p *predicateParser) parseNode(node ast.Node) (interface{}, error) {
return callFunction(fn, arguments) return callFunction(fn, arguments)
case *ast.ParenExpr: case *ast.ParenExpr:
return p.parseNode(n.X) 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) 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 nil, trace.Wrap(err)
} }
return val, nil 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: default:
return nil, trace.BadParameter("%T is not supported", n) 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) { func (p *predicateParser) getJoinFunction(op token.Token) (interface{}, error) {
var fn interface{} var fn interface{}
switch op { switch op {
case token.NOT:
fn = p.d.Operators.NOT
case token.LAND: case token.LAND:
fn = p.d.Operators.AND fn = p.d.Operators.AND
case token.LOR: 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 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. various predicates for configuration, e.g. Latency() > 40 || ErrorRate() > 0.5.
@ -76,6 +93,7 @@ type Operators struct {
OR interface{} OR interface{}
AND interface{} AND interface{}
NOT interface{}
} }
// Parser takes the string with expression and calls the operators and functions. // Parser takes the string with expression and calls the operators and functions.