pax_global_header00006660000000000000000000000064143657636410014531gustar00rootroot0000000000000052 comment=ed0c8bd46f4b709703e0a67cc13b8e9104b10168 prometheus-mqtt-exporter-0.1.7/000077500000000000000000000000001436576364100165625ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/.dockerignore000066400000000000000000000000221436576364100212300ustar00rootroot00000000000000bin/ .git systemd/prometheus-mqtt-exporter-0.1.7/.github/000077500000000000000000000000001436576364100201225ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/.github/renovate.js000066400000000000000000000012531436576364100223040ustar00rootroot00000000000000module.exports = { branchPrefix: 'test-renovate/', dryRun: false, username: 'renovate-release', gitAuthor: 'Renovate Bot ', onboarding: false, platform: 'github', includeForks: false, repositories: [ 'hikhvar/mqtt2prometheus', ], packageRules: [ { description: 'lockFileMaintenance', matchUpdateTypes: [ 'pin', 'digest', 'patch', 'minor', 'major', 'lockFileMaintenance', ], dependencyDashboardApproval: false, stabilityDays: 10, }, ], }; prometheus-mqtt-exporter-0.1.7/.github/workflows/000077500000000000000000000000001436576364100221575ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/.github/workflows/codeql-analysis.yml000066400000000000000000000044651436576364100260030ustar00rootroot00000000000000# 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: '41 18 * * 2' 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 prometheus-mqtt-exporter-0.1.7/.github/workflows/release.yml000066400000000000000000000030511436576364100243210ustar00rootroot00000000000000name: release on: push: tags: - '*' jobs: goreleaser: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Unshallow run: git fetch --prune --unshallow - name: Set up Go uses: actions/setup-go@v3 with: go-version: 1.19.x - name: Test run: go test -cover ./... - name: Vet run: go vet ./... - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 # Login to the registries - name: Login to Github Packages run: echo ${{ secrets.GITHUB_TOKEN }} | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin - name: Login to Dockerhub run: echo ${{ secrets.DOCKERHUB_ACCESS_KEY }} | docker login -u hikhvar --password-stdin # Github Registry does not support the github actions token - name: Login to Github Registry run: echo ${{secrets.PERSONAL_ACCESS_TOKEN }} | docker login ghcr.io -u hikhvar --password-stdin - name: Enable docker experimental features for docker manifests run: | echo '{"experimental": true}' | sudo tee -a /etc/docker/daemon.json sudo systemctl restart docker - name: Run GoReleaser uses: goreleaser/goreleaser-action@v3 with: version: latest args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DOCKER_CLI_EXPERIMENTAL: enabled prometheus-mqtt-exporter-0.1.7/.github/workflows/renovate.yaml000066400000000000000000000013241436576364100246660ustar00rootroot00000000000000name: Renovate on: schedule: # The "*" (#42, asterisk) character has special semantics in YAML, so this # string has to be quoted. - cron: '24 12 * * *' jobs: renovate: runs-on: ubuntu-latest steps: - name: Get token id: get_token uses: machine-learning-apps/actions-app-token@master with: APP_PEM: ${{ secrets.APP_PEM }} APP_ID: ${{ secrets.APP_ID }} - name: Checkout uses: actions/checkout@v2.0.0 - name: Self-hosted Renovate uses: renovatebot/github-action@v32.118.0 with: configurationFile: .github/renovate.js token: 'x-access-token:${{ steps.get_token.outputs.app_token }}' prometheus-mqtt-exporter-0.1.7/.github/workflows/tests.yml000066400000000000000000000027601436576364100240510ustar00rootroot00000000000000on: push: branches: ["master"] pull_request: name: tests jobs: test: strategy: matrix: # Test oldest and newest supported version go-version: [1.14.x, 1.19.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v2 - name: Test run: go test ./... - name: Vet run: go vet ./... linting: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Run golangci-lint uses: golangci/golangci-lint-action@v2 with: only-new-issues: true goreleaser: needs: - test - linting runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Unshallow run: git fetch --prune --unshallow - name: Set up Go uses: actions/setup-go@v3 with: go-version: 1.19.x - name: Test run: go test -cover ./... - name: Vet run: go vet ./... - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - name: Run GoReleaser uses: goreleaser/goreleaser-action@v3 with: version: latest args: release --rm-dist --skip-publish prometheus-mqtt-exporter-0.1.7/.gitignore000066400000000000000000000000531436576364100205500ustar00rootroot00000000000000config.yaml bin/ vendor dist .vscode .idea prometheus-mqtt-exporter-0.1.7/.goreleaser.yml000066400000000000000000000125521436576364100215200ustar00rootroot00000000000000# This is an example goreleaser.yaml file with some sane defaults. # Make sure to check the documentation at http://goreleaser.com before: hooks: # You may remove this if you don't use go modules. - go mod download # you may remove this if you don't need go generate - go generate ./... release: prerelease: auto builds: - env: - CGO_ENABLED=0 main: cmd/mqtt2prometheus.go # GOOS list to build for. # For more info refer to: https://golang.org/doc/install/source#environment # Defaults are darwin and linux. goos: - linux - darwin - freebsd - windows # GOARCH to build for. # For more info refer to: https://golang.org/doc/install/source#environment # Defaults are 386 and amd64. goarch: - amd64 - arm - arm64 - 386 # GOARM to build for when GOARCH is arm. # For more info refer to: https://golang.org/doc/install/source#environment # Default is only 6. goarm: - 5 - 6 - 7 # GOMIPS and GOMIPS64 to build when GOARCH is mips, mips64, mipsle or mips64le. # For more info refer to: https://golang.org/doc/install/source#environment # Default is empty. gomips: - hardfloat - softfloat archives: - replacements: darwin: Darwin linux: Linux windows: Windows 386: i386 amd64: x86_64 checksum: name_template: 'checksums.txt' snapshot: name_template: "{{ .Tag }}-next" changelog: sort: asc filters: exclude: - '^docs:' - '^test:' - '^ci:' nfpms: - id: default vendor: Christoph Petrausch homepage: https://github.com/hikhvar/mqtt2prometheus description: This exporter translates from MQTT topics to prometheus metrics. license: MIT License formats: - deb - rpm - apk conflicts: - prometheus-mqtt-exporter scripts: postinstall: systemd/postinstall.sh contents: # Simple config file - src: config.yaml.dist dst: /etc/mqtt2prometheus/config.yaml type: config - src: ./systemd/mqtt2prometheus.service dst: /etc/systemd/system/mqtt2prometheus.service type: config dockers: - dockerfile: release/Dockerfile.scratch goos: linux goarch: amd64 use: buildx build_flag_templates: - "--platform=linux/amd64" image_templates: - "hikhvar/mqtt2prometheus:{{ .Tag }}-amd64" - "hikhvar/mqtt2prometheus:latest-amd64" - "ghcr.io/hikhvar/mqtt2prometheus:{{ .Tag }}-amd64" - "ghcr.io/hikhvar/mqtt2prometheus:latest-amd64" - dockerfile: release/Dockerfile.scratch goos: linux goarch: arm64 use: buildx build_flag_templates: - "--platform=linux/arm64" image_templates: - "hikhvar/mqtt2prometheus:{{ .Tag }}-arm64" - "hikhvar/mqtt2prometheus:latest-arm64" - "ghcr.io/hikhvar/mqtt2prometheus:{{ .Tag }}-arm64" - "ghcr.io/hikhvar/mqtt2prometheus:latest-arm64" - dockerfile: release/Dockerfile.scratch goos: linux goarch: arm goarm: 6 use: buildx build_flag_templates: - "--platform=linux/arm/v6" image_templates: - "hikhvar/mqtt2prometheus:{{ .Tag }}-arm6" - "hikhvar/mqtt2prometheus:latest-arm6" - "ghcr.io/hikhvar/mqtt2prometheus:{{ .Tag }}-arm6" - "ghcr.io/hikhvar/mqtt2prometheus:latest-arm6" - dockerfile: release/Dockerfile.scratch goos: linux goarch: arm goarm: 7 use: buildx build_flag_templates: - "--platform=linux/arm/v7" image_templates: - "hikhvar/mqtt2prometheus:{{ .Tag }}-arm7" - "hikhvar/mqtt2prometheus:latest-arm7" - "ghcr.io/hikhvar/mqtt2prometheus:{{ .Tag }}-arm7" - "ghcr.io/hikhvar/mqtt2prometheus:latest-arm7" - dockerfile: release/Dockerfile.scratch goos: linux goarch: 386 use: buildx build_flag_templates: - "--platform=linux/386" image_templates: - "hikhvar/mqtt2prometheus:{{ .Tag }}-386" - "hikhvar/mqtt2prometheus:latest-386" - "ghcr.io/hikhvar/mqtt2prometheus:{{ .Tag }}-386" - "ghcr.io/hikhvar/mqtt2prometheus:latest-386" docker_manifests: # Docker Registry - name_template: hikhvar/mqtt2prometheus:{{ .Tag }} image_templates: - "hikhvar/mqtt2prometheus:{{ .Tag }}-amd64" - "hikhvar/mqtt2prometheus:{{ .Tag }}-arm64" - "hikhvar/mqtt2prometheus:{{ .Tag }}-arm6" - "hikhvar/mqtt2prometheus:{{ .Tag }}-arm7" - "hikhvar/mqtt2prometheus:{{ .Tag }}-386" - name_template: hikhvar/mqtt2prometheus:latest image_templates: - "hikhvar/mqtt2prometheus:latest-amd64" - "hikhvar/mqtt2prometheus:latest-arm64" - "hikhvar/mqtt2prometheus:latest-arm6" - "hikhvar/mqtt2prometheus:latest-arm7" - "hikhvar/mqtt2prometheus:latest-386" # Github Registry - name_template: ghcr.io/hikhvar/mqtt2prometheus:{{ .Tag }} image_templates: - "ghcr.io/hikhvar/mqtt2prometheus:{{ .Tag }}-amd64" - "ghcr.io/hikhvar/mqtt2prometheus:{{ .Tag }}-arm64" - "ghcr.io/hikhvar/mqtt2prometheus:{{ .Tag }}-arm6" - "ghcr.io/hikhvar/mqtt2prometheus:{{ .Tag }}-arm7" - "ghcr.io/hikhvar/mqtt2prometheus:{{ .Tag }}-386" - name_template: ghcr.io/hikhvar/mqtt2prometheus:latest image_templates: - "ghcr.io/hikhvar/mqtt2prometheus:latest-amd64" - "ghcr.io/hikhvar/mqtt2prometheus:latest-arm64" - "ghcr.io/hikhvar/mqtt2prometheus:latest-arm6" - "ghcr.io/hikhvar/mqtt2prometheus:latest-arm7" - "ghcr.io/hikhvar/mqtt2prometheus:latest-386" prometheus-mqtt-exporter-0.1.7/Dockerfile000066400000000000000000000004451436576364100205570ustar00rootroot00000000000000FROM golang:1.19 as builder COPY . /build/mqtt2prometheus WORKDIR /build/mqtt2prometheus RUN make static_build TARGET_FILE=/bin/mqtt2prometheus FROM gcr.io/distroless/static-debian10:nonroot WORKDIR / COPY --from=builder /bin/mqtt2prometheus /mqtt2prometheus ENTRYPOINT ["/mqtt2prometheus"] prometheus-mqtt-exporter-0.1.7/LICENSE000066400000000000000000000020641436576364100175710ustar00rootroot00000000000000MIT License Copyright (c) 2018 Christoph Petrausch Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. prometheus-mqtt-exporter-0.1.7/Makefile000066400000000000000000000015551436576364100202300ustar00rootroot00000000000000ifndef GOBINARY GOBINARY:="go" endif ifndef GOPATH GOPATH:=$(shell $(GOBINARY) env GOPATH) endif ifndef GOBIN GOBIN:=$(GOPATH)/bin endif ifndef GOARCH GOARCH:=$(shell $(GOBINARY) env GOARCH) endif ifndef GOOS GOOS:=$(shell $(GOBINARY) env GOOS) endif ifndef GOARM GOARM:=$(shell $(GOBINARY) env GOARM) endif ifndef TARGET_FILE TARGET_FILE:=bin/mqtt2prometheus.$(GOOS)_$(GOARCH)$(GOARM) endif all: build GO111MODULE=on lint: golangci-lint run test: $(GOBINARY) test ./... $(GOBINARY) vet ./... build: GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBINARY) build -o $(TARGET_FILE) ./cmd static_build: CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBINARY) build -o $(TARGET_FILE) -a -tags netgo -ldflags '-w -extldflags "-static"' ./cmd container: docker build -t mqtt2prometheus:latest . test_release: goreleaser --rm-dist --skip-validate --skip-publish prometheus-mqtt-exporter-0.1.7/Readme.md000066400000000000000000000337671436576364100203210ustar00rootroot00000000000000# MQTT2Prometheus ![](https://github.com/hikhvar/mqtt2prometheus/workflows/tests/badge.svg) ![](https://github.com/hikhvar/mqtt2prometheus/workflows/release/badge.svg) This exporter translates from MQTT topics to prometheus metrics. The core design is that clients send arbitrary JSON messages on the topics. The translation between the MQTT representation and prometheus metrics is configured in the mqtt2prometheus exporter since we often can not change the IoT devices sending the messages. Clients can push metrics via MQTT to an MQTT Broker. This exporter subscribes to the broker and expose the received messages as prometheus metrics. Currently, the exporter supports only MQTT 3.1. ![Overview Diagram](docs/overview.drawio.svg) I wrote this exporter to expose metrics from small embedded sensors based on the NodeMCU to prometheus. The used arduino scetch can be found in the [dht22tomqtt](https://github.com/hikhvar/dht22tomqtt) repository. A local hacking environment with mqtt2prometheus, a MQTT broker and a prometheus server is in the [hack](https://github.com/hikhvar/mqtt2prometheus/tree/master/hack) directory. ## Assumptions about Messages and Topics This exporter makes some assumptions about the MQTT topics. This exporter assumes that each client publish the metrics into a dedicated topic. The regular expression in the configuration field `mqtt.device_id_regex` defines how to extract the device ID from the MQTT topic. This allows an arbitrary place of the device ID in the mqtt topic. For example the [tasmota](https://github.com/arendst/Tasmota) firmware pushes the telemetry data to the topics `tele//SENSOR`. Let us assume the default configuration from [configuration file](#config-file). A sensor publishes the following message ```json {"temperature":23.20,"humidity":51.60, "computed": {"heat_index":22.92} } ``` to the MQTT topic `devices/home/livingroom`. This message becomes the following prometheus metrics: ```text temperature{sensor="livingroom",topic="devices/home/livingroom"} 23.2 heat_index{sensor="livingroom",topic="devices/home/livingroom"} 22.92 humidity{sensor="livingroom",topic="devices/home/livingroom"} 51.6 ``` The label `sensor` is extracted with the default `device_id_regex` `(.*/)?(?P.*)` from the MQTT topic `devices/home/livingroom`. The `device_id_regex` is able to extract exactly one label from the topic path. It extracts only the `deviceid` regex capture group into the `sensor` prometheus label. To extract more labels from the topic path, have a look at [this FAQ answer](#extract-more-labels-from-the-topic-path). The topic path can contain multiple wildcards. MQTT has two wildcards: * `+`: Single level of hierarchy in the topic path * `#`: Many levels of hierarchy in the topic path This [page](https://mosquitto.org/man/mqtt-7.html) explains the wildcard in depth. For example the `topic_path: devices/+/sensors/#` will match: * `devices/home/sensors/foo/bar` * `devices/workshop/sensors/temperature` ### JSON Seperator The exporter interprets `mqtt_name` as [gojsonq](https://github.com/thedevsaddam/gojsonq) paths. Those paths will be used to find the value in the JSON message. For example `mqtt_name: computed.heat_index` addresses ```json { "computed": { "heat_index":22.92 } } ``` Some sensors might use a `.` in the JSON keys. Therefore, there the configuration option `json_parsing.seperator` in the exporter config. This allows us to use any other string to separate hierarchies in the gojsonq path. E.g let's assume the following MQTT JSON message: ```json { "computed": { "heat.index":22.92 } } ``` We can now set `json_parsing.seperator` to `/`. This allows us to specify `mqtt_name` as `computed/heat.index`. Keep in mind, `json_parsing.seperator` is a global setting. This affects all `mqtt_name` fields in your configuration. ### Tasmota An example configuration for the tasmota based Gosund SP111 device is given in [examples/gosund_sp111.yaml](examples/gosund_sp111.yaml). ## Build To build the exporter run: ```bash make build ``` Only the latest two Go major versions are tested and supported. ### Docker #### Use Public Image To start the public available image run: ```bash docker run -it -v "$(pwd)/config.yaml:/config.yaml" -p 9641:9641 ghcr.io/hikhvar/mqtt2prometheus:latest ``` Please have a look at the [latest relase](https://github.com/hikhvar/mqtt2prometheus/releases/latest) to get a stable image tag. The latest tag may break at any moment in time since latest is pushed into the registries on every git commit in the master branch. #### Build The Image locally To build a docker container with the mqtt2prometheus exporter included run: ```bash make container ``` To run the container with a given config file: ```bash docker run -it -v "$(pwd)/config.yaml:/config.yaml" -p 9641:9641 mqtt2prometheus:latest ``` ## Configuration The exporter can be configured via command line and config file. ### Commandline Available command line flags: ```text Usage of ./mqtt2prometheus: -config string config file (default "config.yaml") -listen-address string listen address for HTTP server used to expose metrics (default "0.0.0.0") -listen-port string HTTP port used to expose metrics (default "9641") -log-format string set the desired log output format. Valid values are 'console' and 'json' (default "console") -log-level value sets the default loglevel (default: "info") -version show the builds version, date and commit -web-config-file string [EXPERIMENTAL] Path to configuration file that can enable TLS or authentication for metric scraping. -treat-mqtt-password-as-file-name bool (default: false) treat MQTT2PROM_MQTT_PASSWORD environment variable as a secret file path e.g. /var/run/secrets/mqtt-credential. Useful when docker secret or external credential management agents handle the secret file. ``` The logging is implemented via [zap](https://github.com/uber-go/zap). The logs are printed to `stderr` and valid log levels are those supported by zap. ### Config file The config file can look like this: ```yaml mqtt: # The MQTT broker to connect to server: tcp://127.0.0.1:1883 # Optional: Username and Password for authenticating with the MQTT Server user: bob password: happylittleclouds # Optional: for TLS client certificates ca_cert: certs/AmazonRootCA1.pem client_cert: certs/xxxxx-certificate.pem.crt client_key: certs/xxxxx-private.pem.key # Optional: Used to specify ClientID. The default is - client_id: somedevice # The Topic path to subscribe to. Be aware that you have to specify the wildcard, if you want to follow topics for multiple sensors. topic_path: v1/devices/me/+ # Optional: Regular expression to extract the device ID from the topic path. The default regular expression, assumes # that the last "element" of the topic_path is the device id. # The regular expression must contain a named capture group with the name deviceid # For example the expression for tasamota based sensors is "tele/(?P.*)/.*" device_id_regex: "(.*/)?(?P.*)" # The MQTT QoS level qos: 0 # NOTE: Only one of metric_per_topic_config or object_per_topic_config should be specified in the configuration # Optional: Configures mqtt2prometheus to expect a single metric to be published as the value on an mqtt topic. metric_per_topic_config: # A regex used for extracting the metric name from the topic. Must contain a named group for `metricname`. metric_name_regex: "(.*/)?(?P.*)" # Optional: Configures mqtt2prometheus to expect an object containing multiple metrics to be published as the value on an mqtt topic. # This is the default. object_per_topic_config: # The encoding of the object, currently only json is supported encoding: json cache: # Timeout. Each received metric will be presented for this time if no update is send via MQTT. # Set the timeout to -1 to disable the deletion of metrics from the cache. The exporter presents the ingest timestamp # to prometheus. timeout: 24h json_parsing: # Separator. Used to split path to elements when accessing json fields. # You can access json fields with dots in it. F.E. {"key.name": {"nested": "value"}} # Just set separator to -> and use key.name->nested as mqtt_name separator: . # This is a list of valid metrics. Only metrics listed here will be exported metrics: # The name of the metric in prometheus - prom_name: temperature # The name of the metric in a MQTT JSON message mqtt_name: temperature # The prometheus help text for this metric help: DHT22 temperature reading # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: dht22 # The name of the metric in prometheus - prom_name: humidity # The name of the metric in a MQTT JSON message mqtt_name: humidity # The scale of the metric in a MQTT JSON message (prom_value = mqtt_value * scale) mqtt_value_scale: 100 # The prometheus help text for this metric help: DHT22 humidity reading # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: dht22 # The name of the metric in prometheus - prom_name: heat_index # The path of the metric in a MQTT JSON message mqtt_name: computed.heat_index # The prometheus help text for this metric help: DHT22 heatIndex calculation # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: dht22 # The name of the metric in prometheus - prom_name: state # The name of the metric in a MQTT JSON message mqtt_name: state # Regular expression to only match sensors with the given name pattern sensor_name_filter: "^.*-light$" # The prometheus help text for this metric help: Light state # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # according to prometheus exposition format timestamp is not mandatory, we can omit it if the reporting from the sensor is sporadic omit_timestamp: true # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: ikea # When specified, enables mapping between string values to metric values. string_value_mapping: # A map of string to metric value. map: off: 0 low: 0 # Metric value to use if a match cannot be found in the map above. # If not specified, parsing error will occur. error_value: 1 ``` ### Environment Variables Having the MQTT login details in the config file runs the risk of publishing them to a version control system. To avoid this, you can supply these parameters via environment variables. MQTT2Prometheus will look for `MQTT2PROM_MQTT_USER` and `MQTT2PROM_MQTT_PASSWORD` in the local environment and load them on startup. #### Example use with Docker Create a file to store your login details, for example at `~/secrets/mqtt2prom`: ```SHELL #!/bin/bash export MQTT2PROM_MQTT_USER="myUser" export MQTT2PROM_MQTT_PASSWORD="superpassword" ``` Then load that file into the environment before starting the container: ```SHELL source ~/secrets/mqtt2prom && \ docker run -it \ -e MQTT2PROM_MQTT_USER \ -e MQTT2PROM_MQTT_PASSWORD \ -v "$(pwd)/examples/config.yaml:/config.yaml" \ -p 9641:9641 \ ghcr.io/hikhvar/mqtt2prometheus:latest ``` #### Example use with Docker secret (in swarm) Create a docker secret to store the password(`mqtt-credential` in the example below), and pass the optional `treat-mqtt-password-as-file-name` command line argument. ```docker mqtt_exporter_tasmota: image: ghcr.io/hikhvar/mqtt2prometheus:latest secrets: - mqtt-credential environment: - MQTT2PROM_MQTT_USER=mqtt - MQTT2PROM_MQTT_PASSWORD=/var/run/secrets/mqtt-credential entrypoint: - /mqtt2prometheus - -log-level=debug - -treat-mqtt-password-as-file-name=true volumes: - config-tasmota.yml:/config.yaml:ro ``` ## Frequently Asked Questions ### Listen to multiple Topic Pathes The exporter can only listen to one topic_path per instance. If you have to listen to two different topic_paths it is recommended to run two instances of the mqtt2prometheus exporter. You can run both on the same host or if you run in Kubernetes, even in the same pod. ### Extract more Labels from the Topic Path A regular use case is, that user want to extract more labels from the topic path. E.g. they have sensors not only in their `home` but also in their `workshop` and they encode the location in the topic path. E.g. a sensor pushes the message ```json {"temperature":3.0,"humidity":34.60, "computed": {"heat_index":15.92} } ``` to the topic `devices/workshop/storage`, this will produce the prometheus metrics with the default configuration. ```text temperature{sensor="storage",topic="devices/workshop/storage"} 3.0 heat_index{sensor="storage",topic="devices/workshop/storage"} 15.92 humidity{sensor="storage",topic="devices/workshop/storage"} 34.60 ``` The following prometheus [relabel_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config) will extract the location from the topic path as well and attaches the `location` label. ```yaml relabel_config: - source_labels: [ "topic" ] target_label: location regex: '/devices/(.*)/.*' action: replace replacement: "$1" ``` With this config added to your prometheus scrape config you will get the following metrics in prometheus storage: ```text temperature{sensor="storage", location="workshop", topic="devices/workshop/storage"} 3.0 heat_index{sensor="storage", location="workshop", topic="devices/workshop/storage"} 15.92 humidity{sensor="storage", location="workshop", topic="devices/workshop/storage"} 34.60 ``` prometheus-mqtt-exporter-0.1.7/cmd/000077500000000000000000000000001436576364100173255ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/cmd/mqtt2prometheus.go000066400000000000000000000157351436576364100230520ustar00rootroot00000000000000package main import ( "crypto/tls" "crypto/x509" "encoding/json" "flag" "fmt" "io/ioutil" "net/http" "os" "time" "go.uber.org/zap" "go.uber.org/zap/zapcore" mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/go-kit/kit/log" kitzap "github.com/go-kit/kit/log/zap" "github.com/hikhvar/mqtt2prometheus/pkg/config" "github.com/hikhvar/mqtt2prometheus/pkg/metrics" "github.com/hikhvar/mqtt2prometheus/pkg/mqttclient" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/exporter-toolkit/web" ) // These variables are set by goreleaser at linking time. var ( version string commit string date string ) var ( configFlag = flag.String( "config", "config.yaml", "config file", ) portFlag = flag.String( "listen-port", "9641", "HTTP port used to expose metrics", ) addressFlag = flag.String( "listen-address", "0.0.0.0", "listen address for HTTP server used to expose metrics", ) versionFlag = flag.Bool( "version", false, "show the builds version, date and commit", ) logLevelFlag = zap.LevelFlag("log-level", zap.InfoLevel, "sets the default loglevel (default: \"info\")") logEncodingFlag = flag.String( "log-format", "console", "set the desired log output format. Valid values are 'console' and 'json'", ) webConfigFlag = flag.String( "web-config-file", "", "[EXPERIMENTAL] Path to configuration file that can enable TLS or authentication for metric scraping.", ) usePasswordFromFile = flag.Bool( "treat-mqtt-password-as-file-name", false, "treat MQTT2PROM_MQTT_PASSWORD as a secret file path e.g. /var/run/secrets/mqtt-credential", ) ) func main() { flag.Parse() if *versionFlag { mustShowVersion() os.Exit(0) } logger := mustSetupLogger() defer logger.Sync() //nolint:errcheck c := make(chan os.Signal, 1) cfg, err := config.LoadConfig(*configFlag) if err != nil { logger.Fatal("Could not load config", zap.Error(err)) } mqtt_user := os.Getenv("MQTT2PROM_MQTT_USER") if mqtt_user != "" { cfg.MQTT.User = mqtt_user } mqtt_password := os.Getenv("MQTT2PROM_MQTT_PASSWORD") if *usePasswordFromFile { if mqtt_password == "" { logger.Fatal("MQTT2PROM_MQTT_PASSWORD is required") } secret, err := ioutil.ReadFile(mqtt_password) if err != nil { logger.Fatal("unable to read mqtt password from secret file", zap.Error(err)) } cfg.MQTT.Password = string(secret) } else { if mqtt_password != "" { cfg.MQTT.Password = mqtt_password } } mqttClientOptions := mqtt.NewClientOptions() mqttClientOptions.AddBroker(cfg.MQTT.Server).SetCleanSession(true) mqttClientOptions.SetAutoReconnect(true) mqttClientOptions.SetUsername(cfg.MQTT.User) mqttClientOptions.SetPassword(cfg.MQTT.Password) if cfg.MQTT.ClientID != "" { mqttClientOptions.SetClientID(cfg.MQTT.ClientID) } else { mqttClientOptions.SetClientID(mustMQTTClientID()) } if cfg.MQTT.ClientCert != "" || cfg.MQTT.ClientKey != "" { tlsconfig, err := newTLSConfig(cfg) if err != nil { logger.Fatal("Invalid tls certificate settings", zap.Error(err)) } mqttClientOptions.SetTLSConfig(tlsconfig) } collector := metrics.NewCollector(cfg.Cache.Timeout, cfg.Metrics, logger) extractor, err := setupExtractor(cfg) if err != nil { logger.Fatal("could not setup a metric extractor", zap.Error(err)) } ingest := metrics.NewIngest(collector, extractor, cfg.MQTT.DeviceIDRegex) mqttClientOptions.SetOnConnectHandler(ingest.OnConnectHandler) mqttClientOptions.SetConnectionLostHandler(ingest.ConnectionLostHandler) errorChan := make(chan error, 1) for { err = mqttclient.Subscribe(mqttClientOptions, mqttclient.SubscribeOptions{ Topic: cfg.MQTT.TopicPath, QoS: cfg.MQTT.QoS, OnMessageReceived: ingest.SetupSubscriptionHandler(errorChan), Logger: logger, }) if err == nil { // connected, break loop break } logger.Warn("could not connect to mqtt broker, sleep 10 second", zap.Error(err)) time.Sleep(10 * time.Second) } prometheus.MustRegister(ingest.Collector()) prometheus.MustRegister(collector) http.Handle("/metrics", promhttp.Handler()) s := &http.Server{ Addr: getListenAddress(), Handler: http.DefaultServeMux, } go func() { err = web.ListenAndServe(s, *webConfigFlag, setupGoKitLogger(logger)) if err != nil { logger.Fatal("Error while serving http", zap.Error(err)) } }() for { select { case <-c: logger.Info("Terminated via Signal. Stop.") os.Exit(0) case err = <-errorChan: logger.Error("Error while processing message", zap.Error(err)) } } } func getListenAddress() string { return fmt.Sprintf("%s:%s", *addressFlag, *portFlag) } func mustShowVersion() { versionInfo := struct { Version string Commit string Date string }{ Version: version, Commit: commit, Date: date, } err := json.NewEncoder(os.Stdout).Encode(versionInfo) if err != nil { panic(err) } } func mustMQTTClientID() string { host, err := os.Hostname() if err != nil { panic(fmt.Sprintf("failed to get hostname: %v", err)) } pid := os.Getpid() return fmt.Sprintf("%s-%d", host, pid) } func mustSetupLogger() *zap.Logger { cfg := zap.NewProductionConfig() cfg.Level = zap.NewAtomicLevelAt(*logLevelFlag) cfg.Encoding = *logEncodingFlag if cfg.Encoding == "console" { cfg.EncoderConfig.EncodeTime = zapcore.RFC3339TimeEncoder } logger, err := cfg.Build() if err != nil { panic(fmt.Sprintf("failed to build logger: %v", err)) } config.SetProcessContext(logger) return logger } func setupGoKitLogger(l *zap.Logger) log.Logger { return kitzap.NewZapSugarLogger(l, zap.NewAtomicLevelAt(*logLevelFlag).Level()) } func setupExtractor(cfg config.Config) (metrics.Extractor, error) { parser := metrics.NewParser(cfg.Metrics, cfg.JsonParsing.Separator) if cfg.MQTT.ObjectPerTopicConfig != nil { switch cfg.MQTT.ObjectPerTopicConfig.Encoding { case config.EncodingJSON: return metrics.NewJSONObjectExtractor(parser), nil default: return nil, fmt.Errorf("unsupported object format: %s", cfg.MQTT.ObjectPerTopicConfig.Encoding) } } if cfg.MQTT.MetricPerTopicConfig != nil { return metrics.NewMetricPerTopicExtractor(parser, cfg.MQTT.MetricPerTopicConfig.MetricNameRegex), nil } return nil, fmt.Errorf("no extractor configured") } func newTLSConfig(cfg config.Config) (*tls.Config, error) { certpool := x509.NewCertPool() if cfg.MQTT.CACert != "" { pemCerts, err := ioutil.ReadFile(cfg.MQTT.CACert) if err != nil { return nil, fmt.Errorf("failed to load ca_cert file: %w", err) } certpool.AppendCertsFromPEM(pemCerts) } cert, err := tls.LoadX509KeyPair(cfg.MQTT.ClientCert, cfg.MQTT.ClientKey) if err != nil { return nil, fmt.Errorf("failed to load client certificate: %w", err) } cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) if err != nil { return nil, fmt.Errorf("failed to parse client certificate: %w", err) } return &tls.Config{ RootCAs: certpool, InsecureSkipVerify: false, Certificates: []tls.Certificate{cert}, }, nil } prometheus-mqtt-exporter-0.1.7/config.yaml.dist000066400000000000000000000075601436576364100216650ustar00rootroot00000000000000mqtt: # The MQTT broker to connect to server: tcp://127.0.0.1:1883 # Optional: Username and Password for authenticating with the MQTT Server # user: bob # password: happylittleclouds # Optional: for TLS client certificates # ca_cert: certs/AmazonRootCA1.pem # client_cert: certs/xxxxx-certificate.pem.crt # client_key: certs/xxxxx-private.pem.key # Optional: Used to specify ClientID. The default is - # client_id: somedevice # The Topic path to subscribe to. Be aware that you have to specify the wildcard. topic_path: v1/devices/me/+ # Optional: Regular expression to extract the device ID from the topic path. The default regular expression, assumes # that the last "element" of the topic_path is the device id. # The regular expression must contain a named capture group with the name deviceid # For example the expression for tasamota based sensors is "tele/(?P.*)/.*" # device_id_regex: "(.*/)?(?P.*)" # The MQTT QoS level qos: 0 cache: # Timeout. Each received metric will be presented for this time if no update is send via MQTT. # Set the timeout to -1 to disable the deletion of metrics from the cache. The exporter presents the ingest timestamp # to prometheus. timeout: 24h json_parsing: # Separator. Used to split path to elements when accessing json fields. # You can access json fields with dots in it. F.E. {"key.name": {"nested": "value"}} # Just set separator to -> and use key.name->nested as mqtt_name separator: . # This is a list of valid metrics. Only metrics listed here will be exported metrics: # The name of the metric in prometheus - prom_name: temperature # The name of the metric in a MQTT JSON message mqtt_name: temperature # The prometheus help text for this metric help: DHT22 temperature reading # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: dht22 # The name of the metric in prometheus - prom_name: humidity # The name of the metric in a MQTT JSON message mqtt_name: humidity # The prometheus help text for this metric help: DHT22 humidity reading # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: dht22 # The name of the metric in prometheus - prom_name: heat_index # The name of the metric in a MQTT JSON message mqtt_name: heat_index # The prometheus help text for this metric help: DHT22 heatIndex calculation # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: dht22 # The name of the metric in prometheus - prom_name: state # The name of the metric in a MQTT JSON message mqtt_name: state # Regular expression to only match sensors with the given name pattern sensor_name_filter: "^.*-light$" # The prometheus help text for this metric help: Light state # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: ikea # When specified, enables mapping between string values to metric values. string_value_mapping: # A map of string to metric value. map: off: 0 low: 0 # Metric value to use if a match cannot be found in the map above. # If not specified, parsing error will occur. error_value: 1 prometheus-mqtt-exporter-0.1.7/docs/000077500000000000000000000000001436576364100175125ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/docs/overview000066400000000000000000000025531436576364100213100ustar00rootroot000000000000003ZjLcpswFIafhqUzXIxNlrXjpNNJpmmdSdtVR8AJKAFEhYjtPn2lIsCKwJc2duNuPOiXkMT3H+lINpxpuryiKI9vSAiJYZvh0nAuDNs+dz3+K4RVJYzdcSVEFIeVZLXCHP8EKZpSLXEIhdKQEZIwnKtiQLIMAqZoiFKyUJs9kEQdNUcRaMI8QImufsEhi6VqmWZb8R5wFMuhPVdW+Ch4iigpMzleRjKoalJUdyObFjEKyWJNcmaGM6WEsOopXU4hEVRrYtV7lz21zZQpZGyXF54/LL59vrK/e/fTQV7ef51fPl4M7KqXZ5SUEsXNp7s7rkwoeQIqZ85WNSj+Ebl4DFYJzkKgjuFMFjFmMM9RICoWPD64FrM04SWLP/qCDoTXfiM0zD6WjHcDUi+qsLBc/hxiyn3GJONCQUqBciKnCpTBspeB1ZDlsQokBUZXvIl8YVgbKuPU8mR5seZ6bVi8ZvhYakgGWtR03RLnDxL6HgaMNQM05oJV3vvxckkgv25u7gulgVBDGbs6FLMDyuhQUBwNyhyygujhSKvY4gVzeyDuFD/9Jun81vh4HXiGh8IzPA08dS8voktfcUeF5/bAK94mPVulZ/9jeucaJQh5JpVFQllMIpKhZNaqE5Vj2+aakFzSewTGVvJYgEpGutiKgTZv+3xepKQBbHefIRrBJlPsblMoJIjhZ3Uerw65XiJrMXpb+gku4k7618jnpzGFGEpwJFJnwAHxLO5MRGBift55JytSHIaVOcDTrkwewp6c4Iz9/iB3YrgXmyJ7l8zTH0S9ET8wz3gGspSwH8jizvxl77fia9aakIeHgvv+0qBmEn/hmZ63T2tlWPW5+20vjY6jQekXAcU+6Dv4Sa4Oqwew7N48sz1r/Kqro574UE3U6vsHXDqW5mn6gzE7p6LrGMpjpObNR+SR/fLe0HFE7ro3HOyIbLmnvt8Md9xvrB5rjrThjLTgvJqJC7LojY+Ag/9l23G3ZWX7fKRm5beflPWb0m3/pnKs/zZeYz8a/tG1wNt/N+LF9i+qypf2H0Bn9gs=prometheus-mqtt-exporter-0.1.7/docs/overview.drawio.svg000066400000000000000000001054211436576364100233700ustar00rootroot00000000000000
Sensor
Sensor
Sensor
Sensor
Sensors
Sensors
Publish
Publish
Subscribes
Subscribes
mqtt2prometheus
mqtt2prometheus
GET /metrics
GET /metrics
Broker
Viewer does not support full SVG 1.1
prometheus-mqtt-exporter-0.1.7/examples/000077500000000000000000000000001436576364100204005ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/examples/gosund_sp111.yaml000066400000000000000000000036371436576364100235210ustar00rootroot00000000000000# Settings for the MQTT Client. Currently only these three are supported mqtt: # The MQTT broker to connect to server: tcp://192.168.1.11:1883 # Optional: Username and Password for authenticating with the MQTT Server # user: bob # password: happylittleclouds # The Topic path to subscribe to. Be aware that you have to specify the wildcard. topic_path: tele/+/SENSOR # Optional: Regular expression to extract the device ID from the topic path. The default regular expression, assumes # that the last "element" of the topic_path is the device id. # The regular expression must contain a named capture group with the name deviceid # For example the expression for tasamota based sensors is "tele/(?P.*)/.*" device_id_regex: "tele/(?P.*)/SENSOR" # The MQTT QoS level qos: 0 cache: # Timeout. Each received metric will be presented for this time if no update is send via MQTT. # Set the timeout to -1 to disable the deletion of metrics from the cache. The exporter presents the ingest timestamp # to prometheus. timeout: 24h # This is a list of valid metrics. Only metrics listed here will be exported metrics: # The name of the metric in prometheus - prom_name: consumed_energy_kilowatthours_total mqtt_name: "ENERGY.Total" help: "total measured kilowatthours since flash" type: counter - prom_name: voltage_volts mqtt_name: "ENERGY.Voltage" help: "Currently measured voltage" type: gauge - prom_name: current_amperes mqtt_name: "ENERGY.Current" help: "Currently measured current" type: gauge - prom_name: power_watts mqtt_name: "ENERGY.Power" help: "Currently measured power" type: gauge - prom_name: apparent_power_watt mqtt_name: "ENERGY.ApparentPower" help: "Currently apparent power" type: gauge - prom_name: reactive_power_watt mqtt_name: "ENERGY.ReactivePower" help: "Currently reactive power" type: gaugeprometheus-mqtt-exporter-0.1.7/examples/shelly_3em.yaml000066400000000000000000000040471436576364100233350ustar00rootroot00000000000000# Sample mqtt messages processed by this configuration file, # $ mosquitto_sub -t "shellies/shellyem3-123456789/emeter/+/+" -v # # shellies/shellyem3-123456789/emeter/0/power 41.25 # shellies/shellyem3-123456789/emeter/0/pf 0.18 # shellies/shellyem3-123456789/emeter/0/current 0.99 # shellies/shellyem3-123456789/emeter/0/voltage 232.25 # shellies/shellyem3-123456789/emeter/0/total 13372.4 # shellies/shellyem3-123456789/emeter/0/total_returned 0.0 # shellies/shellyem3-123456789/emeter/1/power 275.04 # shellies/shellyem3-123456789/emeter/1/pf 0.72 # shellies/shellyem3-123456789/emeter/1/current 1.65 # shellies/shellyem3-123456789/emeter/1/voltage 232.83 # shellies/shellyem3-123456789/emeter/1/total 27948.4 # shellies/shellyem3-123456789/emeter/1/total_returned 0.0 # shellies/shellyem3-123456789/emeter/2/power -2.23 # shellies/shellyem3-123456789/emeter/2/pf -0.02 # shellies/shellyem3-123456789/emeter/2/current 0.39 # shellies/shellyem3-123456789/emeter/2/voltage 233.14 # shellies/shellyem3-123456789/emeter/2/total 4107.8 # shellies/shellyem3-123456789/emeter/2/total_returned 186.9 # Settings for the MQTT Client. Currently only these three are supported mqtt: # The MQTT broker to connect to server: tcp://127.0.0.1:1883 # Optional: Username and Password for authenticating with the MQTT Server # user: bob # password: happylittleclouds # The Topic path to subscribe to. Be aware that you have to specify the wildcard. topic_path: shellies/shellyem3-123456789/emeter/+/+ # Use the phase number as device_id in order to see all three phases in /metrics device_id_regex: "shellies/(.*)/emeter/(?P.*)/.*" # Metrics are being published on a per-topic basis. metric_per_topic_config: metric_name_regex: "shellies/(?P.*)/emeter/(.*)/(?P.*)" # The MQTT QoS level qos: 0 cache: timeout: 60m metrics: - prom_name: power mqtt_name: power type: gauge const_labels: sensor_type: shelly - prom_name: voltage mqtt_name: voltage type: gauge const_labels: sensor_type: shelly prometheus-mqtt-exporter-0.1.7/freebsd/000077500000000000000000000000001436576364100201745ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/freebsd/mqtt2prometheus.rc000066400000000000000000000006101436576364100237020ustar00rootroot00000000000000#!/bin/sh # # PROVIDE: mqtt2prometheus # REQUIRE: NETWORKING DAEMON . /etc/rc.subr name=mqtt2prometheus rcvar=mqtt2prometheus_enable mqtt2prometheus_config="/usr/local/etc/mqtt2prometheus/config.yaml" command="/usr/local/bin/mqtt2prometheus" start_cmd="/usr/sbin/daemon -T mqtt2prometheus -u nobody -c $command -config=${mqtt2prometheus_config}" load_rc_config $name run_rc_command "$1" prometheus-mqtt-exporter-0.1.7/fuzzing/000077500000000000000000000000001436576364100202565ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/fuzzing/.gitignore000066400000000000000000000000501436576364100222410ustar00rootroot00000000000000corpus crashers supressions fuzz-target prometheus-mqtt-exporter-0.1.7/fuzzing/json_per_topic/000077500000000000000000000000001436576364100232735ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/fuzzing/json_per_topic/fuzz.go000066400000000000000000000014221436576364100246170ustar00rootroot00000000000000// +build gofuzz package json import ( "github.com/hikhvar/mqtt2prometheus/pkg/config" "github.com/hikhvar/mqtt2prometheus/pkg/metrics" ) func Fuzz(data []byte) int { p := metrics.NewParser([]config.MetricConfig{ { PrometheusName: "temperature", ValueType: "gauge", }, { PrometheusName: "enabled", ValueType: "gauge", StringValueMapping: &config.StringValueMappingConfig{ ErrorValue: floatP(12333), Map: map[string]float64{ "foo": 112, "bar": 2, }, }, }, { PrometheusName: "kartoffeln", ValueType: "counter", }, }, ".") json := metrics.NewJSONObjectExtractor(p) mc, err := json("foo", data, "bar") if err != nil && len(mc) > 0 { return 1 } return 0 } func floatP(f float64) *float64 { return &f } prometheus-mqtt-exporter-0.1.7/fuzzing/metric_per_topic/000077500000000000000000000000001436576364100236055ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/fuzzing/metric_per_topic/fuzz.go000066400000000000000000000020621436576364100251320ustar00rootroot00000000000000// +build gofuzz package metric_per_topic import ( "fmt" "github.com/hikhvar/mqtt2prometheus/pkg/config" "github.com/hikhvar/mqtt2prometheus/pkg/metrics" ) func Fuzz(data []byte) int { p := metrics.NewParser([]config.MetricConfig{ { PrometheusName: "temperature", ValueType: "gauge", }, { PrometheusName: "enabled", ValueType: "gauge", StringValueMapping: &config.StringValueMappingConfig{ ErrorValue: floatP(12333), Map: map[string]float64{ "foo": 112, "bar": 2, }, }, }, { PrometheusName: "kartoffeln", ValueType: "counter", }, }) json := metrics.NewMetricPerTopicExtractor(p, config.MustNewRegexp("shellies/(?P.*)/sensor/(?P.*)")) name := "enabled" consumed := 0 if len(data) > 0 { name = []string{"temperature", "enabled", "kartoffel"}[data[0]%3] consumed += 1 } mc, err := json(fmt.Sprintf("shellies/bar/sensor/%s", name), data[consumed:], "bar") if err != nil && len(mc) > 0 { return 1 } return 0 } func floatP(f float64) *float64 { return &f } prometheus-mqtt-exporter-0.1.7/fuzzing/start.sh000077500000000000000000000001301436576364100217440ustar00rootroot00000000000000#!/bin/sh go-fuzz-build -o fuzz-target && go-fuzz -bin fuzz-target rm -rf fuzz-target prometheus-mqtt-exporter-0.1.7/go.mod000066400000000000000000000005671436576364100177000ustar00rootroot00000000000000module github.com/hikhvar/mqtt2prometheus go 1.14 require ( github.com/eclipse/paho.mqtt.golang v1.3.5 github.com/go-kit/kit v0.10.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.11.0 github.com/prometheus/exporter-toolkit v0.7.3 github.com/thedevsaddam/gojsonq/v2 v2.5.2 go.uber.org/zap v1.16.0 gopkg.in/yaml.v2 v2.4.0 ) prometheus-mqtt-exporter-0.1.7/go.sum000066400000000000000000002106631436576364100177250ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y= github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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.3/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/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 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/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.29.0 h1:3jqPBvKT4OHAbje2Ql7KeaaSicDBCxMYwEJU1zRJceE= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/exporter-toolkit v0.7.3 h1:IYBn0CTGi/nYxstdTUKysuSofUNJ3DQW3FmZ/Ub6rgU= github.com/prometheus/exporter-toolkit v0.7.3/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= 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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0= github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20181122145206-62eef0e2fa9b/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= prometheus-mqtt-exporter-0.1.7/hack/000077500000000000000000000000001436576364100174705ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/hack/Readme.md000066400000000000000000000012651436576364100212130ustar00rootroot00000000000000# Hack Scenarios Required is a MQTT client. I use this: https://github.com/shirou/mqttcli ## Shelly (Metric Per Topic) The scenario is the feature requested by issue https://github.com/hikhvar/mqtt2prometheus/issues/26. To start the scenario run: ```bash docker-compose --env-file shelly.env up ``` To publish a message run: ```bash mqttcli pub --host localhost -p 1883 -t shellies/living-room/sensor/temperature '15' ``` ## DHT22 (Object Per Topic) The default scenario To start the scenario run: ```bash docker-compose --env-file dht22.env up ``` To publish a message run: ```bash mqttcli pub --host localhost -p 1883 -t v1/devices/me/test -m '{"temperature":"12", "humidity":21}' ```prometheus-mqtt-exporter-0.1.7/hack/dht.env000066400000000000000000000000211436576364100207520ustar00rootroot00000000000000CONFIG=dht22.yamlprometheus-mqtt-exporter-0.1.7/hack/dht22.yaml000066400000000000000000000053571436576364100213110ustar00rootroot00000000000000# Settings for the MQTT Client. Currently only these three are supported mqtt: # The MQTT broker to connect to server: tcp://mosquitto:1883 # Optional: Username and Password for authenticating with the MQTT Server # user: bob # password: happylittleclouds # The Topic path to subscribe to. Be aware that you have to specify the wildcard. topic_path: v1/devices/me/+ # The MQTT QoS level qos: 0 cache: # Timeout. Each received metric will be presented for this time if no update is send via MQTT timeout: 60m # This is a list of valid metrics. Only metrics listed here will be exported metrics: # The name of the metric in prometheus - prom_name: temperature # The name of the metric in a MQTT JSON message mqtt_name: temperature # The prometheus help text for this metric help: DHT22 temperature reading # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: dht22 # The name of the metric in prometheus - prom_name: humidity # The name of the metric in a MQTT JSON message mqtt_name: humidity # The prometheus help text for this metric help: DHT22 humidity reading # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: dht22 # The name of the metric in prometheus - prom_name: heat_index # The name of the metric in a MQTT JSON message mqtt_name: heat_index # The prometheus help text for this metric help: DHT22 heatIndex calculation # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: dht22 - prom_name: state # The name of the metric in a MQTT JSON message mqtt_name: state # The prometheus help text for this metric help: Light state # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: ikea # When specified, enables mapping between string values to metric values. string_value_mapping: # A map of string to metric value. map: off: 0 low: 0 # Metric value to use if a match cannot be found in the map above. # If not specified, parsing error will occur. error_value: 1 prometheus-mqtt-exporter-0.1.7/hack/docker-compose.yml000066400000000000000000000011551436576364100231270ustar00rootroot00000000000000version: "3.8" services: mqtt2prometheus: build: context: ../ dockerfile: Dockerfile command: - /mqtt2prometheus - -log-level - debug - -config - /config.yaml ports: - 9641:9641 volumes: - type: bind source: ./${CONFIG:-dht22.yaml} target: /config.yaml mosquitto: image: eclipse-mosquitto:1.6.15 ports: - 1883:1883 - 9001:9001 prometheus: image: prom/prometheus:v2.40.7 ports: - 9090:9090 volumes: - type: bind source: ./prometheus.yml target: /etc/prometheus/prometheus.yml prometheus-mqtt-exporter-0.1.7/hack/prometheus.yml000066400000000000000000000012201436576364100224010ustar00rootroot00000000000000global: scrape_interval: 15s scrape_timeout: 10s evaluation_interval: 15s alerting: alertmanagers: - static_configs: - targets: [] scheme: http timeout: 10s api_version: v1 scrape_configs: - job_name: prometheus honor_timestamps: true scrape_interval: 15s scrape_timeout: 10s metrics_path: /metrics scheme: http static_configs: - targets: - localhost:9090 - job_name: mqtt2prometheus honor_timestamps: true scrape_interval: 15s scrape_timeout: 10s metrics_path: /metrics scheme: http static_configs: - targets: - mqtt2prometheus:9641 prometheus-mqtt-exporter-0.1.7/hack/shelly.env000066400000000000000000000000221436576364100214740ustar00rootroot00000000000000CONFIG=shelly.yamlprometheus-mqtt-exporter-0.1.7/hack/shelly.yaml000066400000000000000000000032471436576364100216620ustar00rootroot00000000000000mqtt: server: tcp://mosquitto:1883 topic_path: shellies/+/sensor/+ device_id_regex: "shellies/(?P.*)/sensor" metric_per_topic_config: metric_name_regex: "shellies/(?P.*)/sensor/(?P.*)" qos: 0 cache: timeout: 24h metrics: - prom_name: temperature # The name of the metric in a MQTT JSON message mqtt_name: temperature # The prometheus help text for this metric help: shelly temperature reading # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: shelly # The name of the metric in prometheus - prom_name: humidity # The name of the metric in a MQTT JSON message mqtt_name: humidity # The prometheus help text for this metric help: shelly humidity reading # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: shelly # The name of the metric in prometheus - prom_name: battery # The name of the metric in a MQTT JSON message mqtt_name: battery # The prometheus help text for this metric help: shelly battery reading # The prometheus type for this metric. Valid values are: "gauge" and "counter" type: gauge # A map of string to string for constant labels. This labels will be attached to every prometheus metric const_labels: sensor_type: shelly # The name of the metric in prometheus prometheus-mqtt-exporter-0.1.7/pkg/000077500000000000000000000000001436576364100173435ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/pkg/config/000077500000000000000000000000001436576364100206105ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/pkg/config/config.go000066400000000000000000000142471436576364100224140ustar00rootroot00000000000000package config import ( "fmt" "io/ioutil" "regexp" "time" "github.com/prometheus/client_golang/prometheus" "gopkg.in/yaml.v2" ) const ( GaugeValueType = "gauge" CounterValueType = "counter" DeviceIDRegexGroup = "deviceid" MetricNameRegexGroup = "metricname" ) var MQTTConfigDefaults = MQTTConfig{ Server: "tcp://127.0.0.1:1883", TopicPath: "v1/devices/me", DeviceIDRegex: MustNewRegexp(fmt.Sprintf("(.*/)?(?P<%s>.*)", DeviceIDRegexGroup)), QoS: 0, } var CacheConfigDefaults = CacheConfig{ Timeout: 2 * time.Minute, } var JsonParsingConfigDefaults = JsonParsingConfig{ Separator: ".", } type Regexp struct { r *regexp.Regexp pattern string } func (rf *Regexp) UnmarshalYAML(unmarshal func(interface{}) error) error { var pattern string if err := unmarshal(&pattern); err != nil { return err } r, err := regexp.Compile(pattern) if err != nil { return err } rf.r = r rf.pattern = pattern return nil } func (rf *Regexp) MarshalYAML() (interface{}, error) { if rf == nil { return "", nil } return rf.pattern, nil } func (rf *Regexp) Match(s string) bool { return rf == nil || rf.r == nil || rf.r.MatchString(s) } // GroupValue returns the value of the given group. If the group is not part of the underlying regexp, returns the empty string. func (rf *Regexp) GroupValue(s string, groupName string) string { match := rf.r.FindStringSubmatch(s) groupValues := make(map[string]string) for i, name := range rf.r.SubexpNames() { if len(match) > i && name != "" { groupValues[name] = match[i] } } return groupValues[groupName] } func (rf *Regexp) RegEx() *regexp.Regexp { return rf.r } func MustNewRegexp(pattern string) *Regexp { return &Regexp{ pattern: pattern, r: regexp.MustCompile(pattern), } } type Config struct { JsonParsing *JsonParsingConfig `yaml:"json_parsing,omitempty"` Metrics []MetricConfig `yaml:"metrics"` MQTT *MQTTConfig `yaml:"mqtt,omitempty"` Cache *CacheConfig `yaml:"cache,omitempty"` } type CacheConfig struct { Timeout time.Duration `yaml:"timeout"` } type JsonParsingConfig struct { Separator string `yaml:"separator"` } type MQTTConfig struct { Server string `yaml:"server"` TopicPath string `yaml:"topic_path"` DeviceIDRegex *Regexp `yaml:"device_id_regex"` User string `yaml:"user"` Password string `yaml:"password"` QoS byte `yaml:"qos"` ObjectPerTopicConfig *ObjectPerTopicConfig `yaml:"object_per_topic_config"` MetricPerTopicConfig *MetricPerTopicConfig `yaml:"metric_per_topic_config"` CACert string `yaml:"ca_cert"` ClientCert string `yaml:"client_cert"` ClientKey string `yaml:"client_key"` ClientID string `yaml:"client_id"` } const EncodingJSON = "JSON" type ObjectPerTopicConfig struct { Encoding string `yaml:"encoding"` // Currently only JSON is a valid value } type MetricPerTopicConfig struct { MetricNameRegex *Regexp `yaml:"metric_name_regex"` // Default } // Metrics Config is a mapping between a metric send on mqtt to a prometheus metric type MetricConfig struct { PrometheusName string `yaml:"prom_name"` MQTTName string `yaml:"mqtt_name"` SensorNameFilter Regexp `yaml:"sensor_name_filter"` Help string `yaml:"help"` ValueType string `yaml:"type"` OmitTimestamp bool `yaml:"omit_timestamp"` ConstantLabels map[string]string `yaml:"const_labels"` StringValueMapping *StringValueMappingConfig `yaml:"string_value_mapping"` MQTTValueScale float64 `yaml:"mqtt_value_scale"` } // StringValueMappingConfig defines the mapping from string to float type StringValueMappingConfig struct { // ErrorValue is used when no mapping is found in Map ErrorValue *float64 `yaml:"error_value"` Map map[string]float64 `yaml:"map"` } func (mc *MetricConfig) PrometheusDescription() *prometheus.Desc { return prometheus.NewDesc( mc.PrometheusName, mc.Help, []string{"sensor", "topic"}, mc.ConstantLabels, ) } func (mc *MetricConfig) PrometheusValueType() prometheus.ValueType { switch mc.ValueType { case GaugeValueType: return prometheus.GaugeValue case CounterValueType: return prometheus.CounterValue default: return prometheus.UntypedValue } } func LoadConfig(configFile string) (Config, error) { configData, err := ioutil.ReadFile(configFile) if err != nil { return Config{}, err } var cfg Config if err = yaml.UnmarshalStrict(configData, &cfg); err != nil { return cfg, err } if cfg.MQTT == nil { cfg.MQTT = &MQTTConfigDefaults } if cfg.Cache == nil { cfg.Cache = &CacheConfigDefaults } if cfg.JsonParsing == nil { cfg.JsonParsing = &JsonParsingConfigDefaults } if cfg.MQTT.DeviceIDRegex == nil { cfg.MQTT.DeviceIDRegex = MQTTConfigDefaults.DeviceIDRegex } var validRegex bool for _, name := range cfg.MQTT.DeviceIDRegex.RegEx().SubexpNames() { if name == DeviceIDRegexGroup { validRegex = true } } if !validRegex { return Config{}, fmt.Errorf("device id regex %q does not contain required regex group %q", cfg.MQTT.DeviceIDRegex.pattern, DeviceIDRegexGroup) } if cfg.MQTT.ObjectPerTopicConfig != nil && cfg.MQTT.MetricPerTopicConfig != nil { return Config{}, fmt.Errorf("only one of object_per_topic_config and metric_per_topic_config can be specified") } if cfg.MQTT.ObjectPerTopicConfig == nil && cfg.MQTT.MetricPerTopicConfig == nil { cfg.MQTT.ObjectPerTopicConfig = &ObjectPerTopicConfig{ Encoding: EncodingJSON, } } if cfg.MQTT.MetricPerTopicConfig != nil { validRegex = false for _, name := range cfg.MQTT.MetricPerTopicConfig.MetricNameRegex.RegEx().SubexpNames() { if name == MetricNameRegexGroup { validRegex = true } } if !validRegex { return Config{}, fmt.Errorf("metric name regex %q does not contain required regex group %q", cfg.MQTT.DeviceIDRegex.pattern, MetricNameRegexGroup) } } return cfg, nil } prometheus-mqtt-exporter-0.1.7/pkg/config/config_test.go000066400000000000000000000052271436576364100234510ustar00rootroot00000000000000package config import ( "reflect" "testing" ) func TestRegexp_GroupValue(t *testing.T) { type args struct { s string groupName string } tests := []struct { name string pattern string args args want string }{ { name: "normal match", pattern: "(.*/)?(?P.*)", args: args{ s: "foo/bar", groupName: "deviceid", }, want: "bar", }, { name: "two groups", pattern: "(.*/)?(?P.*)/(?P.*)", args: args{ s: "foo/bar/batz", groupName: "deviceid", }, want: "bar", }, { name: "empty string match", pattern: "(.*/)?(?P.*)", args: args{ s: "", groupName: "deviceid", }, want: "", }, { name: "not match", pattern: "(.*)/(?P.*)", args: args{ s: "bar", groupName: "deviceid", }, want: "", }, { name: "empty pattern", pattern: "", args: args{ s: "bar", groupName: "deviceid", }, want: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rf := MustNewRegexp(tt.pattern) if got := rf.GroupValue(tt.args.s, tt.args.groupName); got != tt.want { t.Errorf("GroupValue() = %v, want %v", got, tt.want) } }) } } func TestRegexp_Match(t *testing.T) { tests := []struct { name string regex *Regexp args string want bool }{ { name: "nil regex matches everything", regex: nil, args: "foo", want: true, }, { name: "empty regex matches everything", regex: &Regexp{}, args: "foo", want: true, }, { name: "regex matches", regex: MustNewRegexp(".*"), args: "foo", want: true, }, { name: "regex matches", regex: MustNewRegexp("a.*"), args: "foo", want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rf := tt.regex if got := rf.Match(tt.args); got != tt.want { t.Errorf("Match() = %v, want %v", got, tt.want) } }) } } func TestRegexp_MarshalYAML(t *testing.T) { tests := []struct { name string regex *Regexp want interface{} wantErr bool }{ { name: "empty", regex: nil, want: "", wantErr: false, }, { name: "with pattern", regex: MustNewRegexp("a.*"), want: "a.*", wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.regex.MarshalYAML() if (err != nil) != tt.wantErr { t.Errorf("MarshalYAML() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("MarshalYAML() got = %v, want %v", got, tt.want) } }) } } prometheus-mqtt-exporter-0.1.7/pkg/config/runtime.go000066400000000000000000000005571436576364100226310ustar00rootroot00000000000000package config import "go.uber.org/zap" // runtimeContext contains process global settings like the logger, type runtimeContext struct { logger *zap.Logger } func (r *runtimeContext) Logger() *zap.Logger { return r.logger } var ProcessContext runtimeContext func SetProcessContext(logger *zap.Logger) { ProcessContext = runtimeContext{ logger: logger, } } prometheus-mqtt-exporter-0.1.7/pkg/metrics/000077500000000000000000000000001436576364100210115ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/pkg/metrics/collector.go000066400000000000000000000041021436576364100233230ustar00rootroot00000000000000package metrics import ( "fmt" "time" "github.com/hikhvar/mqtt2prometheus/pkg/config" gocache "github.com/patrickmn/go-cache" "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" ) type Collector interface { prometheus.Collector Observe(deviceID string, collection MetricCollection) } type MemoryCachedCollector struct { cache *gocache.Cache descriptions []*prometheus.Desc logger *zap.Logger } type Metric struct { Description *prometheus.Desc Value float64 ValueType prometheus.ValueType IngestTime time.Time Topic string } type CacheItem struct { DeviceID string Metric Metric } type MetricCollection []Metric func NewCollector(defaultTimeout time.Duration, possibleMetrics []config.MetricConfig, logger *zap.Logger) Collector { var descs []*prometheus.Desc for _, m := range possibleMetrics { descs = append(descs, m.PrometheusDescription()) } return &MemoryCachedCollector{ cache: gocache.New(defaultTimeout, defaultTimeout*10), descriptions: descs, logger: logger, } } func (c *MemoryCachedCollector) Observe(deviceID string, collection MetricCollection) { for _, m := range collection { item := CacheItem{ DeviceID: deviceID, Metric: m, } c.cache.Set(fmt.Sprintf("%s-%s", deviceID, m.Description.String()), item, gocache.DefaultExpiration) } } func (c *MemoryCachedCollector) Describe(ch chan<- *prometheus.Desc) { for i := range c.descriptions { ch <- c.descriptions[i] } } func (c *MemoryCachedCollector) Collect(mc chan<- prometheus.Metric) { for _, metricsRaw := range c.cache.Items() { item := metricsRaw.Object.(CacheItem) device, metric := item.DeviceID, item.Metric if metric.Description == nil { c.logger.Warn("empty description", zap.String("topic", metric.Topic), zap.Float64("value", metric.Value)) } m := prometheus.MustNewConstMetric( metric.Description, metric.ValueType, metric.Value, device, metric.Topic, ) if metric.IngestTime.IsZero() { mc <- m } else { mc <- prometheus.NewMetricWithTimestamp(metric.IngestTime, m) } } } prometheus-mqtt-exporter-0.1.7/pkg/metrics/extractor.go000066400000000000000000000031161436576364100233540ustar00rootroot00000000000000package metrics import ( "fmt" "github.com/hikhvar/mqtt2prometheus/pkg/config" gojsonq "github.com/thedevsaddam/gojsonq/v2" ) type Extractor func(topic string, payload []byte, deviceID string) (MetricCollection, error) func NewJSONObjectExtractor(p Parser) Extractor { return func(topic string, payload []byte, deviceID string) (MetricCollection, error) { var mc MetricCollection parsed := gojsonq.New(gojsonq.SetSeparator(p.separator)).FromString(string(payload)) for path := range p.config() { rawValue := parsed.Find(path) parsed.Reset() if rawValue == nil { continue } // Find a valid metrics config config, found := p.findMetricConfig(path, deviceID) if !found { continue } m, err := p.parseMetric(config, rawValue) if err != nil { return nil, fmt.Errorf("failed to parse valid metric value: %w", err) } m.Topic = topic mc = append(mc, m) } return mc, nil } } func NewMetricPerTopicExtractor(p Parser, metricNameRegex *config.Regexp) Extractor { return func(topic string, payload []byte, deviceID string) (MetricCollection, error) { metricName := metricNameRegex.GroupValue(topic, config.MetricNameRegexGroup) if metricName == "" { return nil, fmt.Errorf("failed to find valid metric in topic path") } // Find a valid metrics config config, found := p.findMetricConfig(metricName, deviceID) if !found { return nil, nil } m, err := p.parseMetric(config, string(payload)) if err != nil { return nil, fmt.Errorf("failed to parse metric: %w", err) } m.Topic = topic return MetricCollection{m}, nil } } prometheus-mqtt-exporter-0.1.7/pkg/metrics/extractor_test.go000066400000000000000000000075061436576364100244220ustar00rootroot00000000000000package metrics import ( "reflect" "testing" "github.com/hikhvar/mqtt2prometheus/pkg/config" "github.com/prometheus/client_golang/prometheus" ) func TestNewJSONObjectExtractor_parseMetric(t *testing.T) { now = testNow type fields struct { metricConfigs map[string][]config.MetricConfig } type args struct { metricPath string deviceID string value string } tests := []struct { name string separator string fields fields args args want Metric wantErr bool noValue bool }{ { name: "string value", separator: "->", fields: fields{ map[string][]config.MetricConfig{ "SDS0X1->PM2->5": []config.MetricConfig{ { PrometheusName: "temperature", MQTTName: "SDS0X1.PM2.5", ValueType: "gauge", }, }, }, }, args: args{ metricPath: "topic", deviceID: "dht22", value: "{\"SDS0X1\":{\"PM2\":{\"5\":4.9}}}", }, want: Metric{ Description: prometheus.NewDesc("temperature", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 4.9, IngestTime: testNow(), Topic: "topic", }, }, { name: "string value with dots in path", separator: "->", fields: fields{ map[string][]config.MetricConfig{ "SDS0X1->PM2.5": []config.MetricConfig{ { PrometheusName: "temperature", MQTTName: "SDS0X1->PM2.5", ValueType: "gauge", }, }, }, }, args: args{ metricPath: "topic", deviceID: "dht22", value: "{\"SDS0X1\":{\"PM2.5\":4.9,\"PM10\":8.5}}", }, want: Metric{ Description: prometheus.NewDesc("temperature", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 4.9, IngestTime: testNow(), Topic: "topic", }, }, { name: "metric matching SensorNameFilter", separator: ".", fields: fields{ map[string][]config.MetricConfig{ "temperature": []config.MetricConfig{ { PrometheusName: "temperature", MQTTName: "temperature", ValueType: "gauge", SensorNameFilter: *config.MustNewRegexp(".*22$"), }, }, }, }, args: args{ metricPath: "topic", deviceID: "dht22", value: "{\"temperature\": 8.5}", }, want: Metric{ Description: prometheus.NewDesc("temperature", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 8.5, IngestTime: testNow(), Topic: "topic", }, }, { name: "metric not matching SensorNameFilter", separator: ".", fields: fields{ map[string][]config.MetricConfig{ "temperature": []config.MetricConfig{ { PrometheusName: "temperature", MQTTName: "temperature", ValueType: "gauge", SensorNameFilter: *config.MustNewRegexp(".*fail$"), }, }, }, }, args: args{ metricPath: "topic", deviceID: "dht22", value: "{\"temperature\": 8.5}", }, want: Metric{}, noValue: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := Parser{ separator: tt.separator, metricConfigs: tt.fields.metricConfigs, } extractor := NewJSONObjectExtractor(p) got, err := extractor(tt.args.metricPath, []byte(tt.args.value), tt.args.deviceID) if (err != nil) != tt.wantErr { t.Errorf("parseMetric() error = %v, wantErr %v", err, tt.wantErr) return } if len(got) == 0 { if !tt.noValue { t.Errorf("parseMetric() got = %v, want %v", nil, tt.want) } } else if !reflect.DeepEqual(got[0], tt.want) { t.Errorf("parseMetric() got = %v, want %v", got[0], tt.want) } else if len(got) > 1 { t.Errorf("unexpected result got = %v, want %v", got, tt.want) } }) } } prometheus-mqtt-exporter-0.1.7/pkg/metrics/ingest.go000066400000000000000000000031101436576364100226240ustar00rootroot00000000000000package metrics import ( "fmt" "go.uber.org/zap" "github.com/eclipse/paho.mqtt.golang" "github.com/hikhvar/mqtt2prometheus/pkg/config" ) type Ingest struct { instrumentation extractor Extractor deviceIDRegex *config.Regexp collector Collector logger *zap.Logger } func NewIngest(collector Collector, extractor Extractor, deviceIDRegex *config.Regexp) *Ingest { return &Ingest{ instrumentation: defaultInstrumentation, extractor: extractor, deviceIDRegex: deviceIDRegex, collector: collector, logger: config.ProcessContext.Logger(), } } func (i *Ingest) store(topic string, payload []byte) error { deviceID := i.deviceID(topic) mc, err := i.extractor(topic, payload, deviceID) if err != nil { return fmt.Errorf("failed to extract metric values from topic: %w", err) } i.collector.Observe(deviceID, mc) return nil } func (i *Ingest) SetupSubscriptionHandler(errChan chan<- error) mqtt.MessageHandler { return func(c mqtt.Client, m mqtt.Message) { i.logger.Debug("Got message", zap.String("topic", m.Topic()), zap.String("payload", string(m.Payload()))) err := i.store(m.Topic(), m.Payload()) if err != nil { errChan <- fmt.Errorf("could not store metrics '%s' on topic %s: %s", string(m.Payload()), m.Topic(), err.Error()) i.CountStoreError(m.Topic()) return } i.CountSuccess(m.Topic()) } } // deviceID uses the configured DeviceIDRegex to extract the device ID from the given mqtt topic path. func (i *Ingest) deviceID(topic string) string { return i.deviceIDRegex.GroupValue(topic, config.DeviceIDRegexGroup) } prometheus-mqtt-exporter-0.1.7/pkg/metrics/instrumentation.go000066400000000000000000000026731436576364100246130ustar00rootroot00000000000000package metrics import ( mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/prometheus/client_golang/prometheus" ) const ( storeError = "storeError" success = "success" ) var defaultInstrumentation = instrumentation{ messageMetric: prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "received_messages", Help: "received messages per topic and status", }, []string{"status", "topic"}, ), connectedMetric: prometheus.NewGauge( prometheus.GaugeOpts{ Name: "mqtt2prometheus_connected", Help: "is the mqtt2prometheus exporter connected to the broker", }, ), } type instrumentation struct { messageMetric *prometheus.CounterVec connectedMetric prometheus.Gauge } func (i *instrumentation) Collector() prometheus.Collector { return i } func (i *instrumentation) Describe(desc chan<- *prometheus.Desc) { prometheus.DescribeByCollect(i, desc) } func (i *instrumentation) Collect(metrics chan<- prometheus.Metric) { i.connectedMetric.Collect(metrics) i.messageMetric.Collect(metrics) } func (i *instrumentation) CountSuccess(topic string) { i.messageMetric.WithLabelValues(success, topic).Inc() } func (i *instrumentation) CountStoreError(topic string) { i.messageMetric.WithLabelValues(storeError, topic).Inc() } func (i *instrumentation) ConnectionLostHandler(client mqtt.Client, err error) { i.connectedMetric.Set(0) } func (i *instrumentation) OnConnectHandler(client mqtt.Client) { i.connectedMetric.Set(1) } prometheus-mqtt-exporter-0.1.7/pkg/metrics/parser.go000066400000000000000000000052501436576364100226360ustar00rootroot00000000000000package metrics import ( "fmt" "strconv" "time" "github.com/hikhvar/mqtt2prometheus/pkg/config" ) type Parser struct { separator string // Maps the mqtt metric name to a list of configs // The first that matches SensorNameFilter will be used metricConfigs map[string][]config.MetricConfig } var now = time.Now func NewParser(metrics []config.MetricConfig, separator string) Parser { cfgs := make(map[string][]config.MetricConfig) for i := range metrics { key := metrics[i].MQTTName cfgs[key] = append(cfgs[key], metrics[i]) } return Parser{ separator: separator, metricConfigs: cfgs, } } // Config returns the underlying metrics config func (p *Parser) config() map[string][]config.MetricConfig { return p.metricConfigs } // validMetric returns config matching the metric and deviceID // Second return value indicates if config was found. func (p *Parser) findMetricConfig(metric string, deviceID string) (config.MetricConfig, bool) { for _, c := range p.metricConfigs[metric] { if c.SensorNameFilter.Match(deviceID) { return c, true } } return config.MetricConfig{}, false } // parseMetric parses the given value according to the given deviceID and metricPath. The config allows to // parse a metric value according to the device ID. func (p *Parser) parseMetric(cfg config.MetricConfig, value interface{}) (Metric, error) { var metricValue float64 if boolValue, ok := value.(bool); ok { if boolValue { metricValue = 1 } else { metricValue = 0 } } else if strValue, ok := value.(string); ok { // If string value mapping is defined, use that if cfg.StringValueMapping != nil { floatValue, ok := cfg.StringValueMapping.Map[strValue] if ok { metricValue = floatValue } else if cfg.StringValueMapping.ErrorValue != nil { metricValue = *cfg.StringValueMapping.ErrorValue } else { return Metric{}, fmt.Errorf("got unexpected string data '%s'", strValue) } } else { // otherwise try to parse float floatValue, err := strconv.ParseFloat(strValue, 64) if err != nil { return Metric{}, fmt.Errorf("got data with unexpectd type: %T ('%s') and failed to parse to float", value, value) } metricValue = floatValue } } else if floatValue, ok := value.(float64); ok { metricValue = floatValue } else { return Metric{}, fmt.Errorf("got data with unexpectd type: %T ('%s')", value, value) } if cfg.MQTTValueScale != 0 { metricValue = metricValue * cfg.MQTTValueScale } var ingestTime time.Time if !cfg.OmitTimestamp { ingestTime = now() } return Metric{ Description: cfg.PrometheusDescription(), Value: metricValue, ValueType: cfg.PrometheusValueType(), IngestTime: ingestTime, }, nil } prometheus-mqtt-exporter-0.1.7/pkg/metrics/parser_test.go000066400000000000000000000234621436576364100237020ustar00rootroot00000000000000package metrics import ( "reflect" "testing" "time" "github.com/hikhvar/mqtt2prometheus/pkg/config" "github.com/prometheus/client_golang/prometheus" ) func TestParser_parseMetric(t *testing.T) { now = testNow type fields struct { metricConfigs map[string][]config.MetricConfig } type args struct { metricPath string deviceID string value interface{} } tests := []struct { name string fields fields args args want Metric wantErr bool }{ { name: "value without timestamp", fields: fields{ map[string][]config.MetricConfig{ "temperature": []config.MetricConfig{ { PrometheusName: "temperature", ValueType: "gauge", OmitTimestamp: true, }, }, }, }, args: args{ metricPath: "temperature", deviceID: "dht22", value: 12.6, }, want: Metric{ Description: prometheus.NewDesc("temperature", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 12.6, IngestTime: time.Time{}, Topic: "", }, }, { name: "string value", fields: fields{ map[string][]config.MetricConfig{ "temperature": []config.MetricConfig{ { PrometheusName: "temperature", ValueType: "gauge", }, }, }, }, args: args{ metricPath: "temperature", deviceID: "dht22", value: "12.6", }, want: Metric{ Description: prometheus.NewDesc("temperature", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 12.6, IngestTime: testNow(), Topic: "", }, }, { name: "scaled string value", fields: fields{ map[string][]config.MetricConfig{ "temperature": []config.MetricConfig{ { PrometheusName: "temperature", ValueType: "gauge", MQTTValueScale: 0.01, }, }, }, }, args: args{ metricPath: "temperature", deviceID: "dht22", value: "12.6", }, want: Metric{ Description: prometheus.NewDesc("temperature", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 0.126, IngestTime: testNow(), Topic: "", }, }, { name: "string value failure", fields: fields{ map[string][]config.MetricConfig{ "temperature": []config.MetricConfig{ { PrometheusName: "temperature", ValueType: "gauge", }, }, }, }, args: args{ metricPath: "temperature", deviceID: "dht22", value: "12.6.5", }, wantErr: true, }, { name: "float value", fields: fields{ map[string][]config.MetricConfig{ "temperature": []config.MetricConfig{ { PrometheusName: "temperature", ValueType: "gauge", }, }, }, }, args: args{ metricPath: "temperature", deviceID: "dht22", value: 12.6, }, want: Metric{ Description: prometheus.NewDesc("temperature", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 12.6, IngestTime: testNow(), Topic: "", }, }, { name: "scaled float value", fields: fields{ map[string][]config.MetricConfig{ "humidity": []config.MetricConfig{ { PrometheusName: "humidity", ValueType: "gauge", MQTTValueScale: 0.01, }, }, }, }, args: args{ metricPath: "humidity", deviceID: "dht22", value: 12.6, }, want: Metric{ Description: prometheus.NewDesc("humidity", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 0.126, IngestTime: testNow(), Topic: "", }, }, { name: "negative scaled float value", fields: fields{ map[string][]config.MetricConfig{ "humidity": []config.MetricConfig{ { PrometheusName: "humidity", ValueType: "gauge", MQTTValueScale: -2, }, }, }, }, args: args{ metricPath: "humidity", deviceID: "dht22", value: 12.6, }, want: Metric{ Description: prometheus.NewDesc("humidity", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: -25.2, IngestTime: testNow(), Topic: "", }, }, { name: "bool value true", fields: fields{ map[string][]config.MetricConfig{ "enabled": []config.MetricConfig{ { PrometheusName: "enabled", ValueType: "gauge", }, }, }, }, args: args{ metricPath: "enabled", deviceID: "dht22", value: true, }, want: Metric{ Description: prometheus.NewDesc("enabled", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 1, IngestTime: testNow(), Topic: "", }, }, { name: "scaled bool value", fields: fields{ map[string][]config.MetricConfig{ "enabled": []config.MetricConfig{ { PrometheusName: "enabled", ValueType: "gauge", MQTTValueScale: 0.5, }, }, }, }, args: args{ metricPath: "enabled", deviceID: "dht22", value: true, }, want: Metric{ Description: prometheus.NewDesc("enabled", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 0.5, IngestTime: testNow(), Topic: "", }, }, { name: "bool value false", fields: fields{ map[string][]config.MetricConfig{ "enabled": []config.MetricConfig{ { PrometheusName: "enabled", ValueType: "gauge", }, }, }, }, args: args{ metricPath: "enabled", deviceID: "dht22", value: false, }, want: Metric{ Description: prometheus.NewDesc("enabled", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 0, IngestTime: testNow(), Topic: "", }, }, { name: "string mapping value success", fields: fields{ map[string][]config.MetricConfig{ "enabled": []config.MetricConfig{ { PrometheusName: "enabled", ValueType: "gauge", StringValueMapping: &config.StringValueMappingConfig{ Map: map[string]float64{ "foo": 112, "bar": 2, }, }, }, }, }, }, args: args{ metricPath: "enabled", deviceID: "dht22", value: "foo", }, want: Metric{ Description: prometheus.NewDesc("enabled", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 112, IngestTime: testNow(), Topic: "", }, }, { name: "string mapping value failure default to error value", fields: fields{ map[string][]config.MetricConfig{ "enabled": []config.MetricConfig{ { PrometheusName: "enabled", ValueType: "gauge", StringValueMapping: &config.StringValueMappingConfig{ ErrorValue: floatP(12333), Map: map[string]float64{ "foo": 112, "bar": 2, }, }, }, }, }, }, args: args{ metricPath: "enabled", deviceID: "dht22", value: "asd", }, want: Metric{ Description: prometheus.NewDesc("enabled", "", []string{"sensor", "topic"}, nil), ValueType: prometheus.GaugeValue, Value: 12333, IngestTime: testNow(), Topic: "", }, }, { name: "string mapping value failure no error value", fields: fields{ map[string][]config.MetricConfig{ "enabled": []config.MetricConfig{ { PrometheusName: "enabled", ValueType: "gauge", StringValueMapping: &config.StringValueMappingConfig{ Map: map[string]float64{ "foo": 112, "bar": 2, }, }, }, }, }, }, args: args{ metricPath: "enabled", deviceID: "dht22", value: "asd", }, wantErr: true, }, { name: "metric not configured", fields: fields{ map[string][]config.MetricConfig{ "enabled": []config.MetricConfig{ { PrometheusName: "enabled", ValueType: "gauge", StringValueMapping: &config.StringValueMappingConfig{ ErrorValue: floatP(12333), Map: map[string]float64{ "foo": 112, "bar": 2, }, }, }, }, }, }, args: args{ metricPath: "enabled1", deviceID: "dht22", value: "asd", }, wantErr: true, }, { name: "unexpected type", fields: fields{ map[string][]config.MetricConfig{ "enabled": []config.MetricConfig{ { PrometheusName: "enabled", ValueType: "gauge", StringValueMapping: &config.StringValueMappingConfig{ ErrorValue: floatP(12333), Map: map[string]float64{ "foo": 112, "bar": 2, }, }, }, }, }, }, args: args{ metricPath: "enabled", deviceID: "dht22", value: []int{3}, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &Parser{ metricConfigs: tt.fields.metricConfigs, } // Find a valid metrics config config, found := p.findMetricConfig(tt.args.metricPath, tt.args.deviceID) if !found { if !tt.wantErr { t.Errorf("MetricConfig not found") } return } got, err := p.parseMetric(config, tt.args.value) if (err != nil) != tt.wantErr { t.Errorf("parseMetric() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("parseMetric() got = %v, want %v", got, tt.want) } }) } } func testNow() time.Time { now, err := time.Parse( time.RFC3339, "2020-11-01T22:08:41+00:00") if err != nil { panic(err) } return now } func floatP(f float64) *float64 { return &f } prometheus-mqtt-exporter-0.1.7/pkg/mqttclient/000077500000000000000000000000001436576364100215275ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/pkg/mqttclient/mqttClient.go000066400000000000000000000017651436576364100242130ustar00rootroot00000000000000package mqttclient import ( mqtt "github.com/eclipse/paho.mqtt.golang" "go.uber.org/zap" ) type SubscribeOptions struct { Topic string QoS byte OnMessageReceived mqtt.MessageHandler Logger *zap.Logger } func Subscribe(connectionOptions *mqtt.ClientOptions, subscribeOptions SubscribeOptions) error { oldConnect := connectionOptions.OnConnect connectionOptions.OnConnect = func(client mqtt.Client) { logger := subscribeOptions.Logger oldConnect(client) logger.Info("Connected to MQTT Broker") logger.Info("Will subscribe to topic", zap.String("topic", subscribeOptions.Topic)) if token := client.Subscribe(subscribeOptions.Topic, subscribeOptions.QoS, subscribeOptions.OnMessageReceived); token.Wait() && token.Error() != nil { logger.Error("Could not subscribe", zap.Error(token.Error())) } } client := mqtt.NewClient(connectionOptions) if token := client.Connect(); token.Wait() && token.Error() != nil { return token.Error() } return nil } prometheus-mqtt-exporter-0.1.7/release/000077500000000000000000000000001436576364100202025ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/release/Dockerfile.scratch000066400000000000000000000004221436576364100236200ustar00rootroot00000000000000FROM alpine as donor RUN apk add tzdata FROM scratch COPY mqtt2prometheus /mqtt2prometheus # Copy CA Certificates COPY --from=donor /etc/ssl/certs /etc/ssl/certs # Copy Time Zone Data COPY --from=donor /usr/share/zoneinfo /usr/share/zoneinfo ENTRYPOINT ["/mqtt2prometheus"] prometheus-mqtt-exporter-0.1.7/renovate.json000066400000000000000000000010571436576364100213030ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base", ":automergeRequireAllStatusChecks", ":separateMultipleMajorReleases", ":separatePatchReleases", ":renovatePrefix", ":semanticPrefixChore", ":prHourlyLimitNone", ":prConcurrentLimit10" ], "automergeType": "pr", "postUpdateOptions": [ "gomodTidy" ], "labels": [ "dependencies", "versions", "automated" ], "rebaseWhen": "behind-base-branch", "stabilityDays": 10, "internalChecksFilter": "strict" } prometheus-mqtt-exporter-0.1.7/systemd/000077500000000000000000000000001436576364100202525ustar00rootroot00000000000000prometheus-mqtt-exporter-0.1.7/systemd/mqtt2prometheus.service000066400000000000000000000006551436576364100250250ustar00rootroot00000000000000[Unit] Description=Simple translator from mqtt messages to prometheus. Analog to pushgateway Documentation=https://github.com/hikhvar/mqtt2prometheus Before=prometheus.service [Service] Restart=always User=mqtt2prometheus EnvironmentFile=/etc/default/prometheus-mqtt-exporter ExecStart=/opt/mqtt2prometheus/mqtt2prometheus -config /etc/mqtt2prometheus/config.yaml $ARGS TimeoutStopSec=20s [Install] WantedBy=multi-user.target prometheus-mqtt-exporter-0.1.7/systemd/postinstall.sh000077500000000000000000000002331436576364100231630ustar00rootroot00000000000000#!/bin/sh user=mqtt2prometheus if ! getent passwd "${user}" > /dev/null; then useradd --system --home-dir /var/lib/${user} --no-create-home || true fi