Skip to content

add header compatibility with prometheus #82

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions manifests/load-watcher-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
Expand Down
49 changes: 41 additions & 8 deletions pkg/watcher/internal/metricsprovider/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
37 changes: 37 additions & 0 deletions pkg/watcher/metricsprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -75,4 +111,5 @@ type MetricsProviderOpts struct {
AuthToken string
ApplicationKey string
InsecureSkipVerify bool
Headers map[string]string
}