From 87a48e7c9ab783885f5f2ce575056020403778cb Mon Sep 17 00:00:00 2001 From: Thomas de Grenier de Latour Date: Wed, 4 May 2022 00:10:50 +0200 Subject: [PATCH] enable optional use of an HTTP proxy (#237) * added --http.proxy-from-env (default false) Signed-off-by: Thomas de Grenier de Latour \ --- README.md | 9 +++++++++ haproxy_exporter.go | 12 ++++++++---- haproxy_exporter_test.go | 24 ++++++++++++------------ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index cb7a99a..81df31c 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,15 @@ you can disable it using the `--no-haproxy.ssl-verify` flag: haproxy_exporter --no-haproxy.ssl-verify --haproxy.scrape-uri="https://haproxy.example.com/haproxy?stats;csv" ``` +If scraping a remote HAProxy must be done via an HTTP proxy, you can enable reading of the +standard [`$http_proxy` / `$https_proxy` / `$no_proxy` environment variables](https://pkg.go.dev/net/http#ProxyFromEnvironment) by using the +`--http.proxy-from-env` flag (these variables will be ignored otherwise): + +```bash +export HTTP_PROXY="http://proxy:3128" +haproxy_exporter --http.proxy-from-env --haproxy.scrape-uri="http://haproxy.example.com/haproxy?stats;csv" +``` + [basic auth]: https://cbonte.github.io/haproxy-dconv/configuration-1.6.html#4-stats%20auth ### Unix Sockets diff --git a/haproxy_exporter.go b/haproxy_exporter.go index 54b4cbb..e478ce0 100644 --- a/haproxy_exporter.go +++ b/haproxy_exporter.go @@ -252,7 +252,7 @@ type Exporter struct { } // NewExporter returns an initialized Exporter. -func NewExporter(uri string, sslVerify bool, selectedServerMetrics map[int]metricInfo, excludedServerStates string, timeout time.Duration, logger log.Logger) (*Exporter, error) { +func NewExporter(uri string, sslVerify, proxyFromEnv bool, selectedServerMetrics map[int]metricInfo, excludedServerStates string, timeout time.Duration, logger log.Logger) (*Exporter, error) { u, err := url.Parse(uri) if err != nil { return nil, err @@ -262,7 +262,7 @@ func NewExporter(uri string, sslVerify bool, selectedServerMetrics map[int]metri var fetchStat func() (io.ReadCloser, error) switch u.Scheme { case "http", "https", "file": - fetchStat = fetchHTTP(uri, sslVerify, timeout) + fetchStat = fetchHTTP(uri, sslVerify, proxyFromEnv, timeout) case "unix": fetchInfo = fetchUnix(u, showInfoCmd, timeout) fetchStat = fetchUnix(u, showStatCmd, timeout) @@ -331,8 +331,11 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { ch <- e.csvParseFailures } -func fetchHTTP(uri string, sslVerify bool, timeout time.Duration) func() (io.ReadCloser, error) { +func fetchHTTP(uri string, sslVerify, proxyFromEnv bool, timeout time.Duration) func() (io.ReadCloser, error) { tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: !sslVerify}} + if proxyFromEnv { + tr.Proxy = http.ProxyFromEnvironment + } client := http.Client{ Timeout: timeout, Transport: tr, @@ -566,6 +569,7 @@ func main() { haProxyServerExcludeStates = kingpin.Flag("haproxy.server-exclude-states", "Comma-separated list of exported server states to exclude. See https://cbonte.github.io/haproxy-dconv/1.8/management.html#9.1, field 17 statuus").Default(excludedServerStates).String() haProxyTimeout = kingpin.Flag("haproxy.timeout", "Timeout for trying to get stats from HAProxy.").Default("5s").Duration() haProxyPidFile = kingpin.Flag("haproxy.pid-file", pidFileHelpText).Default("").String() + httpProxyFromEnv = kingpin.Flag("http.proxy-from-env", "Flag that enables using HTTP proxy settings from environment variables ($http_proxy, $https_proxy, $no_proxy)").Default("false").Bool() ) promlogConfig := &promlog.Config{} @@ -584,7 +588,7 @@ func main() { level.Info(logger).Log("msg", "Starting haproxy_exporter", "version", version.Info()) level.Info(logger).Log("msg", "Build context", "context", version.BuildContext()) - exporter, err := NewExporter(*haProxyScrapeURI, *haProxySSLVerify, selectedServerMetrics, *haProxyServerExcludeStates, *haProxyTimeout, logger) + exporter, err := NewExporter(*haProxyScrapeURI, *haProxySSLVerify, *httpProxyFromEnv, selectedServerMetrics, *haProxyServerExcludeStates, *haProxyTimeout, logger) if err != nil { level.Error(logger).Log("msg", "Error creating an exporter", "err", err) os.Exit(1) diff --git a/haproxy_exporter_test.go b/haproxy_exporter_test.go index a13303c..f2b15a8 100644 --- a/haproxy_exporter_test.go +++ b/haproxy_exporter_test.go @@ -74,7 +74,7 @@ func TestInvalidConfig(t *testing.T) { h := newHaproxy([]byte("not,enough,fields")) defer h.Close() - e, _ := NewExporter(h.URL, true, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) + e, _ := NewExporter(h.URL, true, false, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) expectMetrics(t, e, "invalid_config.metrics") } @@ -83,7 +83,7 @@ func TestServerWithoutChecks(t *testing.T) { h := newHaproxy([]byte("test,127.0.0.1:8080,0,0,0,0,0,0,0,0,,0,,0,0,0,0,no check,1,1,0,0,,,0,,1,1,1,,0,,2,0,,0,,,,0,0,0,0,0,0,0,,,,0,0,,,,,,,,,,,")) defer h.Close() - e, _ := NewExporter(h.URL, true, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) + e, _ := NewExporter(h.URL, true, false, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) expectMetrics(t, e, "server_without_checks.metrics") } @@ -101,7 +101,7 @@ foo,BACKEND,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,0,0,5007,0,,1,8,1,,0,,2,0,,0,L4O h := newHaproxy([]byte(data)) defer h.Close() - e, _ := NewExporter(h.URL, true, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) + e, _ := NewExporter(h.URL, true, false, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) expectMetrics(t, e, "server_broken_csv.metrics") } @@ -114,7 +114,7 @@ foo,BACKEND,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,0,0,5007,0,,1,8,1,,0,,2, h := newHaproxy([]byte(data)) defer h.Close() - e, _ := NewExporter(h.URL, true, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) + e, _ := NewExporter(h.URL, true, false, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) expectMetrics(t, e, "older_haproxy_versions.metrics") } @@ -123,7 +123,7 @@ func TestConfigChangeDetection(t *testing.T) { h := newHaproxy([]byte("")) defer h.Close() - e, _ := NewExporter(h.URL, true, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) + e, _ := NewExporter(h.URL, true, false, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) ch := make(chan prometheus.Metric) go func() { @@ -150,7 +150,7 @@ func TestDeadline(t *testing.T) { s.Close() }() - e, err := NewExporter(s.URL, true, serverMetrics, excludedServerStates, 1*time.Second, log.NewNopLogger()) + e, err := NewExporter(s.URL, true, false, serverMetrics, excludedServerStates, 1*time.Second, log.NewNopLogger()) if err != nil { t.Fatal(err) } @@ -162,7 +162,7 @@ func TestNotFound(t *testing.T) { s := httptest.NewServer(http.NotFoundHandler()) defer s.Close() - e, err := NewExporter(s.URL, true, serverMetrics, excludedServerStates, 1*time.Second, log.NewNopLogger()) + e, err := NewExporter(s.URL, true, false, serverMetrics, excludedServerStates, 1*time.Second, log.NewNopLogger()) if err != nil { t.Fatal(err) } @@ -221,7 +221,7 @@ func TestUnixDomain(t *testing.T) { } defer srv.Close() - e, err := NewExporter("unix:"+testSocket, true, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) + e, err := NewExporter("unix:"+testSocket, true, false, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) if err != nil { t.Fatal(err) } @@ -238,7 +238,7 @@ func TestUnixDomainNotFound(t *testing.T) { if err := os.Remove(testSocket); err != nil && !os.IsNotExist(err) { t.Fatal(err) } - e, _ := NewExporter("unix:"+testSocket, true, serverMetrics, excludedServerStates, 1*time.Second, log.NewNopLogger()) + e, _ := NewExporter("unix:"+testSocket, true, false, serverMetrics, excludedServerStates, 1*time.Second, log.NewNopLogger()) expectMetrics(t, e, "unix_domain_not_found.metrics") } @@ -271,13 +271,13 @@ func TestUnixDomainDeadline(t *testing.T) { } }() - e, _ := NewExporter("unix:"+testSocket, true, serverMetrics, excludedServerStates, 1*time.Second, log.NewNopLogger()) + e, _ := NewExporter("unix:"+testSocket, true, false, serverMetrics, excludedServerStates, 1*time.Second, log.NewNopLogger()) expectMetrics(t, e, "unix_domain_deadline.metrics") } func TestInvalidScheme(t *testing.T) { - e, err := NewExporter("gopher://gopher.quux.org", true, serverMetrics, excludedServerStates, 1*time.Second, log.NewNopLogger()) + e, err := NewExporter("gopher://gopher.quux.org", true, false, serverMetrics, excludedServerStates, 1*time.Second, log.NewNopLogger()) if expect, got := (*Exporter)(nil), e; expect != got { t.Errorf("expected %v, got %v", expect, got) } @@ -352,7 +352,7 @@ func BenchmarkExtract(b *testing.B) { h := newHaproxy(config) defer h.Close() - e, _ := NewExporter(h.URL, true, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) + e, _ := NewExporter(h.URL, true, false, serverMetrics, excludedServerStates, 5*time.Second, log.NewNopLogger()) var before, after runtime.MemStats runtime.GC()