From b1e82ab54a996299256a138bc9a32b1565db69e6 Mon Sep 17 00:00:00 2001 From: Guillaume LEGRAIN Date: Fri, 30 May 2025 10:43:29 +0200 Subject: [PATCH 1/2] feat: add header support for prometheus --- README.md | 2 + .../internal/metricsprovider/prometheus.go | 30 ++++++++++++++- pkg/watcher/metricsprovider.go | 37 +++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 30d15a4..0910025 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="http_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/pkg/watcher/internal/metricsprovider/prometheus.go b/pkg/watcher/internal/metricsprovider/prometheus.go index ca46bac..75a27e3 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,6 +160,14 @@ func NewPromClient(opts watcher.MetricsProviderOpts) (watcher.MetricsProviderCli } } + // Wrap roundTripper with custom headers if provided + if opts.Headers != nil && len(opts.Headers) > 0 { + roundTripper = &customHeaderRoundTripper{ + headers: opts.Headers, + rt: roundTripper, + } + } + if promToken != "" { client, err = api.NewClient(api.Config{ Address: promAddress, @@ -148,7 +175,8 @@ func NewPromClient(opts watcher.MetricsProviderOpts) (watcher.MetricsProviderCli }) } else { client, err = api.NewClient(api.Config{ - Address: promAddress, + Address: promAddress, + RoundTripper: roundTripper, }) } 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 } From a5a416bd3b8ee9fe73efaa7a3048ef582a9f0f48 Mon Sep 17 00:00:00 2001 From: Guillaume LEGRAIN Date: Fri, 30 May 2025 11:16:50 +0200 Subject: [PATCH 2/2] feat: header implementation using prom existing standard --- README.md | 2 +- manifests/load-watcher-deployment.yaml | 2 ++ .../internal/metricsprovider/prometheus.go | 33 +++++++++++-------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 0910025..92a750c 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ 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="http_x_oauth_bypass_token=token123,X-API-Key=key456"`. +- 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. 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 75a27e3..c953adc 100644 --- a/pkg/watcher/internal/metricsprovider/prometheus.go +++ b/pkg/watcher/internal/metricsprovider/prometheus.go @@ -160,26 +160,31 @@ func NewPromClient(opts watcher.MetricsProviderOpts) (watcher.MetricsProviderCli } } - // Wrap roundTripper with custom headers if provided - if opts.Headers != nil && len(opts.Headers) > 0 { - roundTripper = &customHeaderRoundTripper{ - headers: opts.Headers, - rt: roundTripper, + // 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: roundTripper, - }) + 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