From a8742cff285b5c7a5daef58a60ea7038d45eaf52 Mon Sep 17 00:00:00 2001 From: Kazuhiro Suzuki Date: Tue, 28 Jun 2022 01:51:05 +0900 Subject: [PATCH] Add --prometheus-verb to support POST requests to prometheus servers --- cmd/adapter/adapter.go | 11 ++++++++++- pkg/client/api.go | 38 +++++++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/cmd/adapter/adapter.go b/cmd/adapter/adapter.go index 0169f40a..467660b5 100644 --- a/cmd/adapter/adapter.go +++ b/cmd/adapter/adapter.go @@ -74,6 +74,8 @@ type PrometheusAdapter struct { PrometheusTokenFile string // PrometheusHeaders is a k=v list of headers to set on requests to PrometheusURL PrometheusHeaders []string + // PrometheusVerb is a verb to set on requests to PrometheusURL + PrometheusVerb string // AdapterConfigFile points to the file containing the metrics discovery configuration. AdapterConfigFile string // MetricsRelistInterval is the interval at which to relist the set of available metrics @@ -90,6 +92,10 @@ func (cmd *PrometheusAdapter) makePromClient() (prom.Client, error) { return nil, fmt.Errorf("invalid Prometheus URL %q: %v", baseURL, err) } + if cmd.PrometheusVerb != http.MethodGet && cmd.PrometheusVerb != http.MethodPost { + return nil, fmt.Errorf("unsupported Prometheus Http Verb %q", cmd.PrometheusVerb) + } + var httpClient *http.Client if cmd.PrometheusCAFile != "" { @@ -117,7 +123,7 @@ func (cmd *PrometheusAdapter) makePromClient() (prom.Client, error) { } genericPromClient := prom.NewGenericAPIClient(httpClient, baseURL, parseHeaderArgs(cmd.PrometheusHeaders)) instrumentedGenericPromClient := mprom.InstrumentGenericAPIClient(genericPromClient, baseURL.String()) - return prom.NewClientForAPI(instrumentedGenericPromClient), nil + return prom.NewClientForAPI(instrumentedGenericPromClient, cmd.PrometheusVerb), nil } func (cmd *PrometheusAdapter) addFlags() { @@ -137,6 +143,8 @@ func (cmd *PrometheusAdapter) addFlags() { "Optional file containing the bearer token to use when connecting with Prometheus") cmd.Flags().StringArrayVar(&cmd.PrometheusHeaders, "prometheus-header", cmd.PrometheusHeaders, "Optional header to set on requests to prometheus-url. Can be repeated") + cmd.Flags().StringVar(&cmd.PrometheusVerb, "prometheus-verb", cmd.PrometheusVerb, + "HTTP Verb to set on requests to Prometheus.") cmd.Flags().StringVar(&cmd.AdapterConfigFile, "config", cmd.AdapterConfigFile, "Configuration file containing details of how to transform between Prometheus metrics "+ "and custom metrics API resources") @@ -274,6 +282,7 @@ func main() { // set up flags cmd := &PrometheusAdapter{ PrometheusURL: "https://localhost", + PrometheusVerb: http.MethodGet, MetricsRelistInterval: 10 * time.Minute, } cmd.Name = "prometheus-metrics-adapter" diff --git a/pkg/client/api.go b/pkg/client/api.go index 8541f463..62bf6372 100644 --- a/pkg/client/api.go +++ b/pkg/client/api.go @@ -25,6 +25,7 @@ import ( "net/http" "net/url" "path" + "strings" "time" "github.com/prometheus/common/model" @@ -53,13 +54,26 @@ type httpAPIClient struct { func (c *httpAPIClient) Do(ctx context.Context, verb, endpoint string, query url.Values) (APIResponse, error) { u := *c.baseURL u.Path = path.Join(c.baseURL.Path, endpoint) - u.RawQuery = query.Encode() - req, err := http.NewRequest(verb, u.String(), nil) + var reqBody io.Reader + if verb == http.MethodGet { + u.RawQuery = query.Encode() + } else if verb == http.MethodPost { + reqBody = strings.NewReader(query.Encode()) + } + + req, err := http.NewRequest(verb, u.String(), reqBody) if err != nil { return APIResponse{}, fmt.Errorf("error constructing HTTP request to Prometheus: %v", err) } req.WithContext(ctx) - req.Header = c.headers + for key, values := range c.headers { + for _, value := range values { + req.Header.Add(key, value) + } + } + if verb == http.MethodPost { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } resp, err := c.client.Do(req) defer func() { @@ -131,20 +145,22 @@ const ( // queryClient is a Client that connects to the Prometheus HTTP API. type queryClient struct { - api GenericAPIClient + api GenericAPIClient + verb string } // NewClientForAPI creates a Client for the given generic Prometheus API client. -func NewClientForAPI(client GenericAPIClient) Client { +func NewClientForAPI(client GenericAPIClient, verb string) Client { return &queryClient{ - api: client, + api: client, + verb: verb, } } // NewClient creates a Client for the given HTTP client and base URL (the location of the Prometheus server). -func NewClient(client *http.Client, baseURL *url.URL, headers http.Header) Client { +func NewClient(client *http.Client, baseURL *url.URL, headers http.Header, verb string) Client { genericClient := NewGenericAPIClient(client, baseURL, headers) - return NewClientForAPI(genericClient) + return NewClientForAPI(genericClient, verb) } func (h *queryClient) Series(ctx context.Context, interval model.Interval, selectors ...Selector) ([]Series, error) { @@ -160,7 +176,7 @@ func (h *queryClient) Series(ctx context.Context, interval model.Interval, selec vals.Add("match[]", string(selector)) } - res, err := h.api.Do(ctx, "GET", seriesURL, vals) + res, err := h.api.Do(ctx, h.verb, seriesURL, vals) if err != nil { return nil, err } @@ -180,7 +196,7 @@ func (h *queryClient) Query(ctx context.Context, t model.Time, query Selector) ( vals.Set("timeout", model.Duration(timeout).String()) } - res, err := h.api.Do(ctx, "GET", queryURL, vals) + res, err := h.api.Do(ctx, h.verb, queryURL, vals) if err != nil { return QueryResult{}, err } @@ -207,7 +223,7 @@ func (h *queryClient) QueryRange(ctx context.Context, r Range, query Selector) ( vals.Set("timeout", model.Duration(timeout).String()) } - res, err := h.api.Do(ctx, "GET", queryRangeURL, vals) + res, err := h.api.Do(ctx, h.verb, queryRangeURL, vals) if err != nil { return QueryResult{}, err }