diff --git a/docs/content/providers/http.md b/docs/content/providers/http.md index f99e90a7c..111a6706e 100644 --- a/docs/content/providers/http.md +++ b/docs/content/providers/http.md @@ -76,6 +76,26 @@ providers: --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` _Optional_ diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 3e048d2d3..444a2ce84 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -645,6 +645,9 @@ Enable HTTP backend with default settings. (Default: ```false```) `--providers.http.endpoint`: Load configuration from this endpoint. +`--providers.http.headers.`: +Define custom headers to be sent to the endpoint. + `--providers.http.pollinterval`: Polling interval for endpoint. (Default: ```5```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 088312ec3..3cae404b1 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -645,6 +645,9 @@ Enable HTTP backend with default settings. (Default: ```false```) `TRAEFIK_PROVIDERS_HTTP_ENDPOINT`: Load configuration from this endpoint. +`TRAEFIK_PROVIDERS_HTTP_HEADERS_`: +Define custom headers to be sent to the endpoint. + `TRAEFIK_PROVIDERS_HTTP_POLLINTERVAL`: Polling interval for endpoint. (Default: ```5```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 698e39493..4f8f847f2 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -252,6 +252,9 @@ endpoint = "foobar" pollInterval = "42s" pollTimeout = "42s" + [providers.http.headers] + name0 = "foobar" + name1 = "foobar" [providers.http.tls] ca = "foobar" caOptional = true diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index a2c753ef1..d668d7ac3 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -280,6 +280,9 @@ providers: endpoint: foobar pollInterval: 42s pollTimeout: 42s + headers: + name0: foobar + name1: foobar tls: ca: foobar caOptional: true diff --git a/pkg/provider/http/http.go b/pkg/provider/http/http.go index a9e4bc111..4d21cfb19 100644 --- a/pkg/provider/http/http.go +++ b/pkg/provider/http/http.go @@ -24,10 +24,12 @@ 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"` - 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"` - TLS *types.ClientTLS `description:"Enable TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + 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"` + PollTimeout ptypes.Duration `description:"Polling timeout for endpoint." json:"pollTimeout,omitempty" toml:"pollTimeout,omitempty" yaml:"pollTimeout,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 lastConfigurationHash uint64 } @@ -139,9 +141,18 @@ func (p *Provider) updateConfiguration(configurationChan chan<- dynamic.Message) // fetchConfigurationData fetches the configuration data from the configured endpoint. 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 { - 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() diff --git a/pkg/provider/http/http_test.go b/pkg/provider/http/http_test.go index 19759c8b7..7a0477dd0 100644 --- a/pkg/provider/http/http_test.go +++ b/pkg/provider/http/http_test.go @@ -69,35 +69,53 @@ func TestProvider_SetDefaults(t *testing.T) { func TestProvider_fetchConfigurationData(t *testing.T) { tests := []struct { - desc string - handler func(rw http.ResponseWriter, req *http.Request) - expData []byte - expErr bool + desc string + statusCode int + headers map[string]string + expData []byte + expErr require.ErrorAssertionFunc }{ { - 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 the fetched configuration data", + statusCode: http.StatusOK, + expData: []byte("{}"), + expErr: require.NoError, }, { - 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) + desc: "should send configured headers", + statusCode: http.StatusOK, + headers: map[string]string{ + "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 { t.Run(test.desc, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(test.handler)) - defer server.Close() + handlerCalled := false + 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{ - Endpoint: server.URL, + Endpoint: srv.URL, + Headers: test.headers, PollInterval: 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) configData, err := provider.fetchConfigurationData() - if test.expErr { - require.Error(t, err) - return - } + test.expErr(t, err) - require.NoError(t, err) + assert.True(t, handlerCalled) assert.Equal(t, test.expData, configData) }) }