diff --git a/docs/content/assets/img/providers/nomad.png b/docs/content/assets/img/providers/nomad.png new file mode 100644 index 000000000..36ccd9cc6 Binary files /dev/null and b/docs/content/assets/img/providers/nomad.png differ diff --git a/docs/content/providers/nomad.md b/docs/content/providers/nomad.md new file mode 100644 index 000000000..f9d009f9e --- /dev/null +++ b/docs/content/providers/nomad.md @@ -0,0 +1,544 @@ +--- +title: "Nomad Service Discovery" +description: "Learn how to use Nomad as a provider for configuration discovery in Traefik Proxy. Read the technical documentation." +--- + +# Traefik & Nomad Service Discovery + +A Story of Tags, Services & Nomads +{: .subtitle } + +![Nomad Service Discovery](../assets/img/providers/nomad.png) + +Attach tags to your Nomad services and let Traefik do the rest! + +## Configuration Examples + +??? example "Configuring Nomad & Deploying Services" + + Enabling the nomad provider + + ```yaml tab="File (YAML)" + providers: + nomad: {} + ``` + + ```toml tab="File (TOML)" + [providers.nomad] + ``` + + ```bash tab="CLI" + --providers.nomad=true + ``` + + Attaching tags to services: + + ``` + ... + service { + name = "myService" + tags = [ + "traefik.http.routers.my-router.rule=Host(`example.com`)", + ] + } + ... + ``` + +## Routing Configuration + +See the dedicated section in [routing](../routing/providers/nomad.md). + +## Provider Configuration + +### `refreshInterval` + +_Optional, Default=15s_ + +Defines the polling interval. + +```yaml tab="File (YAML)" +providers: + nomad: + refreshInterval: 30s + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + refreshInterval = "30s" + # ... +``` + +```bash tab="CLI" +--providers.nomad.refreshInterval=30s +# ... +``` + +### `prefix` + +_required, Default="traefik"_ + +The prefix for Nomad service tags defining Traefik labels. + +```yaml tab="File (YAML)" +providers: + nomad: + prefix: test + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + prefix = "test" + # ... +``` + +```bash tab="CLI" +--providers.nomad.prefix=test +# ... +``` + +### `stale` + +_Optional, Default=false_ + +Use stale consistency for Nomad service API reads. + +!!! note "" + + This makes reads very fast and scalable at the cost of a higher likelihood of stale values. + + For more information, see the Nomad [documentation on consistency](https://www.nomadproject.io/api-docs#consistency-modes). + +```yaml tab="File (YAML)" +providers: + nomad: + stale: true + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + stale = true + # ... +``` + +```bash tab="CLI" +--providers.nomad.stale=true +# ... +``` + +### `endpoint` + +Defines the Nomad server endpoint. + +#### `address` + +Defines the address of the Nomad server. + +_Optional, Default="http://127.0.0.1:4646"_ + +```yaml tab="File (YAML)" +providers: + nomad: + endpoint: + address: http://127.0.0.1:4646 + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + [providers.nomad.endpoint] + address = "http://127.0.0.1:4646" + # ... +``` + +```bash tab="CLI" +--providers.nomad.endpoint.address=http://127.0.0.1:4646 +# ... +``` + +#### `datacenter` + +_Optional, Default=""_ + +Defines the datacenter to use. +If not provided in Traefik, Nomad uses the agent datacenter. + +```yaml tab="File (YAML)" +providers: + nomad: + endpoint: + datacenter: dc1 + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + [providers.nomad.endpoint] + datacenter = "dc1" + # ... +``` + +```bash tab="CLI" +--providers.nomad.endpoint.datacenter=dc1 +# ... +``` + +#### `token` + +_Optional, Default=""_ + +Token is used to provide a per-request ACL token, if Nomad ACLs are enabled. + +```yaml tab="File (YAML)" +providers: + nomad: + endpoint: + token: test + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + [providers.nomad.endpoint] + token = "test" + # ... +``` + +```bash tab="CLI" +--providers.nomad.endpoint.token=test +# ... +``` + +#### `endpointWaitTime` + +_Optional, Default=""_ + +Limits the duration for which a Watch can block. +If not provided, the agent default values will be used. + +```yaml tab="File (YAML)" +providers: + nomad: + endpoint: + endpointWaitTime: 15s + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + [providers.nomad.endpoint] + endpointWaitTime = "15s" + # ... +``` + +```bash tab="CLI" +--providers.nomad.endpoint.endpointwaittime=15s +# ... +``` + +#### `httpAuth` + +_Optional_ + +Used to authenticate the HTTP client using HTTP Basic Authentication. + +##### `username` + +_Optional, Default=""_ + +Username to use for HTTP Basic Authentication. + +```yaml tab="File (YAML)" +providers: + nomad: + endpoint: + httpAuth: + username: admin +``` + +```toml tab="File (TOML)" +[providers.nomad.endpoint.httpAuth] + username = "admin" +``` + +```bash tab="CLI" +--providers.nomad.endpoint.httpauth.username=admin +``` + +##### `password` + +_Optional, Default=""_ + +Password to use for HTTP Basic Authentication. + +```yaml tab="File (YAML)" +providers: + nomad: + endpoint: + httpAuth: + password: passw0rd +``` + +```toml tab="File (TOML)" +[providers.nomad.endpoint.httpAuth] + password = "passw0rd" +``` + +```bash tab="CLI" +--providers.nomad.endpoint.httpauth.password=passw0rd +``` + +#### `tls` + +_Optional_ + +Defines the TLS configuration used for the secure connection to the Nomad API. + +##### `ca` + +_Optional_ + +`ca` is the path to the certificate authority used for the secure connection to Nomad, +it defaults to the system bundle. + +```yaml tab="File (YAML)" +providers: + nomad: + endpoint: + tls: + ca: path/to/ca.crt +``` + +```toml tab="File (TOML)" +[providers.nomad.endpoint.tls] + ca = "path/to/ca.crt" +``` + +```bash tab="CLI" +--providers.nomad.endpoint.tls.ca=path/to/ca.crt +``` + +##### `cert` + +_Optional_ + +`cert` is the path to the public certificate used for the secure connection to the Nomad API. +When using this option, setting the `key` option is required. + +```yaml tab="File (YAML)" +providers: + nomad: + endpoint: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[providers.nomad.endpoint.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--providers.nomad.endpoint.tls.cert=path/to/foo.cert +--providers.nomad.endpoint.tls.key=path/to/foo.key +``` + +##### `key` + +_Optional_ + +`key` is the path to the private key used for the secure connection to the Nomad API. +When using this option, setting the `cert` option is required. + +```yaml tab="File (YAML)" +providers: + nomad: + endpoint: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[providers.nomad.endpoint.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--providers.nomad.endpoint.tls.cert=path/to/foo.cert +--providers.nomad.endpoint.tls.key=path/to/foo.key +``` + +##### `insecureSkipVerify` + +_Optional, Default=false_ + +If `insecureSkipVerify` is `true`, the TLS connection to Nomad accepts any certificate presented by the server regardless of the hostnames it covers. + +```yaml tab="File (YAML)" +providers: + nomad: + endpoint: + tls: + insecureSkipVerify: true +``` + +```toml tab="File (TOML)" +[providers.nomad.endpoint.tls] + insecureSkipVerify = true +``` + +```bash tab="CLI" +--providers.nomad.endpoint.tls.insecureskipverify=true +``` + +### `exposedByDefault` + +_Optional, Default=true_ + +Expose Nomad services by default in Traefik. +If set to `false`, services that do not have a `traefik.enable=true` tag will be ignored from the resulting routing configuration. + +For additional information, refer to [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery). + +```yaml tab="File (YAML)" +providers: + nomad: + exposedByDefault: false + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + exposedByDefault = false + # ... +``` + +```bash tab="CLI" +--providers.nomad.exposedByDefault=false +# ... +``` + +### `defaultRule` + +_Optional, Default=```Host(`{{ normalize .Name }}`)```_ + +The default host rule for all services. + +For a given service, if no routing rule was defined by a tag, it is defined by this `defaultRule` instead. +The `defaultRule` must be set to a valid [Go template](https://pkg.go.dev/text/template/), +and can include [sprig template functions](https://masterminds.github.io/sprig/). +The service name can be accessed with the `Name` identifier, +and the template has access to all the labels (i.e. tags beginning with the `prefix`) defined on this service. + +The option can be overridden on an instance basis with the `traefik.http.routers.{name-of-your-choice}.rule` tag. + +```yaml tab="File (YAML)" +providers: + nomad: + defaultRule: "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)" + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)" + # ... +``` + +```bash tab="CLI" +--providers.nomad.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)" +# ... +``` + +### `constraints` + +_Optional, Default=""_ + +The `constraints` option can be set to an expression that Traefik matches against the service tags to determine whether +to create any route for that service. If none of the service tags match the expression, no route for that service is +created. If the expression is empty, all detected services are included. + +The expression syntax is based on the ```Tag(`tag`)```, and ```TagRegex(`tag`)``` functions, +as well as the usual boolean logic, as shown in examples below. + +??? example "Constraints Expression Examples" + + ```toml + # Includes only services having the tag `a.tag.name=foo` + constraints = "Tag(`a.tag.name=foo`)" + ``` + + ```toml + # Excludes services having any tag `a.tag.name=foo` + constraints = "!Tag(`a.tag.name=foo`)" + ``` + + ```toml + # With logical AND. + constraints = "Tag(`a.tag.name`) && Tag(`another.tag.name`)" + ``` + + ```toml + # With logical OR. + constraints = "Tag(`a.tag.name`) || Tag(`another.tag.name`)" + ``` + + ```toml + # With logical AND and OR, with precedence set by parentheses. + constraints = "Tag(`a.tag.name`) && (Tag(`another.tag.name`) || Tag(`yet.another.tag.name`))" + ``` + + ```toml + # Includes only services having a tag matching the `a\.tag\.t.+` regular expression. + constraints = "TagRegex(`a\.tag\.t.+`)" + ``` + +```yaml tab="File (YAML)" +providers: + nomad: + constraints: "Tag(`a.tag.name`)" + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + constraints = "Tag(`a.tag.name`)" + # ... +``` + +```bash tab="CLI" +--providers.nomad.constraints="Tag(`a.tag.name`)" +# ... +``` + +For additional information, refer to [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery). + +### `namespace` + +_Optional, Default=""_ + +The `namespace` option defines the namespace in which the Nomad services will be discovered. + +```yaml tab="File (YAML)" +providers: + nomad: + namespace: "production" + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + namespace = "production" + # ... +``` + +```bash tab="CLI" +--providers.nomad.namespace=production +# ... +``` diff --git a/docs/content/providers/overview.md b/docs/content/providers/overview.md index 0c6ec0d5d..8bacfc7d2 100644 --- a/docs/content/providers/overview.md +++ b/docs/content/providers/overview.md @@ -139,6 +139,7 @@ Below is the list of the currently supported providers in Traefik. | [Kubernetes Ingress](./kubernetes-ingress.md) | Orchestrator | Ingress | `kubernetes` | | [Kubernetes Gateway API](./kubernetes-gateway.md) | Orchestrator | Gateway API Resource | `kubernetesgateway` | | [Consul Catalog](./consul-catalog.md) | Orchestrator | Label | `consulcatalog` | +| [Nomad](./nomad.md) | Orchestrator | Label | `nomad` | | [ECS](./ecs.md) | Orchestrator | Label | `ecs` | | [Marathon](./marathon.md) | Orchestrator | Label | `marathon` | | [Rancher](./rancher.md) | Orchestrator | Label | `rancher` | @@ -213,6 +214,7 @@ List of providers that support these features: - [Docker](./docker.md#exposedbydefault) - [Consul Catalog](./consul-catalog.md#exposedbydefault) +- [Nomad](./nomad.md#exposedbydefault) - [Rancher](./rancher.md#exposedbydefault) - [Marathon](./marathon.md#exposedbydefault) @@ -222,6 +224,7 @@ List of providers that support constraints: - [Docker](./docker.md#constraints) - [Consul Catalog](./consul-catalog.md#constraints) +- [Nomad](./nomad.md#constraints) - [Rancher](./rancher.md#constraints) - [Marathon](./marathon.md#constraints) - [Kubernetes CRD](./kubernetes-crd.md#labelselector) diff --git a/docs/content/reference/dynamic-configuration/nomad.md b/docs/content/reference/dynamic-configuration/nomad.md new file mode 100644 index 000000000..0a8b334ad --- /dev/null +++ b/docs/content/reference/dynamic-configuration/nomad.md @@ -0,0 +1,16 @@ +--- +title: "Traefik Nomad Service Discovery Configuration Documentation" +description: "View the reference for performing dynamic configurations with Traefik Proxy and Nomad Service Discovery. Read the technical documentation." +--- + +# Nomad Service Discovery Configuration Reference + +Dynamic configuration with Nomad Service Discovery +{: .subtitle } + +The labels are case insensitive. + +```yaml +--8<-- "content/reference/dynamic-configuration/nomad.yml" +--8<-- "content/reference/dynamic-configuration/docker-labels.yml" +``` diff --git a/docs/content/reference/dynamic-configuration/nomad.yml b/docs/content/reference/dynamic-configuration/nomad.yml new file mode 100644 index 000000000..23efc00c6 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/nomad.yml @@ -0,0 +1 @@ +- "traefik.enable=true" diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 1f7fba475..665e8448a 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -822,6 +822,57 @@ Display additional provider logs. (Default: ```false```) `--providers.marathon.watch`: Watch provider. (Default: ```true```) +`--providers.nomad`: +Enable Nomad backend with default settings. (Default: ```false```) + +`--providers.nomad.constraints`: +Constraints is an expression that Traefik matches against the Nomad service's tags to determine whether to create route(s) for that service. + +`--providers.nomad.defaultrule`: +Default rule. (Default: ```Host(`{{ normalize .Name }}`)```) + +`--providers.nomad.endpoint.address`: +The address of the Nomad server, including scheme and port. + +`--providers.nomad.endpoint.endpointwaittime`: +WaitTime limits how long a Watch will block. If not provided, the agent default values will be used (Default: ```0```) + +`--providers.nomad.endpoint.region`: +Nomad region to use. If not provided, the local agent region is used. + +`--providers.nomad.endpoint.tls.ca`: +TLS CA + +`--providers.nomad.endpoint.tls.caoptional`: +TLS CA.Optional (Default: ```false```) + +`--providers.nomad.endpoint.tls.cert`: +TLS cert + +`--providers.nomad.endpoint.tls.insecureskipverify`: +TLS insecure skip verify (Default: ```false```) + +`--providers.nomad.endpoint.tls.key`: +TLS key + +`--providers.nomad.endpoint.token`: +Token is used to provide a per-request ACL token. + +`--providers.nomad.exposedbydefault`: +Expose Nomad services by default. (Default: ```true```) + +`--providers.nomad.namespace`: +Sets the Nomad namespace used to discover services. + +`--providers.nomad.prefix`: +Prefix for nomad service tags. (Default: ```traefik```) + +`--providers.nomad.refreshinterval`: +Interval for polling Nomad API. (Default: ```15```) + +`--providers.nomad.stale`: +Use stale consistency for catalog reads. (Default: ```false```) + `--providers.plugin.`: Plugins configuration. diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 861ac7c87..fca125102 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -822,6 +822,57 @@ Display additional provider logs. (Default: ```false```) `TRAEFIK_PROVIDERS_MARATHON_WATCH`: Watch provider. (Default: ```true```) +`TRAEFIK_PROVIDERS_NOMAD`: +Enable Nomad backend with default settings. (Default: ```false```) + +`TRAEFIK_PROVIDERS_NOMAD_CONSTRAINTS`: +Constraints is an expression that Traefik matches against the Nomad service's tags to determine whether to create route(s) for that service. + +`TRAEFIK_PROVIDERS_NOMAD_DEFAULTRULE`: +Default rule. (Default: ```Host(`{{ normalize .Name }}`)```) + +`TRAEFIK_PROVIDERS_NOMAD_ENDPOINT_ADDRESS`: +The address of the Nomad server, including scheme and port. + +`TRAEFIK_PROVIDERS_NOMAD_ENDPOINT_ENDPOINTWAITTIME`: +WaitTime limits how long a Watch will block. If not provided, the agent default values will be used (Default: ```0```) + +`TRAEFIK_PROVIDERS_NOMAD_ENDPOINT_REGION`: +Nomad region to use. If not provided, the local agent region is used. + +`TRAEFIK_PROVIDERS_NOMAD_ENDPOINT_TLS_CA`: +TLS CA + +`TRAEFIK_PROVIDERS_NOMAD_ENDPOINT_TLS_CAOPTIONAL`: +TLS CA.Optional (Default: ```false```) + +`TRAEFIK_PROVIDERS_NOMAD_ENDPOINT_TLS_CERT`: +TLS cert + +`TRAEFIK_PROVIDERS_NOMAD_ENDPOINT_TLS_INSECURESKIPVERIFY`: +TLS insecure skip verify (Default: ```false```) + +`TRAEFIK_PROVIDERS_NOMAD_ENDPOINT_TLS_KEY`: +TLS key + +`TRAEFIK_PROVIDERS_NOMAD_ENDPOINT_TOKEN`: +Token is used to provide a per-request ACL token. + +`TRAEFIK_PROVIDERS_NOMAD_EXPOSEDBYDEFAULT`: +Expose Nomad services by default. (Default: ```true```) + +`TRAEFIK_PROVIDERS_NOMAD_NAMESPACE`: +Sets the Nomad namespace used to discover services. + +`TRAEFIK_PROVIDERS_NOMAD_PREFIX`: +Prefix for nomad service tags. (Default: ```traefik```) + +`TRAEFIK_PROVIDERS_NOMAD_REFRESHINTERVAL`: +Interval for polling Nomad API. (Default: ```15```) + +`TRAEFIK_PROVIDERS_NOMAD_STALE`: +Use stale consistency for catalog reads. (Default: ```false```) + `TRAEFIK_PROVIDERS_PLUGIN_`: Plugins configuration. diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 7f30c61f8..92b3357c6 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -174,6 +174,25 @@ [providers.consulCatalog.endpoint.httpAuth] username = "foobar" password = "foobar" + [providers.nomad] + constraints = "foobar" + prefix = "foobar" + refreshInterval = "42s" + stale = true + exposedByDefault = true + defaultRule = "foobar" + namespace = "foobar" + [providers.nomad.endpoint] + address = "foobar" + region = "foobar" + token = "foobar" + endpointWaitTime = "42s" + [providers.nomad.endpoint.tls] + ca = "foobar" + caOptional = true + cert = "foobar" + key = "foobar" + insecureSkipVerify = true [providers.ecs] constraints = "foobar" exposedByDefault = true diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 656909d9b..c6f47b78b 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -188,6 +188,25 @@ providers: httpAuth: username: foobar password: foobar + nomad: + constraints: foobar + prefix: foobar + refreshInterval: 42s + stale: true + exposedByDefault: true + defaultRule: foobar + namespace: foobar + endpoint: + address: foobar + region: foobar + token: foobar + endpointWaitTime: 42s + tls: + ca: foobar + caOptional: true + cert: foobar + key: foobar + insecureSkipVerify: true ecs: constraints: foobar exposedByDefault: true diff --git a/docs/content/routing/providers/consul-catalog.md b/docs/content/routing/providers/consul-catalog.md index b7c4801da..7115726ce 100644 --- a/docs/content/routing/providers/consul-catalog.md +++ b/docs/content/routing/providers/consul-catalog.md @@ -8,7 +8,7 @@ description: "Learn how to use Consul Catalog as a provider for routing configur A Story of Tags, Services & Instances {: .subtitle } -![Rancher](../../assets/img/providers/consul.png) +![Consul Catalog](../../assets/img/providers/consul.png) Attach tags to your services and let Traefik do the rest! diff --git a/docs/content/routing/providers/nomad.md b/docs/content/routing/providers/nomad.md new file mode 100644 index 000000000..fcba19523 --- /dev/null +++ b/docs/content/routing/providers/nomad.md @@ -0,0 +1,466 @@ +--- +title: "Traefik Nomad Service Discovery Routing" +description: "Learn how to use Nomad Service Discovery as a provider for routing configurations in Traefik Proxy. Read the technical documentation." +--- + +# Traefik and Nomad Service Discovery + +A story of Tags, Services & Nomads +{: .subtitle } + +![Nomad](../../assets/img/providers/nomad.png) + +Attach tags to your Nomad services and let Traefik do the rest! + +## Routing Configuration + +!!! info "tags" + + - tags are case insensitive. + - The complete list of tags can be found [the reference page](../../reference/dynamic-configuration/nomad.md) + +### General + +Traefik creates, for each Nomad service, a corresponding Traefik [service](../services/index.md) and [router](../routers/index.md). + +The Traefik service automatically gets a server per instance in this Nomad service, and the router gets a default rule attached to it, based on the Nomad service name. + +### Routers + +To update the configuration of the Router automatically attached to the service, add tags starting with `traefik.routers.{name-of-your-choice}.` and followed by the option you want to change. + +For example, to change the rule, you could add the tag ```traefik.http.routers.my-service.rule=Host(`example.com`)```. + +??? info "`traefik.http.routers..rule`" + + See [rule](../routers/index.md#rule) for more information. + + ```yaml + traefik.http.routers.myrouter.rule=Host(`example.com`) + ``` + +??? info "`traefik.http.routers..entrypoints`" + + See [entry points](../routers/index.md#entrypoints) for more information. + + ```yaml + traefik.http.routers.myrouter.entrypoints=web,websecure + ``` + +??? info "`traefik.http.routers..middlewares`" + + See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information. + + ```yaml + traefik.http.routers.myrouter.middlewares=auth,prefix,cb + ``` + +??? info "`traefik.http.routers..service`" + + See [rule](../routers/index.md#service) for more information. + + ```yaml + traefik.http.routers.myrouter.service=myservice + ``` + +??? info "`traefik.http.routers..tls`" + + See [tls](../routers/index.md#tls) for more information. + + ```yaml + traefik.http.routers.myrouter.tls=true + ``` + +??? info "`traefik.http.routers..tls.certresolver`" + + See [certResolver](../routers/index.md#certresolver) for more information. + + ```yaml + traefik.http.routers.myrouter.tls.certresolver=myresolver + ``` + +??? info "`traefik.http.routers..tls.domains[n].main`" + + See [domains](../routers/index.md#domains) for more information. + + ```yaml + traefik.http.routers.myrouter.tls.domains[0].main=example.org + ``` + +??? info "`traefik.http.routers..tls.domains[n].sans`" + + See [domains](../routers/index.md#domains) for more information. + + ```yaml + traefik.http.routers.myrouter.tls.domains[0].sans=test.example.org,dev.example.org + ``` + +??? info "`traefik.http.routers..tls.options`" + + See [options](../routers/index.md#options) for more information. + + ```yaml + traefik.http.routers.myrouter.tls.options=foobar + ``` + +??? info "`traefik.http.routers..priority`" + + See [priority](../routers/index.md#priority) for more information. + + ```yaml + traefik.http.routers.myrouter.priority=42 + ``` + +### Services + +To update the configuration of the Service automatically attached to the service, +add tags starting with `traefik.http.services.{name-of-your-choice}.`, followed by the option you want to change. + +For example, to change the `passHostHeader` behavior, +you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.passhostheader=false`. + +??? info "`traefik.http.services..loadbalancer.server.port`" + + Registers a port. + Useful when the service exposes multiples ports. + + ```yaml + traefik.http.services.myservice.loadbalancer.server.port=8080 + ``` + +??? info "`traefik.http.services..loadbalancer.server.scheme`" + + Overrides the default scheme. + + ```yaml + traefik.http.services.myservice.loadbalancer.server.scheme=http + ``` + +??? info "`traefik.http.services..loadbalancer.serverstransport`" + + Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. + See [serverstransport](../services/index.md#serverstransport) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.serverstransport=foobar@file + ``` + +??? info "`traefik.http.services..loadbalancer.passhostheader`" + + See [pass Host header](../services/index.md#pass-host-header) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.passhostheader=true + ``` + +??? info "`traefik.http.services..loadbalancer.healthcheck.headers.`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar + ``` + +??? info "`traefik.http.services..loadbalancer.healthcheck.hostname`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.hostname=example.org + ``` + +??? info "`traefik.http.services..loadbalancer.healthcheck.interval`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.interval=10 + ``` + +??? info "`traefik.http.services..loadbalancer.healthcheck.path`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo + ``` + +??? info "`traefik.http.services..loadbalancer.healthcheck.port`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.port=42 + ``` + +??? info "`traefik.http.services..loadbalancer.healthcheck.scheme`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http + ``` + +??? info "`traefik.http.services..loadbalancer.healthcheck.timeout`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10 + ``` + +??? info "`traefik.http.services..loadbalancer.healthcheck.followredirects`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.followredirects=true + ``` + +??? info "`traefik.http.services..loadbalancer.sticky.cookie`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.sticky.cookie=true + ``` + +??? info "`traefik.http.services..loadbalancer.sticky.cookie.httponly`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true + ``` + +??? info "`traefik.http.services..loadbalancer.sticky.cookie.name`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar + ``` + +??? info "`traefik.http.services..loadbalancer.sticky.cookie.secure`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true + ``` + +??? info "`traefik.http.services..loadbalancer.sticky.cookie.samesite`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none + ``` + +??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" + + See [response forwarding](../services/index.md#response-forwarding) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10 + ``` + +### Middleware + +You can declare pieces of middleware using tags starting with `traefik.http.middlewares.{name-of-your-choice}.`, followed by the middleware type/options. + +For example, to declare a middleware [`redirectscheme`](../../middlewares/http/redirectscheme.md) named `my-redirect`, you'd write `traefik.http.middlewares.my-redirect.redirectscheme.scheme: https`. + +More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md). + +??? example "Declaring and Referencing a Middleware" + + ```yaml + # ... + # Declaring a middleware + traefik.http.middlewares.my-redirect.redirectscheme.scheme=https + # Referencing a middleware + traefik.http.routers.my-service.middlewares=my-redirect + ``` + +!!! warning "Conflicts in Declaration" + + If you declare multiple middleware with the same name but with different parameters, the middleware fails to be declared. + +### TCP + +You can declare TCP Routers and/or Services using tags. + +??? example "Declaring TCP Routers and Services" + + ```yaml + traefik.tcp.routers.my-router.rule=HostSNI(`example.com`) + traefik.tcp.routers.my-router.tls=true + traefik.tcp.services.my-service.loadbalancer.server.port=4123 + ``` + +!!! warning "TCP and HTTP" + + If you declare a TCP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no TCP Router/Service is defined). + You can declare both a TCP Router/Service and an HTTP Router/Service for the same Nomad service (but you have to do so manually). + +#### TCP Routers + +??? info "`traefik.tcp.routers..entrypoints`" + + See [entry points](../routers/index.md#entrypoints_1) for more information. + + ```yaml + traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2 + ``` + +??? info "`traefik.tcp.routers..rule`" + + See [rule](../routers/index.md#rule_1) for more information. + + ```yaml + traefik.tcp.routers.mytcprouter.rule=HostSNI(`example.com`) + ``` + +??? info "`traefik.tcp.routers..service`" + + See [service](../routers/index.md#services) for more information. + + ```yaml + traefik.tcp.routers.mytcprouter.service=myservice + ``` + +??? info "`traefik.tcp.routers..tls`" + + See [TLS](../routers/index.md#tls_1) for more information. + + ```yaml + traefik.tcp.routers.mytcprouter.tls=true + ``` + +??? info "`traefik.tcp.routers..tls.certresolver`" + + See [certResolver](../routers/index.md#certresolver_1) for more information. + + ```yaml + traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver + ``` + +??? info "`traefik.tcp.routers..tls.domains[n].main`" + + See [domains](../routers/index.md#domains_1) for more information. + + ```yaml + traefik.tcp.routers.mytcprouter.tls.domains[0].main=example.org + ``` + +??? info "`traefik.tcp.routers..tls.domains[n].sans`" + + See [domains](../routers/index.md#domains_1) for more information. + + ```yaml + traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.example.org,dev.example.org + ``` + +??? info "`traefik.tcp.routers..tls.options`" + + See [options](../routers/index.md#options_1) for more information. + + ```yaml + traefik.tcp.routers.mytcprouter.tls.options=myoptions + ``` + +??? info "`traefik.tcp.routers..tls.passthrough`" + + See [TLS](../routers/index.md#tls_1) for more information. + + ```yaml + traefik.tcp.routers.mytcprouter.tls.passthrough=true + ``` + +#### TCP Services + +??? info "`traefik.tcp.services..loadbalancer.server.port`" + + Registers a port of the application. + + ```yaml + traefik.tcp.services.mytcpservice.loadbalancer.server.port=423 + ``` + +??? info "`traefik.tcp.services..loadbalancer.terminationdelay`" + + See [termination delay](../services/index.md#termination-delay) for more information. + + ```yaml + traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100 + ``` + +??? info "`traefik.tcp.services..loadbalancer.proxyprotocol.version`" + + See [PROXY protocol](../services/index.md#proxy-protocol) for more information. + + ```yaml + traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version=1 + ``` + +### UDP + +You can declare UDP Routers and/or Services using tags. + +??? example "Declaring UDP Routers and Services" + + ```yaml + traefik.udp.routers.my-router.entrypoints=udp + traefik.udp.services.my-service.loadbalancer.server.port=4123 + ``` + +!!! warning "UDP and HTTP" + + If you declare a UDP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no UDP Router/Service is defined). + You can declare both a UDP Router/Service and an HTTP Router/Service for the same Nomad service (but you have to do so manually). + +#### UDP Routers + +??? info "`traefik.udp.routers..entrypoints`" + + See [entry points](../routers/index.md#entrypoints_2) for more information. + + ```yaml + traefik.udp.routers.myudprouter.entrypoints=ep1,ep2 + ``` + +??? info "`traefik.udp.routers..service`" + + See [service](../routers/index.md#services_1) for more information. + + ```yaml + traefik.udp.routers.myudprouter.service=myservice + ``` + +#### UDP Services + +??? info "`traefik.udp.services..loadbalancer.server.port`" + + Registers a port of the application. + + ```yaml + traefik.udp.services.myudpservice.loadbalancer.server.port=423 + ``` + +### Specific Provider Options + +#### `traefik.enable` + +```yaml +traefik.enable=true +``` + +You can tell Traefik to consider (or not) the service by setting `traefik.enable` to true or false. + +This option overrides the value of `exposedByDefault`. + +#### Port Lookup + +Traefik is capable of detecting the port to use, by following the default Nomad Service Discovery flow. +That means, if you just expose lets say port `:1337` on the Nomad job, traefik will pick up this port and use it. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 08c78cae2..c02fce7c6 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -77,6 +77,7 @@ nav: - 'Kubernetes Ingress': 'providers/kubernetes-ingress.md' - 'Kubernetes Gateway API': 'providers/kubernetes-gateway.md' - 'Consul Catalog': 'providers/consul-catalog.md' + - 'Nomad': 'providers/nomad.md' - 'ECS': 'providers/ecs.md' - 'Marathon': 'providers/marathon.md' - 'Rancher': 'providers/rancher.md' @@ -97,6 +98,7 @@ nav: - 'Kubernetes Ingress': 'routing/providers/kubernetes-ingress.md' - 'Kubernetes Gateway API': 'routing/providers/kubernetes-gateway.md' - 'Consul Catalog': 'routing/providers/consul-catalog.md' + - 'Nomad': 'routing/providers/nomad.md' - 'ECS': 'routing/providers/ecs.md' - 'Marathon': 'routing/providers/marathon.md' - 'Rancher': 'routing/providers/rancher.md' @@ -194,6 +196,7 @@ nav: - 'Kubernetes CRD': 'reference/dynamic-configuration/kubernetes-crd.md' - 'Kubernetes Gateway API': 'reference/dynamic-configuration/kubernetes-gateway.md' - 'Consul Catalog': 'reference/dynamic-configuration/consul-catalog.md' + - 'Nomad': "reference/dynamic-configuration/nomad.md" - 'ECS': 'reference/dynamic-configuration/ecs.md' - 'KV': 'reference/dynamic-configuration/kv.md' - 'Marathon': 'reference/dynamic-configuration/marathon.md' diff --git a/go.mod b/go.mod index dd8a68cb5..c8c78965a 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/hashicorp/go-hclog v0.16.1 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-version v1.3.0 + github.com/hashicorp/nomad/api v0.0.0-20220506174431-b5665129cd1f github.com/influxdata/influxdb-client-go/v2 v2.7.0 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d github.com/instana/go-sensor v1.38.3 @@ -41,7 +42,7 @@ require ( github.com/miekg/dns v1.1.47 github.com/mitchellh/copystructure v1.0.0 github.com/mitchellh/hashstructure v1.0.0 - github.com/mitchellh/mapstructure v1.4.2 + github.com/mitchellh/mapstructure v1.4.3 github.com/opentracing/opentracing-go v1.2.0 github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 github.com/openzipkin/zipkin-go v0.2.2 @@ -176,8 +177,9 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 // indirect github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect github.com/hashicorp/consul/sdk v0.8.0 // indirect + github.com/hashicorp/cronexpr v1.1.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.1 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.0 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect github.com/hashicorp/go-retryablehttp v0.7.1 // indirect @@ -229,7 +231,7 @@ require ( github.com/miekg/pkcs11 v1.0.3 // indirect github.com/mimuret/golang-iij-dpf v0.7.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/go-testing-interface v1.14.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/reflectwalk v1.0.1 // indirect github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf // indirect github.com/moby/locker v1.0.1 // indirect diff --git a/go.sum b/go.sum index 1f63cfccd..fea636ab1 100644 --- a/go.sum +++ b/go.sum @@ -887,6 +887,8 @@ github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/consul/sdk v0.7.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= github.com/hashicorp/consul/sdk v0.8.0 h1:OJtKBtEjboEZvG6AOUdh4Z1Zbyu0WcxQ0qatRrZHTVU= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= +github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -894,8 +896,9 @@ github.com/hashicorp/go-bexpr v0.1.2 h1:ijMXI4qERbzxbCnkxmfUtwMyjrrk3y+Vt0MxojNC github.com/hashicorp/go-bexpr v0.1.2/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-connlimit v0.3.0/go.mod h1:OUj9FGL1tPIhl/2RCfzYHrIiWj+VVPGNyVPnUX8AqS0= github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840/go.mod h1:Abjk0jbRkDaNCzsRhOv2iDCofYpX1eVsjozoiK63qLA= github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088/go.mod h1:vZu6Opqf49xX5lsFAu7iFNewkcVF1sn/wyapZh5ytlg= @@ -963,6 +966,8 @@ github.com/hashicorp/memberlist v0.2.4/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/memberlist v0.3.0 h1:8+567mCcFDnS5ADl7lrpxPMWiFCElyUEeW0gtj34fMA= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q= +github.com/hashicorp/nomad/api v0.0.0-20220506174431-b5665129cd1f h1:jSBbBJcPca465gK6XfwdXRQnFCd63e0oJmqllZTsawI= +github.com/hashicorp/nomad/api v0.0.0-20220506174431-b5665129cd1f/go.mod h1:b/AoT79m3PEpb6tKCFKva/M+q1rKJNUk5mdu1S8DymM= github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= github.com/hashicorp/raft v1.2.0/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= github.com/hashicorp/raft v1.3.2 h1:j2tqHqFnDdWCepLxzuo3b6WzS2krIweBrvEoqBbWMTo= @@ -1099,6 +1104,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -1220,8 +1227,9 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa9sVVvYbuqZ7pIAtI= github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= @@ -1236,8 +1244,9 @@ github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1-0.20210112042008-8ebf2d61a8b4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/pointerstructure v1.0.0 h1:ATSdz4NWrmWPOF1CeCBU4sMCno2hgqdbSrRPFWQSVZI= github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= @@ -1510,6 +1519,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 095ce08ff..4cc699f22 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -26,6 +26,7 @@ import ( "github.com/traefik/traefik/v2/pkg/provider/kv/redis" "github.com/traefik/traefik/v2/pkg/provider/kv/zk" "github.com/traefik/traefik/v2/pkg/provider/marathon" + "github.com/traefik/traefik/v2/pkg/provider/nomad" "github.com/traefik/traefik/v2/pkg/provider/rancher" "github.com/traefik/traefik/v2/pkg/provider/rest" "github.com/traefik/traefik/v2/pkg/tls" @@ -184,6 +185,7 @@ type Providers struct { Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` Rancher *rancher.Provider `description:"Enable Rancher backend with default settings." json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Nomad *nomad.Provider `description:"Enable Nomad backend with default settings." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Consul *consul.ProviderBuilder `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` diff --git a/pkg/provider/aggregator/aggregator.go b/pkg/provider/aggregator/aggregator.go index 16a32003f..07d4488fb 100644 --- a/pkg/provider/aggregator/aggregator.go +++ b/pkg/provider/aggregator/aggregator.go @@ -114,6 +114,10 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator { } } + if conf.Nomad != nil { + p.quietAddProvider(conf.Nomad) + } + if conf.Consul != nil { for _, pvd := range conf.Consul.BuildProviders() { p.quietAddProvider(pvd) diff --git a/pkg/provider/nomad/config.go b/pkg/provider/nomad/config.go new file mode 100644 index 000000000..6ae3ebdfb --- /dev/null +++ b/pkg/provider/nomad/config.go @@ -0,0 +1,267 @@ +package nomad + +import ( + "context" + "errors" + "fmt" + "net" + "strconv" + + "github.com/traefik/traefik/v2/pkg/config/dynamic" + "github.com/traefik/traefik/v2/pkg/config/label" + "github.com/traefik/traefik/v2/pkg/log" + "github.com/traefik/traefik/v2/pkg/provider" + "github.com/traefik/traefik/v2/pkg/provider/constraints" +) + +func (p *Provider) buildConfig(ctx context.Context, items []item) *dynamic.Configuration { + configurations := make(map[string]*dynamic.Configuration) + + for _, i := range items { + svcName := provider.Normalize(i.Node + "-" + i.Name + "-" + i.ID) + ctxSvc := log.With(ctx, log.Str(log.ServiceName, svcName)) + + if !p.keepItem(ctxSvc, i) { + continue + } + + logger := log.FromContext(ctx) + labels := tagsToLabels(i.Tags, p.Prefix) + + config, err := label.DecodeConfiguration(labels) + if err != nil { + logger.Errorf("Failed to decode configuration: %v", err) + continue + } + + var tcpOrUDP bool + + if len(config.TCP.Routers) > 0 || len(config.TCP.Services) > 0 { + tcpOrUDP = true + if err := p.buildTCPConfig(i, config.TCP); err != nil { + logger.Errorf("Failed to build TCP service configuration: %v", err) + continue + } + provider.BuildTCPRouterConfiguration(ctxSvc, config.TCP) + } + + if len(config.UDP.Routers) > 0 || len(config.UDP.Services) > 0 { + tcpOrUDP = true + if err := p.buildUDPConfig(i, config.UDP); err != nil { + logger.Errorf("Failed to build UDP service configuration: %v", err) + continue + } + provider.BuildUDPRouterConfiguration(ctxSvc, config.UDP) + } + + // tcp/udp, skip configuring http service + if tcpOrUDP && len(config.HTTP.Routers) == 0 && + len(config.HTTP.Middlewares) == 0 && + len(config.HTTP.Services) == 0 { + configurations[svcName] = config + continue + } + + // configure http service + if err := p.buildServiceConfig(i, config.HTTP); err != nil { + logger.Errorf("Failed to build HTTP service configuration: %v", err) + continue + } + + model := struct { + Name string + Labels map[string]string + }{ + Name: i.Name, + Labels: labels, + } + + provider.BuildRouterConfiguration(ctx, config.HTTP, provider.Normalize(i.Name), p.defaultRuleTpl, model) + configurations[svcName] = config + } + + return provider.Merge(ctx, configurations) +} + +func (p *Provider) buildTCPConfig(i item, configuration *dynamic.TCPConfiguration) error { + if len(configuration.Services) == 0 { + configuration.Services = make(map[string]*dynamic.TCPService) + + lb := new(dynamic.TCPServersLoadBalancer) + lb.SetDefaults() + + configuration.Services[provider.Normalize(i.Name)] = &dynamic.TCPService{ + LoadBalancer: lb, + } + } + + for _, service := range configuration.Services { + if err := p.addServerTCP(i, service.LoadBalancer); err != nil { + return err + } + } + + return nil +} + +func (p *Provider) buildUDPConfig(i item, configuration *dynamic.UDPConfiguration) error { + if len(configuration.Services) == 0 { + configuration.Services = make(map[string]*dynamic.UDPService) + + configuration.Services[provider.Normalize(i.Name)] = &dynamic.UDPService{ + LoadBalancer: new(dynamic.UDPServersLoadBalancer), + } + } + + for _, service := range configuration.Services { + if err := p.addServerUDP(i, service.LoadBalancer); err != nil { + return err + } + } + + return nil +} + +func (p *Provider) buildServiceConfig(i item, configuration *dynamic.HTTPConfiguration) error { + if len(configuration.Services) == 0 { + configuration.Services = make(map[string]*dynamic.Service) + + lb := new(dynamic.ServersLoadBalancer) + lb.SetDefaults() + + configuration.Services[provider.Normalize(i.Name)] = &dynamic.Service{ + LoadBalancer: lb, + } + } + + for _, service := range configuration.Services { + if err := p.addServer(i, service.LoadBalancer); err != nil { + return err + } + } + + return nil +} + +// TODO: check whether it is mandatory to filter again. +func (p *Provider) keepItem(ctx context.Context, i item) bool { + logger := log.FromContext(ctx) + + if !i.ExtraConf.Enable { + logger.Debug("Filtering disabled item") + return false + } + + matches, err := constraints.MatchTags(i.Tags, p.Constraints) + if err != nil { + logger.Errorf("Error matching constraint expressions: %v", err) + return false + } + if !matches { + logger.Debugf("Filtering out item due to constraints: %q", p.Constraints) + return false + } + + // TODO: filter on health when that information exists (nomad 1.4+) + + return true +} + +func (p *Provider) addServerTCP(i item, lb *dynamic.TCPServersLoadBalancer) error { + if lb == nil { + return errors.New("load-balancer is missing") + } + + var port string + if len(lb.Servers) > 0 { + port = lb.Servers[0].Port + } + + if len(lb.Servers) == 0 { + lb.Servers = []dynamic.TCPServer{{}} + } + + if i.Port != 0 && port == "" { + port = strconv.Itoa(i.Port) + } + lb.Servers[0].Port = "" + + if port == "" { + return errors.New("port is missing") + } + + if i.Address == "" { + return errors.New("address is missing") + } + + lb.Servers[0].Address = net.JoinHostPort(i.Address, port) + return nil +} + +func (p *Provider) addServerUDP(i item, lb *dynamic.UDPServersLoadBalancer) error { + if lb == nil { + return errors.New("load-balancer is missing") + } + + var port string + if len(lb.Servers) > 0 { + port = lb.Servers[0].Port + } + + if len(lb.Servers) == 0 { + lb.Servers = []dynamic.UDPServer{{}} + } + + if i.Port != 0 && port == "" { + port = strconv.Itoa(i.Port) + } + lb.Servers[0].Port = "" + + if port == "" { + return errors.New("port is missing") + } + + if i.Address == "" { + return errors.New("address is missing") + } + + lb.Servers[0].Address = net.JoinHostPort(i.Address, port) + return nil +} + +func (p *Provider) addServer(i item, lb *dynamic.ServersLoadBalancer) error { + if lb == nil { + return errors.New("load-balancer is missing") + } + + var port string + if len(lb.Servers) > 0 { + port = lb.Servers[0].Port + } + + if len(lb.Servers) == 0 { + server := dynamic.Server{} + server.SetDefaults() + + lb.Servers = []dynamic.Server{server} + } + + if i.Port != 0 && port == "" { + port = strconv.Itoa(i.Port) + } + lb.Servers[0].Port = "" + + if port == "" { + return errors.New("port is missing") + } + + if i.Address == "" { + return errors.New("address is missing") + } + + scheme := lb.Servers[0].Scheme + lb.Servers[0].Scheme = "" + lb.Servers[0].URL = fmt.Sprintf("%s://%s", scheme, net.JoinHostPort(i.Address, port)) + + return nil +} diff --git a/pkg/provider/nomad/config_test.go b/pkg/provider/nomad/config_test.go new file mode 100644 index 000000000..53cfc49dd --- /dev/null +++ b/pkg/provider/nomad/config_test.go @@ -0,0 +1,2280 @@ +package nomad + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v2/pkg/config/dynamic" +) + +func Test_defaultRule(t *testing.T) { + testCases := []struct { + desc string + items []item + rule string + expected *dynamic.Configuration + }{ + { + desc: "default rule with no variable", + items: []item{ + { + ID: "id", + Node: "node1", + Name: "Test", + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + rule: "Host(`example.com`)", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`example.com`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "default rule with label", + items: []item{ + { + ID: "id", + Node: "Node1", + Name: "Test", + Address: "127.0.0.1", + Tags: []string{ + "traefik.domain=example.com", + }, + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + rule: `Host("{{ .Name }}.{{ index .Labels "traefik.domain" }}")`, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: `Host("Test.example.com")`, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "invalid rule", + items: []item{ + { + ID: "id", + Node: "Node1", + Name: "Test", + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + rule: `Host"{{ .Invalid }}")`, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "default template rule", + items: []item{ + { + ID: "id", + Node: "Node1", + Name: "Test", + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + rule: defaultTemplateRule, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + p := new(Provider) + p.SetDefaults() + p.DefaultRule = test.rule + err := p.Init() + require.NoError(t, err) + + ctx := context.TODO() + config := p.buildConfig(ctx, test.items) + require.Equal(t, test.expected, config) + }) + } +} + +func Test_buildConfig(t *testing.T) { + testCases := []struct { + desc string + items []item + constraints string + expected *dynamic.Configuration + }{ + { + desc: "one service no tags", + items: []item{ + { + ID: "id", + Node: "Node1", + Name: "dev/Test", + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "dev-Test": { + Service: "dev-Test", + Rule: "Host(`dev-Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "dev-Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "two services no tags", + items: []item{ + { + ID: "id1", + Node: "Node1", + Name: "Test1", + Address: "192.168.1.101", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id2", + Node: "Node2", + Name: "Test2", + Address: "192.168.1.102", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test1": { + Service: "Test1", + Rule: "Host(`Test1.traefik.test`)", + }, + "Test2": { + Service: "Test2", + Rule: "Host(`Test2.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://192.168.1.101:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + "Test2": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://192.168.1.102:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "two services with same name no label", + items: []item{ + { + ID: "id1", + Node: "Node1", + Name: "Test", + Tags: []string{}, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id2", + Node: "Node2", + Name: "Test", + Tags: []string{}, + Address: "127.0.0.2", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + { + URL: "http://127.0.0.2:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "two services same name and id no label same node", + items: []item{ + { + ID: "id1", + Node: "Node1", + Name: "Test", + Tags: []string{}, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id1", + Node: "Node1", + Name: "Test", + Tags: []string{}, + Address: "127.0.0.2", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.2:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "two services same service name and id no label on different nodes", + items: []item{ + { + ID: "id1", + Node: "Node1", + Name: "Test", + Tags: []string{}, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id1", + Node: "Node2", + Name: "Test", + Tags: []string{}, + Address: "127.0.0.2", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + { + URL: "http://127.0.0.2:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service with label (not on server)", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.loadbalancer.passhostheader=true", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service with labels", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.loadbalancer.passhostheader = true", + "traefik.http.routers.Router1.rule = Host(`foo.com`)", + "traefik.http.routers.Router1.service = Service1", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Router1": { + Service: "Service1", + Rule: "Host(`foo.com`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service with rule label", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.routers.Router1.rule = Host(`foo.com`)", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + Routers: map[string]*dynamic.Router{ + "Router1": { + Service: "Test", + Rule: "Host(`foo.com`)", + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service with rule label and one traefik service", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.routers.Router1.rule = Host(`foo.com`)", + "traefik.http.services.Service1.loadbalancer.passhostheader = true", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Router1": { + Service: "Service1", + Rule: "Host(`foo.com`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service with rule label and two traefik services", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.routers.Router1.rule = Host(`foo.com`)", + "traefik.http.services.Service1.loadbalancer.passhostheader= true", + "traefik.http.services.Service2.loadbalancer.passhostheader = true", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + "Service2": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "two services with same traefik service and different passhostheader", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.loadbalancer.passhostheader = true", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id2", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.loadbalancer.passhostheader = false", + }, + Address: "127.0.0.2", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "three services with same name and different passhostheader", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.loadbalancer.passhostheader = false", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id2", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.loadbalancer.passhostheader = true", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id3", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.loadbalancer.passhostheader = true", + }, + Address: "127.0.0.2", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "two services with same name and same LB methods", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.loadbalancer.passhostheader = true", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id2", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.loadbalancer.passhostheader = true", + }, + Address: "127.0.0.2", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + { + URL: "http://127.0.0.2:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service with InFlightReq in label (default value)", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.middlewares.Middleware1.inflightreq.amount = 42", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "Middleware1": { + InFlightReq: &dynamic.InFlightReq{ + Amount: 42, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "two services with same middleware", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.middlewares.Middleware1.inflightreq.amount = 42", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id2", + Name: "Test", + Tags: []string{ + "traefik.http.middlewares.Middleware1.inflightreq.amount = 42", + }, + Address: "127.0.0.2", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "Middleware1": { + InFlightReq: &dynamic.InFlightReq{ + Amount: 42, + }, + }, + }, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + { + URL: "http://127.0.0.2:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "two services same name with different middleware", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.middlewares.Middleware1.inflightreq.amount = 42", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id2", + Name: "Test", + Tags: []string{ + "traefik.http.middlewares.Middleware1.inflightreq.amount = 41", + }, + Address: "127.0.0.2", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + { + URL: "http://127.0.0.2:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "two services with different routers with same name", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.routers.Router1.rule = Host(`foo.com`)", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id2", + Name: "Test", + Tags: []string{ + "traefik.http.routers.Router1.rule = Host(`bar.com`)", + }, + Address: "127.0.0.2", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + { + URL: "http://127.0.0.2:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "two services identical routers", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.routers.Router1.rule = Host(`foo.com`)", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id2", + Name: "Test", + Tags: []string{ + "traefik.http.routers.Router1.rule = Host(`foo.com`)", + }, + Address: "127.0.0.2", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Router1": { + Service: "Test", + Rule: "Host(`foo.com`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + { + URL: "http://127.0.0.2:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service with bad label", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.wrong.label = 42", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service with label port", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.LoadBalancer.server.scheme = h2c", + "traefik.http.services.Service1.LoadBalancer.server.port = 8080", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "h2c://127.0.0.1:8080", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service with label port on two services", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.services.Service1.LoadBalancer.server.port = ", + "traefik.http.services.Service2.LoadBalancer.server.port = 8080", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + "Service2": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:8080", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service without port", + items: []item{ + { + ID: "id1", + Node: "Node1", + Name: "Test", + Tags: []string{}, + Address: "127.0.0.2", + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service without port with middleware", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.middlewares.Middleware1.inflightreq.amount = 42", + }, + Address: "127.0.0.2", + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service with traefik.enable false", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.enable=false", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: false}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service with non matching constraints", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tags=foo", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + constraints: `Tag("traefik.tags=bar")`, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "one service with matching constraints", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tags=foo", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + constraints: `Tag("traefik.tags=foo")`, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "middleware used in router", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.http.middlewares.Middleware1.basicauth.users = test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + "traefik.http.routers.Test.middlewares = Middleware1", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + Middlewares: []string{"Middleware1"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "Middleware1": { + BasicAuth: &dynamic.BasicAuth{ + Users: []string{ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + }, + }, + }, + }, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "middleware used in tcp router", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tcp.routers.Test.rule = HostSNI(`foo.bar`)", + "traefik.tcp.middlewares.Middleware1.ipwhitelist.sourcerange = foobar, fiibar", + "traefik.tcp.routers.Test.middlewares = Middleware1", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "Test": { + Service: "Test", + Rule: "HostSNI(`foo.bar`)", + Middlewares: []string{"Middleware1"}, + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{ + "Middleware1": { + IPWhiteList: &dynamic.TCPIPWhiteList{ + SourceRange: []string{"foobar", "fiibar"}, + }, + }, + }, + Services: map[string]*dynamic.TCPService{ + "Test": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:9999", + }, + }, + TerminationDelay: Int(100), + }, + }, + }, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "tcp with tags", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tcp.routers.foo.rule = HostSNI(`foo.bar`)", + "traefik.tcp.routers.foo.tls = true", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "foo": { + Service: "Test", + Rule: "HostSNI(`foo.bar`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "Test": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:9999", + }, + }, + TerminationDelay: Int(100), + }, + }, + }, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "udp with tags", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.udp.routers.foo.entrypoints = mydns", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "foo": { + Service: "Test", + EntryPoints: []string{"mydns"}, + }, + }, + Services: map[string]*dynamic.UDPService{ + "Test": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "127.0.0.1:9999", + }, + }, + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "tcp with label without rule", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tcp.routers.foo.tls = true", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "Test": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:9999", + }, + }, + TerminationDelay: Int(100), + }, + }, + }, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "tcp with tags and port", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tcp.routers.foo.rule = HostSNI(`foo.bar`)", + "traefik.tcp.routers.foo.tls.options = foo", + "traefik.tcp.services.foo.loadbalancer.server.port = 80", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "foo": { + Service: "foo", + Rule: "HostSNI(`foo.bar`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Options: "foo", + }, + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "foo": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:80", + }, + }, + TerminationDelay: Int(100), + }, + }, + }, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "udp with label and port", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.udp.routers.foo.entrypoints = mydns", + "traefik.udp.services.foo.loadbalancer.server.port = 80", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "foo": { + Service: "foo", + EntryPoints: []string{"mydns"}, + }, + }, + Services: map[string]*dynamic.UDPService{ + "foo": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "127.0.0.1:80", + }, + }, + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "tcp with label and port and http service", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tcp.routers.foo.rule = HostSNI(`foo.bar`)", + "traefik.tcp.routers.foo.tls = true", + "traefik.tcp.services.foo.loadbalancer.server.port = 80", + "traefik.http.services.Service1.loadbalancer.passhostheader = true", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id2", + Name: "Test", + Tags: []string{ + "traefik.tcp.routers.foo.rule = HostSNI(`foo.bar`)", + "traefik.tcp.routers.foo.tls = true", + "traefik.tcp.services.foo.loadbalancer.server.port = 80", + "traefik.http.services.Service1.loadbalancer.passhostheader = true", + }, + Address: "127.0.0.2", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "foo": { + Service: "foo", + Rule: "HostSNI(`foo.bar`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "foo": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:80", + }, + { + Address: "127.0.0.2:80", + }, + }, + TerminationDelay: Int(100), + }, + }, + }, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + { + URL: "http://127.0.0.2:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "udp with label and port and http services", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.udp.routers.foo.entrypoints = mydns", + "traefik.udp.services.foo.loadbalancer.server.port = 80", + "traefik.http.services.Service1.loadbalancer.passhostheader = true", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + { + ID: "id2", + Name: "Test", + Tags: []string{ + "traefik.udp.routers.foo.entrypoints = mydns", + "traefik.udp.services.foo.loadbalancer.server.port = 80", + "traefik.http.services.Service1.loadbalancer.passhostheader = true", + }, + Address: "127.0.0.2", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "foo": { + Service: "foo", + EntryPoints: []string{"mydns"}, + }, + }, + Services: map[string]*dynamic.UDPService{ + "foo": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "127.0.0.1:80", + }, + { + Address: "127.0.0.2:80", + }, + }, + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Service1": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:9999", + }, + { + URL: "http://127.0.0.2:9999", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "tcp with tag for tcp service", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tcp.services.foo.loadbalancer.server.port = 80", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "foo": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:80", + }, + }, + TerminationDelay: Int(100), + }, + }, + }, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "udp with label for tcp service", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.udp.services.foo.loadbalancer.server.port = 80", + }, + Address: "127.0.0.1", + Port: 9999, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{ + "foo": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "127.0.0.1:80", + }, + }, + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "tcp with label for tcp service, with termination delay", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tcp.services.foo.loadbalancer.server.port = 80", + "traefik.tcp.services.foo.loadbalancer.terminationdelay = 200", + }, + Address: "127.0.0.1", + Port: 80, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "foo": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "127.0.0.1:80", + }, + }, + TerminationDelay: Int(200), + }, + }, + }, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + p := new(Provider) + p.SetDefaults() + p.DefaultRule = "Host(`{{ normalize .Name }}.traefik.test`)" + p.Constraints = test.constraints + err := p.Init() + require.NoError(t, err) + + ctx := context.TODO() + c := p.buildConfig(ctx, test.items) + require.Equal(t, test.expected, c) + }) + } +} + +func Test_keepItem(t *testing.T) { + testCases := []struct { + name string + i item + constraints string + exp bool + }{ + { + name: "enable true", + i: item{ExtraConf: configuration{Enable: true}}, + exp: true, + }, + { + name: "enable false", + i: item{ExtraConf: configuration{Enable: false}}, + exp: false, + }, + { + name: "constraint matches", + i: item{ + Tags: []string{"traefik.tags=foo"}, + ExtraConf: configuration{Enable: true}, + }, + constraints: `Tag("traefik.tags=foo")`, + exp: true, + }, + { + name: "constraint not match", + i: item{ + Tags: []string{"traefik.tags=foo"}, + ExtraConf: configuration{Enable: true}, + }, + constraints: `Tag("traefik.tags=bar")`, + exp: false, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + p := new(Provider) + p.SetDefaults() + p.Constraints = test.constraints + ctx := context.TODO() + result := p.keepItem(ctx, test.i) + require.Equal(t, test.exp, result) + }) + } +} + +func Int(v int) *int { return &v } +func Bool(v bool) *bool { return &v } diff --git a/pkg/provider/nomad/nomad.go b/pkg/provider/nomad/nomad.go new file mode 100644 index 000000000..c7afa3b1d --- /dev/null +++ b/pkg/provider/nomad/nomad.go @@ -0,0 +1,278 @@ +package nomad + +import ( + "context" + "fmt" + "strings" + "text/template" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/hashicorp/nomad/api" + ptypes "github.com/traefik/paerser/types" + "github.com/traefik/traefik/v2/pkg/config/dynamic" + "github.com/traefik/traefik/v2/pkg/job" + "github.com/traefik/traefik/v2/pkg/log" + "github.com/traefik/traefik/v2/pkg/provider" + "github.com/traefik/traefik/v2/pkg/provider/constraints" + "github.com/traefik/traefik/v2/pkg/safe" + "github.com/traefik/traefik/v2/pkg/types" +) + +const ( + // providerName is the name of this provider. + providerName = "nomad" + + // defaultTemplateRule is the default template for the default rule. + defaultTemplateRule = "Host(`{{ normalize .Name }}`)" + + // defaultPrefix is the default prefix used in tag values indicating the service + // should be consumed and exposed via traefik. + defaultPrefix = "traefik" +) + +var _ provider.Provider = (*Provider)(nil) + +type item struct { + ID string // service ID + Name string // service name + Namespace string // service namespace + Node string // node ID + Datacenter string // region + Address string // service address + Port int // service port + Tags []string // service tags + + ExtraConf configuration // global options +} + +// Provider holds configurations of the provider. +type Provider struct { + DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"` + Constraints string `description:"Constraints is an expression that Traefik matches against the Nomad service's tags to determine whether to create route(s) for that service." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"` + Endpoint *EndpointConfig `description:"Nomad endpoint settings" json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" export:"true"` + Prefix string `description:"Prefix for nomad service tags." json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"` + Stale bool `description:"Use stale consistency for catalog reads." json:"stale,omitempty" toml:"stale,omitempty" yaml:"stale,omitempty" export:"true"` + Namespace string `description:"Sets the Nomad namespace used to discover services." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty" export:"true"` + ExposedByDefault bool `description:"Expose Nomad services by default." json:"exposedByDefault,omitempty" toml:"exposedByDefault,omitempty" yaml:"exposedByDefault,omitempty" export:"true"` + RefreshInterval ptypes.Duration `description:"Interval for polling Nomad API." json:"refreshInterval,omitempty" toml:"refreshInterval,omitempty" yaml:"refreshInterval,omitempty" export:"true"` + + client *api.Client // client for Nomad API + defaultRuleTpl *template.Template // default routing rule +} + +type EndpointConfig struct { + // Address is the Nomad endpoint address, if empty it defaults to NOMAD_ADDR or "http://localhost:4646". + Address string `description:"The address of the Nomad server, including scheme and port." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` + // Region is the Nomad region, if empty it defaults to NOMAD_REGION or "global". + Region string `description:"Nomad region to use. If not provided, the local agent region is used." json:"region,omitempty" toml:"region,omitempty" yaml:"region,omitempty"` + // Token is the ACL token to connect with Nomad, if empty it defaults to NOMAD_TOKEN. + Token string `description:"Token is used to provide a per-request ACL token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"` + TLS *types.ClientTLS `description:"Configure TLS." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + EndpointWaitTime ptypes.Duration `description:"WaitTime limits how long a Watch will block. If not provided, the agent default values will be used" json:"endpointWaitTime,omitempty" toml:"endpointWaitTime,omitempty" yaml:"endpointWaitTime,omitempty" export:"true"` +} + +// SetDefaults sets the default values for the Nomad Traefik Provider. +func (p *Provider) SetDefaults() { + p.Endpoint = &EndpointConfig{} + p.Prefix = defaultPrefix + p.ExposedByDefault = true + p.RefreshInterval = ptypes.Duration(15 * time.Second) + p.DefaultRule = defaultTemplateRule +} + +// Init the Nomad Traefik Provider. +func (p *Provider) Init() error { + defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil) + if err != nil { + return fmt.Errorf("error while parsing default rule: %w", err) + } + p.defaultRuleTpl = defaultRuleTpl + return nil +} + +// Provide allows the Nomad Traefik Provider to provide configurations to traefik +// using the given configuration channel. +func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { + var err error + p.client, err = createClient(p.Namespace, p.Endpoint) + if err != nil { + return fmt.Errorf("failed to create nomad API client: %w", err) + } + + pool.GoCtx(func(routineCtx context.Context) { + ctxLog := log.With(routineCtx, log.Str(log.ProviderName, providerName)) + logger := log.FromContext(ctxLog) + + operation := func() error { + ctx, cancel := context.WithCancel(ctxLog) + defer cancel() + + // load initial configuration + if err := p.loadConfiguration(ctx, configurationChan); err != nil { + return fmt.Errorf("failed to load initial nomad services: %w", err) + } + + // issue periodic refreshes in the background + // (Nomad does not support Watch style observations) + ticker := time.NewTicker(time.Duration(p.RefreshInterval)) + defer ticker.Stop() + + // enter loop where we wait for and respond to notifications + for { + select { + case <-ctx.Done(): + return nil + case <-ticker.C: + } + // load services due to refresh + if err := p.loadConfiguration(ctx, configurationChan); err != nil { + return fmt.Errorf("failed to refresh nomad services: %w", err) + } + } + } + + failure := func(err error, d time.Duration) { + logger.Errorf("Provider connection error %+v, retrying in %s", err, d) + } + + if retryErr := backoff.RetryNotify( + safe.OperationWithRecover(operation), + backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), + failure, + ); retryErr != nil { + logger.Errorf("Cannot connect to Nomad server %+v", retryErr) + } + }) + + return nil +} + +func (p *Provider) loadConfiguration(ctx context.Context, configurationC chan<- dynamic.Message) error { + items, err := p.getNomadServiceData(ctx) + if err != nil { + return err + } + configurationC <- dynamic.Message{ + ProviderName: providerName, + Configuration: p.buildConfig(ctx, items), + } + + return nil +} + +func createClient(namespace string, endpoint *EndpointConfig) (*api.Client, error) { + config := api.Config{ + Address: endpoint.Address, + Region: endpoint.Region, + WaitTime: time.Duration(endpoint.EndpointWaitTime), + Namespace: namespace, + } + + if endpoint.TLS != nil { + config.TLSConfig = &api.TLSConfig{ + CACert: endpoint.TLS.CA, + ClientCert: endpoint.TLS.Cert, + ClientKey: endpoint.TLS.Key, + Insecure: endpoint.TLS.InsecureSkipVerify, + } + } + + return api.NewClient(&config) +} + +// configuration contains information from the service's tags that are globals +// (not specific to the dynamic configuration). +type configuration struct { + Enable bool // .enable +} + +// globalConfig returns a configuration with settings not specific to the dynamic configuration (i.e. ".enable"). +func (p *Provider) globalConfig(tags []string) configuration { + enabled := p.ExposedByDefault + labels := tagsToLabels(tags, p.Prefix) + + if v, exists := labels["traefik.enable"]; exists { + enabled = strings.EqualFold(v, "true") + } + + return configuration{Enable: enabled} +} + +func (p *Provider) getNomadServiceData(ctx context.Context) ([]item, error) { + // first, get list of service stubs + opts := &api.QueryOptions{AllowStale: p.Stale} + opts = opts.WithContext(ctx) + + stubs, _, err := p.client.Services().List(opts) + if err != nil { + return nil, err + } + + var items []item + + for _, stub := range stubs { + for _, service := range stub.Services { + logger := log.FromContext(log.With(ctx, log.Str("serviceName", service.ServiceName))) + + globalCfg := p.globalConfig(service.Tags) + if !globalCfg.Enable { + logger.Debug("Filter Nomad service that is not enabled") + continue + } + + matches, err := constraints.MatchTags(service.Tags, p.Constraints) + if err != nil { + logger.Errorf("Error matching constraint expressions: %v", err) + continue + } + + if !matches { + logger.Debugf("Filter Nomad service not matching constraints: %q", p.Constraints) + continue + } + + instances, err := p.fetchService(ctx, service.ServiceName) + if err != nil { + return nil, err + } + + for _, i := range instances { + items = append(items, item{ + ID: i.ID, + Name: i.ServiceName, + Namespace: i.Namespace, + Node: i.NodeID, + Datacenter: i.Datacenter, + Address: i.Address, + Port: i.Port, + Tags: i.Tags, + ExtraConf: p.globalConfig(i.Tags), + }) + } + } + } + + return items, nil +} + +// fetchService queries Nomad API for services matching name, +// that also have the .enable=true set in its tags. +func (p *Provider) fetchService(ctx context.Context, name string) ([]*api.ServiceRegistration, error) { + var tagFilter string + if !p.ExposedByDefault { + tagFilter = fmt.Sprintf(`Tags contains %q`, fmt.Sprintf("%s.enable=true", p.Prefix)) + } + + // TODO: Nomad currently (v1.3.0) does not support health checks, + // and as such does not yet return health status information. + // When it does, refactor this section to include health status. + opts := &api.QueryOptions{AllowStale: p.Stale, Filter: tagFilter} + opts = opts.WithContext(ctx) + + services, _, err := p.client.Services().Get(name, opts) + if err != nil { + return nil, fmt.Errorf("failed to fetch services: %w", err) + } + return services, nil +} diff --git a/pkg/provider/nomad/nomad_test.go b/pkg/provider/nomad/nomad_test.go new file mode 100644 index 000000000..3142eb3e9 --- /dev/null +++ b/pkg/provider/nomad/nomad_test.go @@ -0,0 +1,169 @@ +package nomad + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_globalConfig(t *testing.T) { + cases := []struct { + Name string + Prefix string + Tags []string + ExposedByDefault bool + exp configuration + }{ + { + Name: "expose_by_default_no_tags", + Prefix: "traefik", + Tags: nil, + ExposedByDefault: true, + exp: configuration{Enable: true}, + }, + { + Name: "not_expose_by_default_no_tags", + Prefix: "traefik", + Tags: nil, + ExposedByDefault: false, + exp: configuration{Enable: false}, + }, + { + Name: "expose_by_default_tags_enable", + Prefix: "traefik", + Tags: []string{"traefik.enable=true"}, + ExposedByDefault: true, + exp: configuration{Enable: true}, + }, + { + Name: "expose_by_default_tags_disable", + Prefix: "traefik", + Tags: []string{"traefik.enable=false"}, + ExposedByDefault: true, + exp: configuration{Enable: false}, + }, + { + Name: "expose_by_default_tags_enable_custom_prefix", + Prefix: "custom", + Tags: []string{"custom.enable=true"}, + ExposedByDefault: true, + exp: configuration{Enable: true}, + }, + { + Name: "expose_by_default_tags_disable_custom_prefix", + Prefix: "custom", + Tags: []string{"custom.enable=false"}, + ExposedByDefault: true, + exp: configuration{Enable: false}, + }, + } + + for _, test := range cases { + t.Run(test.Name, func(t *testing.T) { + p := Provider{ExposedByDefault: test.ExposedByDefault, Prefix: test.Prefix} + result := p.globalConfig(test.Tags) + require.Equal(t, test.exp, result) + }) + } +} + +func Test_getNomadServiceData(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/services"): + _, _ = w.Write([]byte(services)) + case strings.HasSuffix(r.RequestURI, "/v1/service/redis"): + _, _ = w.Write([]byte(redis)) + case strings.HasSuffix(r.RequestURI, "/v1/service/hello-nomad"): + _, _ = w.Write([]byte(hello)) + } + })) + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.Namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceData(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 2) +} + +const services = ` +[ + { + "Namespace": "default", + "Services": [ + { + "ServiceName": "redis", + "Tags": [ + "traefik.enable=true" + ] + }, + { + "ServiceName": "hello-nomad", + "Tags": [ + "traefik.enable=true", + "traefik.http.routers.hellon.entrypoints=web", + "traefik.http.routers.hellon.service=hello-nomad" + ] + } + ] + } +] +` + +const redis = ` +[ + { + "Address": "127.0.0.1", + "AllocID": "07501480-8175-8071-7da6-133bd1ff890f", + "CreateIndex": 46, + "Datacenter": "dc1", + "ID": "_nomad-task-07501480-8175-8071-7da6-133bd1ff890f-group-redis-redis-redis", + "JobID": "echo", + "ModifyIndex": 46, + "Namespace": "default", + "NodeID": "6d7f412e-e7ff-2e66-d47b-867b0e9d8726", + "Port": 30826, + "ServiceName": "redis", + "Tags": [ + "traefik.enable=true" + ] + } +] +` + +const hello = ` +[ + { + "Address": "127.0.0.1", + "AllocID": "71a63a80-a98a-93ee-4fd7-73b808577c20", + "CreateIndex": 18, + "Datacenter": "dc1", + "ID": "_nomad-task-71a63a80-a98a-93ee-4fd7-73b808577c20-group-hello-nomad-hello-nomad-http", + "JobID": "echo", + "ModifyIndex": 18, + "Namespace": "default", + "NodeID": "6d7f412e-e7ff-2e66-d47b-867b0e9d8726", + "Port": 20627, + "ServiceName": "hello-nomad", + "Tags": [ + "traefik.enable=true", + "traefik.http.routers.hellon.entrypoints=web", + "traefik.http.routers.hellon.service=hello-nomad" + ] + } +] +` diff --git a/pkg/provider/nomad/tag.go b/pkg/provider/nomad/tag.go new file mode 100644 index 000000000..1345091df --- /dev/null +++ b/pkg/provider/nomad/tag.go @@ -0,0 +1,19 @@ +package nomad + +import ( + "strings" +) + +func tagsToLabels(tags []string, prefix string) map[string]string { + labels := make(map[string]string, len(tags)) + for _, tag := range tags { + if strings.HasPrefix(tag, prefix) { + if parts := strings.SplitN(tag, "=", 2); len(parts) == 2 { + left, right := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) + key := "traefik." + strings.TrimPrefix(left, prefix+".") + labels[key] = right + } + } + } + return labels +} diff --git a/pkg/provider/nomad/tag_test.go b/pkg/provider/nomad/tag_test.go new file mode 100644 index 000000000..e15f5486c --- /dev/null +++ b/pkg/provider/nomad/tag_test.go @@ -0,0 +1,109 @@ +package nomad + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_tagsToLabels(t *testing.T) { + testCases := []struct { + desc string + tags []string + prefix string + expected map[string]string + }{ + { + desc: "no tags", + tags: []string{}, + prefix: "traefik", + expected: map[string]string{}, + }, + { + desc: "minimal global config", + tags: []string{"traefik.enable=false"}, + prefix: "traefik", + expected: map[string]string{ + "traefik.enable": "false", + }, + }, + { + desc: "config with domain", + tags: []string{ + "traefik.enable=true", + "traefik.domain=example.com", + }, + prefix: "traefik", + expected: map[string]string{ + "traefik.enable": "true", + "traefik.domain": "example.com", + }, + }, + { + desc: "config with custom prefix", + tags: []string{ + "custom.enable=true", + "custom.domain=example.com", + }, + prefix: "custom", + expected: map[string]string{ + "traefik.enable": "true", + "traefik.domain": "example.com", + }, + }, + { + desc: "config with spaces in tags", + tags: []string{ + "custom.enable = true", + "custom.domain = example.com", + }, + prefix: "custom", + expected: map[string]string{ + "traefik.enable": "true", + "traefik.domain": "example.com", + }, + }, + { + desc: "with a prefix", + prefix: "test", + tags: []string{ + "test.aaa=01", + "test.bbb=02", + "ccc=03", + "test.ddd=04=to", + }, + expected: map[string]string{ + "traefik.aaa": "01", + "traefik.bbb": "02", + "traefik.ddd": "04=to", + }, + }, + { + desc: "with an empty prefix", + prefix: "", + tags: []string{ + "test.aaa=01", + "test.bbb=02", + "ccc=03", + "test.ddd=04=to", + }, + expected: map[string]string{ + "traefik.test.aaa": "01", + "traefik.test.bbb": "02", + "traefik.ccc": "03", + "traefik.test.ddd": "04=to", + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + labels := tagsToLabels(test.tags, test.prefix) + + assert.Equal(t, test.expected, labels) + }) + } +} diff --git a/webui/src/statics/providers/nomad.svg b/webui/src/statics/providers/nomad.svg new file mode 100755 index 000000000..e71d75007 --- /dev/null +++ b/webui/src/statics/providers/nomad.svg @@ -0,0 +1,7 @@ + + + + + +