Add --prometheus-verb to support POST requests to prometheus servers

This commit is contained in:
Kazuhiro Suzuki 2022-06-28 01:51:05 +09:00
parent 9008b12a01
commit a8742cff28
2 changed files with 37 additions and 12 deletions

View file

@ -74,6 +74,8 @@ type PrometheusAdapter struct {
PrometheusTokenFile string PrometheusTokenFile string
// PrometheusHeaders is a k=v list of headers to set on requests to PrometheusURL // PrometheusHeaders is a k=v list of headers to set on requests to PrometheusURL
PrometheusHeaders []string 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 points to the file containing the metrics discovery configuration.
AdapterConfigFile string AdapterConfigFile string
// MetricsRelistInterval is the interval at which to relist the set of available metrics // 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) 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 var httpClient *http.Client
if cmd.PrometheusCAFile != "" { if cmd.PrometheusCAFile != "" {
@ -117,7 +123,7 @@ func (cmd *PrometheusAdapter) makePromClient() (prom.Client, error) {
} }
genericPromClient := prom.NewGenericAPIClient(httpClient, baseURL, parseHeaderArgs(cmd.PrometheusHeaders)) genericPromClient := prom.NewGenericAPIClient(httpClient, baseURL, parseHeaderArgs(cmd.PrometheusHeaders))
instrumentedGenericPromClient := mprom.InstrumentGenericAPIClient(genericPromClient, baseURL.String()) instrumentedGenericPromClient := mprom.InstrumentGenericAPIClient(genericPromClient, baseURL.String())
return prom.NewClientForAPI(instrumentedGenericPromClient), nil return prom.NewClientForAPI(instrumentedGenericPromClient, cmd.PrometheusVerb), nil
} }
func (cmd *PrometheusAdapter) addFlags() { func (cmd *PrometheusAdapter) addFlags() {
@ -137,6 +143,8 @@ func (cmd *PrometheusAdapter) addFlags() {
"Optional file containing the bearer token to use when connecting with Prometheus") "Optional file containing the bearer token to use when connecting with Prometheus")
cmd.Flags().StringArrayVar(&cmd.PrometheusHeaders, "prometheus-header", cmd.PrometheusHeaders, cmd.Flags().StringArrayVar(&cmd.PrometheusHeaders, "prometheus-header", cmd.PrometheusHeaders,
"Optional header to set on requests to prometheus-url. Can be repeated") "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, cmd.Flags().StringVar(&cmd.AdapterConfigFile, "config", cmd.AdapterConfigFile,
"Configuration file containing details of how to transform between Prometheus metrics "+ "Configuration file containing details of how to transform between Prometheus metrics "+
"and custom metrics API resources") "and custom metrics API resources")
@ -274,6 +282,7 @@ func main() {
// set up flags // set up flags
cmd := &PrometheusAdapter{ cmd := &PrometheusAdapter{
PrometheusURL: "https://localhost", PrometheusURL: "https://localhost",
PrometheusVerb: http.MethodGet,
MetricsRelistInterval: 10 * time.Minute, MetricsRelistInterval: 10 * time.Minute,
} }
cmd.Name = "prometheus-metrics-adapter" cmd.Name = "prometheus-metrics-adapter"

View file

@ -25,6 +25,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"strings"
"time" "time"
"github.com/prometheus/common/model" "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) { func (c *httpAPIClient) Do(ctx context.Context, verb, endpoint string, query url.Values) (APIResponse, error) {
u := *c.baseURL u := *c.baseURL
u.Path = path.Join(c.baseURL.Path, endpoint) u.Path = path.Join(c.baseURL.Path, endpoint)
var reqBody io.Reader
if verb == http.MethodGet {
u.RawQuery = query.Encode() u.RawQuery = query.Encode()
req, err := http.NewRequest(verb, u.String(), nil) } else if verb == http.MethodPost {
reqBody = strings.NewReader(query.Encode())
}
req, err := http.NewRequest(verb, u.String(), reqBody)
if err != nil { if err != nil {
return APIResponse{}, fmt.Errorf("error constructing HTTP request to Prometheus: %v", err) return APIResponse{}, fmt.Errorf("error constructing HTTP request to Prometheus: %v", err)
} }
req.WithContext(ctx) 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) resp, err := c.client.Do(req)
defer func() { defer func() {
@ -132,19 +146,21 @@ const (
// queryClient is a Client that connects to the Prometheus HTTP API. // queryClient is a Client that connects to the Prometheus HTTP API.
type queryClient struct { type queryClient struct {
api GenericAPIClient api GenericAPIClient
verb string
} }
// NewClientForAPI creates a Client for the given generic Prometheus API client. // 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{ 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). // 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) 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) { 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)) 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 { if err != nil {
return nil, err 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()) 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 { if err != nil {
return QueryResult{}, err 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()) 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 { if err != nil {
return QueryResult{}, err return QueryResult{}, err
} }