diff --git a/CHANGELOG.md b/CHANGELOG.md index b20c33ef3..ab3111b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Change Log +## [v1.6.2](https://github.com/containous/traefik/tree/v1.6.2) (2018-05-22) +[All Commits](https://github.com/containous/traefik/compare/v1.6.1...v1.6.2) + +**Bug fixes:** +- **[acme]** fix: acme errors management. ([#3329](https://github.com/containous/traefik/pull/3329) by [ldez](https://github.com/ldez)) +- **[acme]** Force to use ACME v02 endpoint. ([#3358](https://github.com/containous/traefik/pull/3358) by [ldez](https://github.com/ldez)) +- **[file]** No template parsing on traefik configuration file ([#3347](https://github.com/containous/traefik/pull/3347) by [Juliens](https://github.com/Juliens)) +- **[k8s]** Add redirect-permanent to kubernetes template ([#3332](https://github.com/containous/traefik/pull/3332) by [dtomcej](https://github.com/dtomcej)) +- **[logs]** Enhance Load-balancing method validation log. ([#3361](https://github.com/containous/traefik/pull/3361) by [ldez](https://github.com/ldez)) +- **[middleware]** Fix error pages content. ([#3337](https://github.com/containous/traefik/pull/3337) by [ldez](https://github.com/ldez)) +- **[webui]** Route rules overlaps in UI ([#3333](https://github.com/containous/traefik/pull/3333) by [ldez](https://github.com/ldez)) +- **[webui]** WebUI typo into the buffering section. ([#3363](https://github.com/containous/traefik/pull/3363) by [ldez](https://github.com/ldez)) + +**Documentation:** +- **[acme]** Update caServer to letsencrypt one in examples ([#3339](https://github.com/containous/traefik/pull/3339) by [woernfl](https://github.com/woernfl)) +- **[docker]** Add command for basic auth with Docker Compose ([#3346](https://github.com/containous/traefik/pull/3346) by [DeamonMV](https://github.com/DeamonMV)) +- **[docker]** Removes ambiguity with the word 'default' ([#3344](https://github.com/containous/traefik/pull/3344) by [ldez](https://github.com/ldez)) +- **[kv]** Add basicAuth example for KV ([#3274](https://github.com/containous/traefik/pull/3274) by [MichaelErmer](https://github.com/MichaelErmer)) +- **[provider]** Update docs to reflect Provider wording ([#3331](https://github.com/containous/traefik/pull/3331) by [dtomcej](https://github.com/dtomcej)) +- **[servicefabric]** Update docs to match SF provider labels ([#3335](https://github.com/containous/traefik/pull/3335) by [jjcollinge](https://github.com/jjcollinge)) + ## [v1.6.1](https://github.com/containous/traefik/tree/v1.6.1) (2018-05-14) [All Commits](https://github.com/containous/traefik/compare/v1.6.0...v1.6.1) diff --git a/Gopkg.lock b/Gopkg.lock index b780fd489..634e070f1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1278,7 +1278,7 @@ "providers/dns/route53", "providers/dns/vultr" ] - revision = "2817d2131186742bc98830c73a5d9c255b3f4537" + revision = "3d653ee2ee38f1d71beb5f09b37b23344eff0ab3" source = "github.com/containous/lego" [[projects]] diff --git a/acme/acme.go b/acme/acme.go index db1984f5b..1f42f21b6 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -613,11 +613,13 @@ func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) { domains = fun.Map(types.CanonicalDomain, domains).([]string) log.Debugf("Loading ACME certificates %s...", domains) bundle := true - certificate, failures := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple) - if len(failures) > 0 { - log.Error(failures) - return nil, fmt.Errorf("cannot obtain certificates %+v", failures) + + certificate, err := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple) + if err != nil { + log.Error(err) + return nil, fmt.Errorf("cannot obtain certificates: %+v", err) } + log.Debugf("Loaded ACME certificates %s", domains) return &Certificate{ Domain: certificate.Domain, diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index faeccc4cc..98f004b5f 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -1116,6 +1116,7 @@ var _templatesKubernetesTmpl = []byte(`[backends] entryPoint = "{{ $frontend.Redirect.EntryPoint }}" regex = "{{ $frontend.Redirect.Regex }}" replacement = "{{ $frontend.Redirect.Replacement }}" + permanent = {{ $frontend.Redirect.Permanent }} {{end}} {{if $frontend.Errors }} diff --git a/configuration/configuration.go b/configuration/configuration.go index 85e4df1ff..ec9452978 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -50,6 +50,9 @@ const ( // DefaultGraceTimeout controls how long Traefik serves pending requests // prior to shutting down. DefaultGraceTimeout = 10 * time.Second + + // DefaultAcmeCAServer is the default ACME API endpoint + DefaultAcmeCAServer = "https://acme-v02.api.letsencrypt.org/directory" ) // GlobalConfiguration holds global configuration (with providers, etc.). @@ -304,14 +307,8 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) { gc.Web.Path += "/" } - // Try to fallback to traefik config file in case the file provider is enabled - // but has no file name configured and is not in a directory mode. - if gc.File != nil && len(gc.File.Filename) == 0 && len(gc.File.Directory) == 0 { - if len(configFile) > 0 { - gc.File.Filename = configFile - } else { - log.Errorln("Error using file configuration backend, no filename defined") - } + if gc.File != nil { + gc.File.TraefikFile = configFile } gc.initACMEProvider() @@ -356,7 +353,14 @@ func (gc *GlobalConfiguration) initTracing() { func (gc *GlobalConfiguration) initACMEProvider() { if gc.ACME != nil { - // TODO: to remove in the futurs + gc.ACME.CAServer = getSafeACMECAServer(gc.ACME.CAServer) + + if gc.ACME.DNSChallenge != nil && gc.ACME.HTTPChallenge != nil { + log.Warn("Unable to use DNS challenge and HTTP challenge at the same time. Fallback to DNS challenge.") + gc.ACME.HTTPChallenge = nil + } + + // TODO: to remove in the future if len(gc.ACME.StorageFile) > 0 && len(gc.ACME.Storage) == 0 { log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead") gc.ACME.Storage = gc.ACME.StorageFile @@ -404,6 +408,26 @@ func (gc *GlobalConfiguration) InitACMEProvider() *acmeprovider.Provider { return nil } +func getSafeACMECAServer(caServerSrc string) string { + if len(caServerSrc) == 0 { + return DefaultAcmeCAServer + } + + if strings.HasPrefix(caServerSrc, "https://acme-v01.api.letsencrypt.org") { + caServer := strings.Replace(caServerSrc, "v01", "v02", 1) + log.Warnf("The CA server %[1]q refers to a v01 endpoint of the ACME API, please change to %[2]q. Fallback to %[2]q.", caServerSrc, caServer) + return caServer + } + + if strings.HasPrefix(caServerSrc, "https://acme-staging.api.letsencrypt.org") { + caServer := strings.Replace(caServerSrc, "https://acme-staging.api.letsencrypt.org", "https://acme-staging-v02.api.letsencrypt.org", 1) + log.Warnf("The CA server %[1]q refers to a v01 endpoint of the ACME API, please change to %[2]q. Fallback to %[2]q.", caServerSrc, caServer) + return caServer + } + + return caServerSrc +} + // ValidateConfiguration validate that configuration is coherent func (gc *GlobalConfiguration) ValidateConfiguration() { if gc.ACME != nil { diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go index 3fb2c89f6..eac50e258 100644 --- a/configuration/configuration_test.go +++ b/configuration/configuration_test.go @@ -64,24 +64,28 @@ func TestSetEffectiveConfigurationGraceTimeout(t *testing.T) { func TestSetEffectiveConfigurationFileProviderFilename(t *testing.T) { testCases := []struct { - desc string - fileProvider *file.Provider - wantFileProviderFilename string + desc string + fileProvider *file.Provider + wantFileProviderFilename string + wantFileProviderTraefikFile string }{ { - desc: "no filename for file provider given", - fileProvider: &file.Provider{}, - wantFileProviderFilename: defaultConfigFile, + desc: "no filename for file provider given", + fileProvider: &file.Provider{}, + wantFileProviderFilename: "", + wantFileProviderTraefikFile: defaultConfigFile, }, { - desc: "filename for file provider given", - fileProvider: &file.Provider{BaseProvider: provider.BaseProvider{Filename: "other.toml"}}, - wantFileProviderFilename: "other.toml", + desc: "filename for file provider given", + fileProvider: &file.Provider{BaseProvider: provider.BaseProvider{Filename: "other.toml"}}, + wantFileProviderFilename: "other.toml", + wantFileProviderTraefikFile: defaultConfigFile, }, { - desc: "directory for file provider given", - fileProvider: &file.Provider{Directory: "/"}, - wantFileProviderFilename: "", + desc: "directory for file provider given", + fileProvider: &file.Provider{Directory: "/"}, + wantFileProviderFilename: "", + wantFileProviderTraefikFile: defaultConfigFile, }, } @@ -97,6 +101,7 @@ func TestSetEffectiveConfigurationFileProviderFilename(t *testing.T) { gc.SetEffectiveConfiguration(defaultConfigFile) assert.Equal(t, test.wantFileProviderFilename, gc.File.Filename) + assert.Equal(t, test.wantFileProviderTraefikFile, gc.File.TraefikFile) }) } } diff --git a/docs/configuration/backends/boltdb.md b/docs/configuration/backends/boltdb.md index 70db84362..cc9dfc1ce 100644 --- a/docs/configuration/backends/boltdb.md +++ b/docs/configuration/backends/boltdb.md @@ -1,13 +1,13 @@ -# BoltDB Backend +# BoltDB Provider -Træfik can be configured to use BoltDB as a backend configuration. +Træfik can be configured to use BoltDB as a provider. ```toml ################################################################ -# BoltDB configuration backend +# BoltDB Provider ################################################################ -# Enable BoltDB configuration backend. +# Enable BoltDB Provider. [boltdb] # BoltDB file. @@ -56,4 +56,4 @@ filename = "boltdb.tmpl" # insecureSkipVerify = true ``` -To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). +To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific). diff --git a/docs/configuration/backends/consul.md b/docs/configuration/backends/consul.md index 816813fd1..46ec56ea9 100644 --- a/docs/configuration/backends/consul.md +++ b/docs/configuration/backends/consul.md @@ -1,13 +1,13 @@ -# Consul Key-Value Backend +# Consul Key-Value Provider -Træfik can be configured to use Consul as a backend configuration. +Træfik can be configured to use Consul as a provider. ```toml ################################################################ -# Consul KV configuration backend +# Consul KV Provider ################################################################ -# Enable Consul KV configuration backend. +# Enable Consul KV Provider. [consul] # Consul server endpoint. @@ -56,6 +56,6 @@ prefix = "traefik" # insecureSkipVerify = true ``` -To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). +To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific). Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure. diff --git a/docs/configuration/backends/consulcatalog.md b/docs/configuration/backends/consulcatalog.md index 3eddfd8fb..f0a829dc6 100644 --- a/docs/configuration/backends/consulcatalog.md +++ b/docs/configuration/backends/consulcatalog.md @@ -1,13 +1,13 @@ -# Consul Catalog backend +# Consul Catalog Provider -Træfik can be configured to use service discovery catalog of Consul as a backend configuration. +Træfik can be configured to use service discovery catalog of Consul as a provider. ```toml ################################################################ -# Consul Catalog configuration backend +# Consul Catalog Provider ################################################################ -# Enable Consul Catalog configuration backend. +# Enable Consul Catalog Provider. [consulCatalog] # Consul server endpoint. @@ -76,9 +76,9 @@ prefix = "traefik" # templateVersion = 2 ``` -This backend will create routes matching on hostname based on the service name used in Consul. +This provider will create routes matching on hostname based on the service name used in Consul. -To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). +To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific). ## Tags diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index bf0d6a7e8..6629a3b4c 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -1,16 +1,16 @@ -# Docker Backend +# Docker Provider -Træfik can be configured to use Docker as a backend configuration. +Træfik can be configured to use Docker as a provider. ## Docker ```toml ################################################################ -# Docker configuration backend +# Docker Provider ################################################################ -# Enable Docker configuration backend. +# Enable Docker Provider. [docker] # Docker server endpoint. Can be a tcp or a unix socket endpoint. @@ -82,17 +82,17 @@ swarmMode = false # insecureSkipVerify = true ``` -To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). +To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific). ## Docker Swarm Mode ```toml ################################################################ -# Docker Swarm Mode configuration backend +# Docker Swarm Mode Provider ################################################################ -# Enable Docker configuration backend. +# Enable Docker Provider. [docker] # Docker server endpoint. @@ -159,7 +159,7 @@ exposedByDefault = false # insecureSkipVerify = true ``` -To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). +To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific). ## Labels: overriding default behavior @@ -221,7 +221,7 @@ Labels can be used on containers to override default behavior. | `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). | | `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.
Must be used in conjunction with the below label to take effect. | | `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by.
Must be used in conjunction with the above label to take effect. | -| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` | +| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` [2] | | `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.
Overrides `defaultEntryPoints` | | `traefik.frontend.errors..backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. | | `traefik.frontend.errors..query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. | @@ -246,6 +246,10 @@ If a container is linked to several networks, be sure to set the proper network For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name. Or if your service references external network use it's name instead. +[2] `traefik.frontend.auth.basic=EXPR`: +To create `user:password` pair, it's possible to use this command `echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g`. +The result will be `user:$$apr1$$9Cv/OMGj$$ZomWQzuQbL.3TRCS81A1g/`, note additional symbol `$` makes escaping. + #### Custom Headers | Label | Description | diff --git a/docs/configuration/backends/dynamodb.md b/docs/configuration/backends/dynamodb.md index 269f2a688..3eb09131f 100644 --- a/docs/configuration/backends/dynamodb.md +++ b/docs/configuration/backends/dynamodb.md @@ -1,15 +1,15 @@ -# DynamoDB Backend +# DynamoDB Provider -Træfik can be configured to use Amazon DynamoDB as a backend configuration. +Træfik can be configured to use Amazon DynamoDB as a provider. ## Configuration ```toml ################################################################ -# DynamoDB configuration backend +# DynamoDB Provider ################################################################ -# Enable DynamoDB configuration backend. +# Enable DynamoDB Provider. [dynamodb] # Region to use when connecting to AWS. @@ -68,4 +68,3 @@ Items in the `dynamodb` table must have three attributes: See `types/types.go` for details. The presence or absence of this attribute determines its type. So an item should never have both a `frontend` and a `backend` attribute. - diff --git a/docs/configuration/backends/ecs.md b/docs/configuration/backends/ecs.md index 697ffeca4..50219978f 100644 --- a/docs/configuration/backends/ecs.md +++ b/docs/configuration/backends/ecs.md @@ -1,15 +1,15 @@ -# ECS Backend +# ECS Provider -Træfik can be configured to use Amazon ECS as a backend configuration. +Træfik can be configured to use Amazon ECS as a provider. ## Configuration ```toml ################################################################ -# ECS configuration backend +# ECS Provider ################################################################ -# Enable ECS configuration backend. +# Enable ECS Provider. [ecs] # ECS Cluster Name. diff --git a/docs/configuration/backends/etcd.md b/docs/configuration/backends/etcd.md index 1defc4a6b..eaa09f3f2 100644 --- a/docs/configuration/backends/etcd.md +++ b/docs/configuration/backends/etcd.md @@ -1,13 +1,13 @@ -# Etcd Backend +# Etcd Provider -Træfik can be configured to use Etcd as a backend configuration. +Træfik can be configured to use Etcd as a provider. ```toml ################################################################ -# Etcd configuration backend +# Etcd Provider ################################################################ -# Enable Etcd configuration backend. +# Enable Etcd Provider. [etcd] # Etcd server endpoint. @@ -66,7 +66,7 @@ useAPIV3 = true # insecureSkipVerify = true ``` -To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). +To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific). Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure. diff --git a/docs/configuration/backends/eureka.md b/docs/configuration/backends/eureka.md index 5eb57edcc..c5c330a15 100644 --- a/docs/configuration/backends/eureka.md +++ b/docs/configuration/backends/eureka.md @@ -1,13 +1,13 @@ -# Eureka Backend +# Eureka Provider -Træfik can be configured to use Eureka as a backend configuration. +Træfik can be configured to use Eureka as a provider. ```toml ################################################################ -# Eureka configuration backend +# Eureka Provider ################################################################ -# Enable Eureka configuration backend. +# Enable Eureka Provider. [eureka] # Eureka server endpoint. diff --git a/docs/configuration/backends/file.md b/docs/configuration/backends/file.md index 1a9ad6ab5..c8050c5a4 100644 --- a/docs/configuration/backends/file.md +++ b/docs/configuration/backends/file.md @@ -1,4 +1,4 @@ -# File Backends +# File Provider Træfik can be configured with a file. diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md index 4d3dff920..4384b78ad 100644 --- a/docs/configuration/backends/kubernetes.md +++ b/docs/configuration/backends/kubernetes.md @@ -1,6 +1,6 @@ -# Kubernetes Ingress Backend +# Kubernetes Ingress Provider -Træfik can be configured to use Kubernetes Ingress as a backend configuration. +Træfik can be configured to use Kubernetes Ingress as a provider. See also [Kubernetes user guide](/user-guide/kubernetes). @@ -8,10 +8,10 @@ See also [Kubernetes user guide](/user-guide/kubernetes). ```toml ################################################################ -# Kubernetes Ingress configuration backend +# Kubernetes Ingress Provider ################################################################ -# Enable Kubernetes Ingress configuration backend. +# Enable Kubernetes Ingress Provider. [kubernetes] # Kubernetes server endpoint. diff --git a/docs/configuration/backends/marathon.md b/docs/configuration/backends/marathon.md index b7ca41b42..2c57c1c43 100644 --- a/docs/configuration/backends/marathon.md +++ b/docs/configuration/backends/marathon.md @@ -1,6 +1,6 @@ -# Marathon Backend +# Marathon Provider -Træfik can be configured to use Marathon as a backend configuration. +Træfik can be configured to use Marathon as a provider. See also [Marathon user guide](/user-guide/marathon). @@ -9,10 +9,10 @@ See also [Marathon user guide](/user-guide/marathon). ```toml ################################################################ -# Mesos/Marathon configuration backend +# Mesos/Marathon Provider ################################################################ -# Enable Marathon configuration backend. +# Enable Marathon Provider. [marathon] # Marathon server endpoint. @@ -157,7 +157,7 @@ domain = "marathon.localhost" # respectReadinessChecks = true ``` -To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). +To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific). ## Labels: overriding default behavior diff --git a/docs/configuration/backends/mesos.md b/docs/configuration/backends/mesos.md index cbd087be2..68ee3733c 100644 --- a/docs/configuration/backends/mesos.md +++ b/docs/configuration/backends/mesos.md @@ -1,13 +1,13 @@ -# Mesos Generic Backend +# Mesos Generic Provider -Træfik can be configured to use Mesos as a backend configuration. +Træfik can be configured to use Mesos as a provider. ```toml ################################################################ -# Mesos configuration backend +# Mesos Provider ################################################################ -# Enable Mesos configuration backend. +# Enable Mesos Provider. [mesos] # Mesos server endpoint. diff --git a/docs/configuration/backends/rancher.md b/docs/configuration/backends/rancher.md index 8e3f8c94d..f761fbbba 100644 --- a/docs/configuration/backends/rancher.md +++ b/docs/configuration/backends/rancher.md @@ -1,15 +1,15 @@ -# Rancher Backend +# Rancher Provider -Træfik can be configured to use Rancher as a backend configuration. +Træfik can be configured to use Rancher as a provider. ## Global Configuration ```toml ################################################################ -# Rancher configuration backend +# Rancher Provider ################################################################ -# Enable Rancher configuration backend. +# Enable Rancher Provider. [rancher] # Default domain used. @@ -64,13 +64,13 @@ enableServiceHealthFilter = true # templateVersion = 2 ``` -To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). +To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific). ## Rancher Metadata Service ```toml -# Enable Rancher metadata service configuration backend instead of the API -# configuration backend. +# Enable Rancher metadata service provider instead of the API +# provider. # # Optional # Default: false @@ -97,7 +97,7 @@ prefix = "/2016-07-29" ## Rancher API ```toml -# Enable Rancher API configuration backend. +# Enable Rancher API provider. # # Optional # Default: true diff --git a/docs/configuration/backends/rest.md b/docs/configuration/backends/rest.md index b8821d45e..fba802c8f 100644 --- a/docs/configuration/backends/rest.md +++ b/docs/configuration/backends/rest.md @@ -1,4 +1,4 @@ -# Rest Backend +# Rest Provider Træfik can be configured: @@ -7,7 +7,7 @@ Træfik can be configured: ## Configuration ```toml -# Enable rest backend. +# Enable REST Provider. [rest] # Name of the related entry point # diff --git a/docs/configuration/backends/servicefabric.md b/docs/configuration/backends/servicefabric.md index 37c4f99b5..04609d61c 100644 --- a/docs/configuration/backends/servicefabric.md +++ b/docs/configuration/backends/servicefabric.md @@ -1,6 +1,6 @@ -# Azure Service Fabric Backend +# Azure Service Fabric Provider -Træfik can be configured to use Azure Service Fabric as a backend configuration. +Træfik can be configured to use Azure Service Fabric as a provider. See [this repository for an example deployment package and further documentation.](https://aka.ms/traefikonsf) @@ -8,10 +8,10 @@ See [this repository for an example deployment package and further documentation ```toml ################################################################ -# Azure Service Fabric provider +# Azure Service Fabric Provider ################################################################ -# Enable Azure Service Fabric configuration backend +# Enable Azure Service Fabric Provider [serviceFabric] # Azure Service Fabric Management Endpoint @@ -61,7 +61,7 @@ Here is an example of an extension setting Træfik labels: - + @@ -98,8 +98,9 @@ Labels, set through extensions or the property manager, can be used on services |------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `traefik.enable=false` | Disable this container in Træfik | | `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend | -| `traefik.backend.group.name` | Group all services with the same name into a single backend in Træfik | -| `traefik.backend.group.weight` | Set the weighting of the current services nodes in the backend group | +| `traefik.servicefabric.groupname` | Group all services with the same name into a single backend in Træfik | +| `traefik.servicefabric.groupweight` | Set the weighting of the current services nodes in the backend group | +| `traefik.servicefabric.enablelabeloverrides` | Toggle whether labels can be overridden using the Service Fabric Property Manager API | | `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. | | `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. | | `traefik.backend.healthcheck.interval=1s` | Define the health check interval. | diff --git a/docs/configuration/backends/web.md b/docs/configuration/backends/web.md index cb27d9e04..0347bddf9 100644 --- a/docs/configuration/backends/web.md +++ b/docs/configuration/backends/web.md @@ -1,4 +1,4 @@ -# Web Backend +# Web Provider !!! danger "DEPRECATED" The web provider is deprecated, please use the [api](/configuration/api.md), the [ping](/configuration/ping.md), the [metrics](/configuration/metrics) and the [rest](/configuration/backends/rest.md) provider. @@ -12,7 +12,7 @@ Træfik can be configured: ## Configuration ```toml -# Enable web backend. +# Enable Web Provider. [web] # Web administration port. diff --git a/docs/configuration/backends/zookeeper.md b/docs/configuration/backends/zookeeper.md index 36f3ac773..2ef55b3af 100644 --- a/docs/configuration/backends/zookeeper.md +++ b/docs/configuration/backends/zookeeper.md @@ -1,13 +1,13 @@ -# Zookeeper Backend +# Zookeeper Provider -Træfik can be configured to use Zookeeper as a backend configuration. +Træfik can be configured to use Zookeeper as a provider. ```toml ################################################################ -# Zookeeper configuration backend +# Zookeeper Provider ################################################################ -# Enable Zookeeperconfiguration backend. +# Enable Zookeeper Provider. [zookeeper] # Zookeeper server endpoint. @@ -56,6 +56,6 @@ prefix = "traefik" # insecureSkipVerify = true ``` -To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific). +To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific). Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure. diff --git a/docs/configuration/commons.md b/docs/configuration/commons.md index 860aecb9c..481843362 100644 --- a/docs/configuration/commons.md +++ b/docs/configuration/commons.md @@ -33,7 +33,7 @@ # # checkNewVersion = false -# Backends throttle duration. +# Providers throttle duration. # # Optional # Default: "2s" @@ -85,7 +85,7 @@ Can be provided in a format supported by [time.ParseDuration](https://golang.org If no units are provided, the value is parsed assuming seconds. **Note:** in this time frame no new requests are accepted. -- `providersThrottleDuration`: Backends throttle duration: minimum duration in seconds between 2 events from providers before applying a new configuration. +- `providersThrottleDuration`: Providers throttle duration: minimum duration in seconds between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time. Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits). If no units are provided, the value is parsed assuming seconds. @@ -108,7 +108,7 @@ Each frontend can specify its own entrypoints. In a micro-service architecture, with a central service discovery, setting constraints limits Træfik scope to a smaller number of routes. -Træfik filters services according to service attributes/tags set in your configuration backends. +Træfik filters services according to service attributes/tags set in your providers. Supported filters: @@ -136,9 +136,9 @@ constraints = ["tag==us-*"] constraints = ["tag!=us-*", "tag!=asia-*"] ``` -### Backend-specific +### provider-specific -Supported backends: +Supported Providers: - Docker - Consul K/V @@ -151,12 +151,12 @@ Supported backends: - Kubernetes (using a provider-specific mechanism based on label selectors) ```toml -# Backend-specific constraint +# Provider-specific constraint [consulCatalog] # ... constraints = ["tag==api"] -# Backend-specific constraint +# Provider-specific constraint [marathon] # ... constraints = ["tag==api", "tag!=v*-beta"] @@ -421,12 +421,12 @@ idleTimeout = "360s" !!! warning For advanced users only. -Supported by all backends except: File backend, Web backend and DynamoDB backend. +Supported by all providers except: File Provider, Web Provider and DynamoDB Provider. ```toml -[backend_name] +[provider_name] -# Override default configuration template. For advanced users :) +# Override default provider configuration template. For advanced users :) # # Optional # Default: "" diff --git a/docs/configuration/tracing.md b/docs/configuration/tracing.md index 60ca7aba2..9b18fef13 100644 --- a/docs/configuration/tracing.md +++ b/docs/configuration/tracing.md @@ -54,7 +54,7 @@ Træfik supports two backends: Jaeger and Zipkin. ``` !!! warning - Træfik is only able to send data over compact thrift protocol to the [Jaeger agent](https://www.jaegertracing.io/docs/deployment/#agent). + Træfik is only able to send data over compact thrift protocol to the [Jaeger agent](https://www.jaegertracing.io/docs/deployment/#agent). ## Zipkin diff --git a/docs/img/traefik-health.png b/docs/img/traefik-health.png index ca53570cb..cf3160975 100644 Binary files a/docs/img/traefik-health.png and b/docs/img/traefik-health.png differ diff --git a/docs/img/web.frontend.png b/docs/img/web.frontend.png index d8072d3ca..62a90e5e9 100644 Binary files a/docs/img/web.frontend.png and b/docs/img/web.frontend.png differ diff --git a/docs/index.md b/docs/index.md index 5d07831e9..dfd37fd98 100644 --- a/docs/index.md +++ b/docs/index.md @@ -47,7 +47,7 @@ _(But if you'd rather configure some of your routes manually, Træfik supports t - Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image -## Supported backends +## Supported Providers - [Docker](/configuration/backends/docker/) / [Swarm mode](/configuration/backends/docker/#docker-swarm-mode) - [Kubernetes](/configuration/backends/kubernetes/) @@ -166,7 +166,7 @@ IP: 172.27.0.4 ### 4 — Enjoy Træfik's Magic Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](/) and let Træfik work for you! -Whatever your infrastructure is, there is probably [an available Træfik backend](/#supported-backends) that will do the job. +Whatever your infrastructure is, there is probably [an available Træfik provider](/#supported-providers) that will do the job. Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](/user-guide/docker-and-lets-encrypt/). diff --git a/docs/user-guide/docker-and-lets-encrypt.md b/docs/user-guide/docker-and-lets-encrypt.md index 364b43fe1..8fd443037 100644 --- a/docs/user-guide/docker-and-lets-encrypt.md +++ b/docs/user-guide/docker-and-lets-encrypt.md @@ -113,7 +113,7 @@ This is the minimum configuration required to do the following: - Log `ERROR`-level messages (or more severe) to the console, but silence `DEBUG`-level messages - Check for new versions of Træfik periodically - Create two entry points, namely an `HTTP` endpoint on port `80`, and an `HTTPS` endpoint on port `443` where all incoming traffic on port `80` will immediately get redirected to `HTTPS`. -- Enable the Docker configuration backend and listen for container events on the Docker unix socket we've mounted earlier. However, **new containers will not be exposed by Træfik by default, we'll get into this in a bit!** +- Enable the Docker provider and listen for container events on the Docker unix socket we've mounted earlier. However, **new containers will not be exposed by Træfik by default, we'll get into this in a bit!** - Enable automatic request and configuration of SSL certificates using Let's Encrypt. These certificates will be stored in the `acme.json` file, which you can back-up yourself and store off-premises. @@ -123,7 +123,7 @@ Alright, let's boot the container. From the `/opt/traefik` directory, run `docke Now that we've fully configured and started Træfik, it's time to get our applications running! -Let's take a simple example of a micro-service project consisting of various services, where some will be exposed to the outside world and some will not. +Let's take a simple example of a micro-service project consisting of various services, where some will be exposed to the outside world and some will not. The `docker-compose.yml` of our project looks like this: @@ -145,12 +145,11 @@ services: expose: - "9000" labels: - - "traefik.backend=my-awesome-app-app" - "traefik.docker.network=web" - - "traefik.frontend.rule=Host:app.my-awesome-app.org" - "traefik.enable=true" - - "traefik.port=9000" - - "traefik.default.protocol=http" + - "traefik.basic.frontend.rule=Host:app.my-awesome-app.org" + - "traefik.basic.port=9000" + - "traefik.basic.protocol=http" - "traefik.admin.frontend.rule=Host:admin-app.my-awesome-app.org" - "traefik.admin.protocol=https" - "traefik.admin.port=9443" @@ -204,12 +203,11 @@ Thanks to Docker labels, we can tell Træfik how to create its internal routing Let's take a look at the labels themselves for the `app` service, which is a HTTP webservice listing on port 9000: ```yaml -- "traefik.backend=my-awesome-app-app" - "traefik.docker.network=web" -- "traefik.frontend.rule=Host:app.my-awesome-app.org" - "traefik.enable=true" -- "traefik.port=9000" -- "traefik.default.protocol=http" +- "traefik.basic.frontend.rule=Host:app.my-awesome-app.org" +- "traefik.basic.port=9000" +- "traefik.basic.protocol=http" - "traefik.admin.frontend.rule=Host:admin-app.my-awesome-app.org" - "traefik.admin.protocol=https" - "traefik.admin.port=9443" @@ -221,11 +219,11 @@ We use both `container labels` and `service labels`. First, we specify the `backend` name which corresponds to the actual service we're routing **to**. -We also tell Træfik to use the `web` network to route HTTP traffic to this container. +We also tell Træfik to use the `web` network to route HTTP traffic to this container. With the `traefik.enable` label, we tell Træfik to include this container in its internal configuration. With the `frontend.rule` label, we tell Træfik that we want to route to this container if the incoming HTTP request contains the `Host` `app.my-awesome-app.org`. -Essentially, this is the actual rule used for Layer-7 load balancing. +Essentially, this is the actual rule used for Layer-7 load balancing. Finally but not unimportantly, we tell Træfik to route **to** port `9000`, since that is the actual TCP/IP port the container actually listens on. @@ -236,11 +234,11 @@ Finally but not unimportantly, we tell Træfik to route **to** port `9000`, sinc When both `container labels` and `service labels` are defined, `container labels` are just used as default values for missing `service labels` but no frontend/backend are going to be defined only with these labels. Obviously, labels `traefik.frontend.rule` and `traefik.port` described above, will only be used to complete information set in `service labels` during the container frontends/bakends creation. -In the example, two service names are defined : `default` and `admin`. +In the example, two service names are defined : `basic` and `admin`. They allow creating two frontends and two backends. -- `default` has only one `service label` : `traefik.default.protocol`. -Træfik will use values set in `traefik.frontend.rule` and `traefik.port` to create the `default` frontend and backend. +- `basic` has only one `service label` : `traefik.basic.protocol`. +Træfik will use values set in `traefik.frontend.rule` and `traefik.port` to create the `basic` frontend and backend. The frontend listens to incoming HTTP requests which contain the `Host` `app.my-awesome-app.org` and redirect them in `HTTP` to the port `9000` of the backend. - `admin` has all the `services labels` needed to create the `admin` frontend and backend (`traefik.admin.frontend.rule`, `traefik.admin.protocol`, `traefik.admin.port`). Træfik will create a frontend to listen to incoming HTTP requests which contain the `Host` `admin-app.my-awesome-app.org` and redirect them in `HTTPS` to the port `9443` of the backend. diff --git a/docs/user-guide/examples.md b/docs/user-guide/examples.md index 16d7a8ff5..a26b5029c 100644 --- a/docs/user-guide/examples.md +++ b/docs/user-guide/examples.md @@ -68,7 +68,7 @@ defaultEntryPoints = ["http", "https"] [acme] email = "test@traefik.io" storage = "acme.json" -caServer = "http://172.18.0.1:4000/directory" +caServer = "https://acme-staging-v02.api.letsencrypt.org/directory" entryPoint = "https" [acme.httpChallenge] entryPoint = "http" @@ -103,7 +103,7 @@ Træfik generates these certificates when it starts and it needs to be restart i email = "test@traefik.io" storage = "acme.json" onHostRule = true -caServer = "http://172.18.0.1:4000/directory" +caServer = "https://acme-staging-v02.api.letsencrypt.org/directory" entryPoint = "https" [acme.httpChallenge] entryPoint = "http" @@ -140,7 +140,7 @@ If a backend is added with a `onHost` rule, Træfik will automatically generate email = "test@traefik.io" storage = "acme.json" onDemand = true -caServer = "http://172.18.0.1:4000/directory" +caServer = "https://acme-staging-v02.api.letsencrypt.org/directory" entryPoint = "https" [acme.httpChallenge] entryPoint = "http" @@ -167,7 +167,7 @@ This configuration allows generating a Let's Encrypt certificate (thanks to `HTT [acme] email = "test@traefik.io" storage = "acme.json" -caServer = "http://172.18.0.1:4000/directory" +caServer = "https://acme-staging-v02.api.letsencrypt.org/directory" entryPoint = "https" [acme.dnsChallenge] provider = "digitalocean" # DNS Provider name (cloudflare, OVH, gandi...) diff --git a/docs/user-guide/kv-config.md b/docs/user-guide/kv-config.md index c2d0878f0..8e94ac52f 100644 --- a/docs/user-guide/kv-config.md +++ b/docs/user-guide/kv-config.md @@ -76,7 +76,7 @@ defaultEntryPoints = ["http", "https"] address = ":80" [entryPoints.https] address = ":443" - + [entryPoints.https.tls] [[entryPoints.https.tls.certificates]] certFile = "integration/fixtures/https/snitest.com.cert" @@ -164,7 +164,7 @@ If a Consul ACL is used to restrict Træfik read/write access, one of the follow key "traefik" { policy = "write" }, - + session "" { policy = "write" } @@ -266,6 +266,10 @@ Here is the toml configuration we would like to store in the store : backend = "backend1" passHostHeader = true priority = 10 + basicAuth = [ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + ] entrypoints = ["https"] # overrides defaultEntryPoints [frontends.frontend2.routes.test_1] rule = "Host:{subdomain:[a-z]+}.localhost" @@ -325,13 +329,15 @@ And there, the same dynamic configuration in a KV Store (using `prefix = "traefi - frontend 2 -| Key | Value | -|----------------------------------------------------|--------------------| -| `/traefik/frontends/frontend2/backend` | `backend1` | -| `/traefik/frontends/frontend2/passhostheader` | `true` | -| `/traefik/frontends/frontend2/priority` | `10` | -| `/traefik/frontends/frontend2/entrypoints` | `http,https` | -| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` | +| Key | Value | +|----------------------------------------------------|-----------------------------------------------| +| `/traefik/frontends/frontend2/backend` | `backend1` | +| `/traefik/frontends/frontend2/passhostheader` | `true` | +| `/traefik/frontends/frontend2/priority` | `10` | +| `/traefik/frontends/frontend2/basicauth/0` | `test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/` | +| `/traefik/frontends/frontend2/basicauth/1` | `test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0` | +| `/traefik/frontends/frontend2/entrypoints` | `http,https` | +| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` | - certificate 1 @@ -422,7 +428,7 @@ Træfik will not start but the [static configuration](/basics/#static-trfik-conf If you configured ACME (Let's Encrypt), your registration account and your certificates will also be uploaded. -If you configured a file backend `[file]`, all your dynamic configuration (backends, frontends...) will be uploaded to the Key-value store. +If you configured a file provider `[file]`, all your dynamic configuration (backends, frontends...) will be uploaded to the Key-value store. To upload your ACME certificates to the KV store, get your Traefik TOML file and add the new `storage` option in the `acme` section: diff --git a/docs/user-guide/swarm-mode.md b/docs/user-guide/swarm-mode.md index 9aef2b1d4..0c36d269b 100644 --- a/docs/user-guide/swarm-mode.md +++ b/docs/user-guide/swarm-mode.md @@ -101,7 +101,7 @@ Let's explain this command: | `--constraint=node.role==manager` | we ask docker to schedule Træfik on a manager node. | | `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock` | we bind mount the docker socket where Træfik is scheduled to be able to speak to the daemon. | | `--network traefik-net` | we attach the Træfik service (and thus the underlying container) to the `traefik-net` network. | -| `--docker` | enable docker backend, and `--docker.swarmMode` to enable the swarm mode on Træfik. | +| `--docker` | enable docker provider, and `--docker.swarmMode` to enable the swarm mode on Træfik. | | `--api | activate the webUI on port 8080 | diff --git a/docs/user-guide/swarm.md b/docs/user-guide/swarm.md index 0cf7296e1..638a11300 100644 --- a/docs/user-guide/swarm.md +++ b/docs/user-guide/swarm.md @@ -104,7 +104,7 @@ Let's explain this command: | `--net=my-net` | run the container on the network my-net | | `-v /var/lib/boot2docker/:/ssl` | mount the ssl keys generated by docker-machine | | `-c /dev/null` | empty config file | -| `--docker` | enable docker backend | +| `--docker` | enable docker provider | | `--docker.endpoint=tcp://172.18.0.1:2376` | connect to the swarm master using the docker_gwbridge network | | `--docker.tls` | enable TLS using the docker-machine keys | | `--api` | activate the webUI on port 8080 | diff --git a/middlewares/errorpages/error_pages.go b/middlewares/errorpages/error_pages.go index dc460dbb6..fb262081e 100644 --- a/middlewares/errorpages/error_pages.go +++ b/middlewares/errorpages/error_pages.go @@ -92,15 +92,23 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http. if err != nil { log.Error(err) w.WriteHeader(recorder.GetCode()) - w.Write([]byte(http.StatusText(recorder.GetCode()))) + fmt.Fprint(w, http.StatusText(recorder.GetCode())) return } + recorderErrorPage := newResponseRecorder(w) utils.CopyHeaders(pageReq.Header, req.Header) - utils.CopyHeaders(w.Header(), recorder.Header()) - w.WriteHeader(recorder.GetCode()) - h.backendHandler.ServeHTTP(w, pageReq.WithContext(req.Context())) + h.backendHandler.ServeHTTP(recorderErrorPage, pageReq.WithContext(req.Context())) + + utils.CopyHeaders(w.Header(), recorder.Header()) + for key := range recorderErrorPage.Header() { + w.Header().Del(key) + } + utils.CopyHeaders(w.Header(), recorderErrorPage.Header()) + + w.WriteHeader(recorder.GetCode()) + w.Write(recorderErrorPage.GetBody().Bytes()) return } } diff --git a/mkdocs.yml b/mkdocs.yml index 1379b428b..a1c499ca1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -69,27 +69,27 @@ pages: - 'Logs': 'configuration/logs.md' - 'EntryPoints': 'configuration/entrypoints.md' - 'Let''s Encrypt': 'configuration/acme.md' - - 'Backend: Web': 'configuration/backends/web.md' - - 'Backend: BoltDB': 'configuration/backends/boltdb.md' - - 'Backend: Consul': 'configuration/backends/consul.md' - - 'Backend: Consul Catalog': 'configuration/backends/consulcatalog.md' - - 'Backend: Docker': 'configuration/backends/docker.md' - - 'Backend: DynamoDB': 'configuration/backends/dynamodb.md' - - 'Backend: ECS': 'configuration/backends/ecs.md' - - 'Backend: Etcd': 'configuration/backends/etcd.md' - - 'Backend: Eureka': 'configuration/backends/eureka.md' - - 'Backend: File': 'configuration/backends/file.md' - - 'Backend: Kubernetes Ingress': 'configuration/backends/kubernetes.md' - - 'Backend: Marathon': 'configuration/backends/marathon.md' - - 'Backend: Mesos': 'configuration/backends/mesos.md' - - 'Backend: Rancher': 'configuration/backends/rancher.md' - - 'Backend: Rest': 'configuration/backends/rest.md' - - 'Backend: Azure Service Fabric': 'configuration/backends/servicefabric.md' - - 'Backend: Zookeeper': 'configuration/backends/zookeeper.md' - 'API / Dashboard': 'configuration/api.md' + - 'BoltDB': 'configuration/backends/boltdb.md' + - 'Consul': 'configuration/backends/consul.md' + - 'Consul Catalog': 'configuration/backends/consulcatalog.md' + - 'Docker': 'configuration/backends/docker.md' + - 'DynamoDB': 'configuration/backends/dynamodb.md' + - 'ECS': 'configuration/backends/ecs.md' + - 'Etcd': 'configuration/backends/etcd.md' + - 'Eureka': 'configuration/backends/eureka.md' + - 'File': 'configuration/backends/file.md' + - 'Kubernetes Ingress': 'configuration/backends/kubernetes.md' + - 'Marathon': 'configuration/backends/marathon.md' + - 'Mesos': 'configuration/backends/mesos.md' + - 'Rancher': 'configuration/backends/rancher.md' + - 'Rest': 'configuration/backends/rest.md' + - 'Azure Service Fabric': 'configuration/backends/servicefabric.md' + - 'Zookeeper': 'configuration/backends/zookeeper.md' - 'Ping': 'configuration/ping.md' - 'Metrics': 'configuration/metrics.md' - 'Tracing': 'configuration/tracing.md' + - 'Web (Deprecated)': 'configuration/backends/web.md' - User Guides: - 'Configuration Examples': 'user-guide/examples.md' - 'Swarm Mode Cluster': 'user-guide/swarm-mode.md' diff --git a/provider/acme/provider.go b/provider/acme/provider.go index c8d64eede..734398308 100644 --- a/provider/acme/provider.go +++ b/provider/acme/provider.go @@ -210,9 +210,9 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati bundle := true - certificate, failures := client.ObtainCertificate(uncheckedDomains, bundle, nil, OSCPMustStaple) - if len(failures) > 0 { - return nil, fmt.Errorf("cannot obtain certificates %+v", failures) + certificate, err := client.ObtainCertificate(uncheckedDomains, bundle, nil, OSCPMustStaple) + if err != nil { + return nil, fmt.Errorf("cannot obtain certificates: %+v", err) } if len(certificate.Certificate) == 0 || len(certificate.PrivateKey) == 0 { diff --git a/provider/file/file.go b/provider/file/file.go index ded4ac123..5a241f92b 100644 --- a/provider/file/file.go +++ b/provider/file/file.go @@ -14,6 +14,7 @@ import ( "github.com/containous/traefik/safe" "github.com/containous/traefik/tls" "github.com/containous/traefik/types" + "github.com/pkg/errors" "gopkg.in/fsnotify.v1" ) @@ -23,6 +24,7 @@ var _ provider.Provider = (*Provider)(nil) type Provider struct { provider.BaseProvider `mapstructure:",squash" export:"true"` Directory string `description:"Load configuration from one or more .toml files in a directory" export:"true"` + TraefikFile string } // Provide allows the file provider to provide configurations to traefik @@ -37,10 +39,12 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s if p.Watch { var watchItem string - if p.Directory != "" { + if len(p.Directory) > 0 { watchItem = p.Directory - } else { + } else if len(p.Filename) > 0 { watchItem = filepath.Dir(p.Filename) + } else { + watchItem = filepath.Dir(p.TraefikFile) } if err := p.addWatcher(pool, watchItem, configurationChan, p.watcherCallback); err != nil { @@ -55,10 +59,19 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s // BuildConfiguration loads configuration either from file or a directory specified by 'Filename'/'Directory' // and returns a 'Configuration' object func (p *Provider) BuildConfiguration() (*types.Configuration, error) { - if p.Directory != "" { + if len(p.Directory) > 0 { return p.loadFileConfigFromDirectory(p.Directory, nil) } - return p.loadFileConfig(p.Filename) + + if len(p.Filename) > 0 { + return p.loadFileConfig(p.Filename, true) + } + + if len(p.TraefikFile) > 0 { + return p.loadFileConfig(p.TraefikFile, false) + } + + return nil, errors.New("Error using file configuration backend, no filename defined") } func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationChan chan<- types.ConfigMessage, callback func(chan<- types.ConfigMessage, fsnotify.Event)) error { @@ -67,6 +80,11 @@ func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationCh return fmt.Errorf("error creating file watcher: %s", err) } + err = watcher.Add(directory) + if err != nil { + return fmt.Errorf("error adding file watcher: %s", err) + } + // Process events pool.Go(func(stop chan bool) { defer watcher.Close() @@ -76,8 +94,15 @@ func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationCh return case evt := <-watcher.Events: if p.Directory == "" { + var filename string + if len(p.Filename) > 0 { + filename = p.Filename + } else { + filename = p.TraefikFile + } + _, evtFileName := filepath.Split(evt.Name) - _, confFileName := filepath.Split(p.Filename) + _, confFileName := filepath.Split(filename) if evtFileName == confFileName { callback(configurationChan, evt) } @@ -89,18 +114,15 @@ func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationCh } } }) - err = watcher.Add(directory) - if err != nil { - return fmt.Errorf("error adding file watcher: %s", err) - } - return nil } func (p *Provider) watcherCallback(configurationChan chan<- types.ConfigMessage, event fsnotify.Event) { - watchItem := p.Filename - if p.Directory != "" { + watchItem := p.TraefikFile + if len(p.Directory) > 0 { watchItem = p.Directory + } else if len(p.Filename) > 0 { + watchItem = p.Filename } if _, err := os.Stat(watchItem); err != nil { @@ -136,12 +158,19 @@ func readFile(filename string) (string, error) { return "", fmt.Errorf("invalid filename: %s", filename) } -func (p *Provider) loadFileConfig(filename string) (*types.Configuration, error) { +func (p *Provider) loadFileConfig(filename string, parseTemplate bool) (*types.Configuration, error) { fileContent, err := readFile(filename) if err != nil { return nil, fmt.Errorf("error reading configuration file: %s - %s", filename, err) } - configuration, err := p.CreateConfiguration(fileContent, template.FuncMap{}, false) + + var configuration *types.Configuration + if parseTemplate { + configuration, err = p.CreateConfiguration(fileContent, template.FuncMap{}, false) + } else { + configuration, err = p.DecodeConfiguration(fileContent) + } + if err != nil { return nil, err } @@ -182,7 +211,7 @@ func (p *Provider) loadFileConfigFromDirectory(directory string, configuration * } var c *types.Configuration - c, err = p.loadFileConfig(path.Join(directory, item.Name())) + c, err = p.loadFileConfig(path.Join(directory, item.Name()), true) if err != nil { return configuration, err diff --git a/provider/file/file_test.go b/provider/file/file_test.go index 8a6caf660..074a11dba 100644 --- a/provider/file/file_test.go +++ b/provider/file/file_test.go @@ -14,216 +14,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestProvideSingleFileAndWatch(t *testing.T) { - tempDir := createTempDir(t, "testfile") - defer os.RemoveAll(tempDir) - - expectedNumFrontends := 2 - expectedNumBackends := 2 - expectedNumTLSConf := 2 - - tempFile := createFile(t, - tempDir, "simple.toml", - createFrontendConfiguration(expectedNumFrontends), - createBackendConfiguration(expectedNumBackends), - createTLS(expectedNumTLSConf)) - - configurationChan, signal := createConfigurationRoutine(t, &expectedNumFrontends, &expectedNumBackends, &expectedNumTLSConf) - - provide(configurationChan, watch, withFile(tempFile)) - - // Wait for initial message to be tested - err := waitForSignal(signal, 2*time.Second, "initial config") - assert.NoError(t, err) - - // Now test again with single frontend and backend - expectedNumFrontends = 1 - expectedNumBackends = 1 - expectedNumTLSConf = 1 - - createFile(t, - tempDir, "simple.toml", - createFrontendConfiguration(expectedNumFrontends), - createBackendConfiguration(expectedNumBackends), - createTLS(expectedNumTLSConf)) - - err = waitForSignal(signal, 2*time.Second, "single frontend, backend, TLS configuration") - assert.NoError(t, err) -} - -func TestProvideSingleFileAndNotWatch(t *testing.T) { - tempDir := createTempDir(t, "testfile") - defer os.RemoveAll(tempDir) - - expectedNumFrontends := 2 - expectedNumBackends := 2 - expectedNumTLSConf := 2 - - tempFile := createFile(t, - tempDir, "simple.toml", - createFrontendConfiguration(expectedNumFrontends), - createBackendConfiguration(expectedNumBackends), - createTLS(expectedNumTLSConf)) - - configurationChan, signal := createConfigurationRoutine(t, &expectedNumFrontends, &expectedNumBackends, &expectedNumTLSConf) - - provide(configurationChan, withFile(tempFile)) - - // Wait for initial message to be tested - err := waitForSignal(signal, 2*time.Second, "initial config") - assert.NoError(t, err) - - // Now test again with single frontend and backend - expectedNumFrontends = 1 - expectedNumBackends = 1 - expectedNumTLSConf = 1 - - createFile(t, - tempDir, "simple.toml", - createFrontendConfiguration(expectedNumFrontends), - createBackendConfiguration(expectedNumBackends), - createTLS(expectedNumTLSConf)) - - // Must fail because we don't watch the changes - err = waitForSignal(signal, 2*time.Second, "single frontend, backend and TLS configuration") - assert.Error(t, err) -} - -func TestProvideDirectoryAndWatch(t *testing.T) { - tempDir := createTempDir(t, "testdir") - defer os.RemoveAll(tempDir) - - expectedNumFrontends := 2 - expectedNumBackends := 2 - expectedNumTLSConf := 2 - - tempFile1 := createRandomFile(t, tempDir, createFrontendConfiguration(expectedNumFrontends)) - tempFile2 := createRandomFile(t, tempDir, createBackendConfiguration(expectedNumBackends)) - tempFile3 := createRandomFile(t, tempDir, createTLS(expectedNumTLSConf)) - - configurationChan, signal := createConfigurationRoutine(t, &expectedNumFrontends, &expectedNumBackends, &expectedNumTLSConf) - - provide(configurationChan, watch, withDirectory(tempDir)) - - // Wait for initial config message to be tested - err := waitForSignal(signal, 2*time.Second, "initial config") - assert.NoError(t, err) - - // Now remove the backends file - expectedNumFrontends = 2 - expectedNumBackends = 0 - expectedNumTLSConf = 2 - os.Remove(tempFile2.Name()) - err = waitForSignal(signal, 2*time.Second, "remove the backends file") - assert.NoError(t, err) - - // Now remove the frontends file - expectedNumFrontends = 0 - expectedNumBackends = 0 - expectedNumTLSConf = 2 - os.Remove(tempFile1.Name()) - err = waitForSignal(signal, 2*time.Second, "remove the frontends file") - assert.NoError(t, err) - - // Now remove the TLS configuration file - expectedNumFrontends = 0 - expectedNumBackends = 0 - expectedNumTLSConf = 0 - os.Remove(tempFile3.Name()) - err = waitForSignal(signal, 2*time.Second, "remove the TLS configuration file") - assert.NoError(t, err) -} - -func TestProvideDirectoryAndNotWatch(t *testing.T) { - tempDir := createTempDir(t, "testdir") - tempTLSDir := createSubDir(t, tempDir, "tls") - defer os.RemoveAll(tempDir) - - expectedNumFrontends := 2 - expectedNumBackends := 2 - expectedNumTLSConf := 2 - - createRandomFile(t, tempDir, createFrontendConfiguration(expectedNumFrontends)) - tempFile2 := createRandomFile(t, tempDir, createBackendConfiguration(expectedNumBackends)) - createRandomFile(t, tempTLSDir, createTLS(expectedNumTLSConf)) - - configurationChan, signal := createConfigurationRoutine(t, &expectedNumFrontends, &expectedNumBackends, &expectedNumTLSConf) - - provide(configurationChan, withDirectory(tempDir)) - - // Wait for initial config message to be tested - err := waitForSignal(signal, 2*time.Second, "initial config") - assert.NoError(t, err) - - // Now remove the backends file - expectedNumFrontends = 2 - expectedNumBackends = 0 - expectedNumTLSConf = 2 - os.Remove(tempFile2.Name()) - - // Must fail because we don't watch the changes - err = waitForSignal(signal, 2*time.Second, "remove the backends file") - assert.Error(t, err) - -} - -func createConfigurationRoutine(t *testing.T, expectedNumFrontends *int, expectedNumBackends *int, expectedNumTLSes *int) (chan types.ConfigMessage, chan interface{}) { - configurationChan := make(chan types.ConfigMessage) - signal := make(chan interface{}) - - safe.Go(func() { - for { - data := <-configurationChan - assert.Equal(t, "file", data.ProviderName) - assert.Len(t, data.Configuration.Frontends, *expectedNumFrontends) - assert.Len(t, data.Configuration.Backends, *expectedNumBackends) - assert.Len(t, data.Configuration.TLS, *expectedNumTLSes) - signal <- nil - } - }) - - return configurationChan, signal -} - -func waitForSignal(signal chan interface{}, timeout time.Duration, caseName string) error { - timer := time.NewTimer(timeout) - defer timer.Stop() - - select { - case <-signal: - - case <-timer.C: - return fmt.Errorf("Timed out waiting for assertions to be tested: %s", caseName) - } - return nil -} - -func provide(configurationChan chan types.ConfigMessage, builders ...func(p *Provider)) { - pvd := &Provider{} - - for _, builder := range builders { - builder(pvd) - } - - pvd.Provide(configurationChan, safe.NewPool(context.Background()), nil) -} - -func watch(pvd *Provider) { - pvd.Watch = true -} - -func withDirectory(name string) func(*Provider) { - return func(pvd *Provider) { - pvd.Directory = name - } -} - -func withFile(tempFile *os.File) func(*Provider) { - return func(p *Provider) { - p.Filename = tempFile.Name() - } -} - // createRandomFile Helper func createRandomFile(t *testing.T, tempDir string, contents ...string) *os.File { return createFile(t, tempDir, fmt.Sprintf("temp%d.toml", time.Now().UnixNano()), contents...) @@ -264,25 +54,12 @@ func createTempDir(t *testing.T, dir string) string { return d } -// createDir Helper -func createSubDir(t *testing.T, rootDir, dir string) string { - t.Helper() - err := os.Mkdir(rootDir+"/"+dir, 0775) - if err != nil { - t.Fatal(err) - } - return rootDir + "/" + dir -} - // createFrontendConfiguration Helper func createFrontendConfiguration(n int) string { - conf := "{{$home := env \"HOME\"}}\n[frontends]\n" + conf := "[frontends]\n" for i := 1; i <= n; i++ { conf += fmt.Sprintf(` [frontends."frontend%[1]d"] backend = "backend%[1]d" -`, i) - conf += fmt.Sprintf(` [frontends."frontend%[1]d".headers] - "PublicKey" = "{{$home}}/pub.key" `, i) } return conf @@ -313,3 +90,240 @@ func createTLS(n int) string { } return conf } + +type ProvideTestCase struct { + desc string + directoryContent []string + fileContent string + traefikFileContent string + expectedNumFrontend int + expectedNumBackend int + expectedNumTLSConf int +} + +func getTestCases() []ProvideTestCase { + return []ProvideTestCase{ + { + desc: "simple file", + fileContent: createFrontendConfiguration(2) + createBackendConfiguration(3) + createTLS(4), + expectedNumFrontend: 2, + expectedNumBackend: 3, + expectedNumTLSConf: 4, + }, + { + desc: "simple file and a traefik file", + fileContent: createFrontendConfiguration(2) + createBackendConfiguration(3) + createTLS(4), + traefikFileContent: ` + debug=true +`, + expectedNumFrontend: 2, + expectedNumBackend: 3, + expectedNumTLSConf: 4, + }, + { + desc: "template file", + fileContent: ` +[frontends] +{{ range $i, $e := until 20 }} + [frontends.frontend{{ $e }}] + backend = "backend" +{{ end }} +`, + expectedNumFrontend: 20, + }, + { + desc: "simple directory", + directoryContent: []string{ + createFrontendConfiguration(2), + createBackendConfiguration(3), + createTLS(4), + }, + expectedNumFrontend: 2, + expectedNumBackend: 3, + expectedNumTLSConf: 4, + }, + { + desc: "template in directory", + directoryContent: []string{ + ` +[frontends] +{{ range $i, $e := until 20 }} + [frontends.frontend{{ $e }}] + backend = "backend" +{{ end }} +`, + ` +[backends] +{{ range $i, $e := until 20 }} + [backends.backend{{ $e }}] + [backends.backend{{ $e }}.servers.server1] + url="http://127.0.0.1" +{{ end }} +`, + }, + expectedNumFrontend: 20, + expectedNumBackend: 20, + }, + { + desc: "simple traefik file", + traefikFileContent: ` + debug=true + [file] + ` + createFrontendConfiguration(2) + createBackendConfiguration(3) + createTLS(4), + expectedNumFrontend: 2, + expectedNumBackend: 3, + expectedNumTLSConf: 4, + }, + { + desc: "simple traefik file with templating", + traefikFileContent: ` + temp="{{ getTag \"test\" }}" + [file] + ` + createFrontendConfiguration(2) + createBackendConfiguration(3) + createTLS(4), + expectedNumFrontend: 2, + expectedNumBackend: 3, + expectedNumTLSConf: 4, + }, + } +} + +func TestProvideWithoutWatch(t *testing.T) { + for _, test := range getTestCases() { + test := test + t.Run(test.desc+" without watch", func(t *testing.T) { + t.Parallel() + + provider, clean := createProvider(t, test, false) + defer clean() + configChan := make(chan types.ConfigMessage) + + go func() { + err := provider.Provide(configChan, safe.NewPool(context.Background()), types.Constraints{}) + assert.NoError(t, err) + }() + + timeout := time.After(time.Second) + select { + case config := <-configChan: + assert.Len(t, config.Configuration.Backends, test.expectedNumBackend) + assert.Len(t, config.Configuration.Frontends, test.expectedNumFrontend) + assert.Len(t, config.Configuration.TLS, test.expectedNumTLSConf) + case <-timeout: + t.Errorf("timeout while waiting for config") + } + }) + } +} + +func TestProvideWithWatch(t *testing.T) { + for _, test := range getTestCases() { + test := test + t.Run(test.desc+" with watch", func(t *testing.T) { + t.Parallel() + + provider, clean := createProvider(t, test, true) + defer clean() + configChan := make(chan types.ConfigMessage) + + go func() { + err := provider.Provide(configChan, safe.NewPool(context.Background()), types.Constraints{}) + assert.NoError(t, err) + }() + + timeout := time.After(time.Second) + select { + case config := <-configChan: + assert.Len(t, config.Configuration.Backends, 0) + assert.Len(t, config.Configuration.Frontends, 0) + assert.Len(t, config.Configuration.TLS, 0) + case <-timeout: + t.Errorf("timeout while waiting for config") + } + + if len(test.fileContent) > 0 { + ioutil.WriteFile(provider.Filename, []byte(test.fileContent), 0755) + } + + if len(test.traefikFileContent) > 0 { + ioutil.WriteFile(provider.TraefikFile, []byte(test.traefikFileContent), 0755) + } + + if len(test.directoryContent) > 0 { + for _, fileContent := range test.directoryContent { + createRandomFile(t, provider.Directory, fileContent) + } + } + + timeout = time.After(time.Second * 1) + success := false + for !success { + select { + case config := <-configChan: + success = assert.Len(t, config.Configuration.Backends, test.expectedNumBackend) + success = success && assert.Len(t, config.Configuration.Frontends, test.expectedNumFrontend) + success = success && assert.Len(t, config.Configuration.TLS, test.expectedNumTLSConf) + case <-timeout: + t.Errorf("timeout while waiting for config") + return + } + } + }) + } +} + +func TestErrorWhenEmptyConfig(t *testing.T) { + provider := &Provider{} + configChan := make(chan types.ConfigMessage) + errorChan := make(chan struct{}) + go func() { + err := provider.Provide(configChan, safe.NewPool(context.Background()), types.Constraints{}) + assert.Error(t, err) + close(errorChan) + }() + + timeout := time.After(time.Second) + select { + case <-configChan: + t.Fatal("We should not receive config message") + case <-timeout: + t.Fatal("timeout while waiting for config") + case <-errorChan: + } +} + +func createProvider(t *testing.T, test ProvideTestCase, watch bool) (*Provider, func()) { + tempDir := createTempDir(t, "testdir") + + provider := &Provider{} + provider.Watch = watch + + if len(test.directoryContent) > 0 { + if !watch { + for _, fileContent := range test.directoryContent { + createRandomFile(t, tempDir, fileContent) + } + } + provider.Directory = tempDir + } + + if len(test.fileContent) > 0 { + if watch { + test.fileContent = "" + } + filename := createRandomFile(t, tempDir, test.fileContent) + provider.Filename = filename.Name() + + } + + if len(test.traefikFileContent) > 0 { + if watch { + test.traefikFileContent = "" + } + filename := createRandomFile(t, tempDir, test.traefikFileContent) + provider.TraefikFile = filename.Name() + } + + return provider, func() { + os.Remove(tempDir) + } +} diff --git a/provider/provider.go b/provider/provider.go index 578a7c570..6bd9688a9 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -62,8 +62,6 @@ func (p *BaseProvider) GetConfiguration(defaultTemplate string, funcMap template // CreateConfiguration create a provider configuration from content using templating func (p *BaseProvider) CreateConfiguration(tmplContent string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) { - configuration := new(types.Configuration) - var defaultFuncMap = sprig.TxtFuncMap() // tolower is deprecated in favor of sprig's lower function defaultFuncMap["tolower"] = strings.ToLower @@ -91,7 +89,13 @@ func (p *BaseProvider) CreateConfiguration(tmplContent string, funcMap template. log.Debugf("Template content: %s", tmplContent) log.Debugf("Rendering results: %s", renderedTemplate) } - if _, err := toml.Decode(renderedTemplate, configuration); err != nil { + return p.DecodeConfiguration(renderedTemplate) +} + +// DecodeConfiguration Decode a *types.Configuration from a content +func (p *BaseProvider) DecodeConfiguration(content string) (*types.Configuration, error) { + configuration := new(types.Configuration) + if _, err := toml.Decode(content, configuration); err != nil { return nil, err } return configuration, nil diff --git a/server/server.go b/server/server.go index f42b38245..3361bd5f6 100644 --- a/server/server.go +++ b/server/server.go @@ -1398,7 +1398,7 @@ func configureBackends(backends map[string]*types.Backend) { } } } else { - log.Debugf("Validation of load balancer method for backend %s failed: %s. Using default method wrr.", backendName, err) + log.Debugf("Backend %s: %v", backendName, err) var stickiness *types.Stickiness if backend.LoadBalancer != nil { diff --git a/templates/kubernetes.tmpl b/templates/kubernetes.tmpl index ef259d8a5..31a19e1d2 100644 --- a/templates/kubernetes.tmpl +++ b/templates/kubernetes.tmpl @@ -69,6 +69,7 @@ entryPoint = "{{ $frontend.Redirect.EntryPoint }}" regex = "{{ $frontend.Redirect.Regex }}" replacement = "{{ $frontend.Redirect.Replacement }}" + permanent = {{ $frontend.Redirect.Permanent }} {{end}} {{if $frontend.Errors }} diff --git a/types/types.go b/types/types.go index 29eb5ab9d..cc4a981b3 100644 --- a/types/types.go +++ b/types/types.go @@ -217,16 +217,21 @@ var loadBalancerMethodNames = []string{ // NewLoadBalancerMethod create a new LoadBalancerMethod from a given LoadBalancer. func NewLoadBalancerMethod(loadBalancer *LoadBalancer) (LoadBalancerMethod, error) { - var method string - if loadBalancer != nil { - method = loadBalancer.Method - for i, name := range loadBalancerMethodNames { - if strings.EqualFold(name, method) { - return LoadBalancerMethod(i), nil - } + if loadBalancer == nil { + return Wrr, errors.New("no load-balancer defined, fallback to 'wrr' method") + } + + if len(loadBalancer.Method) == 0 { + return Wrr, errors.New("no load-balancing method defined, fallback to 'wrr' method") + } + + for i, name := range loadBalancerMethodNames { + if strings.EqualFold(name, loadBalancer.Method) { + return LoadBalancerMethod(i), nil } } - return Wrr, fmt.Errorf("invalid load-balancing method '%s'", method) + + return Wrr, fmt.Errorf("invalid load-balancing method %q, fallback to 'wrr' method", loadBalancer.Method) } // Configurations is for currentConfigurations Map diff --git a/vendor/github.com/xenolf/lego/acmev2/client.go b/vendor/github.com/xenolf/lego/acmev2/client.go index 649c8d9cf..ff3dc866b 100644 --- a/vendor/github.com/xenolf/lego/acmev2/client.go +++ b/vendor/github.com/xenolf/lego/acmev2/client.go @@ -189,7 +189,7 @@ func (c *Client) ResolveAccountByKey() (*RegistrationResource, error) { logf("[INFO] acme: Trying to resolve account by key") acc := accountMessage{OnlyReturnExisting: true} - hdr, err := postJSON(c.jws, c.directory.NewAccountURL, acc, &acc) + hdr, err := postJSON(c.jws, c.directory.NewAccountURL, acc, nil) if err != nil { return nil, err } @@ -265,7 +265,7 @@ func (c *Client) QueryRegistration() (*RegistrationResource, error) { // your issued certificate as a bundle. // This function will never return a partial certificate. If one domain in the list fails, // the whole certificate will fail. -func (c *Client) ObtainCertificateForCSR(csr x509.CertificateRequest, bundle bool) (CertificateResource, map[string]error) { +func (c *Client) ObtainCertificateForCSR(csr x509.CertificateRequest, bundle bool) (CertificateResource, error) { // figure out what domains it concerns // start with the common name domains := []string{csr.Subject.CommonName} @@ -292,30 +292,26 @@ DNSNames: order, err := c.createOrderForIdentifiers(domains) if err != nil { - identErrors := make(map[string]error) - for _, auth := range order.Identifiers { - identErrors[auth.Value] = err - } - return CertificateResource{}, identErrors + return CertificateResource{}, err } - authz, failures := c.getAuthzForOrder(order) - // If any challenge fails - return. Do not generate partial SAN certificates. - if len(failures) > 0 { + authz, err := c.getAuthzForOrder(order) + if err != nil { + // If any challenge fails, return. Do not generate partial SAN certificates. /*for _, auth := range authz { c.disableAuthz(auth) }*/ - - return CertificateResource{}, failures + return CertificateResource{}, err } - errs := c.solveChallengeForAuthz(authz) - // If any challenge fails - return. Do not generate partial SAN certificates. - if len(errs) > 0 { - return CertificateResource{}, errs + err = c.solveChallengeForAuthz(authz) + if err != nil { + // If any challenge fails, return. Do not generate partial SAN certificates. + return CertificateResource{}, err } logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) + failures := make(ObtainError) cert, err := c.requestCertificateForCsr(order, bundle, csr.Raw, nil) if err != nil { for _, chln := range authz { @@ -326,7 +322,12 @@ DNSNames: // Add the CSR to the certificate so that it can be used for renewals. cert.CSR = pemEncode(&csr) - return cert, failures + // do not return an empty failures map, because + // it would still be a non-nil error value + if len(failures) > 0 { + return cert, failures + } + return cert, nil } // ObtainCertificate tries to obtain a single certificate using all domains passed into it. @@ -338,7 +339,11 @@ DNSNames: // your issued certificate as a bundle. // This function will never return a partial certificate. If one domain in the list fails, // the whole certificate will fail. -func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, map[string]error) { +func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, error) { + if len(domains) == 0 { + return CertificateResource{}, errors.New("No domains to obtain a certificate for") + } + if bundle { logf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", ")) } else { @@ -347,30 +352,26 @@ func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto order, err := c.createOrderForIdentifiers(domains) if err != nil { - identErrors := make(map[string]error) - for _, auth := range order.Identifiers { - identErrors[auth.Value] = err - } - return CertificateResource{}, identErrors + return CertificateResource{}, err } - authz, failures := c.getAuthzForOrder(order) - // If any challenge fails - return. Do not generate partial SAN certificates. - if len(failures) > 0 { + authz, err := c.getAuthzForOrder(order) + if err != nil { + // If any challenge fails, return. Do not generate partial SAN certificates. /*for _, auth := range authz { c.disableAuthz(auth) }*/ - - return CertificateResource{}, failures + return CertificateResource{}, err } - errs := c.solveChallengeForAuthz(authz) - // If any challenge fails - return. Do not generate partial SAN certificates. - if len(errs) > 0 { - return CertificateResource{}, errs + err = c.solveChallengeForAuthz(authz) + if err != nil { + // If any challenge fails, return. Do not generate partial SAN certificates. + return CertificateResource{}, err } logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) + failures := make(ObtainError) cert, err := c.requestCertificateForOrder(order, bundle, privKey, mustStaple) if err != nil { for _, auth := range authz { @@ -378,7 +379,12 @@ func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto } } - return cert, failures + // do not return an empty failures map, because + // it would still be a non-nil error value + if len(failures) > 0 { + return cert, failures + } + return cert, nil } // RevokeCertificate takes a PEM encoded certificate or bundle and tries to revoke it at the CA. @@ -433,7 +439,7 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b return CertificateResource{}, err } newCert, failures := c.ObtainCertificateForCSR(*csr, bundle) - return newCert, failures[cert.Domain] + return newCert, failures } var privKey crypto.PrivateKey @@ -445,7 +451,6 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b } var domains []string - var failures map[string]error // check for SAN certificate if len(x509Cert.DNSNames) > 1 { domains = append(domains, x509Cert.Subject.CommonName) @@ -459,8 +464,8 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b domains = append(domains, x509Cert.Subject.CommonName) } - newCert, failures := c.ObtainCertificate(domains, bundle, privKey, mustStaple) - return newCert, failures[cert.Domain] + newCert, err := c.ObtainCertificate(domains, bundle, privKey, mustStaple) + return newCert, err } func (c *Client) createOrderForIdentifiers(domains []string) (orderResource, error) { @@ -490,9 +495,10 @@ func (c *Client) createOrderForIdentifiers(domains []string) (orderResource, err // Looks through the challenge combinations to find a solvable match. // Then solves the challenges in series and returns. -func (c *Client) solveChallengeForAuthz(authorizations []authorization) map[string]error { +func (c *Client) solveChallengeForAuthz(authorizations []authorization) error { + failures := make(ObtainError) + // loop through the resources, basically through the domains. - failures := make(map[string]error) for _, authz := range authorizations { if authz.Status == "valid" { // Boulder might recycle recent validated authz (see issue #267) @@ -513,7 +519,12 @@ func (c *Client) solveChallengeForAuthz(authorizations []authorization) map[stri } } - return failures + // be careful not to return an empty failures map, for + // even an empty ObtainError is a non-nil error value + if len(failures) > 0 { + return failures + } + return nil } // Checks all challenges from the server in order and returns the first matching solver. @@ -528,7 +539,7 @@ func (c *Client) chooseSolver(auth authorization, domain string) (int, solver) { } // Get the challenges needed to proof our identifier to the ACME server. -func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, map[string]error) { +func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, error) { resc, errc := make(chan authorization), make(chan domainError) delay := time.Second / overallRequestLimit @@ -549,7 +560,7 @@ func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, map[str } var responses []authorization - failures := make(map[string]error) + failures := make(ObtainError) for i := 0; i < len(order.Authorizations); i++ { select { case res := <-resc: @@ -564,7 +575,12 @@ func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, map[str close(resc) close(errc) - return responses, failures + // be careful to not return an empty failures map; + // even if empty, they become non-nil error values + if len(failures) > 0 { + return responses, failures + } + return responses, nil } func logAuthz(order orderResource) { diff --git a/vendor/github.com/xenolf/lego/acmev2/error.go b/vendor/github.com/xenolf/lego/acmev2/error.go index 15cbc02bb..b92ce734c 100644 --- a/vendor/github.com/xenolf/lego/acmev2/error.go +++ b/vendor/github.com/xenolf/lego/acmev2/error.go @@ -1,6 +1,7 @@ package acmev2 import ( + "bytes" "encoding/json" "fmt" "io/ioutil" @@ -13,6 +14,18 @@ const ( invalidNonceError = "urn:ietf:params:acme:error:badNonce" ) +// ObtainError is returned when there are specific errors available +// per domain. For example in ObtainCertificate +type ObtainError map[string]error + +func (e ObtainError) Error() string { + buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n") + for dom, err := range e { + buffer.WriteString(fmt.Sprintf("[%s] %s\n", dom, err)) + } + return buffer.String() +} + // RemoteError is the base type for all errors specific to the ACME protocol. type RemoteError struct { StatusCode int `json:"status,omitempty"` diff --git a/webui/package.json b/webui/package.json index b52a59d60..370f0d40a 100644 --- a/webui/package.json +++ b/webui/package.json @@ -30,6 +30,7 @@ "bulma": "^0.7.0", "core-js": "^2.4.1", "d3": "^4.13.0", + "d3-format": "^1.3.0", "date-fns": "^1.29.0", "lodash": "^4.17.5", "rxjs": "^5.5.6", diff --git a/webui/src/app/charts/bar-chart/bar-chart.component.ts b/webui/src/app/charts/bar-chart/bar-chart.component.ts index 47946d9ae..cc77ddfec 100644 --- a/webui/src/app/charts/bar-chart/bar-chart.component.ts +++ b/webui/src/app/charts/bar-chart/bar-chart.component.ts @@ -1,5 +1,6 @@ import { Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { axisBottom, axisLeft, easeLinear, max, min, scaleBand, scaleLinear, select } from 'd3'; +import { format } from 'd3-format'; import * as _ from 'lodash'; import { WindowService } from '../../services/window.service'; @@ -93,7 +94,7 @@ export class BarChartComponent implements OnInit, OnChanges { .call(axisBottom(this.x)); this.g.select('.axis--y') - .call(axisLeft(this.y).tickSize(-this.width)); + .call(axisLeft(this.y).tickFormat(format('~s')).tickSize(-this.width)); // Clean previous graph this.g.selectAll('.bar').remove(); diff --git a/webui/src/app/components/providers/providers.component.html b/webui/src/app/components/providers/providers.component.html index b54c95834..bd9b3cc28 100644 --- a/webui/src/app/components/providers/providers.component.html +++ b/webui/src/app/components/providers/providers.component.html @@ -51,7 +51,7 @@

Route Rule

- +
@@ -569,7 +569,7 @@
- Men + Mem {{ p.buffering.memRequestBodyBytes }}
@@ -592,7 +592,7 @@
- Men + Mem {{ p.buffering.memResponseBodyBytes }}
diff --git a/webui/src/styles/app.sass b/webui/src/styles/app.sass deleted file mode 100644 index 0729c57b2..000000000 --- a/webui/src/styles/app.sass +++ /dev/null @@ -1,27 +0,0 @@ -@charset "utf-8" - -@import 'typography' -@import 'variables' -@import 'colors' -@import '~bulma/sass/utilities/all' -@import '~bulma/sass/base/all' -@import '~bulma/sass/grid/all' -@import '~bulma/sass/elements/container' -@import '~bulma/sass/elements/tag' -@import '~bulma/sass/elements/other' -@import '~bulma/sass/elements/box' -@import '~bulma/sass/elements/form' -@import '~bulma/sass/elements/table' -@import '~bulma/sass/components/navbar' -@import '~bulma/sass/components/tabs' -@import '~bulma/sass/elements/notification' -@import 'nav' -@import 'content' -@import 'message' -@import 'charts' -@import 'helper' - -html - font-family: $open-sans - height: 100% - background: $background diff --git a/webui/yarn.lock b/webui/yarn.lock index 1d00658b7..46117b48d 100644 --- a/webui/yarn.lock +++ b/webui/yarn.lock @@ -1698,6 +1698,10 @@ d3-format@1, d3-format@1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a" +d3-format@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.3.0.tgz#a3ac44269a2011cdb87c7b5693040c18cddfff11" + d3-geo@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356"
{{ route.rule }}