diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index ba2dcbc95..0ac139f29 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -5,7 +5,7 @@ The issue tracker is for reporting bugs and feature requests only. For end-user related support questions, please refer to one of the following: - Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik -- the Traefik community Slack channel: https://slack.traefik.io +- the Traefik community forum: https://community.containo.us/ --> diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 44410d56e..a8b0d8dc9 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -11,7 +11,7 @@ The issue tracker is for reporting bugs and feature requests only. For end-user related support questions, please refer to one of the following: - Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik -- the Traefik community Slack channel: https://slack.traefik.io +- the Traefik community forum: https://community.containo.us/ --> diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index fd788bb8f..c855b1dbc 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -11,7 +11,7 @@ The issue tracker is for reporting bugs and feature requests only. For end-user related support questions, please refer to one of the following: - Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik -- the Traefik community Slack channel: https://slack.traefik.io +- the Traefik community forum: https://community.containo.us/ --> diff --git a/CHANGELOG.md b/CHANGELOG.md index dcc8da153..c4c855118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## [v2.0.0-alpha7](https://github.com/containous/traefik/tree/v2.0.0-alpha7) (2019-06-21) +[All Commits](https://github.com/containous/traefik/compare/v2.0.0-alpha6...v2.0.0-alpha7) + +**Enhancements:** +- **[api]** API: new contract ([#4964](https://github.com/containous/traefik/pull/4964) by [mpl](https://github.com/mpl)) +- **[k8s,k8s/crd,tls]** Define TLS options on the Router configuration for Kubernetes ([#4973](https://github.com/containous/traefik/pull/4973) by [jbdoumenjou](https://github.com/jbdoumenjou)) +- **[middleware,provider]** Change the provider separator from . to @ ([#4982](https://github.com/containous/traefik/pull/4982) by [ldez](https://github.com/ldez)) +- **[provider]** Use name@provider instead of provider@name. ([#4990](https://github.com/containous/traefik/pull/4990) by [ldez](https://github.com/ldez)) +- **[provider]** New constraints management. ([#4965](https://github.com/containous/traefik/pull/4965) by [ldez](https://github.com/ldez)) + +**Bug fixes:** +- **[cli]** Fix some CLI bugs ([#4989](https://github.com/containous/traefik/pull/4989) by [ldez](https://github.com/ldez)) +- **[cli]** Filter env vars configuration ([#4985](https://github.com/containous/traefik/pull/4985) by [ldez](https://github.com/ldez)) +- **[cli]** Return an error when help is called on a non existing command. ([#4977](https://github.com/containous/traefik/pull/4977) by [ldez](https://github.com/ldez)) +- **[tls]** Fix panic in TLS stores handling ([#4997](https://github.com/containous/traefik/pull/4997) by [juliens](https://github.com/juliens)) + +**Documentation:** +- **[acme,tls]** docs: rewrite of the HTTPS and TLS section ([#4980](https://github.com/containous/traefik/pull/4980) by [mpl](https://github.com/mpl)) +- Improve various parts of the documentation. ([#4996](https://github.com/containous/traefik/pull/4996) by [ldez](https://github.com/ldez)) + ## [v2.0.0-alpha6](https://github.com/containous/traefik/tree/v2.0.0-alpha6) (2019-06-18) [All Commits](https://github.com/containous/traefik/compare/v2.0.0-alpha5...v2.0.0-alpha6) diff --git a/Gopkg.lock b/Gopkg.lock index ad88768f6..ef3da06b3 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -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", diff --git a/Gopkg.toml b/Gopkg.toml index 7f7fd3244..918ec36be 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -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" diff --git a/README.md b/README.md index 6c71fe473..9e92a1f28 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Go Report Card](https://goreportcard.com/badge/containous/traefik)](http://goreportcard.com/report/containous/traefik) [![](https://images.microbadger.com/badges/image/traefik.svg)](https://microbadger.com/images/traefik) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/containous/traefik/blob/master/LICENSE.md) -[![Join the chat at https://slack.traefik.io](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://slack.traefik.io) +[![Join the community support forum at https://community.containo.us/](https://img.shields.io/badge/style-register-green.svg?style=social&label=Discourse)](https://community.containo.us/) [![Twitter](https://img.shields.io/twitter/follow/traefik.svg?style=social)](https://twitter.com/intent/follow?screen_name=traefik) @@ -103,7 +103,7 @@ A collection of contributions around Traefik can be found at [https://awesome.tr ## Support To get community support, you can: -- join the Traefik community Slack channel: [![Join the chat at https://slack.traefik.io](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://slack.traefik.io) +- join the Traefik community forum: [![Join the chat at https://community.containo.us/](https://img.shields.io/badge/style-register-green.svg?style=social&label=Discourse)](https://community.containo.us/) - use [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik) (using the `traefik` tag) If you need commercial support, please contact [Containo.us](https://containo.us) by mail: . diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index d69677b05..0527ebfe9 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -44,7 +44,7 @@ func main() { // traefik config inits tConfig := cmd.NewTraefikConfiguration() - loaders := []cli.ResourceLoader{&cli.FileLoader{}, &cli.EnvLoader{}, &cli.FlagLoader{}} + loaders := []cli.ResourceLoader{&cli.FileLoader{}, &cli.FlagLoader{}, &cli.EnvLoader{}} cmdTraefik := &cli.Command{ Name: "traefik", diff --git a/docs/content/contributing/maintainers.md b/docs/content/contributing/maintainers.md index 9dc5d8ebf..b0595ee71 100644 --- a/docs/content/contributing/maintainers.md +++ b/docs/content/contributing/maintainers.md @@ -28,7 +28,7 @@ * Modifying an issue or a pull request (labels, assignees, milestone) is only possible: * During the Contributions Daily Meeting * By an assigned maintainer - * In case of emergency, if a change proposal is approved by 2 other maintainers (on Slack, Discord, etc) + * In case of emergency, if a change proposal is approved by 2 other maintainers (on Slack, Discord, Discourse, etc) ## PR review process: diff --git a/docs/content/contributing/submitting-issues.md b/docs/content/contributing/submitting-issues.md index 18d342058..1f4285b3f 100644 --- a/docs/content/contributing/submitting-issues.md +++ b/docs/content/contributing/submitting-issues.md @@ -11,10 +11,10 @@ To save us some time and get quicker feedback, be sure to follow the guide lines !!! important "Getting Help Vs Reporting an Issue" The issue tracker is not a general support forum, but a place to report bugs and asks for new features. - + For end-user related support questions, try using first: - - - the Traefik community Slack channel: [![Join the chat at https://slack.traefik.io](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://slack.traefik.io) + + - the Traefik community forum: [![Join the chat at https://community.containo.us/](https://img.shields.io/badge/style-register-green.svg?style=social&label=Discourse)](https://community.containo.us/) - [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik) (using the `traefik` tag) ## Issue Title diff --git a/docs/content/getting-started/configuration-overview.md b/docs/content/getting-started/configuration-overview.md index 3620123b4..864b5dc2c 100644 --- a/docs/content/getting-started/configuration-overview.md +++ b/docs/content/getting-started/configuration-overview.md @@ -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. @@ -36,8 +36,8 @@ Traefik gets its _dynamic configuration_ from [providers](../providers/overview. There are three different, mutually exclusive, ways to define static configuration options in Traefik: - In a configuration file -- As environment variables - In the command-line arguments +- As environment variables These ways are evaluated in the order listed above. @@ -70,6 +70,12 @@ docker run traefik[:version] --help # ex: docker run traefik:2.0 --help ``` +All available arguments can also be found [here](../reference/static-configuration/cli.md). + +### Environment Variables + +All available environment variables can be found [here](../reference/static-configuration/env.md) + ## Available Configuration Options All the configuration options are documented in their related section. diff --git a/docs/content/middlewares/overview.md b/docs/content/middlewares/overview.md index 991cccb61..d359af96d 100644 --- a/docs/content/middlewares/overview.md +++ b/docs/content/middlewares/overview.md @@ -16,9 +16,13 @@ Pieces of middleware can be combined in chains to fit every scenario. ```yaml tab="Docker" # As a Docker Label whoami: - image: containous/whoami # A container that exposes an API to show its IP address + # A container that exposes an API to show its IP address + image: containous/whoami labels: + # Create a middleware named `foo-add-prefix` - "traefik.http.middlewares.foo-add-prefix.addprefix.prefix=/foo" + # Apply the middleware named `foo-add-prefix` to the router named `router1` + - "traefik.http.router.router1.Middlewares=foo-add-prefix@docker" ``` ```yaml tab="Kubernetes" @@ -61,14 +65,44 @@ spec: ```json tab="Marathon" "labels": { - "traefik.http.middlewares.foo-add-prefix.addprefix.prefix": "/foo" + "traefik.http.middlewares.foo-add-prefix.addprefix.prefix": "/foo", + "traefik.http.router.router1.Middlewares": "foo-add-prefix@marathon" } ``` ```yaml tab="Rancher" # As a Rancher Label labels: + # Create a middleware named `foo-add-prefix` - "traefik.http.middlewares.foo-add-prefix.addprefix.prefix=/foo" + # Apply the middleware named `foo-add-prefix` to the router named `router1` + - "traefik.http.router.router1.Middlewares=foo-add-prefix@rancher" +``` + +```yaml tab="Kubernetes" +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: tlsoptions.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: TLSOption + plural: tlsoptions + singular: tlsoption + scope: Namespaced + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: mytlsoption + namespace: default + +spec: + minversion: VersionTLS12 ``` ```toml tab="File" @@ -83,25 +117,30 @@ labels: Rule = "Host(`example.com`)" [http.middlewares] - [http.middlewares.foo-add-prefix.AddPrefix] + [http.middlewares.foo-add-prefix.AddPrefix] prefix = "/foo" [http.services] - [http.services.service1] - [http.services.service1.LoadBalancer] + [http.services.service1] + [http.services.service1.LoadBalancer] - [[http.services.service1.LoadBalancer.Servers]] - URL = "http://127.0.0.1:80" + [[http.services.service1.LoadBalancer.Servers]] + URL = "http://127.0.0.1:80" ``` -## Advanced Configuration +## Provider Namespace -When you declare a middleware, it lives in its `provider` namespace. -For example, if you declare a middleware using a Docker label, under the hoods, it will reside in the docker `provider` namespace. +When you declare a middleware, it lives in its provider namespace. +For example, if you declare a middleware using a Docker label, under the hoods, it will reside in the docker provider namespace. -If you use multiple `providers` and wish to reference a middleware declared in another `provider`, then you'll have to prefix the middleware name with the `provider` name. +If you use multiple providers and wish to reference a middleware declared in another provider, +then you'll have to prefix the middleware name with the provider name. -??? abstract "Referencing a Middleware from Another Provider" +```text +@ +``` + +!!! abstract "Referencing a Middleware from Another Provider" Declaring the add-foo-prefix in the file provider. @@ -121,8 +160,8 @@ If you use multiple `providers` and wish to reference a middleware declared in a image: your-docker-image labels: - # Attach file@add-foo-prefix middleware (declared in file) - - "traefik.http.routers.my-container.middlewares=file@add-foo-prefix" + # Attach add-foo-prefix@file middleware (declared in file) + - "traefik.http.routers.my-container.middlewares=add-foo-prefix@file" ``` ## Available Middlewares diff --git a/docs/content/observability/logs.md b/docs/content/observability/logs.md index ba8eda6c2..6d8027676 100644 --- a/docs/content/observability/logs.md +++ b/docs/content/observability/logs.md @@ -5,41 +5,57 @@ Reading What's Happening By default, logs are written to stdout, in text format. -## Configuration Example - -??? example "Writing Logs in a File" - - ```toml - [log] - filePath = "/path/to/traefik.log" - ``` - -??? example "Writing Logs in a File, in JSON" - - ```toml - [log] - filePath = "/path/to/log-file.log" - format = "json" - ``` - -## Configuration Options +## Configuration ### General Traefik logs concern everything that happens to Traefik itself (startup, configuration, events, shutdown, and so on). -#### filePath +#### `filePath` By default, the logs are written to the standard output. You can configure a file path instead using the `filePath` option. -#### format +```toml tab="File" +# Writing Logs to a File +[log] + filePath = "/path/to/traefik.log" +``` + +```bash tab="CLI" +# Writing Logs to a File +--log.filePath="/path/to/traefik.log" +``` + +#### `format` By default, the logs use a text format (`common`), but you can also ask for the `json` format in the `format` option. -#### log level +```toml tab="File" +# Writing Logs to a File, in JSON +[log] + filePath = "/path/to/log-file.log" + format = "json" +``` -By default, the `level` is set to `error`, but you can choose amongst `debug`, `panic`, `fatal`, `error`, `warn`, and `info`. +```bash tab="CLI" +# Writing Logs to a File, in JSON +--log.filePath="/path/to/traefik.log" +--log.format="json" +``` + +#### `level` + +By default, the `level` is set to `ERROR`. Alternative logging levels are `DEBUG`, `PANIC`, `FATAL`, `ERROR`, `WARN`, and `INFO`. + +```toml tab="File" +[log] + level = "DEBUG" +``` + +```bash tab="CLI" +--log.level="DEBUG" +``` ## Log Rotation diff --git a/docs/content/operations/cli.md b/docs/content/operations/cli.md index bef4a8510..4225943b5 100644 --- a/docs/content/operations/cli.md +++ b/docs/content/operations/cli.md @@ -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 ``` diff --git a/docs/content/providers/crd_tls_option.yml b/docs/content/providers/crd_tls_option.yml new file mode 100644 index 000000000..1495e0d0b --- /dev/null +++ b/docs/content/providers/crd_tls_option.yml @@ -0,0 +1,13 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: tlsoptions.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: TLSOption + plural: tlsoptions + singular: tlsoption + scope: Namespaced diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index b6b09e3d9..3fc1daabc 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -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. diff --git a/docs/content/providers/kubernetes-crd.md b/docs/content/providers/kubernetes-crd.md index 19745dfc1..53f6617a2 100644 --- a/docs/content/providers/kubernetes-crd.md +++ b/docs/content/providers/kubernetes-crd.md @@ -230,6 +230,51 @@ spec: More information about available middlewares in the dedicated [middlewares section](../middlewares/overview.md). +### Traefik TLS Option Definition + +Additionally, to allow for the use of tls options in an IngressRoute, we defined the CRD below for the TLSOption kind. +More information about TLS Options is available in the dedicated [TLS Configuration Options](../../https/tls/#tls-options). + +```yaml +--8<-- "content/providers/crd_tls_option.yml" +``` + +Once the TLSOption kind has been registered with the Kubernetes cluster or defined in the File Provider, it can then be used in IngressRoute definitions, such as: + +```yaml +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: mytlsoption + namespace: default + +spec: + minversion: VersionTLS12 + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: ingressroutebar + +spec: + entryPoints: + - web + routes: + - match: Host(`bar.com`) && PathPrefix(`/stripit`) + kind: Rule + services: + - name: whoami + port: 80 + tls: + options: + name: mytlsoption + namespace: default +``` + +!!! note "TLS Option reference and namespace" + If the optional `namespace` attribute is not set, the configuration will be applied with the namespace of the IngressRoute. + ### TLS To allow for TLS, we made use of the `Secret` kind, as it was already defined, and it can be directly used in an `IngressRoute`: diff --git a/docs/content/providers/marathon.md b/docs/content/providers/marathon.md index f8c0ff45a..178bb01ff 100644 --- a/docs/content/providers/marathon.md +++ b/docs/content/providers/marathon.md @@ -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. diff --git a/docs/content/providers/overview.md b/docs/content/providers/overview.md index fc2c2ab7a..796d45d5c 100644 --- a/docs/content/providers/overview.md +++ b/docs/content/providers/overview.md @@ -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. - -## 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) diff --git a/docs/content/providers/rancher.md b/docs/content/providers/rancher.md index edc9de5e6..a9572d61b 100644 --- a/docs/content/providers/rancher.md +++ b/docs/content/providers/rancher.md @@ -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. diff --git a/docs/content/providers/rancher.toml b/docs/content/providers/rancher.toml new file mode 100644 index 000000000..5ce4bac50 --- /dev/null +++ b/docs/content/providers/rancher.toml @@ -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" diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd.yml index 2850d669c..bb016b6d7 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd.yml @@ -26,6 +26,21 @@ spec: singular: middleware scope: Namespaced +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: tlsoptions.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: TLSOption + plural: tlsoptions + singular: tlsoption + scope: Namespaced + --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition @@ -85,6 +100,9 @@ spec: # use an empty tls object for TLS with Let's Encrypt tls: secretName: supersecret + options: + name: myTLSOption + namespace: default --- apiVersion: traefik.containo.us/v1alpha1 @@ -104,3 +122,6 @@ spec: tls: secretName: foosecret passthrough: false + options: + name: myTLSOption + namespace: default diff --git a/docs/content/reference/static-configuration/cli.txt b/docs/content/reference/static-configuration/cli.txt index 22f9c6326..de489cb28 100644 --- a/docs/content/reference/static-configuration/cli.txt +++ b/docs/content/reference/static-configuration/cli.txt @@ -1,4 +1,3 @@ - --accesslog (Default: "false") Access log settings. @@ -173,6 +172,9 @@ --hostresolver.resolvdepth (Default: "5") The maximal depth of DNS recursive resolving +--log (Default: "false") + Traefik log settings. + --log.filepath (Default: "") Traefik log file path. Stdout is used when omitted or empty. @@ -249,17 +251,8 @@ Enable Docker backend with default settings. --providers.docker.constraints (Default: "") - Filter services by constraint, matching with Traefik tags. - ---providers.docker.constraints[n].key (Default: "") - The provider label that will be matched against. In practice, it is always - 'tag'. - ---providers.docker.constraints[n].mustmatch (Default: "false") - Whether the matching operator is equals or not equals. - ---providers.docker.constraints[n].value (Default: "") - The value that will be matched against. + Constraints is an expression that Traefik matches against the container's labels + to determine whether to create any route for that container. --providers.docker.defaultrule (Default: "Host(`{{ normalize .Name }}`)") Default rule. @@ -382,17 +375,8 @@ Basic authentication Password. --providers.marathon.constraints (Default: "") - Filter services by constraint, matching with Traefik tags. - ---providers.marathon.constraints[n].key (Default: "") - The provider label that will be matched against. In practice, it is always - 'tag'. - ---providers.marathon.constraints[n].mustmatch (Default: "false") - Whether the matching operator is equals or not equals. - ---providers.marathon.constraints[n].value (Default: "") - The value that will be matched against. + Constraints is an expression that Traefik matches against the application's + labels to determine whether to create any route for that application. --providers.marathon.dcostoken (Default: "") DCOSToken for DCOS environment, This will override the Authorization header. @@ -409,9 +393,6 @@ --providers.marathon.exposedbydefault (Default: "true") Expose Marathon apps by default. ---providers.marathon.filtermarathonconstraints (Default: "false") - Enable use of Marathon constraints in constraint filtering. - --providers.marathon.forcetaskhostname (Default: "false") Force to use the task's hostname. @@ -457,17 +438,8 @@ Enable Rancher backend with default settings. --providers.rancher.constraints (Default: "") - Filter services by constraint, matching with Traefik tags. - ---providers.rancher.constraints[n].key (Default: "") - The provider label that will be matched against. In practice, it is always - 'tag'. - ---providers.rancher.constraints[n].mustmatch (Default: "false") - Whether the matching operator is equals or not equals. - ---providers.rancher.constraints[n].value (Default: "") - The value that will be matched against. + Constraints is an expression that Traefik matches against the container's labels + to determine whether to create any route for that container. --providers.rancher.defaultrule (Default: "Host(`{{ normalize .Name }}`)") Default rule. diff --git a/docs/content/reference/static-configuration/env.md b/docs/content/reference/static-configuration/env.md index 1b2d4075f..b9f3c67da 100644 --- a/docs/content/reference/static-configuration/env.md +++ b/docs/content/reference/static-configuration/env.md @@ -165,6 +165,9 @@ resolv.conf used for DNS resolving (Default: ```/etc/resolv.conf```) `TRAEFIK_HOSTRESOLVER_RESOLVDEPTH`: The maximal depth of DNS recursive resolving (Default: ```5```) +`TRAEFIK_LOG`: +Traefik log settings. (Default: "false") + `TRAEFIK_LOG_FILEPATH`: Traefik log file path. Stdout is used when omitted or empty. @@ -241,16 +244,7 @@ Middleware list. Enable Docker backend with default settings. (Default: ```false```) `TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS`: -Filter services by constraint, matching with Traefik tags. - -`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS[n]_KEY`: -The provider label that will be matched against. In practice, it is always 'tag'. - -`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS[n]_MUSTMATCH`: -Whether the matching operator is equals or not equals. (Default: ```false```) - -`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS[n]_VALUE`: -The value that will be matched against. +Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container. `TRAEFIK_PROVIDERS_DOCKER_DEFAULTRULE`: Default rule. (Default: ```Host(`{{ normalize .Name }}`)```) @@ -373,16 +367,7 @@ Basic authentication User. Basic authentication Password. `TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS`: -Filter services by constraint, matching with Traefik tags. - -`TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS[n]_KEY`: -The provider label that will be matched against. In practice, it is always 'tag'. - -`TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS[n]_MUSTMATCH`: -Whether the matching operator is equals or not equals. (Default: ```false```) - -`TRAEFIK_PROVIDERS_MARATHON_CONSTRAINTS[n]_VALUE`: -The value that will be matched against. +Constraints is an expression that Traefik matches against the application's labels to determine whether to create any route for that application. `TRAEFIK_PROVIDERS_MARATHON_DCOSTOKEN`: DCOSToken for DCOS environment, This will override the Authorization header. @@ -399,9 +384,6 @@ Marathon server endpoint. You can also specify multiple endpoint for Marathon. ( `TRAEFIK_PROVIDERS_MARATHON_EXPOSEDBYDEFAULT`: Expose Marathon apps by default. (Default: ```true```) -`TRAEFIK_PROVIDERS_MARATHON_FILTERMARATHONCONSTRAINTS`: -Enable use of Marathon constraints in constraint filtering. (Default: ```false```) - `TRAEFIK_PROVIDERS_MARATHON_FORCETASKHOSTNAME`: Force to use the task's hostname. (Default: ```false```) @@ -445,16 +427,7 @@ Backends throttle duration: minimum duration between 2 events from providers bef Enable Rancher backend with default settings. (Default: ```false```) `TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS`: -Filter services by constraint, matching with Traefik tags. - -`TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS[n]_KEY`: -The provider label that will be matched against. In practice, it is always 'tag'. - -`TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS[n]_MUSTMATCH`: -Whether the matching operator is equals or not equals. (Default: ```false```) - -`TRAEFIK_PROVIDERS_RANCHER_CONSTRAINTS[n]_VALUE`: -The value that will be matched against. +Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container. `TRAEFIK_PROVIDERS_RANCHER_DEFAULTRULE`: Default rule. (Default: ```Host(`{{ normalize .Name }}`)```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 05c269221..da26229c5 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -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" diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index 2734bf54c..976ca244c 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -44,22 +44,22 @@ You can define them using a toml file, CLI arguments, or a key-value store. See the complete reference for the list of available options: ```toml tab="File" -[EntryPoints] +[entryPoints] - [EntryPoints.EntryPoint0] + [entryPoints.EntryPoint0] Address = ":8888" - [EntryPoints.EntryPoint0.Transport] - [EntryPoints.EntryPoint0.Transport.LifeCycle] + [entryPoints.EntryPoint0.Transport] + [entryPoints.EntryPoint0.Transport.LifeCycle] RequestAcceptGraceTimeout = 42 GraceTimeOut = 42 - [EntryPoints.EntryPoint0.Transport.RespondingTimeouts] + [entryPoints.EntryPoint0.Transport.RespondingTimeouts] ReadTimeout = 42 WriteTimeout = 42 IdleTimeout = 42 - [EntryPoints.EntryPoint0.ProxyProtocol] + [entryPoints.EntryPoint0.ProxyProtocol] Insecure = true TrustedIPs = ["foobar", "foobar"] - [EntryPoints.EntryPoint0.ForwardedHeaders] + [entryPoints.EntryPoint0.ForwardedHeaders] Insecure = true TrustedIPs = ["foobar", "foobar"] ``` diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index 0dfaa2348..ac8990d32 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -36,8 +36,10 @@ In the process, routers may use pieces of [middleware](../../middlewares/overvie [entryPoints.mysql-default] address = ":80" [entryPoints.mysql-default] - address = ":3306" - + address = ":3306" + ``` + + ```toml [tcp] [tcp.routers] [tcp.routers.to-database] @@ -50,8 +52,8 @@ In the process, routers may use pieces of [middleware](../../middlewares/overvie ### EntryPoints -If not specified, HTTP routers will accept requests from all defined entrypoints. -If you want to limit the router scope to a set of entrypoints, set the entrypoints option. +If not specified, HTTP routers will accept requests from all defined entry points. +If you want to limit the router scope to a set of entry points, set the `entryPoints` option. ??? example "Listens to Every EntryPoint" @@ -63,7 +65,9 @@ If you want to limit the router scope to a set of entrypoints, set the entrypoin # ... [entryPoints.other] # ... - + ``` + + ```toml [http.routers] [http.routers.Router-1] # By default, routers listen to every entrypoints @@ -81,7 +85,9 @@ If you want to limit the router scope to a set of entrypoints, set the entrypoin # ... [entryPoints.other] # ... - + ``` + + ```toml [http.routers] [http.routers.Router-1] entryPoints = ["web-secure", "other"] # won't listen to entrypoint web @@ -97,14 +103,15 @@ If the rule is verified, the router becomes active, calls middlewares, and then ??? example "Host is traefik.io" ```toml - rule = "Host(`traefik.io`)" + rule = "Host(`traefik.io`)" ``` ??? example "Host is traefik.io OR Host is containo.us AND path is /traefik" ```toml - rule = "Host(`traefik.io`) || (Host(`containo.us`) && Path(`/traefik`))" + rule = "Host(`traefik.io`) || (Host(`containo.us`) && Path(`/traefik`))" ``` + The table below lists all the available matchers: | Rule | Description | @@ -126,7 +133,7 @@ The table below lists all the available matchers: !!! tip "Combining Matchers Using Operators and Parenthesis" - You can combine multiple matchers using the AND (`&&`) and OR (`||) operators. You can also use parenthesis. + You can combine multiple matchers using the AND (`&&`) and OR (`||`) operators. You can also use parenthesis. !!! important "Rule, Middleware, and Services" @@ -212,7 +219,6 @@ It refers to a [tlsOptions](../../https/tls.md#tls-options) and will be applied [http.routers.Router-1.tls] # will terminate the TLS request options = "foo" - [tlsOptions] [tlsOptions.foo] minVersion = "VersionTLS12" @@ -234,7 +240,7 @@ If no matching route is found for the TCP routers, then the HTTP routers will ta If not specified, TCP routers will accept requests from all defined entry points. If you want to limit the router scope to a set of entry points, set the entry points option. -??? example "Listens to Every EntryPoint" +??? example "Listens to Every Entry Point" ```toml [entryPoints] @@ -244,7 +250,9 @@ If you want to limit the router scope to a set of entry points, set the entry po # ... [entryPoints.other] # ... - + ``` + + ```toml [tcp.routers] [tcp.routers.Router-1] # By default, routers listen to every entrypoints @@ -253,7 +261,7 @@ If you want to limit the router scope to a set of entry points, set the entry po [tcp.routers.Router-1.tls] # will route TLS requests (and ignore non tls requests) ``` -??? example "Listens to Specific EntryPoints" +??? example "Listens to Specific Entry Points" ```toml [entryPoints] @@ -263,7 +271,9 @@ If you want to limit the router scope to a set of entry points, set the entry po # ... [entryPoints.other] # ... + ``` + ```toml [tcp.routers] [tcp.routers.Router-1] entryPoints = ["web-secure", "other"] # won't listen to entrypoint web @@ -340,7 +350,6 @@ It refers to a [tlsOptions](../../https/tls.md#tls-options) and will be applied [tcp.routers.Router-1.tls] # will terminate the TLS request options = "foo" - [tlsOptions] [tlsOptions.foo] minVersion = "VersionTLS12" diff --git a/docs/content/user-guides/crd-acme/index.md b/docs/content/user-guides/crd-acme/index.md index 2956678b9..5a02451dd 100644 --- a/docs/content/user-guides/crd-acme/index.md +++ b/docs/content/user-guides/crd-acme/index.md @@ -16,6 +16,10 @@ In the following, the Kubernetes resources defined in YAML configuration files c - the first, and usual way, is simply with the `kubectl apply` command. - the second, which can be used for this tutorial, is to directly place the files in the directory used by the k3s docker image for such inputs (`/var/lib/rancher/k3s/server/manifests`). +!!! important "Kubectl Version" + + With the `rancher/k3s` version used in this guide (`0.5.0`), the kubectl version needs to be >= `0.11`. + ## k3s Docker-compose Configuration Our starting point is the docker-compose configuration file, to start the k3s cluster. diff --git a/integration/access_log_test.go b/integration/access_log_test.go index 0dee57484..807d1232b 100644 --- a/integration/access_log_test.go +++ b/integration/access_log_test.go @@ -581,7 +581,7 @@ func CheckAccessLogFormat(c *check.C, line string, i int) { c.Assert(results, checker.HasLen, 14) c.Assert(results[accesslog.OriginStatus], checker.Matches, `^(-|\d{3})$`) c.Assert(results[accesslog.RequestCount], checker.Equals, fmt.Sprintf("%d", i+1)) - c.Assert(results[accesslog.RouterName], checker.HasPrefix, "\"docker@rt-") + c.Assert(results[accesslog.RouterName], checker.Matches, `"rt-.+@docker"`) c.Assert(results[accesslog.ServiceURL], checker.HasPrefix, "\"http://") c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`) } @@ -596,7 +596,7 @@ func checkAccessLogExactValues(c *check.C, line string, i int, v accessLogValue) } c.Assert(results[accesslog.OriginStatus], checker.Equals, v.code) c.Assert(results[accesslog.RequestCount], checker.Equals, fmt.Sprintf("%d", i+1)) - c.Assert(results[accesslog.RouterName], checker.Matches, `^"?(docker@)?`+v.routerName+`.*$`) + c.Assert(results[accesslog.RouterName], checker.Matches, `^"?`+v.routerName+`.*(@docker)?$`) c.Assert(results[accesslog.ServiceURL], checker.Matches, `^"?`+v.serviceURL+`.*$`) c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`) } diff --git a/integration/docker_compose_test.go b/integration/docker_compose_test.go index 8e39fafd9..5d81608ff 100644 --- a/integration/docker_compose_test.go +++ b/integration/docker_compose_test.go @@ -77,7 +77,7 @@ func (s *DockerComposeSuite) TestComposeScale(c *check.C) { services := rtconf.Services c.Assert(services, checker.HasLen, 1) for k, v := range services { - c.Assert(k, checker.Equals, "docker@"+composeService+"_integrationtest"+composeProject) + c.Assert(k, checker.Equals, composeService+"_integrationtest"+composeProject+"@docker") c.Assert(v.LoadBalancer.Servers, checker.HasLen, serviceCount) // We could break here, but we don't just to keep us honest. } diff --git a/integration/fixtures/https/https_tls_options.toml b/integration/fixtures/https/https_tls_options.toml index c0072efcd..680db609c 100644 --- a/integration/fixtures/https/https_tls_options.toml +++ b/integration/fixtures/https/https_tls_options.toml @@ -44,7 +44,6 @@ level = "DEBUG" [[http.services.service2.LoadBalancer.Servers]] URL = "http://127.0.0.1:9020" - [[tls]] [tls.certificate] certFile = "fixtures/https/snitest.com.cert" diff --git a/integration/fixtures/k8s/01-crd.yml b/integration/fixtures/k8s/01-crd.yml index f3992350d..70b00f662 100644 --- a/integration/fixtures/k8s/01-crd.yml +++ b/integration/fixtures/k8s/01-crd.yml @@ -41,3 +41,18 @@ spec: plural: ingressroutetcps singular: ingressroutetcp scope: Namespaced + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: tlsoptions.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: TLSOption + plural: tlsoptions + singular: tlsoption + scope: Namespaced diff --git a/integration/fixtures/k8s/03-ingressroute.yml b/integration/fixtures/k8s/03-ingressroute.yml index 1342e3f4e..fc4a9230f 100644 --- a/integration/fixtures/k8s/03-ingressroute.yml +++ b/integration/fixtures/k8s/03-ingressroute.yml @@ -15,3 +15,7 @@ spec: services: - name: whoami port: 80 + + tls: + options: + name: mytlsoption diff --git a/integration/fixtures/k8s/03-tlsoption.yml b/integration/fixtures/k8s/03-tlsoption.yml new file mode 100644 index 000000000..dea75d7e9 --- /dev/null +++ b/integration/fixtures/k8s/03-tlsoption.yml @@ -0,0 +1,12 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: mytlsoption + namespace: default + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 diff --git a/integration/fixtures/k8s/05-ingressroutetcp.yml b/integration/fixtures/k8s/05-ingressroutetcp.yml index ec84bd21b..f7b1896a8 100644 --- a/integration/fixtures/k8s/05-ingressroutetcp.yml +++ b/integration/fixtures/k8s/05-ingressroutetcp.yml @@ -12,3 +12,6 @@ spec: services: - name: whoamitcp port: 8080 + tls: + options: + name: mytlsoption diff --git a/integration/fixtures/simple_auth.toml b/integration/fixtures/simple_auth.toml index 2bf04f6d6..4796ca6b4 100644 --- a/integration/fixtures/simple_auth.toml +++ b/integration/fixtures/simple_auth.toml @@ -13,7 +13,7 @@ level = "DEBUG" address = ":8001" [api] - middlewares = ["file@authentication"] + middlewares = ["authentication@file"] [ping] diff --git a/integration/https_test.go b/integration/https_test.go index 2b43ae673..6923f076d 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -191,7 +191,7 @@ func (s *HTTPSSuite) TestWithTLSOptions(c *check.C) { c.Assert(err.Error(), checker.Contains, "protocol version not supported") // with unknown tls option - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("unknown TLS options: unknown")) + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("unknown TLS options: unknown@file")) c.Assert(err, checker.IsNil) } diff --git a/integration/simple_test.go b/integration/simple_test.go index e949e9c5a..063f0dfcc 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -444,8 +444,8 @@ func (s *SimpleSuite) TestMultiprovider(c *check.C) { Routers: map[string]*config.Router{ "router1": { EntryPoints: []string{"web"}, - Middlewares: []string{"file@customheader"}, - Service: "file@service", + Middlewares: []string{"customheader@file"}, + Service: "service@file", Rule: "PathPrefix(`/`)", }, }, diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index 708ea5643..abab62087 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -1,14 +1,17 @@ { "routers": { - "kubernetescrd@default/test-crd-6b204d94623b3df4370c": { + "default/test-crd-6b204d94623b3df4370c@kubernetescrd": { "entryPoints": [ "web" ], "service": "default/test-crd-6b204d94623b3df4370c", "rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/bar`)", - "priority": 12 + "priority": 12, + "tls": { + "options": "default/mytlsoption" + } }, - "kubernetescrd@default/test2-crd-23c7f4c450289ee29016": { + "default/test2-crd-23c7f4c450289ee29016@kubernetescrd": { "entryPoints": [ "web" ], @@ -20,82 +23,86 @@ } }, "middlewares": { - "kubernetescrd@default/stripprefix": { + "default/stripprefix@kubernetescrd": { "stripPrefix": { "prefixes": [ "/tobestripped" ] }, "usedBy": [ - "kubernetescrd@default/test2-crd-23c7f4c450289ee29016" + "default/test2-crd-23c7f4c450289ee29016@kubernetescrd" ] } }, "services": { - "kubernetescrd@default/test-crd-6b204d94623b3df4370c": { + "default/test-crd-6b204d94623b3df4370c@kubernetescrd": { "loadbalancer": { "servers": [ { - "url": "http://10.42.0.4:80" + "url": "http://10.42.0.2:80" }, { - "url": "http://10.42.0.5:80" + "url": "http://10.42.0.6:80" } ], "passHostHeader": true }, "usedBy": [ - "kubernetescrd@default/test-crd-6b204d94623b3df4370c" + "default/test-crd-6b204d94623b3df4370c@kubernetescrd" ], "serverStatus": { - "http://10.42.0.4:80": "UP", - "http://10.42.0.5:80": "UP" + "http://10.42.0.2:80": "UP", + "http://10.42.0.6:80": "UP" } }, - "kubernetescrd@default/test2-crd-23c7f4c450289ee29016": { + "default/test2-crd-23c7f4c450289ee29016@kubernetescrd": { "loadbalancer": { "servers": [ { - "url": "http://10.42.0.4:80" + "url": "http://10.42.0.2:80" }, { - "url": "http://10.42.0.5:80" + "url": "http://10.42.0.6:80" } ], "passHostHeader": true }, "usedBy": [ - "kubernetescrd@default/test2-crd-23c7f4c450289ee29016" + "default/test2-crd-23c7f4c450289ee29016@kubernetescrd" ], "serverStatus": { - "http://10.42.0.4:80": "UP", - "http://10.42.0.5:80": "UP" + "http://10.42.0.2:80": "UP", + "http://10.42.0.6:80": "UP" } } }, "tcpRouters": { - "kubernetescrd@default/test3-crd-673acf455cb2dab0b43a": { + "default/test3-crd-673acf455cb2dab0b43a@kubernetescrd": { "entryPoints": [ "footcp" ], "service": "default/test3-crd-673acf455cb2dab0b43a", - "rule": "HostSNI(`*`)" + "rule": "HostSNI(`*`)", + "tls": { + "passthrough": false, + "options": "default/mytlsoption" + } } }, "tcpServices": { - "kubernetescrd@default/test3-crd-673acf455cb2dab0b43a": { + "default/test3-crd-673acf455cb2dab0b43a@kubernetescrd": { "loadbalancer": { "servers": [ { - "address": "10.42.0.2:8080" + "address": "10.42.0.3:8080" }, { - "address": "10.42.0.3:8080" + "address": "10.42.0.4:8080" } ] }, "usedBy": [ - "kubernetescrd@default/test3-crd-673acf455cb2dab0b43a" + "default/test3-crd-673acf455cb2dab0b43a@kubernetescrd" ] } } diff --git a/integration/testdata/rawdata-ingress.json b/integration/testdata/rawdata-ingress.json index 0f7bb021f..de6c54952 100644 --- a/integration/testdata/rawdata-ingress.json +++ b/integration/testdata/rawdata-ingress.json @@ -1,13 +1,13 @@ { "routers": { - "kubernetes@whoami-test/whoami": { + "whoami-test/whoami@kubernetes": { "entryPoints": null, "service": "default/whoami/http", "rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)" } }, "services": { - "kubernetes@default/whoami/http": { + "default/whoami/http@kubernetes": { "loadbalancer": { "servers": [ { @@ -20,7 +20,7 @@ "passHostHeader": true }, "usedBy": [ - "kubernetes@whoami-test/whoami" + "whoami-test/whoami@kubernetes" ], "serverStatus": { "http://10.42.0.2:80": "UP", diff --git a/integration/tracing_test.go b/integration/tracing_test.go index 652ac04f7..76acf33bd 100644 --- a/integration/tracing_test.go +++ b/integration/tracing_test.go @@ -89,7 +89,7 @@ func (s *TracingSuite) TestZipkinRateLimit(c *check.C) { err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests)) c.Assert(err, checker.IsNil) - err = try.GetRequest("http://"+s.ZipkinIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("forward service1/file@router1", "file@ratelimit")) + err = try.GetRequest("http://"+s.ZipkinIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("forward service1/router1@file", "ratelimit@file")) c.Assert(err, checker.IsNil) } @@ -117,7 +117,7 @@ func (s *TracingSuite) TestZipkinRetry(c *check.C) { err = try.GetRequest("http://127.0.0.1:8000/retry", 500*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway)) c.Assert(err, checker.IsNil) - err = try.GetRequest("http://"+s.ZipkinIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("forward service2/file@router2", "file@retry")) + err = try.GetRequest("http://"+s.ZipkinIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("forward service2/router2@file", "retry@file")) c.Assert(err, checker.IsNil) } @@ -144,6 +144,6 @@ func (s *TracingSuite) TestZipkinAuth(c *check.C) { err = try.GetRequest("http://127.0.0.1:8000/auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized)) c.Assert(err, checker.IsNil) - err = try.GetRequest("http://"+s.ZipkinIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("entrypoint web", "file@basic-auth")) + err = try.GetRequest("http://"+s.ZipkinIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("entrypoint web", "basic-auth@file")) c.Assert(err, checker.IsNil) } diff --git a/pkg/anonymize/anonymize_config_test.go b/pkg/anonymize/anonymize_config_test.go index ebf08d390..5dc291eda 100644 --- a/pkg/anonymize/anonymize_config_test.go +++ b/pkg/anonymize/anonymize_config_test.go @@ -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(`/`)", diff --git a/pkg/api/handler.go b/pkg/api/handler.go index 0041e5d5d..4520343d9 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -156,6 +156,7 @@ func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) { return } + rw.Header().Set("Content-Type", "application/json") rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage)) err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex]) @@ -180,6 +181,8 @@ func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) { Provider: getProviderName(routerID), } + rw.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) @@ -209,6 +212,7 @@ func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) { return } + rw.Header().Set("Content-Type", "application/json") rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage)) err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex]) @@ -234,6 +238,8 @@ func (h Handler) getService(rw http.ResponseWriter, request *http.Request) { ServerStatus: service.GetAllStatus(), } + rw.Header().Add("Content-Type", "application/json") + err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) @@ -262,6 +268,7 @@ func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) { return } + rw.Header().Set("Content-Type", "application/json") rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage)) err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex]) @@ -286,6 +293,8 @@ func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) { Provider: getProviderName(middlewareID), } + rw.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) @@ -314,6 +323,7 @@ func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) { return } + rw.Header().Set("Content-Type", "application/json") rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage)) err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex]) @@ -338,6 +348,8 @@ func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) { Provider: getProviderName(routerID), } + rw.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) @@ -366,6 +378,7 @@ func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) { return } + rw.Header().Set("Content-Type", "application/json") rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage)) err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex]) @@ -390,6 +403,8 @@ func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) { Provider: getProviderName(serviceID), } + rw.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) @@ -414,6 +429,8 @@ func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.R TCPServices: h.runtimeConfiguration.TCPServices, } + rw.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(rw).Encode(result) if err != nil { log.FromContext(request.Context()).Error(err) @@ -464,5 +481,5 @@ func getIntParam(request *http.Request, key string, defaultValue int) (int, erro } func getProviderName(id string) string { - return strings.SplitN(id, ".", 2)[0] + return strings.SplitN(id, "@", 2)[1] } diff --git a/pkg/api/handler_test.go b/pkg/api/handler_test.go index bfd570175..6794b977c 100644 --- a/pkg/api/handler_test.go +++ b/pkg/api/handler_test.go @@ -47,20 +47,20 @@ func TestHandlerTCP_API(t *testing.T) { path: "/api/tcp/routers", conf: config.RuntimeConfiguration{ TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@test": { + "test@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar.other`)", TLS: &config.RouterTCPTLSConfig{ Passthrough: false, }, }, }, - "myprovider@bar": { + "bar@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", }, }, @@ -77,24 +77,24 @@ func TestHandlerTCP_API(t *testing.T) { path: "/api/tcp/routers?page=2&per_page=1", conf: config.RuntimeConfiguration{ TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@bar": { + "bar@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", }, }, - "myprovider@baz": { + "baz@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`toto.bar`)", }, }, - "myprovider@test": { + "test@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar.other`)", }, }, @@ -108,13 +108,13 @@ func TestHandlerTCP_API(t *testing.T) { }, { desc: "one TCP router by id", - path: "/api/tcp/routers/myprovider@bar", + path: "/api/tcp/routers/bar@myprovider", conf: config.RuntimeConfiguration{ TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@bar": { + "bar@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", }, }, @@ -127,13 +127,13 @@ func TestHandlerTCP_API(t *testing.T) { }, { desc: "one TCP router by id, that does not exist", - path: "/api/tcp/routers/myprovider@foo", + path: "/api/tcp/routers/foo@myprovider", conf: config.RuntimeConfiguration{ TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@bar": { + "bar@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", }, }, @@ -145,7 +145,7 @@ func TestHandlerTCP_API(t *testing.T) { }, { desc: "one TCP router by id, but no config", - path: "/api/tcp/routers/myprovider@bar", + path: "/api/tcp/routers/bar@myprovider", conf: config.RuntimeConfiguration{}, expected: expected{ statusCode: http.StatusNotFound, @@ -166,7 +166,7 @@ func TestHandlerTCP_API(t *testing.T) { path: "/api/tcp/services", conf: config.RuntimeConfiguration{ TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@bar": { + "bar@myprovider": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -176,9 +176,9 @@ func TestHandlerTCP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo", "myprovider@test"}, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, }, - "myprovider@baz": { + "baz@myprovider": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -188,7 +188,7 @@ func TestHandlerTCP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo"}, + UsedBy: []string{"foo@myprovider"}, }, }, }, @@ -203,7 +203,7 @@ func TestHandlerTCP_API(t *testing.T) { path: "/api/tcp/services?page=2&per_page=1", conf: config.RuntimeConfiguration{ TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@bar": { + "bar@myprovider": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -213,9 +213,9 @@ func TestHandlerTCP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo", "myprovider@test"}, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, }, - "myprovider@baz": { + "baz@myprovider": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -225,9 +225,9 @@ func TestHandlerTCP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo"}, + UsedBy: []string{"foo@myprovider"}, }, - "myprovider@test": { + "test@myprovider": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -248,10 +248,10 @@ func TestHandlerTCP_API(t *testing.T) { }, { desc: "one tcp service by id", - path: "/api/tcp/services/myprovider@bar", + path: "/api/tcp/services/bar@myprovider", conf: config.RuntimeConfiguration{ TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@bar": { + "bar@myprovider": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -261,7 +261,7 @@ func TestHandlerTCP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo", "myprovider@test"}, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, }, }, }, @@ -272,10 +272,10 @@ func TestHandlerTCP_API(t *testing.T) { }, { desc: "one tcp service by id, that does not exist", - path: "/api/tcp/services/myprovider@nono", + path: "/api/tcp/services/nono@myprovider", conf: config.RuntimeConfiguration{ TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@bar": { + "bar@myprovider": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -285,7 +285,7 @@ func TestHandlerTCP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo", "myprovider@test"}, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, }, }, }, @@ -295,7 +295,7 @@ func TestHandlerTCP_API(t *testing.T) { }, { desc: "one tcp service by id, but no config", - path: "/api/tcp/services/myprovider@foo", + path: "/api/tcp/services/foo@myprovider", conf: config.RuntimeConfiguration{}, expected: expected{ statusCode: http.StatusNotFound, @@ -326,6 +326,8 @@ func TestHandlerTCP_API(t *testing.T) { return } + assert.Equal(t, resp.Header.Get("Content-Type"), "application/json") + contents, err := ioutil.ReadAll(resp.Body) require.NoError(t, err) @@ -379,20 +381,20 @@ func TestHandlerHTTP_API(t *testing.T) { path: "/api/http/routers", conf: config.RuntimeConfiguration{ Routers: map[string]*config.RouterInfo{ - "myprovider@test": { + "test@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar.other`)", Middlewares: []string{"addPrefixTest", "auth"}, }, }, - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", - Middlewares: []string{"auth", "anotherprovider@addPrefixTest"}, + Middlewares: []string{"auth", "addPrefixTest@anotherprovider"}, }, }, }, @@ -408,25 +410,25 @@ func TestHandlerHTTP_API(t *testing.T) { path: "/api/http/routers?page=2&per_page=1", conf: config.RuntimeConfiguration{ Routers: map[string]*config.RouterInfo{ - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", - Middlewares: []string{"auth", "anotherprovider@addPrefixTest"}, + Middlewares: []string{"auth", "addPrefixTest@anotherprovider"}, }, }, - "myprovider@baz": { + "baz@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`toto.bar`)", }, }, - "myprovider@test": { + "test@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar.other`)", Middlewares: []string{"addPrefixTest", "auth"}, }, @@ -473,15 +475,15 @@ func TestHandlerHTTP_API(t *testing.T) { }, { desc: "one router by id", - path: "/api/http/routers/myprovider@bar", + path: "/api/http/routers/bar@myprovider", conf: config.RuntimeConfiguration{ Routers: map[string]*config.RouterInfo{ - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", - Middlewares: []string{"auth", "anotherprovider@addPrefixTest"}, + Middlewares: []string{"auth", "addPrefixTest@anotherprovider"}, }, }, }, @@ -493,15 +495,15 @@ func TestHandlerHTTP_API(t *testing.T) { }, { desc: "one router by id, that does not exist", - path: "/api/http/routers/myprovider@foo", + path: "/api/http/routers/foo@myprovider", conf: config.RuntimeConfiguration{ Routers: map[string]*config.RouterInfo{ - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", - Middlewares: []string{"auth", "anotherprovider@addPrefixTest"}, + Middlewares: []string{"auth", "addPrefixTest@anotherprovider"}, }, }, }, @@ -512,7 +514,7 @@ func TestHandlerHTTP_API(t *testing.T) { }, { desc: "one router by id, but no config", - path: "/api/http/routers/myprovider@foo", + path: "/api/http/routers/foo@myprovider", conf: config.RuntimeConfiguration{}, expected: expected{ statusCode: http.StatusNotFound, @@ -533,7 +535,7 @@ func TestHandlerHTTP_API(t *testing.T) { path: "/api/http/services", conf: config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@bar": func() *config.ServiceInfo { + "bar@myprovider": func() *config.ServiceInfo { si := &config.ServiceInfo{ Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ @@ -544,12 +546,12 @@ func TestHandlerHTTP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo", "myprovider@test"}, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, } si.UpdateStatus("http://127.0.0.1", "UP") return si }(), - "myprovider@baz": func() *config.ServiceInfo { + "baz@myprovider": func() *config.ServiceInfo { si := &config.ServiceInfo{ Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ @@ -560,7 +562,7 @@ func TestHandlerHTTP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo"}, + UsedBy: []string{"foo@myprovider"}, } si.UpdateStatus("http://127.0.0.2", "UP") return si @@ -578,7 +580,7 @@ func TestHandlerHTTP_API(t *testing.T) { path: "/api/http/services?page=2&per_page=1", conf: config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@bar": func() *config.ServiceInfo { + "bar@myprovider": func() *config.ServiceInfo { si := &config.ServiceInfo{ Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ @@ -589,12 +591,12 @@ func TestHandlerHTTP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo", "myprovider@test"}, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, } si.UpdateStatus("http://127.0.0.1", "UP") return si }(), - "myprovider@baz": func() *config.ServiceInfo { + "baz@myprovider": func() *config.ServiceInfo { si := &config.ServiceInfo{ Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ @@ -605,12 +607,12 @@ func TestHandlerHTTP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo"}, + UsedBy: []string{"foo@myprovider"}, } si.UpdateStatus("http://127.0.0.2", "UP") return si }(), - "myprovider@test": func() *config.ServiceInfo { + "test@myprovider": func() *config.ServiceInfo { si := &config.ServiceInfo{ Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ @@ -621,7 +623,7 @@ func TestHandlerHTTP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo", "myprovider@test"}, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, } si.UpdateStatus("http://127.0.0.4", "UP") return si @@ -636,10 +638,10 @@ func TestHandlerHTTP_API(t *testing.T) { }, { desc: "one service by id", - path: "/api/http/services/myprovider@bar", + path: "/api/http/services/bar@myprovider", conf: config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@bar": func() *config.ServiceInfo { + "bar@myprovider": func() *config.ServiceInfo { si := &config.ServiceInfo{ Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ @@ -650,7 +652,7 @@ func TestHandlerHTTP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo", "myprovider@test"}, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, } si.UpdateStatus("http://127.0.0.1", "UP") return si @@ -664,10 +666,10 @@ func TestHandlerHTTP_API(t *testing.T) { }, { desc: "one service by id, that does not exist", - path: "/api/http/services/myprovider@nono", + path: "/api/http/services/nono@myprovider", conf: config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@bar": func() *config.ServiceInfo { + "bar@myprovider": func() *config.ServiceInfo { si := &config.ServiceInfo{ Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ @@ -678,7 +680,7 @@ func TestHandlerHTTP_API(t *testing.T) { }, }, }, - UsedBy: []string{"myprovider@foo", "myprovider@test"}, + UsedBy: []string{"foo@myprovider", "test@myprovider"}, } si.UpdateStatus("http://127.0.0.1", "UP") return si @@ -691,7 +693,7 @@ func TestHandlerHTTP_API(t *testing.T) { }, { desc: "one service by id, but no config", - path: "/api/http/services/myprovider@foo", + path: "/api/http/services/foo@myprovider", conf: config.RuntimeConfiguration{}, expected: expected{ statusCode: http.StatusNotFound, @@ -712,29 +714,29 @@ func TestHandlerHTTP_API(t *testing.T) { path: "/api/http/middlewares", conf: config.RuntimeConfiguration{ Middlewares: map[string]*config.MiddlewareInfo{ - "myprovider@auth": { + "auth@myprovider": { Middleware: &config.Middleware{ BasicAuth: &config.BasicAuth{ Users: []string{"admin:admin"}, }, }, - UsedBy: []string{"myprovider@bar", "myprovider@test"}, + UsedBy: []string{"bar@myprovider", "test@myprovider"}, }, - "myprovider@addPrefixTest": { + "addPrefixTest@myprovider": { Middleware: &config.Middleware{ AddPrefix: &config.AddPrefix{ Prefix: "/titi", }, }, - UsedBy: []string{"myprovider@test"}, + UsedBy: []string{"test@myprovider"}, }, - "anotherprovider@addPrefixTest": { + "addPrefixTest@anotherprovider": { Middleware: &config.Middleware{ AddPrefix: &config.AddPrefix{ Prefix: "/toto", }, }, - UsedBy: []string{"myprovider@bar"}, + UsedBy: []string{"bar@myprovider"}, }, }, }, @@ -749,29 +751,29 @@ func TestHandlerHTTP_API(t *testing.T) { path: "/api/http/middlewares?page=2&per_page=1", conf: config.RuntimeConfiguration{ Middlewares: map[string]*config.MiddlewareInfo{ - "myprovider@auth": { + "auth@myprovider": { Middleware: &config.Middleware{ BasicAuth: &config.BasicAuth{ Users: []string{"admin:admin"}, }, }, - UsedBy: []string{"myprovider@bar", "myprovider@test"}, + UsedBy: []string{"bar@myprovider", "test@myprovider"}, }, - "myprovider@addPrefixTest": { + "addPrefixTest@myprovider": { Middleware: &config.Middleware{ AddPrefix: &config.AddPrefix{ Prefix: "/titi", }, }, - UsedBy: []string{"myprovider@test"}, + UsedBy: []string{"test@myprovider"}, }, - "anotherprovider@addPrefixTest": { + "addPrefixTest@anotherprovider": { Middleware: &config.Middleware{ AddPrefix: &config.AddPrefix{ Prefix: "/toto", }, }, - UsedBy: []string{"myprovider@bar"}, + UsedBy: []string{"bar@myprovider"}, }, }, }, @@ -783,32 +785,32 @@ func TestHandlerHTTP_API(t *testing.T) { }, { desc: "one middleware by id", - path: "/api/http/middlewares/myprovider@auth", + path: "/api/http/middlewares/auth@myprovider", conf: config.RuntimeConfiguration{ Middlewares: map[string]*config.MiddlewareInfo{ - "myprovider@auth": { + "auth@myprovider": { Middleware: &config.Middleware{ BasicAuth: &config.BasicAuth{ Users: []string{"admin:admin"}, }, }, - UsedBy: []string{"myprovider@bar", "myprovider@test"}, + UsedBy: []string{"bar@myprovider", "test@myprovider"}, }, - "myprovider@addPrefixTest": { + "addPrefixTest@myprovider": { Middleware: &config.Middleware{ AddPrefix: &config.AddPrefix{ Prefix: "/titi", }, }, - UsedBy: []string{"myprovider@test"}, + UsedBy: []string{"test@myprovider"}, }, - "anotherprovider@addPrefixTest": { + "addPrefixTest@anotherprovider": { Middleware: &config.Middleware{ AddPrefix: &config.AddPrefix{ Prefix: "/toto", }, }, - UsedBy: []string{"myprovider@bar"}, + UsedBy: []string{"bar@myprovider"}, }, }, }, @@ -819,16 +821,16 @@ func TestHandlerHTTP_API(t *testing.T) { }, { desc: "one middleware by id, that does not exist", - path: "/api/http/middlewares/myprovider@foo", + path: "/api/http/middlewares/foo@myprovider", conf: config.RuntimeConfiguration{ Middlewares: map[string]*config.MiddlewareInfo{ - "myprovider@auth": { + "auth@myprovider": { Middleware: &config.Middleware{ BasicAuth: &config.BasicAuth{ Users: []string{"admin:admin"}, }, }, - UsedBy: []string{"myprovider@bar", "myprovider@test"}, + UsedBy: []string{"bar@myprovider", "test@myprovider"}, }, }, }, @@ -838,7 +840,7 @@ func TestHandlerHTTP_API(t *testing.T) { }, { desc: "one middleware by id, but no config", - path: "/api/http/middlewares/myprovider@foo", + path: "/api/http/middlewares/foo@myprovider", conf: config.RuntimeConfiguration{}, expected: expected{ statusCode: http.StatusNotFound, @@ -869,6 +871,7 @@ func TestHandlerHTTP_API(t *testing.T) { return } + assert.Equal(t, resp.Header.Get("Content-Type"), "application/json") contents, err := ioutil.ReadAll(resp.Body) require.NoError(t, err) @@ -912,7 +915,7 @@ func TestHandler_Configuration(t *testing.T) { path: "/api/rawdata", conf: config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ @@ -925,21 +928,21 @@ func TestHandler_Configuration(t *testing.T) { }, }, Middlewares: map[string]*config.MiddlewareInfo{ - "myprovider@auth": { + "auth@myprovider": { Middleware: &config.Middleware{ BasicAuth: &config.BasicAuth{ Users: []string{"admin:admin"}, }, }, }, - "myprovider@addPrefixTest": { + "addPrefixTest@myprovider": { Middleware: &config.Middleware{ AddPrefix: &config.AddPrefix{ Prefix: "/titi", }, }, }, - "anotherprovider@addPrefixTest": { + "addPrefixTest@anotherprovider": { Middleware: &config.Middleware{ AddPrefix: &config.AddPrefix{ Prefix: "/toto", @@ -948,25 +951,25 @@ func TestHandler_Configuration(t *testing.T) { }, }, Routers: map[string]*config.RouterInfo{ - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", - Middlewares: []string{"auth", "anotherprovider@addPrefixTest"}, + Middlewares: []string{"auth", "addPrefixTest@anotherprovider"}, }, }, - "myprovider@test": { + "test@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar.other`)", Middlewares: []string{"addPrefixTest", "auth"}, }, }, }, TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@tcpfoo-service": { + "tcpfoo-service@myprovider": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -979,23 +982,22 @@ func TestHandler_Configuration(t *testing.T) { }, }, TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@tcpbar": { + "tcpbar@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@tcpfoo-service", + Service: "tcpfoo-service@myprovider", Rule: "HostSNI(`foo.bar`)", }, }, - "myprovider@tcptest": { + "tcptest@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@tcpfoo-service", + Service: "tcpfoo-service@myprovider", Rule: "HostSNI(`foo.bar.other`)", }, }, }, }, - expected: expected{ statusCode: http.StatusOK, json: "testdata/getrawdata.json", @@ -1011,6 +1013,7 @@ func TestHandler_Configuration(t *testing.T) { // TODO: server status rtConf := &test.conf + rtConf.PopulateUsedBy() handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf) router := mux.NewRouter() @@ -1022,6 +1025,7 @@ func TestHandler_Configuration(t *testing.T) { require.NoError(t, err) assert.Equal(t, test.expected.statusCode, resp.StatusCode) + assert.Equal(t, resp.Header.Get("Content-Type"), "application/json") contents, err := ioutil.ReadAll(resp.Body) require.NoError(t, err) @@ -1054,10 +1058,10 @@ func TestHandler_Configuration(t *testing.T) { func generateHTTPRouters(nbRouters int) map[string]*config.RouterInfo { routers := make(map[string]*config.RouterInfo, nbRouters) for i := 0; i < nbRouters; i++ { - routers[fmt.Sprintf("myprovider@bar%2d", i)] = &config.RouterInfo{ + routers[fmt.Sprintf("bar%2d@myprovider", i)] = &config.RouterInfo{ Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar" + strconv.Itoa(i) + "`)", }, } diff --git a/pkg/api/testdata/getrawdata.json b/pkg/api/testdata/getrawdata.json index 6e122f372..736104e97 100644 --- a/pkg/api/testdata/getrawdata.json +++ b/pkg/api/testdata/getrawdata.json @@ -1,17 +1,17 @@ { "routers": { - "myprovider@bar": { + "bar@myprovider": { "entryPoints": [ "web" ], "middlewares": [ "auth", - "anotherprovider@addPrefixTest" + "addPrefixTest@anotherprovider" ], - "service": "myprovider@foo-service", + "service": "foo-service@myprovider", "rule": "Host(`foo.bar`)" }, - "myprovider@test": { + "test@myprovider": { "entryPoints": [ "web" ], @@ -19,41 +19,41 @@ "addPrefixTest", "auth" ], - "service": "myprovider@foo-service", + "service": "foo-service@myprovider", "rule": "Host(`foo.bar.other`)" } }, "middlewares": { - "anotherprovider@addPrefixTest": { + "addPrefixTest@anotherprovider": { "addPrefix": { "prefix": "/toto" }, "usedBy": [ - "myprovider@bar" + "bar@myprovider" ] }, - "myprovider@addPrefixTest": { + "addPrefixTest@myprovider": { "addPrefix": { "prefix": "/titi" }, "usedBy": [ - "myprovider@test" + "test@myprovider" ] }, - "myprovider@auth": { + "auth@myprovider": { "basicAuth": { "users": [ "admin:admin" ] }, "usedBy": [ - "myprovider@bar", - "myprovider@test" + "bar@myprovider", + "test@myprovider" ] } }, "services": { - "myprovider@foo-service": { + "foo-service@myprovider": { "loadbalancer": { "servers": [ { @@ -63,29 +63,29 @@ "passHostHeader": false }, "usedBy": [ - "myprovider@bar", - "myprovider@test" + "bar@myprovider", + "test@myprovider" ] } }, "tcpRouters": { - "myprovider@tcpbar": { + "tcpbar@myprovider": { "entryPoints": [ "web" ], - "service": "myprovider@tcpfoo-service", + "service": "tcpfoo-service@myprovider", "rule": "HostSNI(`foo.bar`)" }, - "myprovider@tcptest": { + "tcptest@myprovider": { "entryPoints": [ "web" ], - "service": "myprovider@tcpfoo-service", + "service": "tcpfoo-service@myprovider", "rule": "HostSNI(`foo.bar.other`)" } }, "tcpServices": { - "myprovider@tcpfoo-service": { + "tcpfoo-service@myprovider": { "loadbalancer": { "servers": [ { @@ -94,8 +94,8 @@ ] }, "usedBy": [ - "myprovider@tcpbar", - "myprovider@tcptest" + "tcpbar@myprovider", + "tcptest@myprovider" ] } } diff --git a/pkg/api/testdata/middleware-auth.json b/pkg/api/testdata/middleware-auth.json index 5234429a6..074a90163 100644 --- a/pkg/api/testdata/middleware-auth.json +++ b/pkg/api/testdata/middleware-auth.json @@ -4,10 +4,10 @@ "admin:admin" ] }, - "name": "myprovider@auth", - "provider": "myprovider@auth", + "name": "auth@myprovider", + "provider": "myprovider", "usedBy": [ - "myprovider@bar", - "myprovider@test" + "bar@myprovider", + "test@myprovider" ] } \ No newline at end of file diff --git a/pkg/api/testdata/middlewares-page2.json b/pkg/api/testdata/middlewares-page2.json index fd7bd2ff7..d19b50439 100644 --- a/pkg/api/testdata/middlewares-page2.json +++ b/pkg/api/testdata/middlewares-page2.json @@ -3,10 +3,10 @@ "addPrefix": { "prefix": "/titi" }, - "name": "myprovider@addPrefixTest", - "provider": "myprovider@addPrefixTest", + "name": "addPrefixTest@myprovider", + "provider": "myprovider", "usedBy": [ - "myprovider@test" + "test@myprovider" ] } ] \ No newline at end of file diff --git a/pkg/api/testdata/middlewares.json b/pkg/api/testdata/middlewares.json index 8b59b9f55..61717ebec 100644 --- a/pkg/api/testdata/middlewares.json +++ b/pkg/api/testdata/middlewares.json @@ -3,20 +3,20 @@ "addPrefix": { "prefix": "/toto" }, - "name": "anotherprovider@addPrefixTest", - "provider": "anotherprovider@addPrefixTest", + "name": "addPrefixTest@anotherprovider", + "provider": "anotherprovider", "usedBy": [ - "myprovider@bar" + "bar@myprovider" ] }, { "addPrefix": { "prefix": "/titi" }, - "name": "myprovider@addPrefixTest", - "provider": "myprovider@addPrefixTest", + "name": "addPrefixTest@myprovider", + "provider": "myprovider", "usedBy": [ - "myprovider@test" + "test@myprovider" ] }, { @@ -25,11 +25,11 @@ "admin:admin" ] }, - "name": "myprovider@auth", - "provider": "myprovider@auth", + "name": "auth@myprovider", + "provider": "myprovider", "usedBy": [ - "myprovider@bar", - "myprovider@test" + "bar@myprovider", + "test@myprovider" ] } ] \ No newline at end of file diff --git a/pkg/api/testdata/router-bar.json b/pkg/api/testdata/router-bar.json index f6954e717..ea407a3a3 100644 --- a/pkg/api/testdata/router-bar.json +++ b/pkg/api/testdata/router-bar.json @@ -4,10 +4,10 @@ ], "middlewares": [ "auth", - "anotherprovider@addPrefixTest" + "addPrefixTest@anotherprovider" ], - "name": "myprovider@bar", - "provider": "myprovider@bar", + "name": "bar@myprovider", + "provider": "myprovider", "rule": "Host(`foo.bar`)", - "service": "myprovider@foo-service" + "service": "foo-service@myprovider" } \ No newline at end of file diff --git a/pkg/api/testdata/routers-many-lastpage.json b/pkg/api/testdata/routers-many-lastpage.json index e403a9bb0..c13b87a55 100644 --- a/pkg/api/testdata/routers-many-lastpage.json +++ b/pkg/api/testdata/routers-many-lastpage.json @@ -3,45 +3,45 @@ "entryPoints": [ "web" ], - "name": "myprovider@bar14", - "provider": "myprovider@bar14", + "name": "bar14@myprovider", + "provider": "myprovider", "rule": "Host(`foo.bar14`)", - "service": "myprovider@foo-service" + "service": "foo-service@myprovider" }, { "entryPoints": [ "web" ], - "name": "myprovider@bar15", - "provider": "myprovider@bar15", + "name": "bar15@myprovider", + "provider": "myprovider", "rule": "Host(`foo.bar15`)", - "service": "myprovider@foo-service" + "service": "foo-service@myprovider" }, { "entryPoints": [ "web" ], - "name": "myprovider@bar16", - "provider": "myprovider@bar16", + "name": "bar16@myprovider", + "provider": "myprovider", "rule": "Host(`foo.bar16`)", - "service": "myprovider@foo-service" + "service": "foo-service@myprovider" }, { "entryPoints": [ "web" ], - "name": "myprovider@bar17", - "provider": "myprovider@bar17", + "name": "bar17@myprovider", + "provider": "myprovider", "rule": "Host(`foo.bar17`)", - "service": "myprovider@foo-service" + "service": "foo-service@myprovider" }, { "entryPoints": [ "web" ], - "name": "myprovider@bar18", - "provider": "myprovider@bar18", + "name": "bar18@myprovider", + "provider": "myprovider", "rule": "Host(`foo.bar18`)", - "service": "myprovider@foo-service" + "service": "foo-service@myprovider" } ] \ No newline at end of file diff --git a/pkg/api/testdata/routers-page2.json b/pkg/api/testdata/routers-page2.json index e0235aa16..73d97c6d6 100644 --- a/pkg/api/testdata/routers-page2.json +++ b/pkg/api/testdata/routers-page2.json @@ -3,9 +3,9 @@ "entryPoints": [ "web" ], - "name": "myprovider@baz", - "provider": "myprovider@baz", + "name": "baz@myprovider", + "provider": "myprovider", "rule": "Host(`toto.bar`)", - "service": "myprovider@foo-service" + "service": "foo-service@myprovider" } ] \ No newline at end of file diff --git a/pkg/api/testdata/routers.json b/pkg/api/testdata/routers.json index b734701df..f0e4c37b8 100644 --- a/pkg/api/testdata/routers.json +++ b/pkg/api/testdata/routers.json @@ -5,12 +5,12 @@ ], "middlewares": [ "auth", - "anotherprovider@addPrefixTest" + "addPrefixTest@anotherprovider" ], - "name": "myprovider@bar", - "provider": "myprovider@bar", + "name": "bar@myprovider", + "provider": "myprovider", "rule": "Host(`foo.bar`)", - "service": "myprovider@foo-service" + "service": "foo-service@myprovider" }, { "entryPoints": [ @@ -20,9 +20,9 @@ "addPrefixTest", "auth" ], - "name": "myprovider@test", - "provider": "myprovider@test", + "name": "test@myprovider", + "provider": "myprovider", "rule": "Host(`foo.bar.other`)", - "service": "myprovider@foo-service" + "service": "foo-service@myprovider" } ] \ No newline at end of file diff --git a/pkg/api/testdata/service-bar.json b/pkg/api/testdata/service-bar.json index c9f05c4b2..a67023582 100644 --- a/pkg/api/testdata/service-bar.json +++ b/pkg/api/testdata/service-bar.json @@ -7,13 +7,13 @@ } ] }, - "name": "myprovider@bar", - "provider": "myprovider@bar", + "name": "bar@myprovider", + "provider": "myprovider", "serverStatus": { "http://127.0.0.1": "UP" }, "usedBy": [ - "myprovider@foo", - "myprovider@test" + "foo@myprovider", + "test@myprovider" ] } \ No newline at end of file diff --git a/pkg/api/testdata/services-page2.json b/pkg/api/testdata/services-page2.json index fffd50a1b..13e676ea9 100644 --- a/pkg/api/testdata/services-page2.json +++ b/pkg/api/testdata/services-page2.json @@ -8,13 +8,13 @@ } ] }, - "name": "myprovider@baz", - "provider": "myprovider@baz", + "name": "baz@myprovider", + "provider": "myprovider", "serverStatus": { "http://127.0.0.2": "UP" }, "usedBy": [ - "myprovider@foo" + "foo@myprovider" ] } ] \ No newline at end of file diff --git a/pkg/api/testdata/services.json b/pkg/api/testdata/services.json index 3e0015bd9..ceb1fc381 100644 --- a/pkg/api/testdata/services.json +++ b/pkg/api/testdata/services.json @@ -8,14 +8,14 @@ } ] }, - "name": "myprovider@bar", - "provider": "myprovider@bar", + "name": "bar@myprovider", + "provider": "myprovider", "serverStatus": { "http://127.0.0.1": "UP" }, "usedBy": [ - "myprovider@foo", - "myprovider@test" + "foo@myprovider", + "test@myprovider" ] }, { @@ -27,13 +27,13 @@ } ] }, - "name": "myprovider@baz", - "provider": "myprovider@baz", + "name": "baz@myprovider", + "provider": "myprovider", "serverStatus": { "http://127.0.0.2": "UP" }, "usedBy": [ - "myprovider@foo" + "foo@myprovider" ] } ] \ No newline at end of file diff --git a/pkg/api/testdata/tcprouter-bar.json b/pkg/api/testdata/tcprouter-bar.json index 15660263d..b6b244199 100644 --- a/pkg/api/testdata/tcprouter-bar.json +++ b/pkg/api/testdata/tcprouter-bar.json @@ -2,8 +2,8 @@ "entryPoints": [ "web" ], - "name": "myprovider@bar", - "provider": "myprovider@bar", + "name": "bar@myprovider", + "provider": "myprovider", "rule": "Host(`foo.bar`)", - "service": "myprovider@foo-service" + "service": "foo-service@myprovider" } \ No newline at end of file diff --git a/pkg/api/testdata/tcprouters-page2.json b/pkg/api/testdata/tcprouters-page2.json index e0235aa16..73d97c6d6 100644 --- a/pkg/api/testdata/tcprouters-page2.json +++ b/pkg/api/testdata/tcprouters-page2.json @@ -3,9 +3,9 @@ "entryPoints": [ "web" ], - "name": "myprovider@baz", - "provider": "myprovider@baz", + "name": "baz@myprovider", + "provider": "myprovider", "rule": "Host(`toto.bar`)", - "service": "myprovider@foo-service" + "service": "foo-service@myprovider" } ] \ No newline at end of file diff --git a/pkg/api/testdata/tcprouters.json b/pkg/api/testdata/tcprouters.json index ee775e427..b63a9da9c 100644 --- a/pkg/api/testdata/tcprouters.json +++ b/pkg/api/testdata/tcprouters.json @@ -3,19 +3,19 @@ "entryPoints": [ "web" ], - "name": "myprovider@bar", - "provider": "myprovider@bar", + "name": "bar@myprovider", + "provider": "myprovider", "rule": "Host(`foo.bar`)", - "service": "myprovider@foo-service" + "service": "foo-service@myprovider" }, { "entryPoints": [ "web" ], - "name": "myprovider@test", - "provider": "myprovider@test", + "name": "test@myprovider", + "provider": "myprovider", "rule": "Host(`foo.bar.other`)", - "service": "myprovider@foo-service", + "service": "foo-service@myprovider", "tls": { "passthrough": false } diff --git a/pkg/api/testdata/tcpservice-bar.json b/pkg/api/testdata/tcpservice-bar.json index 6a1169f84..31f3f9405 100644 --- a/pkg/api/testdata/tcpservice-bar.json +++ b/pkg/api/testdata/tcpservice-bar.json @@ -6,10 +6,10 @@ } ] }, - "name": "myprovider@bar", - "provider": "myprovider@bar", + "name": "bar@myprovider", + "provider": "myprovider", "usedBy": [ - "myprovider@foo", - "myprovider@test" + "foo@myprovider", + "test@myprovider" ] } \ No newline at end of file diff --git a/pkg/api/testdata/tcpservices-page2.json b/pkg/api/testdata/tcpservices-page2.json index 6aac0fc2d..0a5bf6940 100644 --- a/pkg/api/testdata/tcpservices-page2.json +++ b/pkg/api/testdata/tcpservices-page2.json @@ -7,10 +7,10 @@ } ] }, - "name": "myprovider@baz", - "provider": "myprovider@baz", + "name": "baz@myprovider", + "provider": "myprovider", "usedBy": [ - "myprovider@foo" + "foo@myprovider" ] } ] \ No newline at end of file diff --git a/pkg/api/testdata/tcpservices.json b/pkg/api/testdata/tcpservices.json index 16badca5e..e9820643c 100644 --- a/pkg/api/testdata/tcpservices.json +++ b/pkg/api/testdata/tcpservices.json @@ -7,11 +7,11 @@ } ] }, - "name": "myprovider@bar", - "provider": "myprovider@bar", + "name": "bar@myprovider", + "provider": "myprovider", "usedBy": [ - "myprovider@foo", - "myprovider@test" + "foo@myprovider", + "test@myprovider" ] }, { @@ -22,10 +22,10 @@ } ] }, - "name": "myprovider@baz", - "provider": "myprovider@baz", + "name": "baz@myprovider", + "provider": "myprovider", "usedBy": [ - "myprovider@foo" + "foo@myprovider" ] } ] \ No newline at end of file diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index e2227c12c..366abd05d 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -128,5 +128,5 @@ func contains(cmds []*Command, name string) bool { } func isFlag(arg string) bool { - return len(arg) > 0 && arg[1] == '-' + return len(arg) > 0 && arg[0] == '-' } diff --git a/pkg/cli/loader_env.go b/pkg/cli/loader_env.go index 3bff0251b..07e40faa4 100644 --- a/pkg/cli/loader_env.go +++ b/pkg/cli/loader_env.go @@ -14,24 +14,14 @@ type EnvLoader struct{} // Load loads the command's configuration from the environment variables. func (e *EnvLoader) Load(_ []string, cmd *Command) (bool, error) { - return e.load(os.Environ(), cmd) -} - -func (*EnvLoader) load(environ []string, cmd *Command) (bool, error) { - var found bool - for _, value := range environ { - if strings.HasPrefix(value, "TRAEFIK_") { - found = true - break - } - } - - if !found { + vars := env.FindPrefixedEnvVars(os.Environ(), env.DefaultNamePrefix, cmd.Configuration) + if len(vars) == 0 { return false, nil } - if err := env.Decode(environ, cmd.Configuration); err != nil { - return false, fmt.Errorf("failed to decode configuration from environment variables: %v", err) + if err := env.Decode(vars, env.DefaultNamePrefix, cmd.Configuration); err != nil { + log.WithoutContext().Debug("environment variables", strings.Join(vars, ", ")) + return false, fmt.Errorf("failed to decode configuration from environment variables: %v ", err) } log.WithoutContext().Println("Configuration loaded from environment variables.") diff --git a/pkg/cli/loader_flag.go b/pkg/cli/loader_flag.go index cb529a9f4..fcf2b3076 100644 --- a/pkg/cli/loader_flag.go +++ b/pkg/cli/loader_flag.go @@ -12,6 +12,10 @@ type FlagLoader struct{} // Load loads the command's configuration from flag arguments. func (*FlagLoader) Load(args []string, cmd *Command) (bool, error) { + if len(args) == 0 { + return false, nil + } + if err := flag.Decode(args, cmd.Configuration); err != nil { return false, fmt.Errorf("failed to decode configuration from flags: %v", err) } diff --git a/pkg/config/env/env.go b/pkg/config/env/env.go index e71314b1d..d915272af 100644 --- a/pkg/config/env/env.go +++ b/pkg/config/env/env.go @@ -2,28 +2,38 @@ package env import ( + "fmt" + "regexp" "strings" "github.com/containous/traefik/pkg/config/parser" ) +// DefaultNamePrefix is the default prefix for environment variable names. +const DefaultNamePrefix = "TRAEFIK_" + // Decode decodes the given environment variables into the given element. // The operation goes through four stages roughly summarized as: // env vars -> map // map -> tree of untyped nodes // untyped nodes -> nodes augmented with metadata such as kind (inferred from element) // "typed" nodes -> typed element -func Decode(environ []string, element interface{}) error { +func Decode(environ []string, prefix string, element interface{}) error { + if err := checkPrefix(prefix); err != nil { + return err + } + vars := make(map[string]string) for _, evr := range environ { n := strings.SplitN(evr, "=", 2) - if strings.HasPrefix(strings.ToUpper(n[0]), "TRAEFIK_") { + if strings.HasPrefix(strings.ToUpper(n[0]), prefix) { key := strings.ReplaceAll(strings.ToLower(n[0]), "_", ".") vars[key] = n[1] } } - return parser.Decode(vars, element) + rootName := strings.ToLower(prefix[:len(prefix)-1]) + return parser.Decode(vars, element, rootName) } // Encode encodes the configuration in element into the environment variables represented in the returned Flats. @@ -36,7 +46,7 @@ func Encode(element interface{}) ([]parser.Flat, error) { return nil, nil } - node, err := parser.EncodeToNode(element, false) + node, err := parser.EncodeToNode(element, parser.DefaultRootName, false) if err != nil { return nil, err } @@ -48,3 +58,17 @@ func Encode(element interface{}) ([]parser.Flat, error) { return parser.EncodeToFlat(element, node, parser.FlatOpts{Case: "upper", Separator: "_"}) } + +func checkPrefix(prefix string) error { + prefixPattern := `[a-zA-Z0-9]+_` + matched, err := regexp.MatchString(prefixPattern, prefix) + if err != nil { + return err + } + + if !matched { + return fmt.Errorf("invalid prefix %q, the prefix pattern must match the following pattern: %s", prefix, prefixPattern) + } + + return nil +} diff --git a/pkg/config/env/env_test.go b/pkg/config/env/env_test.go index 342a1f77a..9294f4da6 100644 --- a/pkg/config/env/env_test.go +++ b/pkg/config/env/env_test.go @@ -173,7 +173,7 @@ func TestDecode(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - err := Decode(test.environ, test.element) + err := Decode(test.environ, DefaultNamePrefix, test.element) require.NoError(t, err) assert.Equal(t, test.expected, test.element) @@ -460,39 +460,3 @@ func TestEncode(t *testing.T) { assert.Equal(t, expected, flats) } - -type Ya struct { - Foo *Yaa - Field1 string - Field2 bool - Field3 int - Field4 map[string]string - Field5 map[string]int - Field6 map[string]struct{ Field string } - Field7 map[string]struct{ Field map[string]string } - Field8 map[string]*struct{ Field string } - Field9 map[string]*struct{ Field map[string]string } - Field10 struct{ Field string } - Field11 *struct{ Field string } - Field12 *string - Field13 *bool - Field14 *int - Field15 []int -} - -type Yaa struct { - FieldIn1 string - FieldIn2 bool - FieldIn3 int - FieldIn4 map[string]string - FieldIn5 map[string]int - FieldIn6 map[string]struct{ Field string } - FieldIn7 map[string]struct{ Field map[string]string } - FieldIn8 map[string]*struct{ Field string } - FieldIn9 map[string]*struct{ Field map[string]string } - FieldIn10 struct{ Field string } - FieldIn11 *struct{ Field string } - FieldIn12 *string - FieldIn13 *bool - FieldIn14 *int -} diff --git a/pkg/config/env/filter.go b/pkg/config/env/filter.go new file mode 100644 index 000000000..78604bc38 --- /dev/null +++ b/pkg/config/env/filter.go @@ -0,0 +1,64 @@ +package env + +import ( + "reflect" + "strings" + + "github.com/containous/traefik/pkg/config/parser" +) + +// FindPrefixedEnvVars finds prefixed environment variables. +func FindPrefixedEnvVars(environ []string, prefix string, element interface{}) []string { + prefixes := getRootPrefixes(element, prefix) + + var values []string + for _, px := range prefixes { + for _, value := range environ { + if strings.HasPrefix(value, px) { + values = append(values, value) + } + } + } + + return values +} + +func getRootPrefixes(element interface{}, prefix string) []string { + if element == nil { + return nil + } + + rootType := reflect.TypeOf(element) + + return getPrefixes(prefix, rootType) +} + +func getPrefixes(prefix string, rootType reflect.Type) []string { + var names []string + + if rootType.Kind() == reflect.Ptr { + rootType = rootType.Elem() + } + + if rootType.Kind() != reflect.Struct { + return nil + } + + for i := 0; i < rootType.NumField(); i++ { + field := rootType.Field(i) + + if !parser.IsExported(field) { + continue + } + + if field.Anonymous && + (field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct || field.Type.Kind() == reflect.Struct) { + names = append(names, getPrefixes(prefix, field.Type)...) + continue + } + + names = append(names, prefix+strings.ToUpper(field.Name)) + } + + return names +} diff --git a/pkg/config/env/filter_test.go b/pkg/config/env/filter_test.go new file mode 100644 index 000000000..ecabc8982 --- /dev/null +++ b/pkg/config/env/filter_test.go @@ -0,0 +1,87 @@ +package env + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFindPrefixedEnvVars(t *testing.T) { + testCases := []struct { + desc string + environ []string + element interface{} + expected []string + }{ + { + desc: "exact name", + environ: []string{"TRAEFIK_FOO"}, + element: &Yo{}, + expected: []string{"TRAEFIK_FOO"}, + }, + { + desc: "prefixed name", + environ: []string{"TRAEFIK_FII01"}, + element: &Yo{}, + expected: []string{"TRAEFIK_FII01"}, + }, + { + desc: "excluded env vars", + environ: []string{"TRAEFIK_NOPE", "TRAEFIK_NO"}, + element: &Yo{}, + expected: nil, + }, + { + desc: "filter", + environ: []string{"TRAEFIK_NOPE", "TRAEFIK_NO", "TRAEFIK_FOO", "TRAEFIK_FII01"}, + element: &Yo{}, + expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII01"}, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + vars := FindPrefixedEnvVars(test.environ, DefaultNamePrefix, test.element) + + assert.Equal(t, test.expected, vars) + }) + } +} + +func Test_getRootFieldNames(t *testing.T) { + testCases := []struct { + desc string + element interface{} + expected []string + }{ + { + desc: "simple fields", + element: &Yo{}, + expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII", "TRAEFIK_FUU", "TRAEFIK_YI", "TRAEFIK_YU"}, + }, + { + desc: "embedded struct", + element: &Yu{}, + expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII", "TRAEFIK_FUU"}, + }, + { + desc: "embedded struct pointer", + element: &Ye{}, + expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII", "TRAEFIK_FUU"}, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + names := getRootPrefixes(test.element, DefaultNamePrefix) + + assert.Equal(t, test.expected, names) + }) + } +} diff --git a/pkg/config/env/fixtures_test.go b/pkg/config/env/fixtures_test.go new file mode 100644 index 000000000..bbcb4c469 --- /dev/null +++ b/pkg/config/env/fixtures_test.go @@ -0,0 +1,69 @@ +package env + +type Ya struct { + Foo *Yaa + Field1 string + Field2 bool + Field3 int + Field4 map[string]string + Field5 map[string]int + Field6 map[string]struct{ Field string } + Field7 map[string]struct{ Field map[string]string } + Field8 map[string]*struct{ Field string } + Field9 map[string]*struct{ Field map[string]string } + Field10 struct{ Field string } + Field11 *struct{ Field string } + Field12 *string + Field13 *bool + Field14 *int + Field15 []int +} + +type Yaa struct { + FieldIn1 string + FieldIn2 bool + FieldIn3 int + FieldIn4 map[string]string + FieldIn5 map[string]int + FieldIn6 map[string]struct{ Field string } + FieldIn7 map[string]struct{ Field map[string]string } + FieldIn8 map[string]*struct{ Field string } + FieldIn9 map[string]*struct{ Field map[string]string } + FieldIn10 struct{ Field string } + FieldIn11 *struct{ Field string } + FieldIn12 *string + FieldIn13 *bool + FieldIn14 *int +} + +type Yo struct { + Foo string `description:"Foo description"` + Fii string `description:"Fii description"` + Fuu string `description:"Fuu description"` + Yi *Yi `label:"allowEmpty"` + Yu *Yi +} + +func (y *Yo) SetDefaults() { + y.Foo = "foo" + y.Fii = "fii" +} + +type Yi struct { + Foo string + Fii string + Fuu string +} + +func (y *Yi) SetDefaults() { + y.Foo = "foo" + y.Fii = "fii" +} + +type Yu struct { + Yi +} + +type Ye struct { + *Yi +} diff --git a/pkg/config/file/file_node.go b/pkg/config/file/file_node.go index d23e2344b..33534d922 100644 --- a/pkg/config/file/file_node.go +++ b/pkg/config/file/file_node.go @@ -36,13 +36,13 @@ func decodeFileToNode(filePath string, filters ...string) (*parser.Node, error) return nil, err } - return decodeRawToNode(data, filters...) + return decodeRawToNode(data, parser.DefaultRootName, filters...) default: return nil, fmt.Errorf("unsupported file extension: %s", filePath) } - return decodeRawToNode(data, filters...) + return decodeRawToNode(data, parser.DefaultRootName, filters...) } func getRootFieldNames(element interface{}) []string { diff --git a/pkg/config/file/file_node_test.go b/pkg/config/file/file_node_test.go index cb8217d12..66be88999 100644 --- a/pkg/config/file/file_node_test.go +++ b/pkg/config/file/file_node_test.go @@ -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"}, diff --git a/pkg/config/file/fixtures/sample.toml b/pkg/config/file/fixtures/sample.toml index a3a6373ba..04d8bc9c5 100644 --- a/pkg/config/file/fixtures/sample.toml +++ b/pkg/config/file/fixtures/sample.toml @@ -74,7 +74,6 @@ DefaultRule = "foobar" ExposedByDefault = true DCOSToken = "foobar" - FilterMarathonConstraints = true DialerTimeout = 42 ResponseHeaderTimeout = 42 TLSHandshakeTimeout = 42 diff --git a/pkg/config/file/fixtures/sample.yml b/pkg/config/file/fixtures/sample.yml index adaf8c02b..d5908ad38 100644 --- a/pkg/config/file/fixtures/sample.yml +++ b/pkg/config/file/fixtures/sample.yml @@ -69,7 +69,6 @@ Providers: DefaultRule: foobar ExposedByDefault: true DCOSToken: foobar - FilterMarathonConstraints: true DialerTimeout: 42 ResponseHeaderTimeout: 42 TLSHandshakeTimeout: 42 diff --git a/pkg/config/file/raw_node.go b/pkg/config/file/raw_node.go index 8bcf776d2..f39499898 100644 --- a/pkg/config/file/raw_node.go +++ b/pkg/config/file/raw_node.go @@ -9,9 +9,9 @@ import ( "github.com/containous/traefik/pkg/config/parser" ) -func decodeRawToNode(data map[string]interface{}, filters ...string) (*parser.Node, error) { +func decodeRawToNode(data map[string]interface{}, rootName string, filters ...string) (*parser.Node, error) { root := &parser.Node{ - Name: "traefik", + Name: rootName, } vData := reflect.ValueOf(data) diff --git a/pkg/config/file/raw_node_test.go b/pkg/config/file/raw_node_test.go index 15cab5957..dd3fea861 100644 --- a/pkg/config/file/raw_node_test.go +++ b/pkg/config/file/raw_node_test.go @@ -531,7 +531,7 @@ func Test_decodeRawToNode(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - node, err := decodeRawToNode(test.data) + node, err := decodeRawToNode(test.data, parser.DefaultRootName) require.NoError(t, err) assert.Equal(t, test.expected, node) diff --git a/pkg/config/flag/flag.go b/pkg/config/flag/flag.go index e6e8f51e5..91a356f3b 100644 --- a/pkg/config/flag/flag.go +++ b/pkg/config/flag/flag.go @@ -17,7 +17,7 @@ func Decode(args []string, element interface{}) error { return err } - return parser.Decode(ref, element) + return parser.Decode(ref, element, parser.DefaultRootName) } // Encode encodes the configuration in element into the flags represented in the returned Flats. @@ -30,7 +30,7 @@ func Encode(element interface{}) ([]parser.Flat, error) { return nil, nil } - node, err := parser.EncodeToNode(element, false) + node, err := parser.EncodeToNode(element, parser.DefaultRootName, false) if err != nil { return nil, err } diff --git a/pkg/config/flag/flagparser.go b/pkg/config/flag/flagparser.go index 3520e00c1..1009102cc 100644 --- a/pkg/config/flag/flagparser.go +++ b/pkg/config/flag/flagparser.go @@ -4,6 +4,8 @@ import ( "fmt" "reflect" "strings" + + "github.com/containous/traefik/pkg/config/parser" ) // Parse parses the command-line flag arguments into a map, @@ -96,7 +98,7 @@ func (f *flagSet) parseOne() (bool, error) { } func (f *flagSet) setValue(name string, value string) { - n := strings.ToLower("traefik." + name) + n := strings.ToLower(parser.DefaultRootName + "." + name) v, ok := f.values[n] if ok && f.flagTypes[name] == reflect.Slice { diff --git a/pkg/config/label/label.go b/pkg/config/label/label.go index e821e21aa..6b5478dee 100644 --- a/pkg/config/label/label.go +++ b/pkg/config/label/label.go @@ -13,7 +13,7 @@ func DecodeConfiguration(labels map[string]string) (*config.Configuration, error TCP: &config.TCPConfiguration{}, } - err := parser.Decode(labels, conf, "traefik.http", "traefik.tcp") + err := parser.Decode(labels, conf, parser.DefaultRootName, "traefik.http", "traefik.tcp") if err != nil { return nil, err } @@ -23,11 +23,11 @@ func DecodeConfiguration(labels map[string]string) (*config.Configuration, error // EncodeConfiguration converts a configuration to labels. func EncodeConfiguration(conf *config.Configuration) (map[string]string, error) { - return parser.Encode(conf) + return parser.Encode(conf, parser.DefaultRootName) } // Decode converts the labels to an element. // labels -> [ node -> node + metadata (type) ] -> element (node) func Decode(labels map[string]string, element interface{}, filters ...string) error { - return parser.Decode(labels, element, filters...) + return parser.Decode(labels, element, parser.DefaultRootName, filters...) } diff --git a/pkg/config/parser/element_nodes.go b/pkg/config/parser/element_nodes.go index 3caabcce2..af0d431fa 100644 --- a/pkg/config/parser/element_nodes.go +++ b/pkg/config/parser/element_nodes.go @@ -9,9 +9,9 @@ import ( // EncodeToNode converts an element to a node. // element -> nodes -func EncodeToNode(element interface{}, omitEmpty bool) (*Node, error) { +func EncodeToNode(element interface{}, rootName string, omitEmpty bool) (*Node, error) { rValue := reflect.ValueOf(element) - node := &Node{Name: "traefik"} + node := &Node{Name: rootName} encoder := encoderToNode{omitEmpty: omitEmpty} diff --git a/pkg/config/parser/element_nodes_test.go b/pkg/config/parser/element_nodes_test.go index 854c50fa7..495b0518b 100644 --- a/pkg/config/parser/element_nodes_test.go +++ b/pkg/config/parser/element_nodes_test.go @@ -723,7 +723,7 @@ func TestEncodeToNode(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - node, err := EncodeToNode(test.element, true) + node, err := EncodeToNode(test.element, DefaultRootName, true) if test.expected.error { require.Error(t, err) diff --git a/pkg/config/parser/labels_decode.go b/pkg/config/parser/labels_decode.go index 13e560314..5f8f508e7 100644 --- a/pkg/config/parser/labels_decode.go +++ b/pkg/config/parser/labels_decode.go @@ -6,18 +6,16 @@ import ( "strings" ) -const labelRoot = "traefik" - // DecodeToNode converts the labels to a tree of nodes. // If any filters are present, labels which do not match the filters are skipped. -func DecodeToNode(labels map[string]string, filters ...string) (*Node, error) { +func DecodeToNode(labels map[string]string, rootName string, filters ...string) (*Node, error) { sortedKeys := sortKeys(labels, filters) var node *Node for i, key := range sortedKeys { split := strings.Split(key, ".") - if split[0] != labelRoot { + if split[0] != rootName { return nil, fmt.Errorf("invalid label root %s", split[0]) } diff --git a/pkg/config/parser/labels_decode_test.go b/pkg/config/parser/labels_decode_test.go index a6442b2a3..267265005 100644 --- a/pkg/config/parser/labels_decode_test.go +++ b/pkg/config/parser/labels_decode_test.go @@ -218,7 +218,7 @@ func TestDecodeToNode(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - out, err := DecodeToNode(test.in, test.filters...) + out, err := DecodeToNode(test.in, DefaultRootName, test.filters...) if test.expected.error { require.Error(t, err) diff --git a/pkg/config/parser/node.go b/pkg/config/parser/node.go index f756a0f07..88f0c7998 100644 --- a/pkg/config/parser/node.go +++ b/pkg/config/parser/node.go @@ -2,6 +2,9 @@ package parser import "reflect" +// DefaultRootName is the default name of the root node and the prefix of element name from the resources. +const DefaultRootName = "traefik" + // MapNamePlaceholder is the placeholder for the map name. const MapNamePlaceholder = "" diff --git a/pkg/config/parser/parser.go b/pkg/config/parser/parser.go index e806ecc89..5258a5624 100644 --- a/pkg/config/parser/parser.go +++ b/pkg/config/parser/parser.go @@ -7,8 +7,8 @@ package parser // labels -> tree of untyped nodes // untyped nodes -> nodes augmented with metadata such as kind (inferred from element) // "typed" nodes -> typed element -func Decode(labels map[string]string, element interface{}, filters ...string) error { - node, err := DecodeToNode(labels, filters...) +func Decode(labels map[string]string, element interface{}, rootName string, filters ...string) error { + node, err := DecodeToNode(labels, rootName, filters...) if err != nil { return err } @@ -28,8 +28,8 @@ func Decode(labels map[string]string, element interface{}, filters ...string) er // Encode converts an element to labels. // element -> node (value) -> label (node) -func Encode(element interface{}) (map[string]string, error) { - node, err := EncodeToNode(element, true) +func Encode(element interface{}, rootName string) (map[string]string, error) { + node, err := EncodeToNode(element, rootName, true) if err != nil { return nil, err } diff --git a/pkg/config/runtime.go b/pkg/config/runtime.go index 17590f548..d5a1c512a 100644 --- a/pkg/config/runtime.go +++ b/pkg/config/runtime.go @@ -265,7 +265,7 @@ type TCPServiceInfo struct { func getProviderName(elementName string) string { parts := strings.Split(elementName, "@") if len(parts) > 1 { - return parts[0] + return parts[1] } return "" } @@ -273,7 +273,7 @@ func getProviderName(elementName string) string { func getQualifiedName(provider, elementName string) string { parts := strings.Split(elementName, "@") if len(parts) == 1 { - return provider + "@" + elementName + return elementName + "@" + provider } return elementName } diff --git a/pkg/config/runtime_test.go b/pkg/config/runtime_test.go index c8851fc08..6bb7be89c 100644 --- a/pkg/config/runtime_test.go +++ b/pkg/config/runtime_test.go @@ -25,23 +25,23 @@ func TestPopulateUsedby(t *testing.T) { desc: "One service used by two routers", conf: &config.RuntimeConfiguration{ Routers: map[string]*config.RouterInfo{ - "myprovider@foo": { + "foo@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`bar.foo`)", }, }, - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", }, }, }, Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ @@ -59,12 +59,12 @@ func TestPopulateUsedby(t *testing.T) { }, expected: config.RuntimeConfiguration{ Routers: map[string]*config.RouterInfo{ - "myprovider@foo": {}, - "myprovider@bar": {}, + "foo@myprovider": {}, + "bar@myprovider": {}, }, Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { - UsedBy: []string{"myprovider@bar", "myprovider@foo"}, + "foo-service@myprovider": { + UsedBy: []string{"bar@myprovider", "foo@myprovider"}, }, }, }, @@ -73,7 +73,7 @@ func TestPopulateUsedby(t *testing.T) { desc: "One service used by two routers, but one router with wrong rule", conf: &config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ @@ -84,17 +84,17 @@ func TestPopulateUsedby(t *testing.T) { }, }, Routers: map[string]*config.RouterInfo{ - "myprovider@foo": { + "foo@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "WrongRule(`bar.foo`)", }, }, - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", }, }, @@ -102,12 +102,12 @@ func TestPopulateUsedby(t *testing.T) { }, expected: config.RuntimeConfiguration{ Routers: map[string]*config.RouterInfo{ - "myprovider@foo": {}, - "myprovider@bar": {}, + "foo@myprovider": {}, + "bar@myprovider": {}, }, Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { - UsedBy: []string{"myprovider@bar", "myprovider@foo"}, + "foo-service@myprovider": { + UsedBy: []string{"bar@myprovider", "foo@myprovider"}, }, }, }, @@ -116,17 +116,17 @@ func TestPopulateUsedby(t *testing.T) { desc: "Broken Service used by one Router", conf: &config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { Service: &config.Service{ LoadBalancer: nil, }, }, }, Routers: map[string]*config.RouterInfo{ - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", }, }, @@ -134,11 +134,11 @@ func TestPopulateUsedby(t *testing.T) { }, expected: config.RuntimeConfiguration{ Routers: map[string]*config.RouterInfo{ - "myprovider@bar": {}, + "bar@myprovider": {}, }, Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { - UsedBy: []string{"myprovider@bar"}, + "foo-service@myprovider": { + UsedBy: []string{"bar@myprovider"}, }, }, }, @@ -147,7 +147,7 @@ func TestPopulateUsedby(t *testing.T) { desc: "2 different Services each used by a disctinct router.", conf: &config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ @@ -165,7 +165,7 @@ func TestPopulateUsedby(t *testing.T) { }, }, }, - "myprovider@bar-service": { + "bar-service@myprovider": { Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ @@ -185,17 +185,17 @@ func TestPopulateUsedby(t *testing.T) { }, }, Routers: map[string]*config.RouterInfo{ - "myprovider@foo": { + "foo@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`bar.foo`)", }, }, - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@bar-service", + Service: "bar-service@myprovider", Rule: "Host(`foo.bar`)", }, }, @@ -203,15 +203,15 @@ func TestPopulateUsedby(t *testing.T) { }, expected: config.RuntimeConfiguration{ Routers: map[string]*config.RouterInfo{ - "myprovider@bar": {}, - "myprovider@foo": {}, + "bar@myprovider": {}, + "foo@myprovider": {}, }, Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { - UsedBy: []string{"myprovider@foo"}, + "foo-service@myprovider": { + UsedBy: []string{"foo@myprovider"}, }, - "myprovider@bar-service": { - UsedBy: []string{"myprovider@bar"}, + "bar-service@myprovider": { + UsedBy: []string{"bar@myprovider"}, }, }, }, @@ -220,7 +220,7 @@ func TestPopulateUsedby(t *testing.T) { desc: "2 middlewares both used by 2 Routers", conf: &config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ @@ -233,14 +233,14 @@ func TestPopulateUsedby(t *testing.T) { }, }, Middlewares: map[string]*config.MiddlewareInfo{ - "myprovider@auth": { + "auth@myprovider": { Middleware: &config.Middleware{ BasicAuth: &config.BasicAuth{ Users: []string{"admin:admin"}, }, }, }, - "myprovider@addPrefixTest": { + "addPrefixTest@myprovider": { Middleware: &config.Middleware{ AddPrefix: &config.AddPrefix{ Prefix: "/toto", @@ -249,18 +249,18 @@ func TestPopulateUsedby(t *testing.T) { }, }, Routers: map[string]*config.RouterInfo{ - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", Middlewares: []string{"auth", "addPrefixTest"}, }, }, - "myprovider@test": { + "test@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar.other`)", Middlewares: []string{"addPrefixTest", "auth"}, }, @@ -269,20 +269,20 @@ func TestPopulateUsedby(t *testing.T) { }, expected: config.RuntimeConfiguration{ Routers: map[string]*config.RouterInfo{ - "myprovider@bar": {}, - "myprovider@test": {}, + "bar@myprovider": {}, + "test@myprovider": {}, }, Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { - UsedBy: []string{"myprovider@bar", "myprovider@test"}, + "foo-service@myprovider": { + UsedBy: []string{"bar@myprovider", "test@myprovider"}, }, }, Middlewares: map[string]*config.MiddlewareInfo{ - "myprovider@auth": { - UsedBy: []string{"myprovider@bar", "myprovider@test"}, + "auth@myprovider": { + UsedBy: []string{"bar@myprovider", "test@myprovider"}, }, - "myprovider@addPrefixTest": { - UsedBy: []string{"myprovider@bar", "myprovider@test"}, + "addPrefixTest@myprovider": { + UsedBy: []string{"bar@myprovider", "test@myprovider"}, }, }, }, @@ -291,7 +291,7 @@ func TestPopulateUsedby(t *testing.T) { desc: "Unknown middleware is not used by the Router", conf: &config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ @@ -304,7 +304,7 @@ func TestPopulateUsedby(t *testing.T) { }, }, Middlewares: map[string]*config.MiddlewareInfo{ - "myprovider@auth": { + "auth@myprovider": { Middleware: &config.Middleware{ BasicAuth: &config.BasicAuth{ Users: []string{"admin:admin"}, @@ -313,10 +313,10 @@ func TestPopulateUsedby(t *testing.T) { }, }, Routers: map[string]*config.RouterInfo{ - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", Middlewares: []string{"unknown"}, }, @@ -325,8 +325,8 @@ func TestPopulateUsedby(t *testing.T) { }, expected: config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { - UsedBy: []string{"myprovider@bar"}, + "foo-service@myprovider": { + UsedBy: []string{"bar@myprovider"}, }, }, }, @@ -335,7 +335,7 @@ func TestPopulateUsedby(t *testing.T) { desc: "Broken middleware is used by Router", conf: &config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ @@ -348,7 +348,7 @@ func TestPopulateUsedby(t *testing.T) { }, }, Middlewares: map[string]*config.MiddlewareInfo{ - "myprovider@auth": { + "auth@myprovider": { Middleware: &config.Middleware{ BasicAuth: &config.BasicAuth{ Users: []string{"badConf"}, @@ -357,28 +357,28 @@ func TestPopulateUsedby(t *testing.T) { }, }, Routers: map[string]*config.RouterInfo{ - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", - Middlewares: []string{"myprovider@auth"}, + Middlewares: []string{"auth@myprovider"}, }, }, }, }, expected: config.RuntimeConfiguration{ Routers: map[string]*config.RouterInfo{ - "myprovider@bar": {}, + "bar@myprovider": {}, }, Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { - UsedBy: []string{"myprovider@bar"}, + "foo-service@myprovider": { + UsedBy: []string{"bar@myprovider"}, }, }, Middlewares: map[string]*config.MiddlewareInfo{ - "myprovider@auth": { - UsedBy: []string{"myprovider@bar"}, + "auth@myprovider": { + UsedBy: []string{"bar@myprovider"}, }, }, }, @@ -387,7 +387,7 @@ func TestPopulateUsedby(t *testing.T) { desc: "2 middlewares from 2 disctinct providers both used by 2 Routers", conf: &config.RuntimeConfiguration{ Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ @@ -400,21 +400,21 @@ func TestPopulateUsedby(t *testing.T) { }, }, Middlewares: map[string]*config.MiddlewareInfo{ - "myprovider@auth": { + "auth@myprovider": { Middleware: &config.Middleware{ BasicAuth: &config.BasicAuth{ Users: []string{"admin:admin"}, }, }, }, - "myprovider@addPrefixTest": { + "addPrefixTest@myprovider": { Middleware: &config.Middleware{ AddPrefix: &config.AddPrefix{ Prefix: "/titi", }, }, }, - "anotherprovider@addPrefixTest": { + "addPrefixTest@anotherprovider": { Middleware: &config.Middleware{ AddPrefix: &config.AddPrefix{ Prefix: "/toto", @@ -423,18 +423,18 @@ func TestPopulateUsedby(t *testing.T) { }, }, Routers: map[string]*config.RouterInfo{ - "myprovider@bar": { + "bar@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", - Middlewares: []string{"auth", "anotherprovider@addPrefixTest"}, + Middlewares: []string{"auth", "addPrefixTest@anotherprovider"}, }, }, - "myprovider@test": { + "test@myprovider": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar.other`)", Middlewares: []string{"addPrefixTest", "auth"}, }, @@ -443,23 +443,23 @@ func TestPopulateUsedby(t *testing.T) { }, expected: config.RuntimeConfiguration{ Routers: map[string]*config.RouterInfo{ - "myprovider@bar": {}, - "myprovider@test": {}, + "bar@myprovider": {}, + "test@myprovider": {}, }, Services: map[string]*config.ServiceInfo{ - "myprovider@foo-service": { - UsedBy: []string{"myprovider@bar", "myprovider@test"}, + "foo-service@myprovider": { + UsedBy: []string{"bar@myprovider", "test@myprovider"}, }, }, Middlewares: map[string]*config.MiddlewareInfo{ - "myprovider@auth": { - UsedBy: []string{"myprovider@bar", "myprovider@test"}, + "auth@myprovider": { + UsedBy: []string{"bar@myprovider", "test@myprovider"}, }, - "myprovider@addPrefixTest": { - UsedBy: []string{"myprovider@test"}, + "addPrefixTest@myprovider": { + UsedBy: []string{"test@myprovider"}, }, - "anotherprovider@addPrefixTest": { - UsedBy: []string{"myprovider@bar"}, + "addPrefixTest@anotherprovider": { + UsedBy: []string{"bar@myprovider"}, }, }, }, @@ -470,23 +470,23 @@ func TestPopulateUsedby(t *testing.T) { desc: "TCP, One service used by two routers", conf: &config.RuntimeConfiguration{ TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@foo": { + "foo@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`bar.foo`)", }, }, - "myprovider@bar": { + "bar@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", }, }, }, TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -506,12 +506,12 @@ func TestPopulateUsedby(t *testing.T) { }, expected: config.RuntimeConfiguration{ TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@foo": {}, - "myprovider@bar": {}, + "foo@myprovider": {}, + "bar@myprovider": {}, }, TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@foo-service": { - UsedBy: []string{"myprovider@bar", "myprovider@foo"}, + "foo-service@myprovider": { + UsedBy: []string{"bar@myprovider", "foo@myprovider"}, }, }, }, @@ -520,7 +520,7 @@ func TestPopulateUsedby(t *testing.T) { desc: "TCP, One service used by two routers, but one router with wrong rule", conf: &config.RuntimeConfiguration{ TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -533,17 +533,17 @@ func TestPopulateUsedby(t *testing.T) { }, }, TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@foo": { + "foo@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "WrongRule(`bar.foo`)", }, }, - "myprovider@bar": { + "bar@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", }, }, @@ -551,12 +551,12 @@ func TestPopulateUsedby(t *testing.T) { }, expected: config.RuntimeConfiguration{ TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@foo": {}, - "myprovider@bar": {}, + "foo@myprovider": {}, + "bar@myprovider": {}, }, TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@foo-service": { - UsedBy: []string{"myprovider@bar", "myprovider@foo"}, + "foo-service@myprovider": { + UsedBy: []string{"bar@myprovider", "foo@myprovider"}, }, }, }, @@ -565,17 +565,17 @@ func TestPopulateUsedby(t *testing.T) { desc: "TCP, Broken Service used by one Router", conf: &config.RuntimeConfiguration{ TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { TCPService: &config.TCPService{ LoadBalancer: nil, }, }, }, TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@bar": { + "bar@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`foo.bar`)", }, }, @@ -583,11 +583,11 @@ func TestPopulateUsedby(t *testing.T) { }, expected: config.RuntimeConfiguration{ TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@bar": {}, + "bar@myprovider": {}, }, TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@foo-service": { - UsedBy: []string{"myprovider@bar"}, + "foo-service@myprovider": { + UsedBy: []string{"bar@myprovider"}, }, }, }, @@ -596,7 +596,7 @@ func TestPopulateUsedby(t *testing.T) { desc: "TCP, 2 different Services each used by a disctinct router.", conf: &config.RuntimeConfiguration{ TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@foo-service": { + "foo-service@myprovider": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -612,7 +612,7 @@ func TestPopulateUsedby(t *testing.T) { }, }, }, - "myprovider@bar-service": { + "bar-service@myprovider": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -630,17 +630,17 @@ func TestPopulateUsedby(t *testing.T) { }, }, TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@foo": { + "foo@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`bar.foo`)", }, }, - "myprovider@bar": { + "bar@myprovider": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@bar-service", + Service: "bar-service@myprovider", Rule: "Host(`foo.bar`)", }, }, @@ -648,15 +648,15 @@ func TestPopulateUsedby(t *testing.T) { }, expected: config.RuntimeConfiguration{ TCPRouters: map[string]*config.TCPRouterInfo{ - "myprovider@bar": {}, - "myprovider@foo": {}, + "bar@myprovider": {}, + "foo@myprovider": {}, }, TCPServices: map[string]*config.TCPServiceInfo{ - "myprovider@foo-service": { - UsedBy: []string{"myprovider@foo"}, + "foo-service@myprovider": { + UsedBy: []string{"foo@myprovider"}, }, - "myprovider@bar-service": { - UsedBy: []string{"myprovider@bar"}, + "bar-service@myprovider": { + UsedBy: []string{"bar@myprovider"}, }, }, }, @@ -716,7 +716,7 @@ func TestGetTCPRoutersByEntrypoints(t *testing.T) { Routers: map[string]*config.Router{ "foo": { EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`bar.foo`)", }, }, @@ -725,7 +725,7 @@ func TestGetTCPRoutersByEntrypoints(t *testing.T) { Routers: map[string]*config.TCPRouter{ "foo": { EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "HostSNI(`bar.foo`)", }, }, @@ -741,17 +741,17 @@ func TestGetTCPRoutersByEntrypoints(t *testing.T) { Routers: map[string]*config.Router{ "foo": { EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`bar.foo`)", }, "bar": { EntryPoints: []string{"webs"}, - Service: "myprovider@bar-service", + Service: "bar-service@myprovider", Rule: "Host(`foo.bar`)", }, "foobar": { EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "Host(`bar.foobar`)", }, }, @@ -760,17 +760,17 @@ func TestGetTCPRoutersByEntrypoints(t *testing.T) { Routers: map[string]*config.TCPRouter{ "foo": { EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "HostSNI(`bar.foo`)", }, "bar": { EntryPoints: []string{"webs"}, - Service: "myprovider@bar-service", + Service: "bar-service@myprovider", Rule: "HostSNI(`foo.bar`)", }, "foobar": { EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "HostSNI(`bar.foobar`)", }, }, @@ -782,14 +782,14 @@ func TestGetTCPRoutersByEntrypoints(t *testing.T) { "foo": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "HostSNI(`bar.foo`)", }, }, "foobar": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "HostSNI(`bar.foobar`)", }, }, @@ -803,17 +803,17 @@ func TestGetTCPRoutersByEntrypoints(t *testing.T) { Routers: map[string]*config.Router{ "foo": { EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`bar.foo`)", }, "bar": { EntryPoints: []string{"webs"}, - Service: "myprovider@bar-service", + Service: "bar-service@myprovider", Rule: "Host(`foo.bar`)", }, "foobar": { EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "Host(`bar.foobar`)", }, }, @@ -822,17 +822,17 @@ func TestGetTCPRoutersByEntrypoints(t *testing.T) { Routers: map[string]*config.TCPRouter{ "foo": { EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "HostSNI(`bar.foo`)", }, "bar": { EntryPoints: []string{"webs"}, - Service: "myprovider@bar-service", + Service: "bar-service@myprovider", Rule: "HostSNI(`foo.bar`)", }, "foobar": { EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "HostSNI(`bar.foobar`)", }, }, @@ -844,14 +844,14 @@ func TestGetTCPRoutersByEntrypoints(t *testing.T) { "foo": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "HostSNI(`bar.foo`)", }, }, "foobar": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "HostSNI(`bar.foobar`)", }, }, @@ -861,14 +861,14 @@ func TestGetTCPRoutersByEntrypoints(t *testing.T) { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"webs"}, - Service: "myprovider@bar-service", + Service: "bar-service@myprovider", Rule: "HostSNI(`foo.bar`)", }, }, "foobar": { TCPRouter: &config.TCPRouter{ EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "HostSNI(`bar.foobar`)", }, }, @@ -914,7 +914,7 @@ func TestGetRoutersByEntrypoints(t *testing.T) { Routers: map[string]*config.Router{ "foo": { EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`bar.foo`)", }, }, @@ -923,7 +923,7 @@ func TestGetRoutersByEntrypoints(t *testing.T) { Routers: map[string]*config.TCPRouter{ "foo": { EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "HostSNI(`bar.foo`)", }, }, @@ -939,17 +939,17 @@ func TestGetRoutersByEntrypoints(t *testing.T) { Routers: map[string]*config.Router{ "foo": { EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`bar.foo`)", }, "bar": { EntryPoints: []string{"webs"}, - Service: "myprovider@bar-service", + Service: "bar-service@myprovider", Rule: "Host(`foo.bar`)", }, "foobar": { EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "Host(`bar.foobar`)", }, }, @@ -958,17 +958,17 @@ func TestGetRoutersByEntrypoints(t *testing.T) { Routers: map[string]*config.TCPRouter{ "foo": { EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "HostSNI(`bar.foo`)", }, "bar": { EntryPoints: []string{"webs"}, - Service: "myprovider@bar-service", + Service: "bar-service@myprovider", Rule: "HostSNI(`foo.bar`)", }, "foobar": { EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "HostSNI(`bar.foobar`)", }, }, @@ -980,14 +980,14 @@ func TestGetRoutersByEntrypoints(t *testing.T) { "foo": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`bar.foo`)", }, }, "foobar": { Router: &config.Router{ EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "Host(`bar.foobar`)", }, }, @@ -1001,17 +1001,17 @@ func TestGetRoutersByEntrypoints(t *testing.T) { Routers: map[string]*config.Router{ "foo": { EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`bar.foo`)", }, "bar": { EntryPoints: []string{"webs"}, - Service: "myprovider@bar-service", + Service: "bar-service@myprovider", Rule: "Host(`foo.bar`)", }, "foobar": { EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "Host(`bar.foobar`)", }, }, @@ -1020,17 +1020,17 @@ func TestGetRoutersByEntrypoints(t *testing.T) { Routers: map[string]*config.TCPRouter{ "foo": { EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "HostSNI(`bar.foo`)", }, "bar": { EntryPoints: []string{"webs"}, - Service: "myprovider@bar-service", + Service: "bar-service@myprovider", Rule: "HostSNI(`foo.bar`)", }, "foobar": { EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "HostSNI(`bar.foobar`)", }, }, @@ -1042,14 +1042,14 @@ func TestGetRoutersByEntrypoints(t *testing.T) { "foo": { Router: &config.Router{ EntryPoints: []string{"web"}, - Service: "myprovider@foo-service", + Service: "foo-service@myprovider", Rule: "Host(`bar.foo`)", }, }, "foobar": { Router: &config.Router{ EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "Host(`bar.foobar`)", }, }, @@ -1059,14 +1059,14 @@ func TestGetRoutersByEntrypoints(t *testing.T) { Router: &config.Router{ EntryPoints: []string{"webs"}, - Service: "myprovider@bar-service", + Service: "bar-service@myprovider", Rule: "Host(`foo.bar`)", }, }, "foobar": { Router: &config.Router{ EntryPoints: []string{"web", "webs"}, - Service: "myprovider@foobar-service", + Service: "foobar-service@myprovider", Rule: "Host(`bar.foobar`)", }, }, diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 11d50bbb0..c6ff9f4bd 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -55,7 +55,7 @@ type Configuration struct { Ping *ping.Handler `description:"Enable ping." export:"true" label:"allowEmpty"` // Rest *rest.Provider `description:"Enable Rest backend with default settings" export:"true"` - Log *types.TraefikLog `description:"Traefik log settings." export:"true"` + Log *types.TraefikLog `description:"Traefik log settings." export:"true" label:"allowEmpty"` AccessLog *types.AccessLog `description:"Access log settings." export:"true" label:"allowEmpty"` Tracing *Tracing `description:"OpenTracing configuration." export:"true" label:"allowEmpty"` diff --git a/pkg/provider/constrainer.go b/pkg/provider/constrainer.go deleted file mode 100644 index 2afe1abcd..000000000 --- a/pkg/provider/constrainer.go +++ /dev/null @@ -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 -} diff --git a/pkg/provider/constraints/constraints.go b/pkg/provider/constraints/constraints.go new file mode 100644 index 000000000..5094442b3 --- /dev/null +++ b/pkg/provider/constraints/constraints.go @@ -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) + } +} diff --git a/pkg/provider/constraints/constraints_test.go b/pkg/provider/constraints/constraints_test.go new file mode 100644 index 000000000..c6198a329 --- /dev/null +++ b/pkg/provider/constraints/constraints_test.go @@ -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) + }) + } +} diff --git a/pkg/provider/docker/config.go b/pkg/provider/docker/config.go index c8cf95d3f..ccb293820 100644 --- a/pkg/provider/docker/config.go +++ b/pkg/provider/docker/config.go @@ -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 } diff --git a/pkg/provider/docker/config_test.go b/pkg/provider/docker/config_test.go index ac801157d..f7b8d94fb 100644 --- a/pkg/provider/docker/config_test.go +++ b/pkg/provider/docker/config_test.go @@ -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{}, diff --git a/pkg/provider/docker/docker.go b/pkg/provider/docker/docker.go index 09bea10ce..3e4507647 100644 --- a/pkg/provider/docker/docker.go +++ b/pkg/provider/docker/docker.go @@ -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."` diff --git a/pkg/provider/docker/label.go b/pkg/provider/docker/label.go index 99d8fb349..53705c0a7 100644 --- a/pkg/provider/docker/label.go +++ b/pkg/provider/docker/label.go @@ -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 } diff --git a/pkg/provider/kubernetes/crd/client.go b/pkg/provider/kubernetes/crd/client.go index de37b2446..ab5fb3f0e 100644 --- a/pkg/provider/kubernetes/crd/client.go +++ b/pkg/provider/kubernetes/crd/client.go @@ -49,6 +49,7 @@ type Client interface { GetIngressRoutes() []*v1alpha1.IngressRoute GetIngressRouteTCPs() []*v1alpha1.IngressRouteTCP GetMiddlewares() []*v1alpha1.Middleware + GetTLSOptions() []*v1alpha1.TLSOption GetIngresses() []*extensionsv1beta1.Ingress GetService(namespace, name string) (*corev1.Service, bool, error) @@ -158,6 +159,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< factoryCrd.Traefik().V1alpha1().IngressRoutes().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().Middlewares().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().IngressRouteTCPs().Informer().AddEventHandler(eventHandler) + factoryCrd.Traefik().V1alpha1().TLSOptions().Informer().AddEventHandler(eventHandler) factoryKube := informers.NewFilteredSharedInformerFactory(c.csKube, resyncPeriod, ns, nil) factoryKube.Extensions().V1beta1().Ingresses().Informer().AddEventHandler(eventHandler) @@ -241,6 +243,21 @@ func (c *clientWrapper) GetMiddlewares() []*v1alpha1.Middleware { return result } +// GetTLSOptions +func (c *clientWrapper) GetTLSOptions() []*v1alpha1.TLSOption { + var result []*v1alpha1.TLSOption + + for ns, factory := range c.factoriesCrd { + options, err := factory.Traefik().V1alpha1().TLSOptions().Lister().List(c.labelSelector) + if err != nil { + log.Errorf("Failed to list tls options in namespace %s: %s", ns, err) + } + result = append(result, options...) + } + + return result +} + // GetIngresses returns all Ingresses for observed namespaces in the cluster. func (c *clientWrapper) GetIngresses() []*extensionsv1beta1.Ingress { var result []*extensionsv1beta1.Ingress diff --git a/pkg/provider/kubernetes/crd/client_mock_test.go b/pkg/provider/kubernetes/crd/client_mock_test.go index 70ff381fe..9d0478bf7 100644 --- a/pkg/provider/kubernetes/crd/client_mock_test.go +++ b/pkg/provider/kubernetes/crd/client_mock_test.go @@ -37,6 +37,7 @@ type clientMock struct { ingressRoutes []*v1alpha1.IngressRoute ingressRouteTCPs []*v1alpha1.IngressRouteTCP middlewares []*v1alpha1.Middleware + tlsOptions []*v1alpha1.TLSOption watchChan chan interface{} } @@ -63,6 +64,8 @@ func newClientMock(paths ...string) clientMock { c.ingressRouteTCPs = append(c.ingressRouteTCPs, o) case *v1alpha1.Middleware: c.middlewares = append(c.middlewares, o) + case *v1alpha1.TLSOption: + c.tlsOptions = append(c.tlsOptions, o) case *v1beta12.Ingress: c.ingresses = append(c.ingresses, o) case *corev1.Secret: @@ -88,6 +91,20 @@ func (c clientMock) GetMiddlewares() []*v1alpha1.Middleware { return c.middlewares } +func (c clientMock) GetTLSOptions() []*v1alpha1.TLSOption { + return c.tlsOptions +} + +func (c clientMock) GetTLSOption(namespace, name string) (*v1alpha1.TLSOption, bool, error) { + for _, option := range c.tlsOptions { + if option.Namespace == namespace && option.Name == name { + return option, true, nil + } + } + + return nil, false, nil +} + func (c clientMock) GetIngresses() []*extensionsv1beta1.Ingress { return c.ingresses } diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_bad_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_bad_tls_options.yml new file mode 100644 index 000000000..81c08b434 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_bad_tls_options.yml @@ -0,0 +1,70 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secretCA1 + namespace: default + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: secretCA2 + namespace: default + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientca: + secretnames: + - secretCA1 + - secretUnknown + - emptySecret + optional: true + +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteTCP +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 + + tls: + options: + name: foo diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_options.yml new file mode 100644 index 000000000..8666918bc --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_options.yml @@ -0,0 +1,69 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secretCA1 + namespace: default + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: secretCA2 + namespace: default + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientca: + secretnames: + - secretCA1 + - secretCA2 + optional: true + +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteTCP +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 + + tls: + options: + name: foo diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_options_and_specific_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_options_and_specific_namespace.yml new file mode 100644 index 000000000..49c1b4bb5 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_options_and_specific_namespace.yml @@ -0,0 +1,70 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secretCA1 + namespace: myns + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: secretCA2 + namespace: myns + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: myns + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientca: + secretnames: + - secretCA1 + - secretCA2 + optional: true + +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteTCP +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 + + tls: + options: + name: foo + namespace: myns diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_unknown_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_unknown_tls_options.yml new file mode 100644 index 000000000..d42471762 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_unknown_tls_options.yml @@ -0,0 +1,30 @@ +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteTCP +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 + + tls: + options: + name: unknown diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_unknown_tls_options_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_unknown_tls_options_namespace.yml new file mode 100644 index 000000000..743ab8072 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_unknown_tls_options_namespace.yml @@ -0,0 +1,31 @@ +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteTCP +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 + + tls: + options: + name: foo + namespace: unknown diff --git a/pkg/provider/kubernetes/crd/fixtures/with_bad_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/with_bad_tls_options.yml new file mode 100644 index 000000000..a1db6f972 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_bad_tls_options.yml @@ -0,0 +1,61 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secretCA1 + namespace: default + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: badSecret + namespace: default + +data: + tls.ca: + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientca: + secretnames: + - secretCA1 + - secretUnknown + - emptySecret + optional: true + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - web + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 + + tls: + options: + name: foo diff --git a/pkg/provider/kubernetes/crd/fixtures/with_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/with_tls_options.yml new file mode 100644 index 000000000..10839d487 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_tls_options.yml @@ -0,0 +1,60 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secretCA1 + namespace: default + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: secretCA2 + namespace: default + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientca: + secretnames: + - secretCA1 + - secretCA2 + optional: true + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - web + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 + + tls: + options: + name: foo diff --git a/pkg/provider/kubernetes/crd/fixtures/with_tls_options_and_specific_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/with_tls_options_and_specific_namespace.yml new file mode 100644 index 000000000..c378a82f3 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_tls_options_and_specific_namespace.yml @@ -0,0 +1,61 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secretCA1 + namespace: myns + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: secretCA2 + namespace: myns + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: myns + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientca: + secretnames: + - secretCA1 + - secretCA2 + optional: true + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - web + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 + + tls: + options: + name: foo + namespace: myns \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/fixtures/with_unknown_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/with_unknown_tls_options.yml new file mode 100644 index 000000000..d39f40468 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_unknown_tls_options.yml @@ -0,0 +1,31 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - web + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 + + tls: + options: + name: unknown diff --git a/pkg/provider/kubernetes/crd/fixtures/with_unknown_tls_options_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/with_unknown_tls_options_namespace.yml new file mode 100644 index 000000000..7b2b3111a --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_unknown_tls_options_namespace.yml @@ -0,0 +1,32 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - web + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 + + tls: + options: + name: foo + namespace: unknown \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsoption.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsoption.go new file mode 100644 index 000000000..f7450621a --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsoption.go @@ -0,0 +1,136 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2019 Containous SAS + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/containous/traefik/pkg/provider/kubernetes/crd/traefik/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeTLSOptions implements TLSOptionInterface +type FakeTLSOptions struct { + Fake *FakeTraefikV1alpha1 + ns string +} + +var tlsoptionsResource = schema.GroupVersionResource{Group: "traefik.containo.us", Version: "v1alpha1", Resource: "tlsoptions"} + +var tlsoptionsKind = schema.GroupVersionKind{Group: "traefik.containo.us", Version: "v1alpha1", Kind: "TLSOption"} + +// Get takes name of the tLSOption, and returns the corresponding tLSOption object, and an error if there is any. +func (c *FakeTLSOptions) Get(name string, options v1.GetOptions) (result *v1alpha1.TLSOption, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(tlsoptionsResource, c.ns, name), &v1alpha1.TLSOption{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TLSOption), err +} + +// List takes label and field selectors, and returns the list of TLSOptions that match those selectors. +func (c *FakeTLSOptions) List(opts v1.ListOptions) (result *v1alpha1.TLSOptionList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(tlsoptionsResource, tlsoptionsKind, c.ns, opts), &v1alpha1.TLSOptionList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.TLSOptionList{ListMeta: obj.(*v1alpha1.TLSOptionList).ListMeta} + for _, item := range obj.(*v1alpha1.TLSOptionList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested tLSOptions. +func (c *FakeTLSOptions) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(tlsoptionsResource, c.ns, opts)) + +} + +// Create takes the representation of a tLSOption and creates it. Returns the server's representation of the tLSOption, and an error, if there is any. +func (c *FakeTLSOptions) Create(tLSOption *v1alpha1.TLSOption) (result *v1alpha1.TLSOption, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(tlsoptionsResource, c.ns, tLSOption), &v1alpha1.TLSOption{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TLSOption), err +} + +// Update takes the representation of a tLSOption and updates it. Returns the server's representation of the tLSOption, and an error, if there is any. +func (c *FakeTLSOptions) Update(tLSOption *v1alpha1.TLSOption) (result *v1alpha1.TLSOption, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(tlsoptionsResource, c.ns, tLSOption), &v1alpha1.TLSOption{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TLSOption), err +} + +// Delete takes name of the tLSOption and deletes it. Returns an error if one occurs. +func (c *FakeTLSOptions) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(tlsoptionsResource, c.ns, name), &v1alpha1.TLSOption{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeTLSOptions) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(tlsoptionsResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha1.TLSOptionList{}) + return err +} + +// Patch applies the patch and returns the patched tLSOption. +func (c *FakeTLSOptions) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TLSOption, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(tlsoptionsResource, c.ns, name, data, subresources...), &v1alpha1.TLSOption{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TLSOption), err +} diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go index 2a4094a68..92998021f 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go @@ -48,6 +48,10 @@ func (c *FakeTraefikV1alpha1) Middlewares(namespace string) v1alpha1.MiddlewareI return &FakeMiddlewares{c, namespace} } +func (c *FakeTraefikV1alpha1) TLSOptions(namespace string) v1alpha1.TLSOptionInterface { + return &FakeTLSOptions{c, namespace} +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeTraefikV1alpha1) RESTClient() rest.Interface { diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go index 2a9108930..30a0f1a1b 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go @@ -31,3 +31,5 @@ type IngressRouteExpansion interface{} type IngressRouteTCPExpansion interface{} type MiddlewareExpansion interface{} + +type TLSOptionExpansion interface{} diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/tlsoption.go new file mode 100644 index 000000000..800b1f6cc --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/tlsoption.go @@ -0,0 +1,165 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2019 Containous SAS + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + scheme "github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme" + v1alpha1 "github.com/containous/traefik/pkg/provider/kubernetes/crd/traefik/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// TLSOptionsGetter has a method to return a TLSOptionInterface. +// A group's client should implement this interface. +type TLSOptionsGetter interface { + TLSOptions(namespace string) TLSOptionInterface +} + +// TLSOptionInterface has methods to work with TLSOption resources. +type TLSOptionInterface interface { + Create(*v1alpha1.TLSOption) (*v1alpha1.TLSOption, error) + Update(*v1alpha1.TLSOption) (*v1alpha1.TLSOption, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha1.TLSOption, error) + List(opts v1.ListOptions) (*v1alpha1.TLSOptionList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TLSOption, err error) + TLSOptionExpansion +} + +// tLSOptions implements TLSOptionInterface +type tLSOptions struct { + client rest.Interface + ns string +} + +// newTLSOptions returns a TLSOptions +func newTLSOptions(c *TraefikV1alpha1Client, namespace string) *tLSOptions { + return &tLSOptions{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the tLSOption, and returns the corresponding tLSOption object, and an error if there is any. +func (c *tLSOptions) Get(name string, options v1.GetOptions) (result *v1alpha1.TLSOption, err error) { + result = &v1alpha1.TLSOption{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tlsoptions"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of TLSOptions that match those selectors. +func (c *tLSOptions) List(opts v1.ListOptions) (result *v1alpha1.TLSOptionList, err error) { + result = &v1alpha1.TLSOptionList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tlsoptions"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested tLSOptions. +func (c *tLSOptions) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("tlsoptions"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a tLSOption and creates it. Returns the server's representation of the tLSOption, and an error, if there is any. +func (c *tLSOptions) Create(tLSOption *v1alpha1.TLSOption) (result *v1alpha1.TLSOption, err error) { + result = &v1alpha1.TLSOption{} + err = c.client.Post(). + Namespace(c.ns). + Resource("tlsoptions"). + Body(tLSOption). + Do(). + Into(result) + return +} + +// Update takes the representation of a tLSOption and updates it. Returns the server's representation of the tLSOption, and an error, if there is any. +func (c *tLSOptions) Update(tLSOption *v1alpha1.TLSOption) (result *v1alpha1.TLSOption, err error) { + result = &v1alpha1.TLSOption{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tlsoptions"). + Name(tLSOption.Name). + Body(tLSOption). + Do(). + Into(result) + return +} + +// Delete takes name of the tLSOption and deletes it. Returns an error if one occurs. +func (c *tLSOptions) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("tlsoptions"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *tLSOptions) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("tlsoptions"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched tLSOption. +func (c *tLSOptions) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TLSOption, err error) { + result = &v1alpha1.TLSOption{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("tlsoptions"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go index e1b0f47b5..bb050537e 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go @@ -38,6 +38,7 @@ type TraefikV1alpha1Interface interface { IngressRoutesGetter IngressRouteTCPsGetter MiddlewaresGetter + TLSOptionsGetter } // TraefikV1alpha1Client is used to interact with features provided by the traefik.containo.us group. @@ -57,6 +58,10 @@ func (c *TraefikV1alpha1Client) Middlewares(namespace string) MiddlewareInterfac return newMiddlewares(c, namespace) } +func (c *TraefikV1alpha1Client) TLSOptions(namespace string) TLSOptionInterface { + return newTLSOptions(c, namespace) +} + // NewForConfig creates a new TraefikV1alpha1Client for the given config. func NewForConfig(c *rest.Config) (*TraefikV1alpha1Client, error) { config := *c diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go index fcd191a3c..70060f80a 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go @@ -67,6 +67,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().IngressRouteTCPs().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("middlewares"): return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().Middlewares().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("tlsoptions"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().TLSOptions().Informer()}, nil } diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/interface.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/interface.go index 80c6a617d..54b02039a 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/interface.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/interface.go @@ -38,6 +38,8 @@ type Interface interface { IngressRouteTCPs() IngressRouteTCPInformer // Middlewares returns a MiddlewareInformer. Middlewares() MiddlewareInformer + // TLSOptions returns a TLSOptionInformer. + TLSOptions() TLSOptionInformer } type version struct { @@ -65,3 +67,8 @@ func (v *version) IngressRouteTCPs() IngressRouteTCPInformer { func (v *version) Middlewares() MiddlewareInformer { return &middlewareInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } + +// TLSOptions returns a TLSOptionInformer. +func (v *version) TLSOptions() TLSOptionInformer { + return &tLSOptionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/tlsoption.go new file mode 100644 index 000000000..4bbf1d981 --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/tlsoption.go @@ -0,0 +1,97 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2019 Containous SAS + +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + time "time" + + versioned "github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/clientset/versioned" + internalinterfaces "github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1" + traefikv1alpha1 "github.com/containous/traefik/pkg/provider/kubernetes/crd/traefik/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// TLSOptionInformer provides access to a shared informer and lister for +// TLSOptions. +type TLSOptionInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.TLSOptionLister +} + +type tLSOptionInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewTLSOptionInformer constructs a new informer for TLSOption type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTLSOptionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTLSOptionInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredTLSOptionInformer constructs a new informer for TLSOption type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredTLSOptionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TraefikV1alpha1().TLSOptions(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TraefikV1alpha1().TLSOptions(namespace).Watch(options) + }, + }, + &traefikv1alpha1.TLSOption{}, + resyncPeriod, + indexers, + ) +} + +func (f *tLSOptionInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTLSOptionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *tLSOptionInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&traefikv1alpha1.TLSOption{}, f.defaultInformer) +} + +func (f *tLSOptionInformer) Lister() v1alpha1.TLSOptionLister { + return v1alpha1.NewTLSOptionLister(f.Informer().GetIndexer()) +} diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/expansion_generated.go b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/expansion_generated.go index 1463476eb..dc40e3ba8 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/expansion_generated.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/expansion_generated.go @@ -49,3 +49,11 @@ type MiddlewareListerExpansion interface{} // MiddlewareNamespaceListerExpansion allows custom methods to be added to // MiddlewareNamespaceLister. type MiddlewareNamespaceListerExpansion interface{} + +// TLSOptionListerExpansion allows custom methods to be added to +// TLSOptionLister. +type TLSOptionListerExpansion interface{} + +// TLSOptionNamespaceListerExpansion allows custom methods to be added to +// TLSOptionNamespaceLister. +type TLSOptionNamespaceListerExpansion interface{} diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/tlsoption.go new file mode 100644 index 000000000..d8e2ca80b --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/tlsoption.go @@ -0,0 +1,102 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2019 Containous SAS + +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/containous/traefik/pkg/provider/kubernetes/crd/traefik/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// TLSOptionLister helps list TLSOptions. +type TLSOptionLister interface { + // List lists all TLSOptions in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.TLSOption, err error) + // TLSOptions returns an object that can list and get TLSOptions. + TLSOptions(namespace string) TLSOptionNamespaceLister + TLSOptionListerExpansion +} + +// tLSOptionLister implements the TLSOptionLister interface. +type tLSOptionLister struct { + indexer cache.Indexer +} + +// NewTLSOptionLister returns a new TLSOptionLister. +func NewTLSOptionLister(indexer cache.Indexer) TLSOptionLister { + return &tLSOptionLister{indexer: indexer} +} + +// List lists all TLSOptions in the indexer. +func (s *tLSOptionLister) List(selector labels.Selector) (ret []*v1alpha1.TLSOption, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TLSOption)) + }) + return ret, err +} + +// TLSOptions returns an object that can list and get TLSOptions. +func (s *tLSOptionLister) TLSOptions(namespace string) TLSOptionNamespaceLister { + return tLSOptionNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// TLSOptionNamespaceLister helps list and get TLSOptions. +type TLSOptionNamespaceLister interface { + // List lists all TLSOptions in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.TLSOption, err error) + // Get retrieves the TLSOption from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.TLSOption, error) + TLSOptionNamespaceListerExpansion +} + +// tLSOptionNamespaceLister implements the TLSOptionNamespaceLister +// interface. +type tLSOptionNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all TLSOptions in the indexer for a given namespace. +func (s tLSOptionNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.TLSOption, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TLSOption)) + }) + return ret, err +} + +// Get retrieves the TLSOption from the indexer for a given namespace and name. +func (s tLSOptionNamespaceLister) Get(name string) (*v1alpha1.TLSOption, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("tlsoption"), name) + } + return obj.(*v1alpha1.TLSOption), nil +} diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 389a0485e..c950b29ea 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -118,7 +118,7 @@ func (p *Provider) Provide(configurationChan chan<- config.Message, pool *safe.P case <-stop: return nil case event := <-eventsChan: - conf := p.loadConfigurationFromIngresses(ctxLog, k8sClient) + conf := p.loadConfigurationFromCRD(ctxLog, k8sClient) if reflect.DeepEqual(p.lastConfiguration.Get(), conf) { logger.Debugf("Skipping Kubernetes event kind %T", event) @@ -293,19 +293,59 @@ func loadServers(client Client, namespace string, svc v1alpha1.Service) ([]confi return servers, nil } -func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Client) *config.Configuration { - conf := &config.Configuration{ - HTTP: &config.HTTPConfiguration{ - Routers: map[string]*config.Router{}, - Middlewares: map[string]*config.Middleware{}, - Services: map[string]*config.Service{}, - }, - TCP: &config.TCPConfiguration{ - Routers: map[string]*config.TCPRouter{}, - Services: map[string]*config.TCPService{}, - }, +func buildTLSOptions(ctx context.Context, client Client) map[string]tls.TLS { + tlsOptionsCRD := client.GetTLSOptions() + var tlsOptions map[string]tls.TLS + + if len(tlsOptionsCRD) == 0 { + return tlsOptions + } + tlsOptions = make(map[string]tls.TLS) + + for _, tlsOption := range tlsOptionsCRD { + logger := log.FromContext(log.With(ctx, log.Str("tlsOption", tlsOption.Name), log.Str("namespace", tlsOption.Namespace))) + var clientCAs []tls.FileOrContent + + for _, secretName := range tlsOption.Spec.ClientCA.SecretNames { + secret, exists, err := client.GetSecret(tlsOption.Namespace, secretName) + if err != nil { + logger.Errorf("Failed to fetch secret %s/%s: %v", tlsOption.Namespace, secretName, err) + continue + } + + if !exists { + logger.Warnf("Secret %s/%s does not exist", tlsOption.Namespace, secretName) + continue + } + + cert, err := getCABlocks(secret, tlsOption.Namespace, secretName) + if err != nil { + logger.Errorf("Failed to extract CA from secret %s/%s: %v", tlsOption.Namespace, secretName, err) + continue + } + + clientCAs = append(clientCAs, tls.FileOrContent(cert)) + } + + tlsOptions[makeID(tlsOption.Namespace, tlsOption.Name)] = tls.TLS{ + MinVersion: tlsOption.Spec.MinVersion, + CipherSuites: tlsOption.Spec.CipherSuites, + ClientCA: tls.ClientCA{ + Files: clientCAs, + Optional: tlsOption.Spec.ClientCA.Optional, + }, + SniStrict: tlsOption.Spec.SniStrict, + } + } + return tlsOptions +} + +func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Client, tlsConfigs map[string]*tls.Configuration) *config.HTTPConfiguration { + conf := &config.HTTPConfiguration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, } - tlsConfigs := make(map[string]*tls.Configuration) for _, ingressRoute := range client.GetIngressRoutes() { logger := log.FromContext(log.With(ctx, log.Str("ingress", ingressRoute.Name), log.Str("namespace", ingressRoute.Namespace))) @@ -377,17 +417,33 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl serviceName := makeID(ingressRoute.Namespace, key) - conf.HTTP.Routers[serviceName] = &config.Router{ + conf.Routers[serviceName] = &config.Router{ Middlewares: mds, Priority: route.Priority, EntryPoints: ingressRoute.Spec.EntryPoints, Rule: route.Match, Service: serviceName, } + if ingressRoute.Spec.TLS != nil { - conf.HTTP.Routers[serviceName].TLS = &config.RouterTLSConfig{} + tlsConf := &config.RouterTLSConfig{} + if ingressRoute.Spec.TLS.Options != nil && len(ingressRoute.Spec.TLS.Options.Name) > 0 { + tlsOptionsName := ingressRoute.Spec.TLS.Options.Name + // Is a Kubernetes CRD reference, (i.e. not a cross-provider default) + if !strings.Contains(tlsOptionsName, "@") { + ns := ingressRoute.Spec.TLS.Options.Namespace + if len(ns) == 0 { + ns = ingressRoute.Namespace + } + tlsOptionsName = makeID(ns, tlsOptionsName) + } + + tlsConf.Options = tlsOptionsName + } + conf.Routers[serviceName].TLS = tlsConf } - conf.HTTP.Services[serviceName] = &config.Service{ + + conf.Services[serviceName] = &config.Service{ LoadBalancer: &config.LoadBalancerService{ Servers: allServers, // TODO: support other strategies. @@ -397,8 +453,13 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl } } - for _, middleware := range client.GetMiddlewares() { - conf.HTTP.Middlewares[makeID(middleware.Namespace, middleware.Name)] = &middleware.Spec + return conf +} + +func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client Client, tlsConfigs map[string]*tls.Configuration) *config.TCPConfiguration { + conf := &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{}, + Services: map[string]*config.TCPService{}, } for _, ingressRouteTCP := range client.GetIngressRouteTCPs() { @@ -452,19 +513,34 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl } serviceName := makeID(ingressRouteTCP.Namespace, key) - conf.TCP.Routers[serviceName] = &config.TCPRouter{ + conf.Routers[serviceName] = &config.TCPRouter{ EntryPoints: ingressRouteTCP.Spec.EntryPoints, Rule: route.Match, Service: serviceName, } if ingressRouteTCP.Spec.TLS != nil { - conf.TCP.Routers[serviceName].TLS = &config.RouterTCPTLSConfig{ + conf.Routers[serviceName].TLS = &config.RouterTCPTLSConfig{ Passthrough: ingressRouteTCP.Spec.TLS.Passthrough, } + + if ingressRouteTCP.Spec.TLS.Options != nil && len(ingressRouteTCP.Spec.TLS.Options.Name) > 0 { + tlsOptionsName := ingressRouteTCP.Spec.TLS.Options.Name + // Is a Kubernetes CRD reference (i.e. not a cross-provider reference) + if !strings.Contains(tlsOptionsName, "@") { + ns := ingressRouteTCP.Spec.TLS.Options.Namespace + if len(ns) == 0 { + ns = ingressRouteTCP.Namespace + } + tlsOptionsName = makeID(ns, tlsOptionsName) + } + + conf.Routers[serviceName].TLS.Options = tlsOptionsName + + } } - conf.TCP.Services[serviceName] = &config.TCPService{ + conf.Services[serviceName] = &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: allServers, }, @@ -472,7 +548,21 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl } } - conf.TLS = getTLSConfig(tlsConfigs) + return conf +} + +func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) *config.Configuration { + tlsConfigs := make(map[string]*tls.Configuration) + conf := &config.Configuration{ + HTTP: p.loadIngressRouteConfiguration(ctx, client, tlsConfigs), + TCP: p.loadIngressRouteTCPConfiguration(ctx, client, tlsConfigs), + TLSOptions: buildTLSOptions(ctx, client), + TLS: getTLSConfig(tlsConfigs), + } + + for _, middleware := range client.GetMiddlewares() { + conf.HTTP.Middlewares[makeID(middleware.Namespace, middleware.Name)] = &middleware.Spec + } return conf } @@ -618,3 +708,19 @@ func getCertificateBlocks(secret *corev1.Secret, namespace, secretName string) ( return cert, key, nil } + +func getCABlocks(secret *corev1.Secret, namespace, secretName string) (string, error) { + tlsCrtData, tlsCrtExists := secret.Data["tls.ca"] + if !tlsCrtExists { + return "", fmt.Errorf("the tls.ca entry is missing from secret %s/%s", + namespace, secretName) + } + + cert := string(tlsCrtData) + if cert == "" { + return "", fmt.Errorf("the tls.ca entry in secret %s/%s is empty", + namespace, secretName) + } + + return cert, nil +} diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 159b65f2b..37ae2da3f 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -297,6 +297,261 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, }, + { + desc: "TLS with tls options", + paths: []string{"tcp/services.yml", "tcp/with_tls_options.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientCA: tls.ClientCA{ + Files: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + Optional: true, + }, + SniStrict: true, + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{ + "default/test-crd-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default/test-crd-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + TLS: &config.RouterTCPTLSConfig{ + Options: "default/foo", + }, + }, + }, + Services: map[string]*config.TCPService{ + "default/test-crd-fdd3e9338e47a45efefc": { + LoadBalancer: &config.TCPLoadBalancerService{ + Servers: []config.TCPServer{ + { + Address: "10.10.0.1:8000", + Port: "", + }, + { + Address: "10.10.0.2:8000", + Port: "", + }, + }, + }, + }, + }, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + }, + { + desc: "TLS with tls options and specific namespace", + paths: []string{"tcp/services.yml", "tcp/with_tls_options_and_specific_namespace.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "myns/foo": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientCA: tls.ClientCA{ + Files: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + Optional: true, + }, + SniStrict: true, + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{ + "default/test-crd-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default/test-crd-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + TLS: &config.RouterTCPTLSConfig{ + Options: "myns/foo", + }, + }, + }, + Services: map[string]*config.TCPService{ + "default/test-crd-fdd3e9338e47a45efefc": { + LoadBalancer: &config.TCPLoadBalancerService{ + Servers: []config.TCPServer{ + { + Address: "10.10.0.1:8000", + Port: "", + }, + { + Address: "10.10.0.2:8000", + Port: "", + }, + }, + }, + }, + }, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + }, + { + desc: "TLS with bad tls options", + paths: []string{"tcp/services.yml", "tcp/with_bad_tls_options.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientCA: tls.ClientCA{ + Files: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + Optional: true, + }, + SniStrict: true, + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{ + "default/test-crd-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default/test-crd-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + TLS: &config.RouterTCPTLSConfig{ + Options: "default/foo", + }, + }, + }, + Services: map[string]*config.TCPService{ + "default/test-crd-fdd3e9338e47a45efefc": { + LoadBalancer: &config.TCPLoadBalancerService{ + Servers: []config.TCPServer{ + { + Address: "10.10.0.1:8000", + Port: "", + }, + { + Address: "10.10.0.2:8000", + Port: "", + }, + }, + }, + }, + }, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + }, + { + desc: "TLS with unknown tls options", + paths: []string{"tcp/services.yml", "tcp/with_unknown_tls_options.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{ + "default/test-crd-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default/test-crd-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + TLS: &config.RouterTCPTLSConfig{ + Options: "default/unknown", + }, + }, + }, + Services: map[string]*config.TCPService{ + "default/test-crd-fdd3e9338e47a45efefc": { + LoadBalancer: &config.TCPLoadBalancerService{ + Servers: []config.TCPServer{ + { + Address: "10.10.0.1:8000", + Port: "", + }, + { + Address: "10.10.0.2:8000", + Port: "", + }, + }, + }, + }, + }, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + }, + { + desc: "TLS with unknown tls options namespace", + paths: []string{"tcp/services.yml", "tcp/with_unknown_tls_options_namespace.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{ + "default/test-crd-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default/test-crd-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + TLS: &config.RouterTCPTLSConfig{ + Options: "unknown/foo", + }, + }, + }, + Services: map[string]*config.TCPService{ + "default/test-crd-fdd3e9338e47a45efefc": { + LoadBalancer: &config.TCPLoadBalancerService{ + Servers: []config.TCPServer{ + { + Address: "10.10.0.1:8000", + Port: "", + }, + { + Address: "10.10.0.2:8000", + Port: "", + }, + }, + }, + }, + }, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + }, { desc: "TLS with ACME", paths: []string{"tcp/services.yml", "tcp/with_tls_acme.yml"}, @@ -338,6 +593,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) { for _, test := range testCases { test := test + t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -346,7 +602,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) { } p := Provider{IngressClass: test.ingressClass} - conf := p.loadConfigurationFromIngresses(context.Background(), newClientMock(test.paths...)) + conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) }) } @@ -660,6 +916,261 @@ func TestLoadIngressRoutes(t *testing.T) { }, }, }, + { + desc: "TLS with tls options", + paths: []string{"services.yml", "with_tls_options.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientCA: tls.ClientCA{ + Files: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + Optional: true, + }, + SniStrict: true, + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{}, + Services: map[string]*config.TCPService{}, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{ + "default/test-crd-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default/test-crd-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &config.RouterTLSConfig{ + Options: "default/foo", + }, + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "default/test-crd-6b204d94623b3df4370c": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: true, + }, + }, + }, + }, + }, + }, + { + desc: "TLS with tls options and specific namespace", + paths: []string{"services.yml", "with_tls_options_and_specific_namespace.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "myns/foo": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientCA: tls.ClientCA{ + Files: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + Optional: true, + }, + SniStrict: true, + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{}, + Services: map[string]*config.TCPService{}, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{ + "default/test-crd-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default/test-crd-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &config.RouterTLSConfig{ + Options: "myns/foo", + }, + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "default/test-crd-6b204d94623b3df4370c": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: true, + }, + }, + }, + }, + }, + }, + { + desc: "TLS with bad tls options", + paths: []string{"services.yml", "with_bad_tls_options.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientCA: tls.ClientCA{ + Files: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + Optional: true, + }, + SniStrict: true, + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{}, + Services: map[string]*config.TCPService{}, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{ + "default/test-crd-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default/test-crd-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &config.RouterTLSConfig{ + Options: "default/foo", + }, + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "default/test-crd-6b204d94623b3df4370c": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: true, + }, + }, + }, + }, + }, + }, + { + desc: "TLS with unknown tls options", + paths: []string{"services.yml", "with_unknown_tls_options.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{}, + Services: map[string]*config.TCPService{}, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{ + "default/test-crd-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default/test-crd-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &config.RouterTLSConfig{ + Options: "default/unknown", + }, + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "default/test-crd-6b204d94623b3df4370c": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: true, + }, + }, + }, + }, + }, + }, + { + desc: "TLS with unknown tls options namespace", + paths: []string{"services.yml", "with_unknown_tls_options_namespace.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{}, + Services: map[string]*config.TCPService{}, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{ + "default/test-crd-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default/test-crd-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &config.RouterTLSConfig{ + Options: "unknown/foo", + }, + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "default/test-crd-6b204d94623b3df4370c": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: true, + }, + }, + }, + }, + }, + }, { desc: "TLS with ACME", paths: []string{"services.yml", "with_tls_acme.yml"}, @@ -740,6 +1251,7 @@ func TestLoadIngressRoutes(t *testing.T) { for _, test := range testCases { test := test + t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -748,7 +1260,7 @@ func TestLoadIngressRoutes(t *testing.T) { } p := Provider{IngressClass: test.ingressClass} - conf := p.loadConfigurationFromIngresses(context.Background(), newClientMock(test.paths...)) + conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) }) } diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go index 9d37e876c..26f534ad8 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go @@ -31,7 +31,14 @@ type TLS struct { // SecretName is the name of the referenced Kubernetes Secret to specify the // certificate details. SecretName string `json:"secretName"` - // TODO MinimumProtocolVersion string `json:"minimumProtocolVersion,omitempty"` + // Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. + Options *TLSOptionRef `json:"options"` +} + +// TLSOptionRef is a ref to the TLSOption resources. +type TLSOptionRef struct { + Name string `json:"name"` + Namespace string `json:"namespace"` } // Service defines an upstream to proxy traffic. diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go index 0a3ec20c4..4b844722c 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go @@ -29,6 +29,14 @@ type TLSTCP struct { // certificate details. SecretName string `json:"secretName"` Passthrough bool `json:"passthrough"` + // Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. + Options *TLSOptionTCPRef `json:"options"` +} + +// TLSOptionTCPRef is a ref to the TLSOption resources. +type TLSOptionTCPRef struct { + Name string `json:"name"` + Namespace string `json:"namespace"` } // ServiceTCP defines an upstream to proxy traffic. diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go index b64fdb3ce..15278474a 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go @@ -39,6 +39,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &IngressRouteTCPList{}, &Middleware{}, &MiddlewareList{}, + &TLSOption{}, + &TLSOptionList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go new file mode 100644 index 000000000..22e04e3e5 --- /dev/null +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go @@ -0,0 +1,48 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// TLSOption is a specification for a TLSOption resource. +type TLSOption struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec TLSOptionSpec `json:"spec"` +} + +// +k8s:deepcopy-gen=true + +// TLSOptionSpec configures TLS for an entry point +type TLSOptionSpec struct { + MinVersion string `json:"minversion"` + CipherSuites []string `json:"ciphersuites"` + ClientCA ClientCA `json:"clientca"` + SniStrict bool `json:"snistrict"` +} + +// +k8s:deepcopy-gen=true + +// ClientCA defines traefik CA files for an entryPoint +// and it indicates if they are mandatory or have just to be analyzed if provided +type ClientCA struct { + // SecretName is the name of the referenced Kubernetes Secret to specify the + // certificate details. + SecretNames []string `json:"secretnames"` + // Optional indicates if ClientCA are mandatory or have just to be analyzed if provided + Optional bool `json:"optional"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// TLSOptionList is a list of TLSOption resources. +type TLSOptionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []TLSOption `json:"items"` +} diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go index afed89fc6..e1d756c35 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go @@ -32,6 +32,27 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClientCA) DeepCopyInto(out *ClientCA) { + *out = *in + if in.SecretNames != nil { + in, out := &in.SecretNames, &out.SecretNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientCA. +func (in *ClientCA) DeepCopy() *ClientCA { + if in == nil { + return nil + } + out := new(ClientCA) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HealthCheck) DeepCopyInto(out *HealthCheck) { *out = *in @@ -133,7 +154,7 @@ func (in *IngressRouteSpec) DeepCopyInto(out *IngressRouteSpec) { if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLS) - **out = **in + (*in).DeepCopyInto(*out) } return } @@ -226,7 +247,7 @@ func (in *IngressRouteTCPSpec) DeepCopyInto(out *IngressRouteTCPSpec) { if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSTCP) - **out = **in + (*in).DeepCopyInto(*out) } return } @@ -406,6 +427,11 @@ func (in *ServiceTCP) DeepCopy() *ServiceTCP { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLS) DeepCopyInto(out *TLS) { *out = *in + if in.Options != nil { + in, out := &in.Options, &out.Options + *out = new(TLSOptionRef) + **out = **in + } return } @@ -419,9 +445,128 @@ func (in *TLS) DeepCopy() *TLS { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSOption) DeepCopyInto(out *TLSOption) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSOption. +func (in *TLSOption) DeepCopy() *TLSOption { + if in == nil { + return nil + } + out := new(TLSOption) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TLSOption) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSOptionList) DeepCopyInto(out *TLSOptionList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TLSOption, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSOptionList. +func (in *TLSOptionList) DeepCopy() *TLSOptionList { + if in == nil { + return nil + } + out := new(TLSOptionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TLSOptionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSOptionRef) DeepCopyInto(out *TLSOptionRef) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSOptionRef. +func (in *TLSOptionRef) DeepCopy() *TLSOptionRef { + if in == nil { + return nil + } + out := new(TLSOptionRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSOptionSpec) DeepCopyInto(out *TLSOptionSpec) { + *out = *in + if in.CipherSuites != nil { + in, out := &in.CipherSuites, &out.CipherSuites + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.ClientCA.DeepCopyInto(&out.ClientCA) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSOptionSpec. +func (in *TLSOptionSpec) DeepCopy() *TLSOptionSpec { + if in == nil { + return nil + } + out := new(TLSOptionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSOptionTCPRef) DeepCopyInto(out *TLSOptionTCPRef) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSOptionTCPRef. +func (in *TLSOptionTCPRef) DeepCopy() *TLSOptionTCPRef { + if in == nil { + return nil + } + out := new(TLSOptionTCPRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSTCP) DeepCopyInto(out *TLSTCP) { *out = *in + if in.Options != nil { + in, out := &in.Options, &out.Options + *out = new(TLSOptionTCPRef) + **out = **in + } return } diff --git a/pkg/provider/kubernetes/k8s/parser.go b/pkg/provider/kubernetes/k8s/parser.go index e8359df62..0c4ae4cd1 100644 --- a/pkg/provider/kubernetes/k8s/parser.go +++ b/pkg/provider/kubernetes/k8s/parser.go @@ -12,7 +12,7 @@ import ( // MustParseYaml parses a YAML to objects. func MustParseYaml(content []byte) []runtime.Object { - acceptedK8sTypes := regexp.MustCompile(`(Deployment|Endpoints|Service|Ingress|IngressRoute|Middleware|Secret)`) + acceptedK8sTypes := regexp.MustCompile(`(Deployment|Endpoints|Service|Ingress|IngressRoute|Middleware|Secret|TLSOption)`) files := strings.Split(string(content), "---") retVal := make([]runtime.Object, 0, len(files)) diff --git a/pkg/provider/marathon/config.go b/pkg/provider/marathon/config.go index 166775bad..84f98b256 100644 --- a/pkg/provider/marathon/config.go +++ b/pkg/provider/marathon/config.go @@ -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 } diff --git a/pkg/provider/marathon/config_test.go b/pkg/provider/marathon/config_test.go index 4e8529de8..34adeaf4c 100644 --- a/pkg/provider/marathon/config_test.go +++ b/pkg/provider/marathon/config_test.go @@ -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) } }) diff --git a/pkg/provider/marathon/label.go b/pkg/provider/marathon/label.go index 42a8a9bbe..3dba526db 100644 --- a/pkg/provider/marathon/label.go +++ b/pkg/provider/marathon/label.go @@ -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 } diff --git a/pkg/provider/marathon/label_test.go b/pkg/provider/marathon/label_test.go index 7cb011939..472b91d23 100644 --- a/pkg/provider/marathon/label_test.go +++ b/pkg/provider/marathon/label_test.go @@ -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, }, diff --git a/pkg/provider/marathon/marathon.go b/pkg/provider/marathon/marathon.go index 3c7112c7d..5fb64ccd7 100644 --- a/pkg/provider/marathon/marathon.go +++ b/pkg/provider/marathon/marathon.go @@ -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. diff --git a/pkg/provider/rancher/config.go b/pkg/provider/rancher/config.go index e6bf6a044..3eeae9a37 100644 --- a/pkg/provider/rancher/config.go +++ b/pkg/provider/rancher/config.go @@ -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 } diff --git a/pkg/provider/rancher/config_test.go b/pkg/provider/rancher/config_test.go index 077eb42a2..0adff55f1 100644 --- a/pkg/provider/rancher/config_test.go +++ b/pkg/provider/rancher/config_test.go @@ -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{}, diff --git a/pkg/provider/rancher/label.go b/pkg/provider/rancher/label.go index 64c36842f..bd42ff51c 100644 --- a/pkg/provider/rancher/label.go +++ b/pkg/provider/rancher/label.go @@ -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 } diff --git a/pkg/provider/rancher/rancher.go b/pkg/provider/rancher/rancher.go index 1d120af36..76a3f9028 100644 --- a/pkg/provider/rancher/rancher.go +++ b/pkg/provider/rancher/rancher.go @@ -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"` diff --git a/pkg/provider/rest/rest.go b/pkg/provider/rest/rest.go index 60db12d8b..02aa10c5f 100644 --- a/pkg/provider/rest/rest.go +++ b/pkg/provider/rest/rest.go @@ -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"}) diff --git a/pkg/server/aggregator.go b/pkg/server/aggregator.go index 09684dd1c..60db24001 100644 --- a/pkg/server/aggregator.go +++ b/pkg/server/aggregator.go @@ -2,6 +2,7 @@ package server import ( "github.com/containous/traefik/pkg/config" + "github.com/containous/traefik/pkg/log" "github.com/containous/traefik/pkg/server/internal" "github.com/containous/traefik/pkg/tls" ) @@ -21,6 +22,7 @@ func mergeConfiguration(configurations config.Configurations) config.Configurati TLSStores: make(map[string]tls.Store), } + var defaultTLSOptionProviders []string for provider, configuration := range configurations { if configuration.HTTP != nil { for routerName, router := range configuration.HTTP.Routers { @@ -48,10 +50,25 @@ func mergeConfiguration(configurations config.Configurations) config.Configurati conf.TLSStores[key] = store } - for key, config := range configuration.TLSOptions { - conf.TLSOptions[key] = config + for tlsOptionsName, config := range configuration.TLSOptions { + if tlsOptionsName != "default" { + tlsOptionsName = internal.MakeQualifiedName(provider, tlsOptionsName) + } else { + defaultTLSOptionProviders = append(defaultTLSOptionProviders, provider) + } + + conf.TLSOptions[tlsOptionsName] = config } } + if len(defaultTLSOptionProviders) == 0 { + conf.TLSOptions["default"] = tls.TLS{} + } else if len(defaultTLSOptionProviders) > 1 { + log.WithoutContext().Errorf("Default TLS Options defined multiple times in %v", defaultTLSOptionProviders) + // We do not set an empty tls.TLS{} as above so that we actually get a "cascading failure" later on, + // i.e. routers depending on this missing TLS option will fail to initialize as well. + delete(conf.TLSOptions, "default") + } + return conf } diff --git a/pkg/server/aggregator_test.go b/pkg/server/aggregator_test.go index 93596b0e9..6267b819a 100644 --- a/pkg/server/aggregator_test.go +++ b/pkg/server/aggregator_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/containous/traefik/pkg/config" + "github.com/containous/traefik/pkg/tls" "github.com/stretchr/testify/assert" ) @@ -41,13 +42,13 @@ func TestAggregator(t *testing.T) { }, expected: &config.HTTPConfiguration{ Routers: map[string]*config.Router{ - "provider-1@router-1": {}, + "router-1@provider-1": {}, }, Middlewares: map[string]*config.Middleware{ - "provider-1@middleware-1": {}, + "middleware-1@provider-1": {}, }, Services: map[string]*config.Service{ - "provider-1@service-1": {}, + "service-1@provider-1": {}, }, }, }, @@ -83,16 +84,16 @@ func TestAggregator(t *testing.T) { }, expected: &config.HTTPConfiguration{ Routers: map[string]*config.Router{ - "provider-1@router-1": {}, - "provider-2@router-1": {}, + "router-1@provider-1": {}, + "router-1@provider-2": {}, }, Middlewares: map[string]*config.Middleware{ - "provider-1@middleware-1": {}, - "provider-2@middleware-1": {}, + "middleware-1@provider-1": {}, + "middleware-1@provider-2": {}, }, Services: map[string]*config.Service{ - "provider-1@service-1": {}, - "provider-2@service-1": {}, + "service-1@provider-1": {}, + "service-1@provider-2": {}, }, }, }, @@ -108,3 +109,170 @@ func TestAggregator(t *testing.T) { }) } } + +func TestAggregator_tlsoptions(t *testing.T) { + testCases := []struct { + desc string + given config.Configurations + expected map[string]tls.TLS + }{ + { + desc: "Nil returns an empty configuration", + given: nil, + expected: map[string]tls.TLS{ + "default": {}, + }, + }, + { + desc: "Returns fully qualified elements from a mono-provider configuration map", + given: config.Configurations{ + "provider-1": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS12", + }, + }, + }, + }, + expected: map[string]tls.TLS{ + "default": {}, + "foo@provider-1": { + MinVersion: "VersionTLS12", + }, + }, + }, + { + desc: "Returns fully qualified elements from a multi-provider configuration map", + given: config.Configurations{ + "provider-1": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS13", + }, + }, + }, + "provider-2": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS12", + }, + }, + }, + }, + expected: map[string]tls.TLS{ + "default": {}, + "foo@provider-1": { + MinVersion: "VersionTLS13", + }, + "foo@provider-2": { + MinVersion: "VersionTLS12", + }, + }, + }, + { + desc: "Create a valid default tls option when appears only in one provider", + given: config.Configurations{ + "provider-1": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS13", + }, + "default": { + MinVersion: "VersionTLS11", + }, + }, + }, + "provider-2": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS12", + }, + }, + }, + }, + expected: map[string]tls.TLS{ + "default": { + MinVersion: "VersionTLS11", + }, + "foo@provider-1": { + MinVersion: "VersionTLS13", + }, + "foo@provider-2": { + MinVersion: "VersionTLS12", + }, + }, + }, + { + desc: "No default tls option if it is defined in multiple providers", + given: config.Configurations{ + "provider-1": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS12", + }, + "default": { + MinVersion: "VersionTLS11", + }, + }, + }, + "provider-2": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS13", + }, + "default": { + MinVersion: "VersionTLS12", + }, + }, + }, + }, + expected: map[string]tls.TLS{ + "foo@provider-1": { + MinVersion: "VersionTLS12", + }, + "foo@provider-2": { + MinVersion: "VersionTLS13", + }, + }, + }, + { + desc: "Create a default TLS Options configuration if none was provided", + given: config.Configurations{ + "provider-1": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS12", + }, + }, + }, + "provider-2": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS13", + }, + }, + }, + }, + expected: map[string]tls.TLS{ + "default": {}, + "foo@provider-1": { + MinVersion: "VersionTLS12", + }, + "foo@provider-2": { + MinVersion: "VersionTLS13", + }, + }, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := mergeConfiguration(test.given) + assert.Equal(t, test.expected, actual.TLSOptions) + }) + } +} diff --git a/pkg/server/internal/provider.go b/pkg/server/internal/provider.go index dece5f365..3750d339a 100644 --- a/pkg/server/internal/provider.go +++ b/pkg/server/internal/provider.go @@ -21,11 +21,11 @@ func AddProviderInContext(ctx context.Context, elementName string) context.Conte return ctx } - if name, ok := ctx.Value(providerKey).(string); ok && name == parts[0] { + if name, ok := ctx.Value(providerKey).(string); ok && name == parts[1] { return ctx } - return context.WithValue(ctx, providerKey, parts[0]) + return context.WithValue(ctx, providerKey, parts[1]) } // GetQualifiedName Gets the fully qualified name. @@ -41,5 +41,5 @@ func GetQualifiedName(ctx context.Context, elementName string) string { // MakeQualifiedName Creates a qualified name for an element func MakeQualifiedName(providerName string, elementName string) string { - return providerName + "@" + elementName + return elementName + "@" + providerName } diff --git a/pkg/server/middleware/middlewares_test.go b/pkg/server/middleware/middlewares_test.go index e78838041..0de9b0ef5 100644 --- a/pkg/server/middleware/middlewares_test.go +++ b/pkg/server/middleware/middlewares_test.go @@ -74,41 +74,41 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { expected: map[string]string{"middleware-1": "value-middleware-1"}, }, { - desc: "Should prefix the middlewareName with the provider in the context", + desc: "Should suffix the middlewareName with the provider in the context", buildChain: []string{"middleware-1"}, configuration: map[string]*config.Middleware{ - "provider-1@middleware-1": { + "middleware-1@provider-1": { Headers: &config.Headers{ - CustomRequestHeaders: map[string]string{"provider-1.middleware-1": "value-middleware-1"}, + CustomRequestHeaders: map[string]string{"middleware-1@provider-1": "value-middleware-1"}, }, }, }, - expected: map[string]string{"provider-1.middleware-1": "value-middleware-1"}, + expected: map[string]string{"middleware-1@provider-1": "value-middleware-1"}, contextProvider: "provider-1", }, { - desc: "Should not prefix a qualified middlewareName with the provider in the context", - buildChain: []string{"provider-1@middleware-1"}, + desc: "Should not suffix a qualified middlewareName with the provider in the context", + buildChain: []string{"middleware-1@provider-1"}, configuration: map[string]*config.Middleware{ - "provider-1@middleware-1": { + "middleware-1@provider-1": { Headers: &config.Headers{ - CustomRequestHeaders: map[string]string{"provider-1.middleware-1": "value-middleware-1"}, + CustomRequestHeaders: map[string]string{"middleware-1@provider-1": "value-middleware-1"}, }, }, }, - expected: map[string]string{"provider-1.middleware-1": "value-middleware-1"}, + expected: map[string]string{"middleware-1@provider-1": "value-middleware-1"}, contextProvider: "provider-1", }, { desc: "Should be context aware if a chain references another middleware", - buildChain: []string{"provider-1@middleware-chain-1"}, + buildChain: []string{"middleware-chain-1@provider-1"}, configuration: map[string]*config.Middleware{ - "provider-1@middleware-1": { + "middleware-1@provider-1": { Headers: &config.Headers{ CustomRequestHeaders: map[string]string{"middleware-1": "value-middleware-1"}, }, }, - "provider-1@middleware-chain-1": { + "middleware-chain-1@provider-1": { Chain: &config.Chain{ Middlewares: []string{"middleware-1"}, }, @@ -118,31 +118,31 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { }, { desc: "Should handle nested chains with different context", - buildChain: []string{"provider-1@middleware-chain-1", "middleware-chain-1"}, + buildChain: []string{"middleware-chain-1@provider-1", "middleware-chain-1"}, configuration: map[string]*config.Middleware{ - "provider-1@middleware-1": { + "middleware-1@provider-1": { Headers: &config.Headers{ CustomRequestHeaders: map[string]string{"middleware-1": "value-middleware-1"}, }, }, - "provider-1@middleware-2": { + "middleware-2@provider-1": { Headers: &config.Headers{ CustomRequestHeaders: map[string]string{"middleware-2": "value-middleware-2"}, }, }, - "provider-1@middleware-chain-1": { + "middleware-chain-1@provider-1": { Chain: &config.Chain{ Middlewares: []string{"middleware-1"}, }, }, - "provider-1@middleware-chain-2": { + "middleware-chain-2@provider-1": { Chain: &config.Chain{ Middlewares: []string{"middleware-2"}, }, }, - "provider-2@middleware-chain-1": { + "middleware-chain-1@provider-2": { Chain: &config.Chain{ - Middlewares: []string{"provider-1@middleware-2", "provider-1@middleware-chain-2"}, + Middlewares: []string{"middleware-2@provider-1", "middleware-chain-2@provider-1"}, }, }, }, @@ -176,28 +176,28 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { }, { desc: "Detects recursion in Middleware chain", - buildChain: []string{"provider@m1"}, + buildChain: []string{"m1@provider"}, configuration: map[string]*config.Middleware{ - "provider2@ok": { + "ok@provider2": { Retry: &config.Retry{}, }, - "provider@m1": { + "m1@provider": { Chain: &config.Chain{ - Middlewares: []string{"provider2@m2"}, + Middlewares: []string{"m2@provider2"}, }, }, - "provider2@m2": { + "m2@provider2": { Chain: &config.Chain{ - Middlewares: []string{"ok", "provider@m3"}, + Middlewares: []string{"ok", "m3@provider"}, }, }, - "provider@m3": { + "m3@provider": { Chain: &config.Chain{ Middlewares: []string{"m1"}, }, }, }, - expectedError: errors.New("could not instantiate middleware provider@m1: recursion detected in provider@m1->provider2@m2->provider@m3->provider@m1"), + expectedError: errors.New("could not instantiate middleware m1@provider: recursion detected in m1@provider->m2@provider2->m3@provider->m1@provider"), }, { buildChain: []string{"ok", "m0"}, @@ -261,7 +261,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { ctx := context.Background() if len(test.contextProvider) > 0 { - ctx = internal.AddProviderInContext(ctx, test.contextProvider+"@foobar") + ctx = internal.AddProviderInContext(ctx, "foobar@"+test.contextProvider) } rtConf := config.NewRuntimeConfig(config.Configuration{ diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index 9a6e131f6..6c17df155 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -204,14 +204,14 @@ func TestRouterManager_Get(t *testing.T) { { desc: "no middleware with provider name", routersConfig: map[string]*config.Router{ - "provider-1@foo": { + "foo@provider-1": { EntryPoints: []string{"web"}, Service: "foo-service", Rule: "Host(`foo.bar`)", }, }, serviceConfig: map[string]*config.Service{ - "provider-1@foo-service": { + "foo-service@provider-1": { LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ { @@ -227,14 +227,14 @@ func TestRouterManager_Get(t *testing.T) { { desc: "no middleware with specified provider name", routersConfig: map[string]*config.Router{ - "provider-1@foo": { + "foo@provider-1": { EntryPoints: []string{"web"}, - Service: "provider-2@foo-service", + Service: "foo-service@provider-2", Rule: "Host(`foo.bar`)", }, }, serviceConfig: map[string]*config.Service{ - "provider-2@foo-service": { + "foo-service@provider-2": { LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ { @@ -250,15 +250,15 @@ func TestRouterManager_Get(t *testing.T) { { desc: "middleware: chain with provider name", routersConfig: map[string]*config.Router{ - "provider-1@foo": { + "foo@provider-1": { EntryPoints: []string{"web"}, - Middlewares: []string{"provider-2@chain-middle", "headers-middle"}, + Middlewares: []string{"chain-middle@provider-2", "headers-middle"}, Service: "foo-service", Rule: "Host(`foo.bar`)", }, }, serviceConfig: map[string]*config.Service{ - "provider-1@foo-service": { + "foo-service@provider-1": { LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ { @@ -269,15 +269,15 @@ func TestRouterManager_Get(t *testing.T) { }, }, middlewaresConfig: map[string]*config.Middleware{ - "provider-2@chain-middle": { + "chain-middle@provider-2": { Chain: &config.Chain{Middlewares: []string{"auth-middle"}}, }, - "provider-2@auth-middle": { + "auth-middle@provider-2": { BasicAuth: &config.BasicAuth{ Users: []string{"toto:titi"}, }, }, - "provider-1@headers-middle": { + "headers-middle@provider-1": { Headers: &config.Headers{ CustomRequestHeaders: map[string]string{"X-Apero": "beer"}, }, diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index 18299b793..9ea4995f7 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -81,8 +81,9 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.TCPRouterInfo, configsHTTP map[string]*config.RouterInfo, handlerHTTP http.Handler, handlerHTTPS http.Handler) (*tcp.Router, error) { router := &tcp.Router{} router.HTTPHandler(handlerHTTP) + const defaultTLSConfigName = "default" - defaultTLSConf, err := m.tlsManager.Get("default", "default") + defaultTLSConf, err := m.tlsManager.Get("default", defaultTLSConfigName) if err != nil { return nil, err } @@ -90,7 +91,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string router.HTTPSHandler(handlerHTTPS, defaultTLSConf) for routerHTTPName, routerHTTPConfig := range configsHTTP { - if len(routerHTTPConfig.TLS.Options) == 0 || routerHTTPConfig.TLS.Options == "default" { + if len(routerHTTPConfig.TLS.Options) == 0 || routerHTTPConfig.TLS.Options == defaultTLSConfigName { continue } @@ -111,7 +112,12 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string for _, domain := range domains { if routerHTTPConfig.TLS != nil { - tlsConf, err := m.tlsManager.Get("default", routerHTTPConfig.TLS.Options) + tlsOptionsName := routerHTTPConfig.TLS.Options + if tlsOptionsName != defaultTLSConfigName { + tlsOptionsName = internal.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options) + } + + tlsConf, err := m.tlsManager.Get("default", tlsOptionsName) if err != nil { routerHTTPConfig.Err = err.Error() logger.Debug(err) @@ -149,12 +155,17 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string if routerConfig.TLS.Passthrough { router.AddRoute(domain, handler) } else { - configName := "default" - if len(routerConfig.TLS.Options) > 0 { - configName = routerConfig.TLS.Options + tlsOptionsName := routerConfig.TLS.Options + + if len(tlsOptionsName) == 0 { + tlsOptionsName = defaultTLSConfigName } - tlsConf, err := m.tlsManager.Get("default", configName) + if tlsOptionsName != defaultTLSConfigName { + tlsOptionsName = internal.GetQualifiedName(ctxRouter, tlsOptionsName) + } + + tlsConf, err := m.tlsManager.Get("default", tlsOptionsName) if err != nil { routerConfig.Err = err.Error() logger.Debug(err) diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go index b8657b985..1050e7938 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -204,6 +204,9 @@ func TestRuntimeConfiguration(t *testing.T) { tlsManager.UpdateConfigs( map[string]tls.Store{}, map[string]tls.TLS{ + "default": { + MinVersion: "VersionTLS10", + }, "foo": { MinVersion: "VersionTLS12", }, diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go index 75646e9f8..f2892a61f 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -303,9 +303,9 @@ func TestManager_Build(t *testing.T) { }, { desc: "Service name with provider", - serviceName: "provider-1@serviceName", + serviceName: "serviceName@provider-1", configs: map[string]*config.ServiceInfo{ - "provider-1@serviceName": { + "serviceName@provider-1": { Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{}, }, @@ -316,7 +316,7 @@ func TestManager_Build(t *testing.T) { desc: "Service name with provider in context", serviceName: "serviceName", configs: map[string]*config.ServiceInfo{ - "provider-1@serviceName": { + "serviceName@provider-1": { Service: &config.Service{ LoadBalancer: &config.LoadBalancerService{}, }, @@ -335,7 +335,7 @@ func TestManager_Build(t *testing.T) { ctx := context.Background() if len(test.providerName) > 0 { - ctx = internal.AddProviderInContext(ctx, test.providerName+"@foobar") + ctx = internal.AddProviderInContext(ctx, "foobar@"+test.providerName) } _, err := manager.BuildHTTP(ctx, test.serviceName, nil) diff --git a/pkg/server/service/tcp/service_test.go b/pkg/server/service/tcp/service_test.go index 5c9c3a944..13b27ed6d 100644 --- a/pkg/server/service/tcp/service_test.go +++ b/pkg/server/service/tcp/service_test.go @@ -77,9 +77,9 @@ func TestManager_BuildTCP(t *testing.T) { }, { desc: "Service name with provider", - serviceName: "provider-1@serviceName", + serviceName: "serviceName@provider-1", configs: map[string]*config.TCPServiceInfo{ - "provider-1@serviceName": { + "serviceName@provider-1": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{}, }, @@ -90,7 +90,7 @@ func TestManager_BuildTCP(t *testing.T) { desc: "Service name with provider in context", serviceName: "serviceName", configs: map[string]*config.TCPServiceInfo{ - "provider-1@serviceName": { + "serviceName@provider-1": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{}, }, @@ -102,7 +102,7 @@ func TestManager_BuildTCP(t *testing.T) { desc: "Server with correct host:port as address", serviceName: "serviceName", configs: map[string]*config.TCPServiceInfo{ - "provider-1@serviceName": { + "serviceName@provider-1": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -120,7 +120,7 @@ func TestManager_BuildTCP(t *testing.T) { desc: "Server with correct ip:port as address", serviceName: "serviceName", configs: map[string]*config.TCPServiceInfo{ - "provider-1@serviceName": { + "serviceName@provider-1": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -138,7 +138,7 @@ func TestManager_BuildTCP(t *testing.T) { desc: "missing port in address with hostname, server is skipped, error is logged", serviceName: "serviceName", configs: map[string]*config.TCPServiceInfo{ - "provider-1@serviceName": { + "serviceName@provider-1": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -156,7 +156,7 @@ func TestManager_BuildTCP(t *testing.T) { desc: "missing port in address with ip, server is skipped, error is logged", serviceName: "serviceName", configs: map[string]*config.TCPServiceInfo{ - "provider-1@serviceName": { + "serviceName@provider-1": { TCPService: &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: []config.TCPServer{ @@ -183,7 +183,7 @@ func TestManager_BuildTCP(t *testing.T) { ctx := context.Background() if len(test.providerName) > 0 { - ctx = internal.AddProviderInContext(ctx, test.providerName+"@foobar") + ctx = internal.AddProviderInContext(ctx, "foobar@"+test.providerName) } handler, err := manager.BuildTCP(ctx, test.serviceName) diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go index 98dfbfc03..60791ace0 100644 --- a/pkg/tls/tlsmanager.go +++ b/pkg/tls/tlsmanager.go @@ -39,11 +39,12 @@ func (m *Manager) UpdateConfigs(stores map[string]Store, configs map[string]TLS, m.stores = make(map[string]*CertificateStore) for storeName, storeConfig := range m.storesConfig { - var err error - m.stores[storeName], err = buildCertificateStore(storeConfig) + store, err := buildCertificateStore(storeConfig) if err != nil { - log.Errorf("Error while creating certificate store %s", storeName) + log.Errorf("Error while creating certificate store %s: %v", storeName, err) + continue } + m.stores[storeName] = store } storesCertificates := make(map[string]map[string]*tls.Certificate) @@ -73,7 +74,7 @@ func (m *Manager) Get(storeName string, configName string) (*tls.Config, error) defer m.lock.RUnlock() config, ok := m.configs[configName] - if !ok && configName != "default" { + if !ok { return nil, fmt.Errorf("unknown TLS options: %s", configName) } @@ -137,14 +138,14 @@ func buildCertificateStore(tlsStore Store) (*CertificateStore, error) { if tlsStore.DefaultCertificate != nil { cert, err := buildDefaultCertificate(tlsStore.DefaultCertificate) if err != nil { - return nil, err + return certificateStore, err } certificateStore.DefaultCertificate = cert } else { log.Debug("No default certificate, generate one") cert, err := generate.DefaultCertificate() if err != nil { - return nil, err + return certificateStore, err } certificateStore.DefaultCertificate = cert } diff --git a/pkg/tls/tlsmanager_test.go b/pkg/tls/tlsmanager_test.go index 253a0a1b0..848d55389 100644 --- a/pkg/tls/tlsmanager_test.go +++ b/pkg/tls/tlsmanager_test.go @@ -3,6 +3,8 @@ package tls import ( "crypto/tls" "testing" + + "github.com/stretchr/testify/assert" ) // LocalhostCert is a PEM-encoded TLS cert with SAN IPs @@ -62,3 +64,94 @@ func TestTLSInStore(t *testing.T) { t.Fatal("got error: default store must have TLS certificates.") } } + +func TestTLSInvalidStore(t *testing.T) { + dynamicConfigs := + []*Configuration{ + { + Certificate: &Certificate{ + CertFile: localhostCert, + KeyFile: localhostKey, + }, + }, + } + + tlsManager := NewManager() + tlsManager.UpdateConfigs(map[string]Store{ + "default": { + DefaultCertificate: &Certificate{ + CertFile: "/wrong", + KeyFile: "/wrong", + }, + }, + }, nil, dynamicConfigs) + + certs := tlsManager.GetStore("default").DynamicCerts.Get().(map[string]*tls.Certificate) + if len(certs) == 0 { + t.Fatal("got error: default store must have TLS certificates.") + } +} + +func TestManager_Get(t *testing.T) { + dynamicConfigs := + []*Configuration{ + { + Certificate: &Certificate{ + CertFile: localhostCert, + KeyFile: localhostKey, + }, + }, + } + tlsConfigs := map[string]TLS{ + "foo": {MinVersion: "VersionTLS12"}, + "bar": {MinVersion: "VersionTLS11"}, + } + + testCases := []struct { + desc string + tlsOptionsName string + expectedMinVersion uint16 + expectedError bool + }{ + { + desc: "Get a tls config from a valid name", + tlsOptionsName: "foo", + expectedMinVersion: uint16(tls.VersionTLS12), + }, + { + desc: "Get another tls config from a valid name", + tlsOptionsName: "bar", + expectedMinVersion: uint16(tls.VersionTLS11), + }, + { + desc: "Get an tls config from an invalid name", + tlsOptionsName: "unknown", + expectedError: true, + }, + { + desc: "Get an tls config from unexisting 'default' name", + tlsOptionsName: "default", + expectedError: true, + }, + } + + tlsManager := NewManager() + tlsManager.UpdateConfigs(nil, tlsConfigs, dynamicConfigs) + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + config, err := tlsManager.Get("default", test.tlsOptionsName) + if test.expectedError { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, config.MinVersion, test.expectedMinVersion) + }) + } + +} diff --git a/pkg/types/constraints.go b/pkg/types/constraints.go deleted file mode 100644 index 2328e795e..000000000 --- a/pkg/types/constraints.go +++ /dev/null @@ -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 -} diff --git a/pkg/types/metrics.go b/pkg/types/metrics.go index 736f4d306..9a1a763b2 100644 --- a/pkg/types/metrics.go +++ b/pkg/types/metrics.go @@ -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 diff --git a/script/update-generated-crd-code.sh b/script/update-generated-crd-code.sh index 9fddab5ba..f6178477d 100755 --- a/script/update-generated-crd-code.sh +++ b/script/update-generated-crd-code.sh @@ -11,4 +11,4 @@ REPO_ROOT=${HACK_DIR}/.. --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl \ "$@" -deepcopy-gen --input-dirs github.com/containous/traefik/pkg/config -O zz_generated.deepcopy --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl +deepcopy-gen --input-dirs github.com/containous/traefik/pkg/config -O zz_generated.deepcopy --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl diff --git a/vendor/github.com/ryanuber/go-glob/LICENSE b/vendor/github.com/ryanuber/go-glob/LICENSE deleted file mode 100644 index bdfbd9514..000000000 --- a/vendor/github.com/ryanuber/go-glob/LICENSE +++ /dev/null @@ -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. diff --git a/vendor/github.com/ryanuber/go-glob/glob.go b/vendor/github.com/ryanuber/go-glob/glob.go deleted file mode 100644 index e67db3be1..000000000 --- a/vendor/github.com/ryanuber/go-glob/glob.go +++ /dev/null @@ -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]) -} diff --git a/vendor/github.com/vulcand/predicate/lib.go b/vendor/github.com/vulcand/predicate/lib.go index 013e16c50..c313c1bae 100644 --- a/vendor/github.com/vulcand/predicate/lib.go +++ b/vendor/github.com/vulcand/predicate/lib.go @@ -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 { diff --git a/vendor/github.com/vulcand/predicate/parse.go b/vendor/github.com/vulcand/predicate/parse.go index 03518cf16..b80e7361a 100644 --- a/vendor/github.com/vulcand/predicate/parse.go +++ b/vendor/github.com/vulcand/predicate/parse.go @@ -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: diff --git a/vendor/github.com/vulcand/predicate/predicate.go b/vendor/github.com/vulcand/predicate/predicate.go index 256e70146..c5aacb488 100644 --- a/vendor/github.com/vulcand/predicate/predicate.go +++ b/vendor/github.com/vulcand/predicate/predicate.go @@ -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.