Add HTTP Provider
* feat: add HTTP provider implementation * refactor: add SetDefaults and struct tag for the new file parser * feat: add TLS configuration property * refactor: rework HTTP provider implementation * feat: provide config only once if fetched config is unchanged * style: lint * ui: add HTTP provider icon * tests: simplify and fix integration test * docs: add reference config for file * docs: move http reference config for file Co-authored-by: Daniel Tomcej <daniel.tomcej@gmail.com>
This commit is contained in:
parent
285ded6e49
commit
1ef93fead7
15 changed files with 816 additions and 1 deletions
189
docs/content/providers/http.md
Normal file
189
docs/content/providers/http.md
Normal file
|
@ -0,0 +1,189 @@
|
|||
# Traefik & HTTP
|
||||
|
||||
Provide your [dynamic configuration](./overview.md) via an HTTP(s) endpoint and let Traefik do the rest!
|
||||
|
||||
## Routing Configuration
|
||||
|
||||
The HTTP provider uses the same configuration as the [File Provider](./file.md) in YAML or JSON format.
|
||||
|
||||
## Provider Configuration
|
||||
|
||||
### `endpoint`
|
||||
|
||||
_Required_
|
||||
|
||||
Defines the HTTP(s) endpoint to poll.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.http]
|
||||
endpoint = "http://127.0.0.1:9000/api"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
http:
|
||||
endpoint:
|
||||
- "http://127.0.0.1:9000/api"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.http.endpoint=http://127.0.0.1:9000/api
|
||||
```
|
||||
|
||||
### `pollInterval`
|
||||
|
||||
_Optional, Default="5s"_
|
||||
|
||||
Defines the polling interval.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.http]
|
||||
pollInterval = "5s"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
http:
|
||||
pollInterval: "5s"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.http.pollInterval=5s
|
||||
```
|
||||
|
||||
### `pollTimeout`
|
||||
|
||||
_Optional, Default="5s"_
|
||||
|
||||
Defines the polling timeout when connecting to the configured endpoint.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.http]
|
||||
pollTimeout = "5s"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
http:
|
||||
pollTimeout: "5s"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.http.pollTimeout=5s
|
||||
```
|
||||
|
||||
### `tls`
|
||||
|
||||
_Optional_
|
||||
|
||||
#### `tls.ca`
|
||||
|
||||
Certificate Authority used for the secured connection to the configured Endpoint.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.http.tls]
|
||||
ca = "path/to/ca.crt"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
http:
|
||||
tls:
|
||||
ca: path/to/ca.crt
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.http.tls.ca=path/to/ca.crt
|
||||
```
|
||||
|
||||
#### `tls.caOptional`
|
||||
|
||||
Policy followed for the secured connection with TLS Client Authentication to the configured Endpoint.
|
||||
Requires `tls.ca` to be defined.
|
||||
|
||||
- `true`: VerifyClientCertIfGiven
|
||||
- `false`: RequireAndVerifyClientCert
|
||||
- if `tls.ca` is undefined NoClientCert
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.http.tls]
|
||||
caOptional = true
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
http:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.http.tls.caOptional=true
|
||||
```
|
||||
|
||||
#### `tls.cert`
|
||||
|
||||
Public certificate used for the secured connection to the configured Endpoint.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.http.tls]
|
||||
cert = "path/to/foo.cert"
|
||||
key = "path/to/foo.key"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
http:
|
||||
tls:
|
||||
cert: path/to/foo.cert
|
||||
key: path/to/foo.key
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.http.tls.cert=path/to/foo.cert
|
||||
--providers.http.tls.key=path/to/foo.key
|
||||
```
|
||||
|
||||
#### `tls.key`
|
||||
|
||||
Private certificate used for the secured connection to the configured Endpoint.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.http.tls]
|
||||
cert = "path/to/foo.cert"
|
||||
key = "path/to/foo.key"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
http:
|
||||
tls:
|
||||
cert: path/to/foo.cert
|
||||
key: path/to/foo.key
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.http.tls.cert=path/to/foo.cert
|
||||
--providers.http.tls.key=path/to/foo.key
|
||||
```
|
||||
|
||||
#### `tls.insecureSkipVerify`
|
||||
|
||||
If `insecureSkipVerify` is `true`, TLS connection to the configured Endpoint accepts any certificate presented by the
|
||||
server and any host name in that certificate.
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.http.tls]
|
||||
insecureSkipVerify = true
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
http:
|
||||
tls:
|
||||
insecureSkipVerify: true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.http.tls.insecureSkipVerify=true
|
||||
```
|
|
@ -35,9 +35,10 @@ Below is the list of the currently supported providers in Traefik.
|
|||
| [Rancher](./rancher.md) | Orchestrator | Label |
|
||||
| [File](./file.md) | Manual | TOML/YAML format |
|
||||
| [Consul](./consul.md) | KV | KV |
|
||||
| [etcd](./etcd.md) | KV | KV |
|
||||
| [Etcd](./etcd.md) | KV | KV |
|
||||
| [Redis](./redis.md) | KV | KV |
|
||||
| [ZooKeeper](./zookeeper.md) | KV | KV |
|
||||
| [HTTP](./http.md) | Manual | JSON format |
|
||||
|
||||
!!! info "More Providers"
|
||||
|
||||
|
|
|
@ -486,6 +486,33 @@ Load dynamic configuration from a file.
|
|||
`--providers.file.watch`:
|
||||
Watch provider. (Default: ```true```)
|
||||
|
||||
`--providers.http`:
|
||||
Enable HTTP backend with default settings. (Default: ```false```)
|
||||
|
||||
`--providers.http.endpoint`:
|
||||
Load configuration from this endpoint.
|
||||
|
||||
`--providers.http.pollinterval`:
|
||||
Polling interval for endpoint. (Default: ```5```)
|
||||
|
||||
`--providers.http.polltimeout`:
|
||||
Polling timeout for endpoint. (Default: ```5```)
|
||||
|
||||
`--providers.http.tls.ca`:
|
||||
TLS CA
|
||||
|
||||
`--providers.http.tls.caoptional`:
|
||||
TLS CA.Optional (Default: ```false```)
|
||||
|
||||
`--providers.http.tls.cert`:
|
||||
TLS cert
|
||||
|
||||
`--providers.http.tls.insecureskipverify`:
|
||||
TLS insecure skip verify (Default: ```false```)
|
||||
|
||||
`--providers.http.tls.key`:
|
||||
TLS key
|
||||
|
||||
`--providers.kubernetescrd`:
|
||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||
|
||||
|
|
|
@ -486,6 +486,33 @@ Load dynamic configuration from a file.
|
|||
`TRAEFIK_PROVIDERS_FILE_WATCH`:
|
||||
Watch provider. (Default: ```true```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_HTTP`:
|
||||
Enable HTTP backend with default settings. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_HTTP_ENDPOINT`:
|
||||
Load configuration from this endpoint.
|
||||
|
||||
`TRAEFIK_PROVIDERS_HTTP_POLLINTERVAL`:
|
||||
Polling interval for endpoint. (Default: ```5```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_HTTP_POLLTIMEOUT`:
|
||||
Polling timeout for endpoint. (Default: ```5```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_HTTP_TLS_CA`:
|
||||
TLS CA
|
||||
|
||||
`TRAEFIK_PROVIDERS_HTTP_TLS_CAOPTIONAL`:
|
||||
TLS CA.Optional (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_HTTP_TLS_CERT`:
|
||||
TLS cert
|
||||
|
||||
`TRAEFIK_PROVIDERS_HTTP_TLS_INSECURESKIPVERIFY`:
|
||||
TLS insecure skip verify (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_HTTP_TLS_KEY`:
|
||||
TLS key
|
||||
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD`:
|
||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||
|
||||
|
|
|
@ -205,6 +205,16 @@
|
|||
cert = "foobar"
|
||||
key = "foobar"
|
||||
insecureSkipVerify = true
|
||||
[providers.http]
|
||||
endpoint = "foobar"
|
||||
pollInterval = 42
|
||||
pollTimeout = 42
|
||||
[providers.http.tls]
|
||||
ca = "foobar"
|
||||
caOptional = true
|
||||
cert = "foobar"
|
||||
key = "foobar"
|
||||
insecureSkipVerify = true
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
|
|
@ -225,6 +225,16 @@ providers:
|
|||
cert: foobar
|
||||
key: foobar
|
||||
insecureSkipVerify: true
|
||||
http:
|
||||
endpoint: foobar
|
||||
pollInterval: 42
|
||||
pollTimeout: 42
|
||||
tls:
|
||||
ca: foobar
|
||||
caOptional: true
|
||||
cert: foobar
|
||||
key: foobar
|
||||
insecureSkipVerify: true
|
||||
api:
|
||||
insecure: true
|
||||
dashboard: true
|
||||
|
|
|
@ -85,6 +85,7 @@ nav:
|
|||
- 'Etcd': 'providers/etcd.md'
|
||||
- 'ZooKeeper': 'providers/zookeeper.md'
|
||||
- 'Redis': 'providers/redis.md'
|
||||
- 'HTTP': 'providers/http.md'
|
||||
- 'Routing & Load Balancing':
|
||||
- 'Overview': 'routing/overview.md'
|
||||
- 'EntryPoints': 'routing/entrypoints.md'
|
||||
|
|
20
integration/fixtures/http/simple.toml
Normal file
20
integration/fixtures/http/simple.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":8000"
|
||||
[entryPoints.traefik]
|
||||
address = ":9090"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers]
|
||||
[providers.http]
|
||||
endpoint = "http://127.0.0.1:9000"
|
||||
pollInterval = "100ms"
|
87
integration/http_test.go
Normal file
87
integration/http_test.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/v2/integration/try"
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/go-check/check"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
type HTTPSuite struct{ BaseSuite }
|
||||
|
||||
func (s *HTTPSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd, display := s.traefikCmd(withConfigFile("fixtures/http/simple.toml"))
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// Expect a 404 as we configured nothing.
|
||||
err = try.GetRequest("http://127.0.0.1:8000/", time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Provide a configuration, fetched by Traefik provider.
|
||||
configuration := &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"routerHTTP": {
|
||||
EntryPoints: []string{"web"},
|
||||
Middlewares: []string{},
|
||||
Service: "serviceHTTP",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"serviceHTTP": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: boolRef(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://bacon:80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
configData, err := json.Marshal(configuration)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
server := startTestServerWithResponse(configData)
|
||||
defer server.Close()
|
||||
|
||||
// Expect configuration to be applied.
|
||||
err = try.GetRequest("http://127.0.0.1:9090/api/rawdata", 3*time.Second, try.BodyContains("routerHTTP@http", "serviceHTTP@http", "http://bacon:80"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func startTestServerWithResponse(response []byte) (ts *httptest.Server) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write(response)
|
||||
})
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:9000")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ts = &httptest.Server{
|
||||
Listener: listener,
|
||||
Config: &http.Server{Handler: handler},
|
||||
}
|
||||
ts.Start()
|
||||
return ts
|
||||
}
|
||||
|
||||
func boolRef(b bool) *bool {
|
||||
return &b
|
||||
}
|
|
@ -49,6 +49,7 @@ func Test(t *testing.T) {
|
|||
check.Suite(&HealthCheckSuite{})
|
||||
check.Suite(&HeadersSuite{})
|
||||
check.Suite(&HostResolverSuite{})
|
||||
check.Suite(&HTTPSuite{})
|
||||
check.Suite(&HTTPSSuite{})
|
||||
check.Suite(&KeepAliveSuite{})
|
||||
check.Suite(&LogRotationSuite{})
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/containous/traefik/v2/pkg/provider/docker"
|
||||
"github.com/containous/traefik/v2/pkg/provider/ecs"
|
||||
"github.com/containous/traefik/v2/pkg/provider/file"
|
||||
"github.com/containous/traefik/v2/pkg/provider/http"
|
||||
"github.com/containous/traefik/v2/pkg/provider/kubernetes/crd"
|
||||
"github.com/containous/traefik/v2/pkg/provider/kubernetes/ingress"
|
||||
"github.com/containous/traefik/v2/pkg/provider/kv/consul"
|
||||
|
@ -177,6 +178,7 @@ type Providers struct {
|
|||
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
HTTP *http.Provider `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
}
|
||||
|
||||
// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones.
|
||||
|
|
|
@ -73,6 +73,10 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator {
|
|||
p.quietAddProvider(conf.Redis)
|
||||
}
|
||||
|
||||
if conf.HTTP != nil {
|
||||
p.quietAddProvider(conf.HTTP)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
|
|
172
pkg/provider/http/http.go
Normal file
172
pkg/provider/http/http.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/v2/pkg/config/file"
|
||||
"github.com/containous/traefik/v2/pkg/job"
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
"github.com/containous/traefik/v2/pkg/provider"
|
||||
"github.com/containous/traefik/v2/pkg/safe"
|
||||
"github.com/containous/traefik/v2/pkg/tls"
|
||||
"github.com/containous/traefik/v2/pkg/types"
|
||||
)
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
||||
// Provider is a provider.Provider implementation that queries an HTTP(s) endpoint for a configuration.
|
||||
type Provider struct {
|
||||
Endpoint string `description:"Load configuration from this endpoint." json:"endpoint" toml:"endpoint" yaml:"endpoint" export:"true"`
|
||||
PollInterval types.Duration `description:"Polling interval for endpoint." json:"pollInterval,omitempty" toml:"pollInterval,omitempty" yaml:"pollInterval,omitempty"`
|
||||
PollTimeout types.Duration `description:"Polling timeout for endpoint." json:"pollTimeout,omitempty" toml:"pollTimeout,omitempty" yaml:"pollTimeout,omitempty"`
|
||||
TLS *types.ClientTLS `description:"Enable TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||
httpClient *http.Client
|
||||
lastConfigurationHash uint64
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.PollInterval = types.Duration(5 * time.Second)
|
||||
p.PollTimeout = types.Duration(5 * time.Second)
|
||||
}
|
||||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
if p.Endpoint == "" {
|
||||
return fmt.Errorf("non-empty endpoint is required")
|
||||
}
|
||||
|
||||
if p.PollInterval <= 0 {
|
||||
return fmt.Errorf("poll interval must be greater than 0")
|
||||
}
|
||||
|
||||
p.httpClient = &http.Client{
|
||||
Timeout: time.Duration(p.PollTimeout),
|
||||
}
|
||||
|
||||
if p.TLS != nil {
|
||||
tlsConfig, err := p.TLS.CreateTLSConfig(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create TLS configuration: %w", err)
|
||||
}
|
||||
|
||||
p.httpClient.Transport = &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik using the given configuration channel.
|
||||
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
||||
pool.GoCtx(func(routineCtx context.Context) {
|
||||
ctxLog := log.With(routineCtx, log.Str(log.ProviderName, "http"))
|
||||
logger := log.FromContext(ctxLog)
|
||||
|
||||
operation := func() error {
|
||||
ticker := time.NewTicker(time.Duration(p.PollInterval))
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
configData, err := p.fetchConfigurationData()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot fetch configuration data: %w", err)
|
||||
}
|
||||
|
||||
fnvHasher := fnv.New64()
|
||||
|
||||
_, err = fnvHasher.Write(configData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot hash configuration data: %w", err)
|
||||
}
|
||||
|
||||
hash := fnvHasher.Sum64()
|
||||
if hash == p.lastConfigurationHash {
|
||||
continue
|
||||
}
|
||||
|
||||
p.lastConfigurationHash = hash
|
||||
|
||||
configuration, err := decodeConfiguration(configData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot decode configuration data: %w", err)
|
||||
}
|
||||
|
||||
configurationChan <- dynamic.Message{
|
||||
ProviderName: "http",
|
||||
Configuration: configuration,
|
||||
}
|
||||
|
||||
case <-routineCtx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
logger.Errorf("Provider connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify)
|
||||
if err != nil {
|
||||
logger.Errorf("Cannot connect to server endpoint %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchConfigurationData fetches the configuration data from the configured endpoint.
|
||||
func (p *Provider) fetchConfigurationData() ([]byte, error) {
|
||||
res, err := p.httpClient.Get(p.Endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("received non-ok response code: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
return ioutil.ReadAll(res.Body)
|
||||
}
|
||||
|
||||
// decodeConfiguration decodes and returns the dynamic configuration from the given data.
|
||||
func decodeConfiguration(data []byte) (*dynamic.Configuration, error) {
|
||||
configuration := &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: make(map[string]*dynamic.Router),
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
Services: make(map[string]*dynamic.Service),
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
Services: make(map[string]*dynamic.TCPService),
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Stores: make(map[string]tls.Store),
|
||||
Options: make(map[string]tls.Options),
|
||||
},
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: make(map[string]*dynamic.UDPRouter),
|
||||
Services: make(map[string]*dynamic.UDPService),
|
||||
},
|
||||
}
|
||||
|
||||
err := file.DecodeContent(string(data), ".yaml", configuration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return configuration, nil
|
||||
}
|
253
pkg/provider/http/http_test.go
Normal file
253
pkg/provider/http/http_test.go
Normal file
|
@ -0,0 +1,253 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/v2/pkg/safe"
|
||||
"github.com/containous/traefik/v2/pkg/tls"
|
||||
"github.com/containous/traefik/v2/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProvider_Init(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
endpoint string
|
||||
pollInterval types.Duration
|
||||
expErr bool
|
||||
}{
|
||||
{
|
||||
desc: "should return an error if no endpoint is configured",
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
desc: "should return an error if pollInterval is equal to 0",
|
||||
endpoint: "http://localhost:8080",
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
desc: "should not return an error",
|
||||
endpoint: "http://localhost:8080",
|
||||
pollInterval: types.Duration(time.Second),
|
||||
expErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
provider := &Provider{
|
||||
Endpoint: test.endpoint,
|
||||
PollInterval: test.pollInterval,
|
||||
}
|
||||
|
||||
err := provider.Init()
|
||||
if test.expErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_SetDefaults(t *testing.T) {
|
||||
provider := &Provider{}
|
||||
|
||||
provider.SetDefaults()
|
||||
|
||||
assert.Equal(t, provider.PollInterval, types.Duration(5*time.Second))
|
||||
assert.Equal(t, provider.PollTimeout, types.Duration(5*time.Second))
|
||||
}
|
||||
|
||||
func TestProvider_fetchConfigurationData(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
handler func(rw http.ResponseWriter, req *http.Request)
|
||||
expData []byte
|
||||
expErr bool
|
||||
}{
|
||||
{
|
||||
desc: "should return the fetched configuration data",
|
||||
expData: []byte("{}"),
|
||||
handler: func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, _ = fmt.Fprintf(rw, "{}")
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should return an error if endpoint does not return an OK status code",
|
||||
expErr: true,
|
||||
handler: func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(test.handler))
|
||||
defer server.Close()
|
||||
|
||||
provider := Provider{
|
||||
Endpoint: server.URL,
|
||||
PollInterval: types.Duration(1 * time.Second),
|
||||
PollTimeout: types.Duration(1 * time.Second),
|
||||
}
|
||||
|
||||
err := provider.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
configData, err := provider.fetchConfigurationData()
|
||||
if test.expErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expData, configData)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_decodeConfiguration(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
configData []byte
|
||||
expConfig *dynamic.Configuration
|
||||
expErr bool
|
||||
}{
|
||||
{
|
||||
desc: "should return an error if the configuration data cannot be decoded",
|
||||
expErr: true,
|
||||
configData: []byte("{"),
|
||||
},
|
||||
{
|
||||
desc: "should return the decoded dynamic configuration",
|
||||
configData: []byte("{\"tcp\":{\"routers\":{\"foo\":{}}}}"),
|
||||
expConfig: &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: make(map[string]*dynamic.Router),
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
Services: make(map[string]*dynamic.Service),
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"foo": {},
|
||||
},
|
||||
Services: make(map[string]*dynamic.TCPService),
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Stores: make(map[string]tls.Store),
|
||||
Options: make(map[string]tls.Options),
|
||||
},
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: make(map[string]*dynamic.UDPRouter),
|
||||
Services: make(map[string]*dynamic.UDPService),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
configuration, err := decodeConfiguration(test.configData)
|
||||
if test.expErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expConfig, configuration)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_Provide(t *testing.T) {
|
||||
handler := func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, _ = fmt.Fprintf(rw, "{}")
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer server.Close()
|
||||
|
||||
provider := Provider{
|
||||
Endpoint: server.URL,
|
||||
PollTimeout: types.Duration(1 * time.Second),
|
||||
PollInterval: types.Duration(100 * time.Millisecond),
|
||||
}
|
||||
|
||||
err := provider.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
configurationChan := make(chan dynamic.Message)
|
||||
|
||||
expConfiguration := &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: make(map[string]*dynamic.Router),
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
Services: make(map[string]*dynamic.Service),
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
Services: make(map[string]*dynamic.TCPService),
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Stores: make(map[string]tls.Store),
|
||||
Options: make(map[string]tls.Options),
|
||||
},
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: make(map[string]*dynamic.UDPRouter),
|
||||
Services: make(map[string]*dynamic.UDPService),
|
||||
},
|
||||
}
|
||||
|
||||
err = provider.Provide(configurationChan, safe.NewPool(context.Background()))
|
||||
require.NoError(t, err)
|
||||
|
||||
timeout := time.After(time.Second)
|
||||
|
||||
select {
|
||||
case configuration := <-configurationChan:
|
||||
assert.NotNil(t, configuration.Configuration)
|
||||
assert.Equal(t, expConfiguration, configuration.Configuration)
|
||||
case <-timeout:
|
||||
t.Errorf("timeout while waiting for config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_ProvideConfigurationOnlyOnceIfUnchanged(t *testing.T) {
|
||||
handler := func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, _ = fmt.Fprintf(rw, "{}")
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer server.Close()
|
||||
|
||||
provider := Provider{
|
||||
Endpoint: server.URL + "/endpoint",
|
||||
PollTimeout: types.Duration(1 * time.Second),
|
||||
PollInterval: types.Duration(100 * time.Millisecond),
|
||||
}
|
||||
|
||||
err := provider.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
configurationChan := make(chan dynamic.Message, 10)
|
||||
|
||||
err = provider.Provide(configurationChan, safe.NewPool(context.Background()))
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
assert.Equal(t, 1, len(configurationChan))
|
||||
}
|
11
webui/src/statics/providers/http.svg
Normal file
11
webui/src/statics/providers/http.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<svg width="32" height="32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<path d="M18.748 23.93a13.78 13.78 0 002.476-6.88h3.102a8.413 8.413 0 01-5.578 6.88zM7.672 17.05h3.142a13.547 13.547 0 002.463 6.89 8.416 8.416 0 01-5.605-6.89zm5.638-9a13.801 13.801 0 00-2.492 6.9H7.672a8.414 8.414 0 015.638-6.9zm-.377 6.9c.321-3.436 2.079-5.827 3.094-6.933 1.05 1.124 2.78 3.494 3.08 6.933h-6.174zm.001 2.1h6.176c-.321 3.44-2.083 5.833-3.097 6.938a11.57 11.57 0 01-3.079-6.938zm11.393-2.1h-3.1a13.537 13.537 0 00-2.445-6.866 8.409 8.409 0 015.544 6.866zM26.5 16c0-5.78-4.695-10.481-10.47-10.498h-.014L16 5.5C10.21 5.5 5.5 10.211 5.5 16c0 5.79 4.71 10.5 10.5 10.5l.016-.001.005.001.008-.002C21.805 26.482 26.5 21.779 26.5 16z" id="a"/>
|
||||
</defs>
|
||||
<g fill-rule="nonzero" fill="none">
|
||||
<circle fill="#45BBEA" cx="16" cy="16" r="16"/>
|
||||
<use fill="#FFF" xlink:href="#a"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 918 B |
Loading…
Reference in a new issue