Support custom headers when fetching configuration through HTTP

This commit is contained in:
Kevin Pollet 2022-10-14 15:10:10 +02:00 committed by GitHub
parent 188ef84c4f
commit 33f0aed5ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 86 additions and 28 deletions

View file

@ -76,6 +76,26 @@ providers:
--providers.http.pollTimeout=5s --providers.http.pollTimeout=5s
``` ```
### `headers`
_Optional_
Defines custom headers to be sent to the endpoint.
```yaml tab="File (YAML)"
providers:
headers:
name: value
```
```toml tab="File (TOML)"
[providers.http.headers]
name = "value"
```
```bash tab="CLI"
--providers.http.headers.name=value
### `tls` ### `tls`
_Optional_ _Optional_

View file

@ -645,6 +645,9 @@ Enable HTTP backend with default settings. (Default: ```false```)
`--providers.http.endpoint`: `--providers.http.endpoint`:
Load configuration from this endpoint. Load configuration from this endpoint.
`--providers.http.headers.<name>`:
Define custom headers to be sent to the endpoint.
`--providers.http.pollinterval`: `--providers.http.pollinterval`:
Polling interval for endpoint. (Default: ```5```) Polling interval for endpoint. (Default: ```5```)

View file

@ -645,6 +645,9 @@ Enable HTTP backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_HTTP_ENDPOINT`: `TRAEFIK_PROVIDERS_HTTP_ENDPOINT`:
Load configuration from this endpoint. Load configuration from this endpoint.
`TRAEFIK_PROVIDERS_HTTP_HEADERS_<NAME>`:
Define custom headers to be sent to the endpoint.
`TRAEFIK_PROVIDERS_HTTP_POLLINTERVAL`: `TRAEFIK_PROVIDERS_HTTP_POLLINTERVAL`:
Polling interval for endpoint. (Default: ```5```) Polling interval for endpoint. (Default: ```5```)

View file

@ -252,6 +252,9 @@
endpoint = "foobar" endpoint = "foobar"
pollInterval = "42s" pollInterval = "42s"
pollTimeout = "42s" pollTimeout = "42s"
[providers.http.headers]
name0 = "foobar"
name1 = "foobar"
[providers.http.tls] [providers.http.tls]
ca = "foobar" ca = "foobar"
caOptional = true caOptional = true

View file

@ -280,6 +280,9 @@ providers:
endpoint: foobar endpoint: foobar
pollInterval: 42s pollInterval: 42s
pollTimeout: 42s pollTimeout: 42s
headers:
name0: foobar
name1: foobar
tls: tls:
ca: foobar ca: foobar
caOptional: true caOptional: true

View file

@ -24,10 +24,12 @@ var _ provider.Provider = (*Provider)(nil)
// Provider is a provider.Provider implementation that queries an HTTP(s) endpoint for a configuration. // Provider is a provider.Provider implementation that queries an HTTP(s) endpoint for a configuration.
type Provider struct { type Provider struct {
Endpoint string `description:"Load configuration from this endpoint." json:"endpoint" toml:"endpoint" yaml:"endpoint"` Endpoint string `description:"Load configuration from this endpoint." json:"endpoint" toml:"endpoint" yaml:"endpoint"`
PollInterval ptypes.Duration `description:"Polling interval for endpoint." json:"pollInterval,omitempty" toml:"pollInterval,omitempty" yaml:"pollInterval,omitempty" export:"true"` PollInterval ptypes.Duration `description:"Polling interval for endpoint." json:"pollInterval,omitempty" toml:"pollInterval,omitempty" yaml:"pollInterval,omitempty" export:"true"`
PollTimeout ptypes.Duration `description:"Polling timeout for endpoint." json:"pollTimeout,omitempty" toml:"pollTimeout,omitempty" yaml:"pollTimeout,omitempty" export:"true"` PollTimeout ptypes.Duration `description:"Polling timeout for endpoint." json:"pollTimeout,omitempty" toml:"pollTimeout,omitempty" yaml:"pollTimeout,omitempty" export:"true"`
TLS *types.ClientTLS `description:"Enable TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` Headers map[string]string `description:"Define custom headers to be sent to the endpoint." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"`
TLS *types.ClientTLS `description:"Enable TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
httpClient *http.Client httpClient *http.Client
lastConfigurationHash uint64 lastConfigurationHash uint64
} }
@ -139,9 +141,18 @@ func (p *Provider) updateConfiguration(configurationChan chan<- dynamic.Message)
// fetchConfigurationData fetches the configuration data from the configured endpoint. // fetchConfigurationData fetches the configuration data from the configured endpoint.
func (p *Provider) fetchConfigurationData() ([]byte, error) { func (p *Provider) fetchConfigurationData() ([]byte, error) {
res, err := p.httpClient.Get(p.Endpoint) req, err := http.NewRequest(http.MethodGet, p.Endpoint, http.NoBody)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("create fetch request: %w", err)
}
for k, v := range p.Headers {
req.Header.Set(k, v)
}
res, err := p.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("do fetch request: %w", err)
} }
defer res.Body.Close() defer res.Body.Close()

View file

@ -69,35 +69,53 @@ func TestProvider_SetDefaults(t *testing.T) {
func TestProvider_fetchConfigurationData(t *testing.T) { func TestProvider_fetchConfigurationData(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string
handler func(rw http.ResponseWriter, req *http.Request) statusCode int
expData []byte headers map[string]string
expErr bool expData []byte
expErr require.ErrorAssertionFunc
}{ }{
{ {
desc: "should return the fetched configuration data", desc: "should return the fetched configuration data",
expData: []byte("{}"), statusCode: http.StatusOK,
handler: func(rw http.ResponseWriter, req *http.Request) { expData: []byte("{}"),
rw.WriteHeader(http.StatusOK) expErr: require.NoError,
_, _ = fmt.Fprintf(rw, "{}")
},
}, },
{ {
desc: "should return an error if endpoint does not return an OK status code", desc: "should send configured headers",
expErr: true, statusCode: http.StatusOK,
handler: func(rw http.ResponseWriter, req *http.Request) { headers: map[string]string{
rw.WriteHeader(http.StatusNoContent) "Foo": "bar",
"Bar": "baz",
}, },
expData: []byte("{}"),
expErr: require.NoError,
},
{
desc: "should return an error if endpoint does not return an OK status code",
statusCode: http.StatusInternalServerError,
expErr: require.Error,
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(test.handler)) handlerCalled := false
defer server.Close() srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
handlerCalled = true
for k, v := range test.headers {
assert.Equal(t, v, req.Header.Get(k))
}
rw.WriteHeader(test.statusCode)
_, _ = rw.Write([]byte("{}"))
}))
defer srv.Close()
provider := Provider{ provider := Provider{
Endpoint: server.URL, Endpoint: srv.URL,
Headers: test.headers,
PollInterval: ptypes.Duration(1 * time.Second), PollInterval: ptypes.Duration(1 * time.Second),
PollTimeout: ptypes.Duration(1 * time.Second), PollTimeout: ptypes.Duration(1 * time.Second),
} }
@ -106,12 +124,9 @@ func TestProvider_fetchConfigurationData(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
configData, err := provider.fetchConfigurationData() configData, err := provider.fetchConfigurationData()
if test.expErr { test.expErr(t, err)
require.Error(t, err)
return
}
require.NoError(t, err) assert.True(t, handlerCalled)
assert.Equal(t, test.expData, configData) assert.Equal(t, test.expData, configData)
}) })
} }