New Upstream Release - prometheus-exporter-exporter

Ready changes

Summary

Merged new upstream version: 0.5.0 (was: 0.4.5).

Resulting package

Built on 2023-08-24T06:31 (took 8m40s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases prometheus-exporter-exporter

Diff

diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..96773af
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,67 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [ master ]
+  schedule:
+    - cron: '16 11 * * 1'
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: [ 'go' ]
+        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+        # Learn more:
+        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v2
+
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v1
+      with:
+        languages: ${{ matrix.language }}
+        # If you wish to specify custom queries, you can do so here or in a config file.
+        # By default, queries listed here will override any specified in a config file.
+        # Prefix the list here with "+" to use these queries and those in the config file.
+        # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+    # If this step fails, then you should remove it and run the build manually (see below)
+    - name: Autobuild
+      uses: github/codeql-action/autobuild@v1
+
+    # ℹ️ Command-line programs to run using the OS shell.
+    # 📚 https://git.io/JvXDl
+
+    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+    #    and modify them (or add more) to build your code if your project
+    #    uses a compiled language
+
+    #- run: |
+    #   make bootstrap
+    #   make release
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v1
diff --git a/AUTHORS b/AUTHORS
index f3bd3c5..0334bf6 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,8 +1,20 @@
+Ben Ritcey <ben+github@ritcey.com>
 Brian Brazil <brian.brazil@robustperception.io>
 Brian Candler <b.candler@pobox.com>
+Daniel Thamdrup <dallemon@protonmail.com>
 Dan Poltawski <dan.poltawski@tnp.net.uk>
+Jonathan Neugebauer <jonathan.neugebauer@wi.uni-muenster.de>
+Karbas <karbas@cafebazaar.ir>
 Kirill Shestakov <freyr.sh@gmail.com>
 Laurie Clark-Michalek <laurie@qubit.com>
+LDaneliukas <linas@daneliukas.eu>
+Linas Daneliukas <linas@daneliukas.eu>
 Linas <linas@daneliukas.eu>
+Nathan Delhaye <the@red.cat>
+Ramiro Rikkert <rrikkert@ripe.net>
+Richard Steinbrück <richard.steinbrueck@googlemail.com>
+Romain Dessort <romain.dessort@makina-corpus.com>
+Steve Boyle <sysboy@gmail.com>
 Thomas Frössman <thomasf@jossystem.se>
+Thomas Steen Rasmussen <thomas@gibfest.dk>
 Tristan Colgate-McFarlane <tcolgate@gmail.com>
diff --git a/Dockerfile b/Dockerfile
index 757cf90..c7e2e1f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,14 @@
-FROM golang:1.14.2-alpine AS build
-RUN mkdir /src
-WORKDIR /src
-COPY *.go go.mod go.sum /src/
-RUN apk add git
-RUN go build .
+#syntax=docker/dockerfile:1.5.1
 
-FROM alpine
-COPY --from=build /src/exporter_exporter /usr/bin/
-ENTRYPOINT ["/usr/bin/exporter_exporter"]
+FROM golang:1.20-alpine AS build
+WORKDIR /go/src/exporter_exporter
+COPY . .
+ENV CGO_ENABLED=0
+ENV GOOS=linux
+
+RUN go mod download ;\
+    go build -trimpath
+
+FROM gcr.io/distroless/static:latest AS runtime
+COPY --from=build /go/src/exporter_exporter/exporter_exporter /exporter_exporter
+ENTRYPOINT [ "/exporter_exporter" ]
diff --git a/Makefile b/Makefile
index 57a90ab..688fad9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 GITHUB_ORG  = QubitProducts
 GITHUB_REPO = exporter_exporter
-VERSION      = 0.4.0
+VERSION      = 0.5.0
 
 DOCKER_REGISTRY     = qubitproducts
 DOCKER_NAME         = exporter_exporter
@@ -75,6 +75,7 @@ AUTHORS:
 $(PACKAGE_FILE): prepare-package
 	cd dist && \
 	  fpm \
+		-f \
 	  -t $(PACKAGE_TARGET) \
 	  -m $(PACKAGE_MAINTAINER) \
 	  -n $(PACKAGE_NAME) \
@@ -89,7 +90,7 @@ $(PACKAGE_FILE): prepare-package
 	  .
 
 .PHONY: build-docker release-docker
-build-docker: 
+build-docker:
 	docker build -t $(DOCKER_IMAGE) .
 
 release-docker: build-docker
@@ -104,12 +105,19 @@ LDFLAGS = -X main.Version=$(VERSION) \
 					-X main.BuildDate=$(BUILDDATE)
 
 build/$(BINNAME)-$(VERSION).windows-amd64/$(BINNAME).exe: $(SRCS)
-	GOOS=$* GOARCH=amd64 $(GO) build \
+	@GOOS=windows GOARCH=amd64 $(GO) build \
 	 -ldflags "$(LDFLAGS)" \
 	 -o $@ \
 	 .
-build/$(BINNAME)-$(VERSION).windows-amd64.zip: build/expoter_exporter-$(VERSION).windows-amd64/$(BINNAME).exe
-	zip $@ $<
+
+build/$(BINNAME)-$(VERSION).windows-amd64.zip: build/exporter_exporter-$(VERSION).windows-amd64/$(BINNAME).exe
+	zip -j $@ $<
+
+build/$(BINNAME)-$(VERSION).%-arm64/$(BINNAME): $(SRCS)
+	GOOS=$* GOARCH=arm64 $(GO) build \
+ 	 -ldflags "$(LDFLAGS)" \
+ 	 -o $@ \
+	 .
 
 build/$(BINNAME)-$(VERSION).%-amd64/$(BINNAME): $(SRCS)
 	GOOS=$* GOARCH=amd64 $(GO) build \
@@ -117,11 +125,14 @@ build/$(BINNAME)-$(VERSION).%-amd64/$(BINNAME): $(SRCS)
 	 -o $@ \
 	 .
 
+build/$(BINNAME)-$(VERSION).%-arm64.tar.gz: build/$(BINNAME)-$(VERSION).%-arm64/$(BINNAME)
+	cd build && \
+		tar cfzv $(BINNAME)-$(VERSION).$*-arm64.tar.gz $(BINNAME)-$(VERSION).$*-arm64
+
 build/$(BINNAME)-$(VERSION).%-amd64.tar.gz: build/$(BINNAME)-$(VERSION).%-amd64/$(BINNAME)
 	cd build && \
 		tar cfzv $(BINNAME)-$(VERSION).$*-amd64.tar.gz $(BINNAME)-$(VERSION).$*-amd64
 
-
 package: $(PACKAGE_FILE)
 
 package-release: $(PACKAGE_FILE)
@@ -140,15 +151,22 @@ release-windows: build/exporter_exporter-$(VERSION).windows-amd64.zip
 		--name exporter_exporter-$(VERSION).windows-amd64.zip \
 		-f ./build/exporter_exporter-$(VERSION).windows-amd64.zip
 
-release-%: build/exporter_exporter-$(VERSION).%-amd64.tar.gz
+.PRECIOUS: \
+	build/exporter_exporter-$(VERSION).darwin-amd64.tar.gz \
+	build/exporter_exporter-$(VERSION).linux-arm64.tar.gz \
+	build/exporter_exporter-$(VERSION).linux-amd64.tar.gz \
+	build/exporter_exporter-$(VERSION).windows-amd64.zip
+
+
+release-%: build/exporter_exporter-$(VERSION).%.tar.gz
 	go run github.com/aktau/github-release upload \
 		-u $(GITHUB_ORG) \
 		-r $(GITHUB_REPO) \
 		--tag v$(VERSION) \
-		--name exporter_exporter-$(VERSION).$*-amd64.tar.gz \
-		-f ./build/exporter_exporter-$(VERSION).$*-amd64.tar.gz
+		--name exporter_exporter-$(VERSION).$*.tar.gz \
+		-f ./build/exporter_exporter-$(VERSION).$*.tar.gz
 
-release: 
+release:
 	git tag v$(VERSION)
 	git push origin v$(VERSION)
 	go run github.com/aktau/github-release release \
@@ -156,4 +174,4 @@ release:
 		-r $(GITHUB_REPO) \
 		--tag v$(VERSION) \
 		--name v$(VERSION)
-	make release-darwin release-linux release-windows package-release
+	make release-darwin-amd64 release-linux-amd64 release-linux-arm64 release-windows package-release release-docker
diff --git a/README.md b/README.md
index c37b305..e09ecff 100644
--- a/README.md
+++ b/README.md
@@ -21,9 +21,11 @@ The exporter has three endpoints.
   - Returns JSON if the header "Accept: application/json" is passed
 
 - /proxy: which takes the following parameters:
-  - module: the name of the module from the configuration to execute.
-  - args (optional): arguments to pass to the module.
-  - params (optional): named parameter to pass to the module (either as CLI args, or http parameters).
+  - *module*: the name of the module from the configuration to execute. (a default
+    module can be selected using the defaultModule config option)
+  - *args*: (only for exec modules): additional arguments to the backend command.
+  - all other query string parameters are passed on to any http backend module.
+    (excluding the first *module* parameter value).
 
 - /metrics: this exposes the metrics for the collector itself.
 
@@ -56,6 +58,7 @@ and can only be changed by uninstalling/installing it again (or modifying the Wi
 In expexp.yaml list each exporter listening on localhost with its known port.
 
 ```
+defaultModule: node # called if "module" param is not supplied
 modules:
   node:
     method: http
@@ -70,17 +73,23 @@ modules:
           foo: bar
 
   cadvisor:
-    verify: false
     method: http
     http:
+       verify: false
        port: 4194
-  
+
   netdata:
     method: http
     http:
        port: 19999
        path: '/api/v1/allmetrics?format=prometheus'
 
+  blackbox:
+    method: http
+    http:
+       port: 9115
+       path: '/probe'
+
   somescript:
     method: exec
     timeout: 1s
@@ -92,6 +101,11 @@ modules:
       env:
         THING: "1"
         THING2: "2"
+
+  somefile:
+    method: file
+    file:
+      path: /tmp/myfile.prometheus.txt
 ```
 
 In your prometheus configuration
@@ -126,8 +140,37 @@ scrape_configs:
         - somescript
     static_configs:
       - targets: ['host:9999']
+  - job_name: 'blackbox'
+    metrics_path: /proxy
+    params:
+      module:
+        - blackbox
+        - icmp_example
+    static_configs:
+      - targets:
+        - 8.8.8.8
+        - 8.8.4.4
+    relabel_configs:
+      - source_labels: [__address__]
+        target_label: __param_target
+      - source_labels: [__param_target]
+        target_label: instance
+      - target_label: __address__
+        replacement: host:9999
 ```
 
+### Blackbox Exporter
+
+The blackbox exporter also uses the "module" query string parameter. To query it via
+exporter_exporter we rely on the stripping of the initial "module" parameter. For example
+ 
+```
+curl http://localhost:9999/proxy\?module\=blackbox\&module\=icmp_example\&target\=8.8.8.8
+```
+
+Will query the icmp_example module in your blackbox configuration.
+
+
 ## Directory-based configuration
 
 You can also specify `-config.dirs` to break the configuration into separate
@@ -135,6 +178,10 @@ files.  The module name is taken from the name of the file (minus the
 yml/yaml extension), and the configuration for that module goes in at the
 top level.
 
+Note that if you want to use *only* this configuration method and not the file-based 
+configuration (`-config.file` option), you must provide an empty string for the file
+option : `./exporter_exporter -config.file "" -config.dirs "/etc/exporter_exporter/"`
+
 ```
 ==> expexp.yaml <==
 modules: {}
@@ -165,8 +212,8 @@ prometheus server and one key/cert shared between all the remote nodes.
 Firstly, create the keys and certs:
 
 ```
-openssl req -x509 -newkey rsa:1024 -keyout prom_node_key.pem -out prom_node_cert.pem -days 29220 -nodes -subj /commonName=prom_node/
-openssl req -x509 -newkey rsa:1024 -keyout prometheus_key.pem -out prometheus_cert.pem -days 29220 -nodes -subj /commonName=prometheus/
+openssl req -x509 -newkey rsa:1024 -keyout prom_node_key.pem -out prom_node_cert.pem -days 29220 -nodes -subj /commonName=prom_node/ -addext "subjectAltName=DNS:prom_node"
+openssl req -x509 -newkey rsa:1024 -keyout prometheus_key.pem -out prometheus_cert.pem -days 29220 -nodes -subj /commonName=prometheus/ -addext "subjectAltName=DNS:prometheus"
 ```
 
 Create an `/etc/prometheus/ssl/` directory on the prometheus server and all
@@ -224,7 +271,7 @@ When this is working, configure your prometheus server to use https. Example:
       - source_labels: [__address__]
         target_label: instance
       - source_labels: [__address__]
-        regex: '[^:]+'
+        regex: '([^:]+)'
         target_label: __address__
         replacement: '${1}:9998'
 ```
diff --git a/config.go b/config.go
index 06eddad..19b5160 100644
--- a/config.go
+++ b/config.go
@@ -10,7 +10,6 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-
 package main
 
 import (
@@ -19,19 +18,20 @@ import (
 	"crypto/x509"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httputil"
+	"os"
 	"time"
 
-	yaml "gopkg.in/yaml.v2"
+	yaml "gopkg.in/yaml.v3"
 )
 
 type config struct {
 	Global struct {
 	}
-	Modules map[string]*moduleConfig
-	XXX     map[string]interface{} `yaml:",inline"`
+	Modules       map[string]*moduleConfig
+	DefaultModule string                 `yaml:"defaultModule"`
+	XXX           map[string]interface{} `yaml:",inline"`
 
 	bearerToken   string
 	proxyPath     string
@@ -45,6 +45,7 @@ type moduleConfig struct {
 
 	Exec execConfig `yaml:"exec"`
 	HTTP httpConfig `yaml:"http"`
+	File fileConfig `yaml:"file"`
 
 	name string
 }
@@ -60,6 +61,8 @@ type httpConfig struct {
 	Scheme                string                 `yaml:"scheme"`                   // http
 	Address               string                 `yaml:"address"`                  // 127.0.0.1
 	Headers               map[string]string      `yaml:"headers"`                  // no default
+	BasicAuthUsername     string                 `yaml:"basic_auth_username"`      // no default
+	BasicAuthPassword     string                 `yaml:"basic_auth_password"`      // no default
 	XXX                   map[string]interface{} `yaml:",inline"`
 
 	tlsConfig              *tls.Config
@@ -76,6 +79,11 @@ type execConfig struct {
 	mcfg *moduleConfig
 }
 
+type fileConfig struct {
+	Path string `yaml:"path"`
+	mcfg *moduleConfig
+}
+
 func readConfig(r io.Reader) (*config, error) {
 	buf := bytes.Buffer{}
 	io.Copy(&buf, r)
@@ -84,12 +92,12 @@ func readConfig(r io.Reader) (*config, error) {
 	err := yaml.Unmarshal(buf.Bytes(), &cfg)
 
 	if len(cfg.XXX) != 0 {
-		return nil, fmt.Errorf("Unknown configuration fields: %v", cfg.XXX)
+		return nil, fmt.Errorf("unknown configuration fields: %v", cfg.XXX)
 	}
 
 	for s := range cfg.Modules {
-		if err := checkModuleConfig(s, cfg.Modules[s]); err != nil {
-			return nil, fmt.Errorf("bad config for module %s, %w", s, err)
+		if merr := checkModuleConfig(s, cfg.Modules[s]); merr != nil {
+			return nil, fmt.Errorf("bad config for module %s, %w", s, merr)
 		}
 	}
 
@@ -123,7 +131,7 @@ func checkModuleConfig(name string, cfg *moduleConfig) error {
 	switch cfg.Method {
 	case "http":
 		if len(cfg.HTTP.XXX) != 0 {
-			return fmt.Errorf("Unknown http module configuration fields: %v", cfg.HTTP.XXX)
+			return fmt.Errorf("unknown http module configuration fields: %v", cfg.HTTP.XXX)
 		}
 
 		if cfg.HTTP.Port == 0 {
@@ -164,10 +172,14 @@ func checkModuleConfig(name string, cfg *moduleConfig) error {
 		}
 	case "exec":
 		if len(cfg.Exec.XXX) != 0 {
-			return fmt.Errorf("Unknown exec module configuration fields: %v", cfg.Exec.XXX)
+			return fmt.Errorf("unknown exec module configuration fields: %v", cfg.Exec.XXX)
+		}
+	case "file":
+		if cfg.File.Path == "" {
+			return fmt.Errorf("path argument for file module is mandatory")
 		}
 	default:
-		return fmt.Errorf("Unknown module method: %v", cfg.Method)
+		return fmt.Errorf("unknown module method: %v", cfg.Method)
 	}
 
 	return nil
@@ -179,7 +191,7 @@ func (c httpConfig) getTLSConfig() (*tls.Config, error) {
 		MinVersion:         tls.VersionTLS12,
 	}
 	if c.TLSCACertFile != nil {
-		caCert, err := ioutil.ReadFile(*c.TLSCACertFile)
+		caCert, err := os.ReadFile(*c.TLSCACertFile)
 		if err != nil {
 			return nil, fmt.Errorf("could not read ca from %v, %w", *c.TLSCACertFile, err)
 		}
diff --git a/debian/changelog b/debian/changelog
index 1cb0317..5b62d48 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+prometheus-exporter-exporter (0.5.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 24 Aug 2023 06:23:45 -0000
+
 prometheus-exporter-exporter (0.4.0-1) unstable; urgency=medium
 
   * Initial release (Closes: #968029).
diff --git a/debian/patches/000-change-default-config-file-path.patch b/debian/patches/000-change-default-config-file-path.patch
index af8e66e..c3955f3 100644
--- a/debian/patches/000-change-default-config-file-path.patch
+++ b/debian/patches/000-change-default-config-file-path.patch
@@ -2,9 +2,11 @@ Description: Set default config path to /etc/prometheus/exporter-exporter.yml
 Author: Johan Fleury <jfleury@arcaik.net>
 Forwarded: not-needed
 
---- prometheus-exporter-exporter.orig/main.go
-+++ prometheus-exporter-exporter/main.go
-@@ -42,7 +42,7 @@ import (
+Index: prometheus-exporter-exporter.git/main.go
+===================================================================
+--- prometheus-exporter-exporter.git.orig/main.go
++++ prometheus-exporter-exporter.git/main.go
+@@ -41,7 +41,7 @@ import (
  var (
  	printVersion = flag.Bool("version", false, "Print the version and exit")
  
diff --git a/expexp.yaml b/expexp.yaml
index 18783d8..d035f26 100644
--- a/expexp.yaml
+++ b/expexp.yaml
@@ -1,3 +1,4 @@
+defaultModule: node
 modules:
   node:
     method: http
@@ -26,5 +27,5 @@ modules:
     method: http
     http:
        port: 9115
-       path: probe
+       path: /probe
 
diff --git a/file.go b/file.go
new file mode 100644
index 0000000..66da244
--- /dev/null
+++ b/file.go
@@ -0,0 +1,148 @@
+// Copyright 2016 Qubit Ltd.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"time"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
+	dto "github.com/prometheus/client_model/go"
+	"github.com/prometheus/common/expfmt"
+	log "github.com/sirupsen/logrus"
+)
+
+var (
+	fileFailsCount = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Name: "expexp_file_fails_total",
+			Help: "Count of commands with non-zero exits",
+		},
+		[]string{"module"},
+	)
+)
+
+func readFileWithDeadline(path string, t time.Time) ([]byte, time.Time, error) {
+	f, err := os.Open(path)
+	mtime := time.Time{}
+	if err != nil {
+		return nil, mtime, err
+	}
+	defer f.Close()
+
+	if !t.IsZero() {
+		f.SetDeadline(t)
+	}
+
+	if info, err := f.Stat(); err == nil {
+		if info.Mode().IsRegular() {
+			mtime = info.ModTime()
+		}
+	}
+	data, err := io.ReadAll(f)
+	return data, mtime, err
+}
+
+var (
+	mtimeName        = "expexp_file_mtime_timestamp"
+	mtimeHelp        = "Time of modification of parsed file"
+	mtimeType        = dto.MetricType_GAUGE
+	mtimeLabelModule = "module"
+	mtimeLabelPath   = "path"
+)
+
+func (c fileConfig) GatherWithContext(ctx context.Context, r *http.Request) prometheus.GathererFunc {
+	return func() ([]*dto.MetricFamily, error) {
+
+		errc := make(chan error, 1)
+		datc := make(chan []byte, 1)
+		timec := make(chan time.Time, 1)
+		go func() {
+			defer close(errc)
+			defer close(datc)
+			defer close(timec)
+
+			deadline, _ := ctx.Deadline()
+			dat, mtime, err := readFileWithDeadline(c.Path, deadline)
+			errc <- err
+			if err == nil {
+				datc <- dat
+				timec <- mtime
+			}
+		}()
+
+		err := <-errc
+		if err != nil {
+			err = fmt.Errorf("file module %v failed to read file %v, %w", c.mcfg.name, c.Path, err)
+			log.Warnf(err.Error())
+			fileFailsCount.WithLabelValues(c.mcfg.name).Inc()
+			if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, os.ErrDeadlineExceeded) {
+				proxyTimeoutCount.WithLabelValues(c.mcfg.name).Inc()
+			}
+			return nil, err
+		}
+		dat := <-datc
+		mtime := <-timec
+		var prsr expfmt.TextParser
+
+		var result []*dto.MetricFamily
+		mfs, err := prsr.TextToMetricFamilies(bytes.NewReader(dat))
+		if err != nil {
+			proxyMalformedCount.WithLabelValues(c.mcfg.name).Inc()
+			return nil, err
+		}
+		for _, mf := range mfs {
+			result = append(result, mf)
+		}
+
+		if !mtime.IsZero() {
+			v := float64(mtime.UnixNano()) / float64(time.Second)
+			g := dto.Gauge{Value: &v}
+			l := make([]*dto.LabelPair, 2)
+			l[0] = &dto.LabelPair{
+				Name:  &mtimeLabelModule,
+				Value: &c.mcfg.name,
+			}
+			l[1] = &dto.LabelPair{
+				Name:  &mtimeLabelPath,
+				Value: &c.Path,
+			}
+			m := dto.Metric{
+				Label: l,
+				Gauge: &g,
+			}
+			mf := dto.MetricFamily{
+				Name: &mtimeName,
+				Help: &mtimeHelp,
+				Type: &mtimeType,
+			}
+			mf.Metric = append(mf.Metric, &m)
+			result = append(result, &mf)
+		}
+		return result, nil
+	}
+}
+
+func (c fileConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	ctx := r.Context()
+	g := c.GatherWithContext(ctx, r)
+	promhttp.HandlerFor(g, promhttp.HandlerOpts{}).ServeHTTP(w, r)
+}
diff --git a/go.mod b/go.mod
index 423ebf3..5a63fbe 100644
--- a/go.mod
+++ b/go.mod
@@ -1,24 +1,29 @@
 module github.com/QubitProducts/exporter_exporter
 
+go 1.20
+
 require (
-	github.com/aktau/github-release v0.7.2
-	github.com/dustin/go-humanize v1.0.0 // indirect
-	github.com/golang/protobuf v1.3.1 // indirect
-	github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
-	github.com/kr/pretty v0.1.0 // indirect
-	github.com/prometheus/client_golang v0.9.2
-	github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
-	github.com/prometheus/common v0.2.0
-	github.com/prometheus/procfs v0.0.0-20190322151404-55ae3d9d5573 // indirect
-	github.com/sirupsen/logrus v1.4.0
-	github.com/stretchr/testify v1.3.0 // indirect
+	github.com/aktau/github-release v0.10.0
+	github.com/prometheus/client_golang v1.14.0
+	github.com/prometheus/client_model v0.3.0
+	github.com/prometheus/common v0.39.0
+	github.com/sirupsen/logrus v1.9.0
+	golang.org/x/sync v0.1.0
+	golang.org/x/sys v0.4.0
+	gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/cespare/xxhash/v2 v2.2.0 // indirect
+	github.com/dustin/go-humanize v1.0.1 // indirect
+	github.com/github-release/github-release v0.10.0 // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/kevinburke/rest v0.0.0-20230118171807-ac09c3f0ec45 // indirect
+	github.com/kr/pretty v0.3.1 // indirect
+	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+	github.com/prometheus/procfs v0.9.0 // indirect
 	github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
 	github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2 // indirect
-	golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c // indirect
-	golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6
-	golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc
-	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
-	gopkg.in/yaml.v2 v2.2.2
+	google.golang.org/protobuf v1.28.1 // indirect
 )
-
-go 1.14
diff --git a/go.sum b/go.sum
index e2b504b..d341232 100644
--- a/go.sum
+++ b/go.sum
@@ -1,83 +1,67 @@
-github.com/aktau/github-release v0.7.2 h1:la7AnShr2MQPIlBEcRA9MPbI8av0YFmpFP9WM5EoqJs=
-github.com/aktau/github-release v0.7.2/go.mod h1:cPkP83iRnV8pAJyQlQ4vjLJoC+JE+aT5sOrYz3sTsX0=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/aktau/github-release v0.10.0 h1:4U+9iRM7n094ZCROdnoah084FDmdQ01hwQsz0f0hwIw=
+github.com/aktau/github-release v0.10.0/go.mod h1:cPkP83iRnV8pAJyQlQ4vjLJoC+JE+aT5sOrYz3sTsX0=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/github-release/github-release v0.10.0 h1:nJ3oEV2JrC0brYi6B8CsXumn/ORFeiAEOB2fwN9epOw=
+github.com/github-release/github-release v0.10.0/go.mod h1:CcaWgA5VoBGz94mOHYIXavqUA8kADNZxU+5/oDQxF6o=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
-github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/kevinburke/rest v0.0.0-20230118171807-ac09c3f0ec45 h1:WMM9MCVDgEtKsp7eQe0DCCkMaqykkNwdx38wn86NlVk=
+github.com/kevinburke/rest v0.0.0-20230118171807-ac09c3f0ec45/go.mod h1:pD+iEcdAGVXld5foVN4e24zb/6fnb60tgZPZ3P/3T/I=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
-github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
-github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190322151404-55ae3d9d5573 h1:gAuD3LIrjkoOOPLlhGlZWZXztrQII9a9kT6HS5jFtSY=
-github.com/prometheus/procfs v0.0.0-20190322151404-55ae3d9d5573/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.0 h1:yKenngtzGh+cUSSh6GWbxW2abRqhYUSR/t/6+2QqNvE=
-github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
+github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
+github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
+github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
+github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI=
+github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y=
+github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
+github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
 github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
 github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2 h1:txplJASvd6b/hrE0s/Ixfpp2cuwH9IO9oZBAN9iYa4A=
 github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2/go.mod h1:DGCIhurYgnLz8J9ga1fMV/fbLDyUvTyrWXVWUIyJon4=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
-golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc h1:4gbWbmmPFp4ySWICouJl6emP0MyS31yy9SrTlAGFT+g=
-golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
+golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/http.go b/http.go
index ef82529..e2a3c08 100644
--- a/http.go
+++ b/http.go
@@ -20,7 +20,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/url"
@@ -33,13 +32,14 @@ import (
 )
 
 const (
-	// Msg to send in response body when verification of proxied server
+	// VerificationErrorMsg to send in response body when verification of proxied server
 	// response is failed
 	VerificationErrorMsg = "Internal Server Error: " +
 		"Response from proxied server failed verification. " +
 		"See server logs for details"
 )
 
+// VerifyError is an error type that supports reporting verification errors
 type VerifyError struct {
 	msg   string
 	cause error
@@ -73,7 +73,13 @@ func (cfg moduleConfig) getReverseProxyDirectorFunc() (func(*http.Request), erro
 
 		r.URL.Scheme = cfg.HTTP.Scheme
 		r.URL.Host = net.JoinHostPort(cfg.HTTP.Address, strconv.Itoa(cfg.HTTP.Port))
+		if _, ok := cfg.HTTP.Headers["host"]; ok {
+			r.Host = cfg.HTTP.Headers["host"]
+		}
 		r.URL.Path = base.Path
+		if cfg.HTTP.BasicAuthUsername != "" && cfg.HTTP.BasicAuthPassword != "" {
+			r.SetBasicAuth(cfg.HTTP.BasicAuthUsername, cfg.HTTP.BasicAuthPassword)
+		}
 	}, nil
 }
 
@@ -94,7 +100,7 @@ func (cfg moduleConfig) getReverseProxyModifyResponseFunc() func(*http.Response)
 			return &VerifyError{"Failed to read body from proxied server", err}
 		}
 
-		resp.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes()))
+		resp.Body = io.NopCloser(bytes.NewReader(body.Bytes()))
 
 		var bodyReader io.ReadCloser
 		if resp.Header.Get("Content-Encoding") == "gzip" {
@@ -103,7 +109,7 @@ func (cfg moduleConfig) getReverseProxyModifyResponseFunc() func(*http.Response)
 				return &VerifyError{"Failed to decode gzipped response", err}
 			}
 		} else {
-			bodyReader = ioutil.NopCloser(bytes.NewReader(body.Bytes()))
+			bodyReader = io.NopCloser(bytes.NewReader(body.Bytes()))
 		}
 		defer bodyReader.Close()
 
@@ -125,7 +131,7 @@ func (cfg moduleConfig) getReverseProxyModifyResponseFunc() func(*http.Response)
 }
 
 func (cfg moduleConfig) getReverseProxyErrorHandlerFunc() func(http.ResponseWriter, *http.Request, error) {
-	return func(w http.ResponseWriter, r *http.Request, err error) {
+	return func(w http.ResponseWriter, _ *http.Request, err error) {
 		var verifyError *VerifyError
 		if errors.As(err, &verifyError) {
 			log.Errorf("Verification for module '%s' failed: %v", cfg.name, err)
@@ -144,7 +150,8 @@ func (cfg moduleConfig) getReverseProxyErrorHandlerFunc() func(http.ResponseWrit
 	}
 }
 
-// BearerAuthMiddleware
+// BearerAuthMiddleware checks an Authorization: Berarer header for a known
+// token
 type BearerAuthMiddleware struct {
 	http.Handler
 	Token string
@@ -171,6 +178,8 @@ func (b BearerAuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	b.Handler.ServeHTTP(w, r)
 }
 
+// IPAddressAuthMiddleware matches all incoming requests to a known
+// set of remote net.IPNet networks
 type IPAddressAuthMiddleware struct {
 	http.Handler
 	ACL []net.IPNet
diff --git a/http_test.go b/http_test.go
index d7a5cae..8fc83db 100644
--- a/http_test.go
+++ b/http_test.go
@@ -19,13 +19,13 @@ import (
 func BenchmarkReverseProxyHandler(b *testing.B) {
 	body := genRandomMetricsResponse(10000, 10)
 
-	test_exporter := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+	testExporter := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
 		reader := bytes.NewReader(body.Bytes())
 		io.Copy(w, reader)
 	}))
-	defer test_exporter.Close()
+	defer testExporter.Close()
 
-	URL, _ := url.Parse(test_exporter.URL)
+	URL, _ := url.Parse(testExporter.URL)
 	verify := true
 	port, _ := strconv.ParseInt(URL.Port(), 0, 0)
 	modCfg := &moduleConfig{
@@ -70,21 +70,21 @@ func BenchmarkReverseProxyHandler(b *testing.B) {
 // metric names in format 'metric{random number}'. m_num controls number of metrics
 // inside each metric family. Metrics inside metric families differ in values of
 // label 'label'.
-func genRandomMetricsResponse(mf_num int, m_num int) *bytes.Buffer {
+func genRandomMetricsResponse(mfNum int, mNum int) *bytes.Buffer {
 	rand.Seed(time.Now().UnixNano())
 	helpMsg := "help msg"
 	labelName := "label"
-	metricFamilies := make([]*dto.MetricFamily, mf_num)
+	metricFamilies := make([]*dto.MetricFamily, mfNum)
 	metricType := dto.MetricType_GAUGE
-	for i, _ := range metricFamilies {
-		metrics := make([]*dto.Metric, m_num)
-		for i, _ := range metrics {
+	for i := range metricFamilies {
+		metrics := make([]*dto.Metric, mNum)
+		for i := range metrics {
 			labelValue := fmt.Sprint(rand.Int63())
 			value := rand.Float64()
 			ts := time.Now().UnixNano()
 			metrics[i] = &dto.Metric{
 				Label: []*dto.LabelPair{
-					&dto.LabelPair{
+					{
 						Name:  &labelName,
 						Value: &labelValue,
 					},
diff --git a/main.go b/main.go
index 2917e6e..3c95733 100644
--- a/main.go
+++ b/main.go
@@ -22,7 +22,6 @@ import (
 	"flag"
 	"fmt"
 	"html/template"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
@@ -64,7 +63,7 @@ var (
 	pPath = flag.String("web.proxy-path", "/proxy", "The address to listen on for HTTP requests.")
 
 	logLevel = LogLevelFlag(log.WarnLevel)
-	logJson  = flag.Bool("log.json", false, "Serialize log messages in JSON")
+	logJSON  = flag.Bool("log.json", false, "Serialize log messages in JSON")
 
 	proxyDuration = prometheus.NewSummaryVec(
 		prometheus.SummaryOpts{
@@ -128,14 +127,14 @@ func setup() (*config, error) {
 		if err != nil {
 			return nil, err
 		}
-		for mn, _ := range cfg.Modules {
+		for mn := range cfg.Modules {
 			log.Debugf("read module config '%s' from: %s", mn, *cfgFile)
 		}
 	}
 
 cfgDirs:
 	for _, cfgDir := range cfgDirs {
-		mfs, err := ioutil.ReadDir(cfgDir)
+		mfs, err := os.ReadDir(cfgDir)
 		if err != nil {
 			if *skipDirs && os.IsNotExist(err) {
 				log.Warnf("skipping non existent config.dirs entry '%s'", cfgDir)
@@ -186,7 +185,7 @@ cfgDirs:
 		if *bearerToken != "" {
 			return nil, errors.New(("web.bearer.token and web.bearer.token-file are mutually exclusive options"))
 		}
-		bs, err := ioutil.ReadFile(*bearerTokenFile)
+		bs, err := os.ReadFile(*bearerTokenFile)
 		if err != nil {
 			return nil, fmt.Errorf("failed reading bearer file %s, %w", *bearerTokenFile, err)
 		}
@@ -207,7 +206,7 @@ cfgDirs:
 }
 
 func getClientValidator(r *regexp.Regexp, helloInfo *tls.ClientHelloInfo) func([][]byte, [][]*x509.Certificate) error {
-	return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
+	return func(_ [][]byte, verifiedChains [][]*x509.Certificate) error {
 		for _, c := range verifiedChains {
 			leaf := c[0]
 
@@ -238,7 +237,7 @@ func setupTLS() (*tls.Config, error) {
 
 	cert, err := tls.LoadX509KeyPair(*certPath, *keyPath)
 	if err != nil {
-		return nil, fmt.Errorf("Could not parse key/cert, %w", err)
+		return nil, fmt.Errorf("could not parse key/cert, %w", err)
 	}
 
 	tlsConfig = &tls.Config{
@@ -257,13 +256,13 @@ func setupTLS() (*tls.Config, error) {
 
 	if *verify {
 		pool := x509.NewCertPool()
-		cabs, err := ioutil.ReadFile(*caPath)
+		cabs, err := os.ReadFile(*caPath)
 		if err != nil {
-			return nil, fmt.Errorf("Could not open ca file, %w", err)
+			return nil, fmt.Errorf("could not open ca file, %w", err)
 		}
 		ok := pool.AppendCertsFromPEM(cabs)
 		if !ok {
-			return nil, errors.New("Failed loading ca certs")
+			return nil, errors.New("failed loading ca certs")
 		}
 		tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
 		tlsConfig.ClientCAs = pool
@@ -368,7 +367,7 @@ func main() {
 	}
 
 	log.SetLevel(log.Level(logLevel))
-	if *logJson {
+	if *logJSON {
 		log.SetFormatter(&log.JSONFormatter{})
 	}
 	handler = &AccessLogMiddleware{handler}
@@ -400,6 +399,7 @@ func (w *responseWriterWithStatus) WriteHeader(status int) {
 	w.ResponseWriter.WriteHeader(status)
 }
 
+// AccessLogMiddleware logs all request at Info level
 type AccessLogMiddleware struct {
 	http.Handler
 }
@@ -414,7 +414,7 @@ func (middleware AccessLogMiddleware) ServeHTTP(w http.ResponseWriter, r *http.R
 		log.Infof(
 			"%s - %s \"%s\" %d %s (took %s)",
 			remoteHost, r.Method, r.URL.RequestURI(), statusWriter.status,
-			http.StatusText(statusWriter.status), time.Now().Sub(start),
+			http.StatusText(statusWriter.status), time.Since(start),
 		)
 	}()
 	middleware.Handler.ServeHTTP(statusWriter, r)
@@ -422,24 +422,29 @@ func (middleware AccessLogMiddleware) ServeHTTP(w http.ResponseWriter, r *http.R
 
 func (cfg *config) doProxy(w http.ResponseWriter, r *http.Request) {
 	mod, ok := r.URL.Query()["module"]
-	if !ok {
+	if !ok && cfg.DefaultModule == "" {
 		log.Errorf("no module given")
 		http.Error(w, fmt.Sprintf("require parameter module is missing%v\n", mod), http.StatusBadRequest)
 		return
 	}
 
-	log.Debugf("running module %v\n", mod)
+	if len(mod) == 0 {
+		mod = append(mod, cfg.DefaultModule)
+	}
+
+	log.Debugf("running module %v\n", mod[0])
 
 	var h http.Handler
-	if m, ok := cfg.Modules[mod[0]]; !ok {
+	m, ok := cfg.Modules[mod[0]]
+	if !ok {
 		proxyErrorCount.WithLabelValues("unknown").Inc()
 		log.Warnf("unknown module requested  %v\n", mod)
 		http.Error(w, fmt.Sprintf("unknown module %v\n", mod), http.StatusNotFound)
 		return
-	} else {
-		h = m
 	}
 
+	h = m
+
 	h.ServeHTTP(w, r)
 }
 
@@ -471,7 +476,6 @@ func (cfg *config) listModules(w http.ResponseWriter, r *http.Request) {
 			http.Error(w, "Can't execute the template", http.StatusInternalServerError)
 		}
 	}
-	return
 }
 
 func (m moduleConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -498,6 +502,9 @@ func (m moduleConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	case "http":
 		m.HTTP.mcfg = &m
 		m.HTTP.ServeHTTP(w, nr)
+	case "file":
+		m.File.mcfg = &m
+		m.File.ServeHTTP(w, nr)
 	default:
 		log.Errorf("unknown module method  %v\n", m.Method)
 		proxyErrorCount.WithLabelValues(m.name).Inc()
@@ -506,7 +513,7 @@ func (m moduleConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-// StringSliceFlags collects multiple uses of a named flag into a slice.
+// StringSliceFlag collects multiple uses of a named flag into a slice.
 type StringSliceFlag []string
 
 func (s *StringSliceFlag) String() string {
@@ -515,6 +522,7 @@ func (s *StringSliceFlag) String() string {
 	return strings.Join(*s, ", ")
 }
 
+// Set fullfills the flag.Value interface
 func (s *StringSliceFlag) Set(value string) error {
 	*s = append(*s, value)
 	return nil
@@ -532,26 +540,33 @@ func (nets IPNetSliceFlag) String() string {
 	return strings.Join(netsStr, ", ")
 }
 
+// Set fullfills the flag.Value interface
 func (nets *IPNetSliceFlag) Set(value string) error {
-	if _, net, err := net.ParseCIDR(value); err != nil {
+	_, net, err := net.ParseCIDR(value)
+	if err != nil {
 		return err
-	} else {
-		*nets = append(*nets, *net)
 	}
+
+	*nets = append(*nets, *net)
+
 	return nil
 }
 
+// LogLevelFlag implements a flag.Value that sets the logging level
 type LogLevelFlag log.Level
 
 func (level LogLevelFlag) String() string {
 	return log.Level(level).String()
 }
 
+// Set fullfills the flag.Value interface
 func (level *LogLevelFlag) Set(value string) error {
-	if lvl, err := log.ParseLevel(value); err != nil {
+	lvl, err := log.ParseLevel(value)
+	if err != nil {
 		return err
-	} else {
-		*level = LogLevelFlag(lvl)
 	}
+
+	*level = LogLevelFlag(lvl)
+
 	return nil
 }
diff --git a/service_windows.go b/service_windows.go
index 2dfeb65..35f74b0 100644
--- a/service_windows.go
+++ b/service_windows.go
@@ -7,7 +7,7 @@ import (
 	"path/filepath"
 	"time"
 
-	"github.com/prometheus/common/log"
+	log "github.com/sirupsen/logrus"
 	"golang.org/x/sys/windows/svc"
 	"golang.org/x/sys/windows/svc/eventlog"
 	"golang.org/x/sys/windows/svc/mgr"
diff --git a/version.go b/version.go
index fbb2ec4..66364d7 100644
--- a/version.go
+++ b/version.go
@@ -8,12 +8,13 @@ import (
 )
 
 var (
-	Version   = "unset"
-	Revision  = "unset"
-	Branch    = "unset"
-	BuildUser = "unset"
-	BuildDate = "unset"
-	GoVersion = runtime.Version()
+	Version   = "unset" // Version is set at build time
+	Revision  = "unset" // Revision is set at build time
+	Branch    = "unset" // Branch is set at build time
+	BuildUser = "unset" // BuildUser is set at build time
+	BuildDate = "unset" // BuildDate is set at build time
+
+	goVersion = runtime.Version()
 )
 
 var (
@@ -28,7 +29,7 @@ var (
 
 func init() {
 	prometheus.MustRegister(buildInfo)
-	buildInfo.WithLabelValues(Version, Revision, Branch, GoVersion).Set(1)
+	buildInfo.WithLabelValues(Version, Revision, Branch, goVersion).Set(1)
 }
 
 func versionStr() string {

Debdiff

File lists identical (after any substitutions)

Control files: lines which differ (wdiff format)

  • Built-Using: golang-github-beorn7-perks (= 1.0.1-1), golang-github-cespare-xxhash (= 2.1.1-2), golang-github-golang-protobuf-1-3 (= 1.3.5-4), 1.3.5-4~jan+lint1), golang-github-prometheus-client-golang (= 1.14.0-3), 1.16.0-4), golang-github-prometheus-client-model (= 0.3.0-3), 0.4.0-2), golang-github-prometheus-common (= 0.39.0-2), 0.44.0-4), golang-github-prometheus-procfs (= 0.8.0-3), 0.11.1-1), golang-golang-x-sync (= 0.1.0-1), 0.2.0-1), golang-golang-x-sys (= 0.3.0-1), 0.8.0-1), golang-google-protobuf (= 1.28.1-3), golang-gopkg-yaml.v3 (= 3.0.1-3), golang-logrus (= 1.9.0-1), 1.9.0-1~jan+lint1), golang-protobuf-extensions (= 1.0.1-3), golang-yaml.v2 (= 2.4.0-4~jan+lint1) 1.0.4-2)
  • Depends: daemon | systemd-sysv, libc6 (>= 2.34), libgcc-s1 (>= 3.4), libgo21 libgo22

More details

Full run details