From f43e7401b28e494986b3f8abfc325834307ac2a2 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 30 Mar 2020 09:46:20 -0400 Subject: [PATCH] Set accept header for prometheus client scraping (#17291) * Set accept header for prometheus to match the same as prometheus upstream. * Fix go.mod. * Add changelog entry. (cherry picked from commit 268511d41b004ad4bdc124cbddf1dc2f1b68964a) --- CHANGELOG.next.asciidoc | 1 + metricbeat/helper/http.go | 27 ++++++++++++++------- metricbeat/helper/http_test.go | 28 ++++++++++++++++++++++ metricbeat/helper/prometheus/prometheus.go | 5 +++- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 1965149d353..243e90e6177 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -175,6 +175,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Check if CCR feature is available on Elasticsearch cluster before attempting to call CCR APIs from `elasticsearch/ccr` metricset. {issue}16511[16511] {pull}17073[17073] - Use max in k8s overview dashboard aggregations. {pull}17015[17015] - Fix Disk Used and Disk Usage visualizations in the Metricbeat System dashboards. {issue}12435[12435] {pull}17272[17272] +- Fix missing Accept header for Prometheus and OpenMetrics module. {issue}16870[16870] {pull}17291[17291] *Packetbeat* diff --git a/metricbeat/helper/http.go b/metricbeat/helper/http.go index 7388e530193..14481182589 100644 --- a/metricbeat/helper/http.go +++ b/metricbeat/helper/http.go @@ -39,7 +39,7 @@ import ( type HTTP struct { hostData mb.HostData client *http.Client // HTTP client that is reused across requests. - headers map[string]string + headers http.Header name string uri string method string @@ -58,16 +58,20 @@ func NewHTTP(base mb.BaseMetricSet) (*HTTP, error) { // newHTTPWithConfig creates a new http helper from some configuration func newHTTPFromConfig(config Config, name string, hostData mb.HostData) (*HTTP, error) { + headers := http.Header{} if config.Headers == nil { config.Headers = map[string]string{} } + for k, v := range config.Headers { + headers.Set(k, v) + } if config.BearerTokenFile != "" { header, err := getAuthHeaderFromToken(config.BearerTokenFile) if err != nil { return nil, err } - config.Headers["Authorization"] = header + headers.Set("Authorization", header) } tlsConfig, err := tlscommon.LoadTLSConfig(config.TLS) @@ -103,7 +107,7 @@ func newHTTPFromConfig(config Config, name string, hostData mb.HostData) (*HTTP, }, Timeout: config.Timeout, }, - headers: config.Headers, + headers: headers, method: "GET", uri: hostData.SanitizedURI, body: nil, @@ -124,14 +128,11 @@ func (h *HTTP) FetchResponse() (*http.Response, error) { if err != nil { return nil, errors.Wrap(err, "failed to create HTTP request") } + req.Header = h.headers if h.hostData.User != "" || h.hostData.Password != "" { req.SetBasicAuth(h.hostData.User, h.hostData.Password) } - for k, v := range h.headers { - req.Header.Set(k, v) - } - resp, err := h.client.Do(req) if err != nil { return nil, fmt.Errorf("error making http request: %v", err) @@ -142,7 +143,17 @@ func (h *HTTP) FetchResponse() (*http.Response, error) { // SetHeader sets HTTP headers to use in requests func (h *HTTP) SetHeader(key, value string) { - h.headers[key] = value + h.headers.Set(key, value) +} + +// SetHeaderDefault sets HTTP header as default +// +// Note: This will only set the header when the header is not already set. +func (h *HTTP) SetHeaderDefault(key, value string) { + c := h.headers.Get(key) + if c == "" { + h.headers.Set(key, value) + } } // SetMethod sets HTTP method to use in requests diff --git a/metricbeat/helper/http_test.go b/metricbeat/helper/http_test.go index efbecffd93f..f083c3eb21f 100644 --- a/metricbeat/helper/http_test.go +++ b/metricbeat/helper/http_test.go @@ -162,6 +162,34 @@ func TestAuthentication(t *testing.T) { assert.Equal(t, http.StatusOK, response.StatusCode, "response status code") } +func TestSetHeader(t *testing.T) { + cfg := defaultConfig() + cfg.Headers = map[string]string{ + "Override": "default", + } + + h, err := newHTTPFromConfig(cfg, "test", mb.HostData{}) + require.NoError(t, err) + + h.SetHeader("Override", "overridden") + v := h.headers.Get("override") + assert.Equal(t, "overridden", v) +} + +func TestSetHeaderDefault(t *testing.T) { + cfg := defaultConfig() + cfg.Headers = map[string]string{ + "Override": "default", + } + + h, err := newHTTPFromConfig(cfg, "test", mb.HostData{}) + require.NoError(t, err) + + h.SetHeaderDefault("Override", "overridden") + v := h.headers.Get("override") + assert.Equal(t, "default", v) +} + func TestOverUnixSocket(t *testing.T) { if runtime.GOOS == "windows" { t.Skipf("unix domain socket aren't supported under Windows") diff --git a/metricbeat/helper/prometheus/prometheus.go b/metricbeat/helper/prometheus/prometheus.go index 6da2b91c4f3..2859178d98f 100644 --- a/metricbeat/helper/prometheus/prometheus.go +++ b/metricbeat/helper/prometheus/prometheus.go @@ -33,6 +33,8 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" ) +const acceptHeader = `application/openmetrics-text; version=0.0.1,text/plain;version=0.0.4;q=0.5,*/*;q=0.1` + // Prometheus helper retrieves prometheus formatted metrics type Prometheus interface { // GetFamilies requests metric families from prometheus endpoint and returns them @@ -55,10 +57,11 @@ type httpfetcher interface { // NewPrometheusClient creates new prometheus helper func NewPrometheusClient(base mb.BaseMetricSet) (Prometheus, error) { http, err := helper.NewHTTP(base) - if err != nil { return nil, err } + + http.SetHeaderDefault("Accept", acceptHeader) return &prometheus{http, base.Logger()}, nil }