diff --git a/README.md b/README.md index 30d15a4..92a750c 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ This will return metrics for all nodes. A query parameter to filter by host can - To use the Prometheus client, please configure environment variables `METRICS_PROVIDER_NAME`, `METRICS_PROVIDER_ADDRESS` and `METRICS_PROVIDER_TOKEN` to `Prometheus`, Prometheus address and auth token. Please do not set `METRICS_PROVIDER_TOKEN` if no authentication is needed to access the Prometheus APIs. Default value of address set is `http://prometheus-k8s:9090` for Prometheus client. +- To add custom headers for Prometheus authentication (e.g., OAuth bypass tokens), use the `METRICS_PROVIDER_HEADERS` environment variable with format `key1=value1,key2=value2`. Example: `METRICS_PROVIDER_HEADERS="X-Oauth-Bypass-Token=token123,X-API-Key=key456"`. + - To use the SignalFx client, please configure environment variables `METRICS_PROVIDER_NAME`, `METRICS_PROVIDER_ADDRESS` and `METRICS_PROVIDER_TOKEN` to `SignalFx`, SignalFx address and auth token respectively. Default value of address set is `https://api.signalfx.com` for SignalFx client. ## Deploy `load-watcher` as a service diff --git a/manifests/load-watcher-deployment.yaml b/manifests/load-watcher-deployment.yaml index 493a98a..dce4805 100644 --- a/manifests/load-watcher-deployment.yaml +++ b/manifests/load-watcher-deployment.yaml @@ -30,6 +30,8 @@ spec: value: [metrics_provider_endpoint] - name: METRICS_PROVIDER_TOKEN value: [token] + - name: METRICS_PROVIDER_HEADERS + value: "X-Oauth-Bypass-Token=token123" ports: - containerPort: 2020 --- diff --git a/pkg/watcher/internal/metricsprovider/prometheus.go b/pkg/watcher/internal/metricsprovider/prometheus.go index ca46bac..c953adc 100644 --- a/pkg/watcher/internal/metricsprovider/prometheus.go +++ b/pkg/watcher/internal/metricsprovider/prometheus.go @@ -71,6 +71,25 @@ type promClient struct { client api.Client } +// customHeaderRoundTripper wraps an existing RoundTripper and adds custom headers +type customHeaderRoundTripper struct { + headers map[string]string + rt http.RoundTripper +} + +// RoundTrip implements the RoundTripper interface +func (c *customHeaderRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + // Clone the request to avoid modifying the original + req = req.Clone(req.Context()) + + // Add custom headers + for key, value := range c.headers { + req.Header.Set(key, value) + } + + return c.rt.RoundTrip(req) +} + func loadCAFile(filepath string) (*x509.CertPool, error) { caCert, err := ioutil.ReadFile(filepath) if err != nil { @@ -141,17 +160,31 @@ func NewPromClient(opts watcher.MetricsProviderOpts) (watcher.MetricsProviderCli } } + // Apply custom headers if provided + if len(opts.Headers) > 0 { + // Convert map[string]string to *config.Headers + configHeaders := &config.Headers{ + Headers: make(map[string]config.Header), + } + for key, value := range opts.Headers { + configHeaders.Headers[key] = config.Header{ + Values: []string{value}, + } + } + roundTripper = config.NewHeadersRoundTripper(configHeaders, roundTripper) + } + + // Apply auth token if provided if promToken != "" { - client, err = api.NewClient(api.Config{ - Address: promAddress, - RoundTripper: config.NewAuthorizationCredentialsRoundTripper("Bearer", config.NewInlineSecret(opts.AuthToken), roundTripper), - }) - } else { - client, err = api.NewClient(api.Config{ - Address: promAddress, - }) + roundTripper = config.NewAuthorizationCredentialsRoundTripper("Bearer", config.NewInlineSecret(opts.AuthToken), roundTripper) } + // Create the client with the final round tripper + client, err = api.NewClient(api.Config{ + Address: promAddress, + RoundTripper: roundTripper, + }) + if err != nil { log.Errorf("error creating prometheus client: %v", err) return nil, err diff --git a/pkg/watcher/metricsprovider.go b/pkg/watcher/metricsprovider.go index db878eb..e6636b6 100644 --- a/pkg/watcher/metricsprovider.go +++ b/pkg/watcher/metricsprovider.go @@ -32,12 +32,44 @@ const ( MetricsProviderTokenKey = "METRICS_PROVIDER_TOKEN" MetricsProviderAppKey = "METRICS_PROVIDER_APP_KEY" InsecureSkipVerify = "INSECURE_SKIP_VERIFY" + MetricsProviderHeadersKey = "METRICS_PROVIDER_HEADERS" ) var ( EnvMetricProviderOpts MetricsProviderOpts ) +// parseHeadersFromEnv parses headers from environment variable +// Expected format: "key1=value1,key2=value2,key3=value3" +// Example: "http_x_oauth_bypass_token=token123,X-API-Key=key456" +func parseHeadersFromEnv(headersEnv string) map[string]string { + headers := make(map[string]string) + if headersEnv == "" { + return headers + } + + // Split by comma to get individual header pairs + headerPairs := strings.Split(headersEnv, ",") + for _, pair := range headerPairs { + pair = strings.TrimSpace(pair) + if pair == "" { + continue + } + + // Split by = to get key and value + parts := strings.SplitN(pair, "=", 2) + if len(parts) == 2 { + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + if key != "" && value != "" { + headers[key] = value + } + } + } + + return headers +} + func init() { var ok bool EnvMetricProviderOpts.Name, ok = os.LookupEnv(MetricsProviderNameKey) @@ -53,6 +85,10 @@ func init() { } else { EnvMetricProviderOpts.InsecureSkipVerify = false } + + // Parse headers from environment variable + headersEnv, _ := os.LookupEnv(MetricsProviderHeadersKey) + EnvMetricProviderOpts.Headers = parseHeadersFromEnv(headersEnv) } // Interface to be implemented by any metrics provider client to interact with Watcher @@ -75,4 +111,5 @@ type MetricsProviderOpts struct { AuthToken string ApplicationKey string InsecureSkipVerify bool + Headers map[string]string }