commit fe0019c3830214af8ea7ee834f5bc72f510a524d Author: Devin Buhl Date: Sun Apr 12 08:21:46 2020 -0400 initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..68ab989 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.git +.github/ +.gitignore +Dockerfile +LICENSE +README.md +k8s/ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..0537b8e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,32 @@ +name: main +on: + push: + branches: master + tags: + - v* +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Set up Docker Buildx + id: buildx + uses: crazy-max/ghaction-docker-buildx@v1 + with: + version: latest + + - name: Login to GitHub Docker Registry + run: echo "${DOCKERHUB_PASSWORD}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin + env: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Build Container Image + run: | + docker buildx build \ + -t onedr0p/radarr-exporter:${GITHUB_REF##*/} \ + --platform linux/amd64,linux/arm/v7,linux/arm64 \ + -f ./build/package/Dockerfile \ + --push . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e378703 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +./radarr-exporter +/vendor/ +/.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..736b646 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Devin Buhl + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a375908 --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# radarr-exporter + +Prometheus Exporter for Radarr + +[![Docker Pulls](https://img.shields.io/docker/pulls/onedr0p/radarr-exporter)](https://hub.docker.com/r/onedr0p/radarr-exporter) +[![Go Report Card](https://goreportcard.com/badge/github.com/onedr0p/radarr-exporter)](https://goreportcard.com/report/github.com/onedr0p/radarr-exporter) + +## Usage + +|Name |Description |Default | +|----------------------------|-------------------------------------------------------------|-----------------------| +|`RADARR_URL` |Your Radarr URL |`http://127.0.0.1:7878`| +|`RADARR_APIKEY` |Your Radarr API Key | | +|`RADARR_BASIC_AUTH_ENABLED` |Set to `true` to enable Basic Auth |`false` | +|`RADARR_BASIC_AUTH_USERNAME`|Set to your username if enabled Basic Auth | | +|`RADARR_BASIC_AUTH_PASSWORD`|Set to your password if enabled Basic Auth | | +|`DISABLE_SSL_VERIFY` |Set to `true` to disable SSL verification (use with caution) |`false` | +|`LISTEN_PORT` |The port the exporter will listen on |`9707` | +|`LISTEN_IP` |The IP the exporter will listen on |`0.0.0.0` | +|`LOG_LEVEL` |Set the default Log Level |`INFO` | + +### Run with Docker Compose + +```yaml +version: '3.7' +services: + radarr-exporter: + image: onedr0p/radarr-exporter:v2.2.0 + environment: + RADARR_URL: "http://localhost:7878" + RADARR_APIKEY: "..." +``` + +### Run with Kubernetes + +See [radarr-exporter.yaml](./examples/kubernetes/radarr-exporter.yaml) + +### Run with the command line + +```cmd +NAME: + radarr-exporter start - + +USAGE: + radarr-exporter start [command options] [arguments...] + +DESCRIPTION: + Radarr Exporter + +OPTIONS: + --listen-port value Port the exporter will listen on (default: 9707) [$LISTEN_PORT] + --listen-ip value IP the exporter will listen on (default: "0.0.0.0") [$LISTEN_IP] + --log-level value Set the default Log Level (default: "INFO") [$LOG_LEVEL] + --disable-ssl-verify Disable SSL Verifications (default: false) [$DISABLE_SSL_VERIFY] + --url value Full URL to Radarr (default: "http://localhost:7878") [$RADARR_URL] + --api-key value Radarr's API Key (default: "") [$RADARR_APIKEY] + --basic-auth-enabled Enable Basic Auth (default: false) [$RADARR_BASIC_AUTH_ENABLED] + --basic-auth-username value If Basic Auth is enabled, provide the username (default: "") [$RADARR_BASIC_AUTH_USERNAME] + --basic-auth-password value If Basic Auth is enabled, provide the password (default: "") [$RADARR_BASIC_AUTH_PASSWORD] + --help, -h show help (default: false) +``` + +### Metrics + +> **Note:** Due to changes in versions, these may not reflect what is currently being exported. + +```bash +# HELP radarr_history_total Total number of records in history +# TYPE radarr_history_total gauge +radarr_history_total{url="http://localhost:7878"} 6174 +# HELP radarr_movie_download_total Total number of downloaded movies +# TYPE radarr_movie_download_total gauge +radarr_movie_download_total{url="http://localhost:7878"} 4717 +# HELP radarr_movie_missing_total Total number of missing movies +# TYPE radarr_movie_missing_total gauge +radarr_movie_missing_total{url="http://localhost:7878"} 97 +# HELP radarr_movie_monitored_total Total number of monitored movies +# TYPE radarr_movie_monitored_total gauge +radarr_movie_monitored_total{url="http://localhost:7878"} 155 +# HELP radarr_movie_quality_total Total number of downloaded movies by quality +# TYPE radarr_movie_quality_total gauge +radarr_movie_quality_total{url="http://localhost:7878",quality="Bluray-1080p"} 1222 +radarr_movie_quality_total{url="http://localhost:7878",quality="Bluray-480p"} 99 +radarr_movie_quality_total{url="http://localhost:7878",quality="Bluray-576p"} 267 +radarr_movie_quality_total{url="http://localhost:7878",quality="Bluray-720p"} 1004 +radarr_movie_quality_total{url="http://localhost:7878",quality="DVD"} 1347 +radarr_movie_quality_total{url="http://localhost:7878",quality="HDTV-1080p"} 43 +radarr_movie_quality_total{url="http://localhost:7878",quality="HDTV-720p"} 46 +radarr_movie_quality_total{url="http://localhost:7878",quality="Remux-1080p"} 48 +radarr_movie_quality_total{url="http://localhost:7878",quality="SDTV"} 25 +radarr_movie_quality_total{url="http://localhost:7878",quality="WEBDL-1080p"} 465 +radarr_movie_quality_total{url="http://localhost:7878",quality="WEBDL-480p"} 53 +radarr_movie_quality_total{url="http://localhost:7878",quality="WEBDL-720p"} 94 +radarr_movie_quality_total{url="http://localhost:7878",quality="WEBRip-1080p"} 2 +# HELP radarr_movie_total Total number of movies +# TYPE radarr_movie_total gauge +radarr_movie_total{url="http://localhost:7878"} 4817 +# HELP radarr_movie_unmonitored_total Total number of unmonitored movies +# TYPE radarr_movie_unmonitored_total gauge +radarr_movie_unmonitored_total{url="http://localhost:7878"} 4662 +# HELP radarr_movies_bytes Total file size of all movies in bytes +# TYPE radarr_movies_bytes gauge +radarr_movie_bytes{url="http://localhost:7878"} 2.3326478328365e+13 +# HELP radarr_queue_total Total number of movies in queue by status +# TYPE radarr_queue_total gauge +radarr_queue_total{url="http://localhost:7878",status="Ok"} 1 +radarr_queue_total{url="http://localhost:7878",status="Warning"} 9 +# HELP radarr_root_folder_space_bytes Root folder space in bytes +# TYPE radarr_root_folder_space_bytes gauge +radarr_rootfolder_freespace_bytes{folder="/media/Library/Movies/",url="http://localhost:7878"} 2.5011930497024e+13 +# HELP radarr_status System Status +# TYPE radarr_status gauge +radarr_status{url="http://localhost:7878"} 1 +# HELP radarr_health_issues Health issues in Radarr +# TYPE radarr_health_issues gauge +radarr_health_issues{url="http://localhost:7878",message="Branch develop is for a previous version of Radarr, set branch to 'Aphrodite' for further updates",type="error",wikiurl="https://github.com/Radarr/Radarr/wiki/Health-checks#branch-develop-is-for-a-previous-version-of-radarr-set-branch-to-aphrodite-for-further-updates"} 1 +radarr_health_issues{url="http://localhost:7878",message="No download client is available",type="warning",wikiurl="https://github.com/Radarr/Radarr/wiki/Health-checks#no-download-client-is-available"} 1 +radarr_health_issues{url="http://localhost:7878",message="No indexers available with Automatic Search enabled, Radarr will not provide any automatic search results",type="warning",wikiurl="https://github.com/Radarr/Radarr/wiki/Health-checks#no-indexers-available-with-automatic-search-enabled-radarr-will-not-provide-any-automatic-search-results"} 1 +radarr_health_issues{url="http://localhost:7878",message="No indexers available with RSS sync enabled, Radarr will not grab new releases automatically",type="error",wikiurl="https://github.com/Radarr/Radarr/wiki/Health-checks#no-indexers-available-with-rss-sync-enabled-radarr-will-not-grab-new-releases-automatically"} 1 +``` diff --git a/build/package/Dockerfile b/build/package/Dockerfile new file mode 100644 index 0000000..3b6df03 --- /dev/null +++ b/build/package/Dockerfile @@ -0,0 +1,25 @@ +FROM golang:1.14-alpine as build + +ENV GO111MODULE=on \ + CGO_ENABLED=0 + +WORKDIR /build + +COPY . . + +RUN apk --no-cache add ca-certificates \ + && export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) \ + && export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ + && GOARM=$(echo ${TARGETPLATFORM} | cut -d / -f3); export GOARM=${GOARM:1} \ + && go mod download \ + && go build -a -tags netgo -ldflags '-w -extldflags "-static"' -o radarr-exporter /build/cmd/radarr-exporter/. \ + && chmod +x radarr-exporter + +FROM scratch + +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +COPY --from=build /build/radarr-exporter / + +ENTRYPOINT ["/radarr-exporter"] +CMD ["start"] \ No newline at end of file diff --git a/cmd/exportarr/main.go b/cmd/exportarr/main.go new file mode 100644 index 0000000..c51944f --- /dev/null +++ b/cmd/exportarr/main.go @@ -0,0 +1,298 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "strings" + + "github.com/onedr0p/exportarr/internal/collector" + "github.com/onedr0p/exportarr/internal/handlers" + "github.com/onedr0p/exportarr/internal/utils" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +func main() { + app := cli.NewApp() + app.Name = "Exportarr" + app.EnableBashCompletion = true + app.HideVersion = true + app.Authors = []*cli.Author{ + &cli.Author{ + Name: "onedr0p", + Email: "onedr0p@users.noreply.github.com", + }, + &cli.Author{ + Name: "DirtyCajunRice", + Email: "DirtyCajunRice@users.noreply.github.com", + }, + } + app.Commands = []*cli.Command{ + { + Name: "radarr", + Aliases: []string{"r"}, + Description: strings.Title("Radarr Exporter"), + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "listen-port", + Usage: "Port the exporter will listen on", + Value: 9707, + Required: false, + EnvVars: []string{"RADARR_LISTEN_PORT"}, + }, + &cli.StringFlag{ + Name: "listen-ip", + Usage: "IP the exporter will listen on", + Value: "0.0.0.0", + Required: false, + EnvVars: []string{"RADARR_LISTEN_IP"}, + }, + // Move to top level arg? + &cli.StringFlag{ + Name: "log-level", + Usage: "Set the default Log Level", + Value: "INFO", + Required: false, + EnvVars: []string{"RADARR_LOG_LEVEL"}, + }, + &cli.BoolFlag{ + Name: "disable-ssl-verify", + Usage: "Disable SSL Verifications", + Value: false, + Required: false, + EnvVars: []string{"RADARR_DISABLE_SSL_VERIFY"}, + }, + &cli.StringFlag{ + Name: "url", + Value: "http://127.0.0.1:7878", + Usage: "Full URL to Radarr", + Required: false, + EnvVars: []string{"RADARR_URL"}, + }, + &cli.StringFlag{ + Name: "api-key", + Usage: "Radarr's API Key", + Required: true, + EnvVars: []string{"RADARR_APIKEY"}, + }, + &cli.BoolFlag{ + Name: "basic-auth-enabled", + Usage: "Enable Basic Auth", + Value: false, + Required: false, + EnvVars: []string{"RADARR_BASIC_AUTH_ENABLED"}, + }, + &cli.StringFlag{ + Name: "basic-auth-username", + Usage: "If Basic Auth is enabled, provide the username", + Required: false, + EnvVars: []string{"RADARR_BASIC_AUTH_USERNAME"}, + }, + &cli.StringFlag{ + Name: "basic-auth-password", + Usage: "If Basic Auth is enabled, provide the password", + Required: false, + EnvVars: []string{"RADARR_BASIC_AUTH_PASSWORD"}, + }, + }, + Action: radarr, + }, + { + Name: "sonarr", + Aliases: []string{"s"}, + Description: strings.Title("Sonarr Exporter"), + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "listen-port", + Usage: "Port the exporter will listen on", + Value: 9707, + Required: false, + EnvVars: []string{"SONARR_LISTEN_PORT"}, + }, + &cli.StringFlag{ + Name: "listen-ip", + Usage: "IP the exporter will listen on", + Value: "0.0.0.0", + Required: false, + EnvVars: []string{"SONARR_LISTEN_IP"}, + }, + // Move to top level arg? + &cli.StringFlag{ + Name: "log-level", + Usage: "Set the default Log Level", + Value: "INFO", + Required: false, + EnvVars: []string{"SONARR_LOG_LEVEL"}, + }, + &cli.BoolFlag{ + Name: "disable-ssl-verify", + Usage: "Disable SSL Verifications", + Value: false, + Required: false, + EnvVars: []string{"SONARR_DISABLE_SSL_VERIFY"}, + }, + &cli.StringFlag{ + Name: "url", + Value: "http://127.0.0.1:7878", + Usage: "Full URL to Radarr", + Required: false, + EnvVars: []string{"SONARR_URL"}, + }, + &cli.StringFlag{ + Name: "api-key", + Usage: "Radarr's API Key", + Required: true, + EnvVars: []string{"SONARR_APIKEY"}, + }, + &cli.BoolFlag{ + Name: "basic-auth-enabled", + Usage: "Enable Basic Auth", + Value: false, + Required: false, + EnvVars: []string{"SONARR_BASIC_AUTH_ENABLED"}, + }, + &cli.StringFlag{ + Name: "basic-auth-username", + Usage: "If Basic Auth is enabled, provide the username", + Required: false, + EnvVars: []string{"SONARR_BASIC_AUTH_USERNAME"}, + }, + &cli.StringFlag{ + Name: "basic-auth-password", + Usage: "If Basic Auth is enabled, provide the password", + Required: false, + EnvVars: []string{"SONARR_BASIC_AUTH_PASSWORD"}, + }, + }, + Action: sonarr, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} + +func radarr(c *cli.Context) (err error) { + // Wire up Logging + switch strings.ToUpper(c.String("log-level")) { + case "TRACE": + log.SetLevel(log.TraceLevel) + case "DEBUG": + log.SetLevel(log.DebugLevel) + case "INFO": + log.SetLevel(log.InfoLevel) + case "WARN": + log.SetLevel(log.WarnLevel) + default: + log.SetLevel(log.InfoLevel) + } + log.SetFormatter(&log.TextFormatter{}) + log.SetOutput(os.Stdout) + + // Validate Url + if !utils.IsValidUrl(c.String("url")) { + log.Fatalf("%s is not a proper URL", c.String("url")) + } + + // Validate API Key + if !utils.IsValidApikey(c.String("api-key")) { + log.Fatalf("%s is not a proper API Key", c.String("api-key")) + } + + // Register the Collectors + r := prometheus.NewRegistry() + r.MustRegister( + collector.NewMovieCollector(c), + collector.NewQueueCollector(c), + collector.NewHistoryCollector(c), + collector.NewRootFolderCollector(c), + collector.NewSystemStatusCollector(c), + collector.NewSystemHealthCollector(c), + ) + + // Set up the handlers + handler := promhttp.HandlerFor(r, promhttp.HandlerOpts{}) + http.HandleFunc("/", handlers.IndexHandler) + http.HandleFunc("/healthz", handlers.HealthzHandler) + http.Handle("/metrics", handler) + + // Serve up the metrics + log.Infof("Listening on %s:%d", c.String("listen-ip"), c.Int("listen-port")) + httpErr := http.ListenAndServe( + fmt.Sprintf("%s:%d", c.String("listen-ip"), c.Int("listen-port")), + logRequest(http.DefaultServeMux), + ) + if httpErr != nil { + return httpErr + } + return nil +} + +func sonarr(c *cli.Context) (err error) { + // Wire up Logging + switch strings.ToUpper(c.String("log-level")) { + case "TRACE": + log.SetLevel(log.TraceLevel) + case "DEBUG": + log.SetLevel(log.DebugLevel) + case "INFO": + log.SetLevel(log.InfoLevel) + case "WARN": + log.SetLevel(log.WarnLevel) + default: + log.SetLevel(log.InfoLevel) + } + log.SetFormatter(&log.TextFormatter{}) + log.SetOutput(os.Stdout) + + // Validate Url + if !utils.IsValidUrl(c.String("url")) { + log.Fatalf("%s is not a proper URL", c.String("url")) + } + + // Validate API Key + if !utils.IsValidApikey(c.String("api-key")) { + log.Fatalf("%s is not a proper API Key", c.String("api-key")) + } + + // Register the Collectors + r := prometheus.NewRegistry() + r.MustRegister( + collector.NewMovieCollector(c), + collector.NewQueueCollector(c), + collector.NewHistoryCollector(c), + collector.NewRootFolderCollector(c), + collector.NewSystemStatusCollector(c), + collector.NewSystemHealthCollector(c), + ) + + // Set up the handlers + handler := promhttp.HandlerFor(r, promhttp.HandlerOpts{}) + http.HandleFunc("/", handlers.IndexHandler) + http.HandleFunc("/healthz", handlers.HealthzHandler) + http.Handle("/metrics", handler) + + // Serve up the metrics + log.Infof("Listening on %s:%d", c.String("listen-ip"), c.Int("listen-port")) + httpErr := http.ListenAndServe( + fmt.Sprintf("%s:%d", c.String("listen-ip"), c.Int("listen-port")), + logRequest(http.DefaultServeMux), + ) + if httpErr != nil { + return httpErr + } + return nil +} + +// Log internal request to stdout +func logRequest(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Debugf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL) + handler.ServeHTTP(w, r) + }) +} diff --git a/examples/grafana/dashboard.json b/examples/grafana/dashboard.json new file mode 100644 index 0000000..9fd3fb0 --- /dev/null +++ b/examples/grafana/dashboard.json @@ -0,0 +1,648 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 30, + "links": [], + "panels": [ + { + "datasource": "Prometheus", + "gridPos": { + "h": 5, + "w": 7, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": [ + "last" + ], + "defaults": { + "mappings": [ + { + "from": "", + "id": 1, + "operator": "", + "text": "Online", + "to": "", + "type": 1, + "value": "1" + }, + { + "from": "", + "id": 2, + "operator": "", + "text": "Offline", + "to": "", + "type": 1, + "value": "null" + }, + { + "from": "", + "id": 3, + "operator": "", + "text": "Offline", + "to": "", + "type": 1, + "value": "0" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(255, 194, 48)", + "value": null + } + ] + }, + "title": "status" + }, + "overrides": [], + "values": false + }, + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto" + }, + "pluginVersion": "6.6.2", + "targets": [ + { + "expr": "radarr_system_status", + "format": "time_series", + "instant": true, + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "type": "stat" + }, + { + "datasource": "Prometheus", + "gridPos": { + "h": 5, + "w": 4, + "x": 7, + "y": 0 + }, + "id": 4, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": [ + "lastNotNull" + ], + "defaults": { + "mappings": [ + { + "from": "", + "id": 1, + "operator": "", + "text": "0", + "to": "", + "type": 1, + "value": "null" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(255, 194, 48)", + "value": null + } + ] + }, + "title": "in queue", + "unit": "locale" + }, + "overrides": [], + "values": false + }, + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto" + }, + "pluginVersion": "6.6.2", + "targets": [ + { + "expr": "sum(radarr_queue_total)", + "format": "table", + "instant": true, + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "type": "stat" + }, + { + "datasource": "Prometheus", + "gridPos": { + "h": 5, + "w": 4, + "x": 11, + "y": 0 + }, + "id": 5, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": [ + "lastNotNull" + ], + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(255, 194, 48)", + "value": null + } + ] + }, + "title": "missing", + "unit": "locale" + }, + "overrides": [], + "values": false + }, + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto" + }, + "pluginVersion": "6.6.2", + "targets": [ + { + "expr": "radarr_movie_missing_total", + "format": "table", + "instant": true, + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "type": "stat" + }, + { + "datasource": "Prometheus", + "gridPos": { + "h": 5, + "w": 4, + "x": 15, + "y": 0 + }, + "id": 3, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": [ + "last" + ], + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(255, 194, 48)", + "value": null + } + ] + }, + "title": "wanted", + "unit": "locale" + }, + "overrides": [], + "values": false + }, + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto" + }, + "pluginVersion": "6.6.2", + "targets": [ + { + "expr": "radarr_movie_wanted_total", + "format": "table", + "instant": true, + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "type": "stat" + }, + { + "datasource": "Prometheus", + "gridPos": { + "h": 5, + "w": 7, + "x": 0, + "y": 5 + }, + "id": 6, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": [ + "lastNotNull" + ], + "defaults": { + "mappings": [ + { + "from": "", + "id": 1, + "operator": "", + "text": "0", + "to": "", + "type": 1, + "value": "null" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(255, 194, 48)", + "value": null + } + ] + }, + "title": "movies", + "unit": "locale" + }, + "overrides": [], + "values": false + }, + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto" + }, + "pluginVersion": "6.6.2", + "targets": [ + { + "expr": "radarr_movie_total", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "type": "stat" + }, + { + "datasource": "Prometheus", + "gridPos": { + "h": 5, + "w": 4, + "x": 7, + "y": 5 + }, + "id": 10, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": [ + "lastNotNull" + ], + "defaults": { + "mappings": [ + { + "from": "", + "id": 1, + "operator": "", + "text": "0", + "to": "", + "type": 1, + "value": "null" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(255, 194, 48)", + "value": null + } + ] + }, + "title": "monitored", + "unit": "locale" + }, + "overrides": [], + "values": false + }, + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto" + }, + "pluginVersion": "6.6.2", + "targets": [ + { + "expr": "radarr_movie_monitored_total", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "type": "stat" + }, + { + "datasource": "Prometheus", + "gridPos": { + "h": 5, + "w": 4, + "x": 11, + "y": 5 + }, + "id": 8, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": [ + "last" + ], + "defaults": { + "mappings": [ + { + "from": "", + "id": 1, + "operator": "", + "text": "0", + "to": "", + "type": 1, + "value": "null" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(255, 194, 48)", + "value": null + } + ] + }, + "title": "d/l past month", + "unit": "locale" + }, + "overrides": [], + "values": false + }, + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto" + }, + "pluginVersion": "6.6.2", + "targets": [ + { + "expr": "sum(increase(radarr_movie_download_total[30d]))", + "format": "table", + "instant": true, + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "type": "stat" + }, + { + "datasource": "Prometheus", + "gridPos": { + "h": 5, + "w": 4, + "x": 15, + "y": 5 + }, + "id": 7, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": [ + "last" + ], + "defaults": { + "mappings": [ + { + "from": "", + "id": 1, + "operator": "", + "text": "0", + "to": "", + "type": 1, + "value": "null" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(255, 194, 48)", + "value": null + } + ] + }, + "title": "d/l past week", + "unit": "locale" + }, + "overrides": [], + "values": false + }, + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto" + }, + "pluginVersion": "6.6.2", + "targets": [ + { + "expr": "sum(increase(radarr_movie_download_total[7d]))", + "format": "table", + "instant": true, + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 10, + "w": 19, + "x": 0, + "y": 10 + }, + "hiddenSeries": false, + "id": 13, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "sideWidth": null, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sort_desc(sum(radarr_movie_quality_total) by (quality))", + "format": "time_series", + "instant": true, + "legendFormat": "{{quality}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "series", + "name": null, + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "decimals": null, + "format": "locale", + "label": "movies by quality", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": 3 + } + }, + { + "datasource": "loki", + "gridPos": { + "h": 11, + "w": 19, + "x": 0, + "y": 20 + }, + "id": 15, + "options": { + "showLabels": false, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": true + }, + "targets": [ + { + "expr": "{app_kubernetes_io_name=\"radarr\"}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "type": "logs" + } + ], + "refresh": "1m", + "schemaVersion": 22, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-2d", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Radarr", + "uid": "A8iPssjZk", + "version": 12 +} diff --git a/examples/kubernetes/radarr-exporter.yaml b/examples/kubernetes/radarr-exporter.yaml new file mode 100644 index 0000000..72874ba --- /dev/null +++ b/examples/kubernetes/radarr-exporter.yaml @@ -0,0 +1,98 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: radarr-exporter + namespace: monitoring + labels: + app.kubernetes.io/name: radarr-exporter + app.kubernetes.io/instance: radarr-exporter +spec: + clusterIP: None + selector: + app.kubernetes.io/name: radarr-exporter + app.kubernetes.io/instance: radarr-exporter + ports: + - name: monitoring + port: 9707 +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: radarr-exporter + namespace: monitoring + labels: + app.kubernetes.io/name: radarr-exporter + app.kubernetes.io/instance: radarr-exporter +spec: + selector: + matchLabels: + app.kubernetes.io/name: radarr-exporter + app.kubernetes.io/instance: radarr-exporter + endpoints: + - port: monitoring + interval: 4m + path: /metrics +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: radarr-exporter + namespace: monitoring + labels: + app.kubernetes.io/name: radarr-exporter + app.kubernetes.io/instance: radarr-exporter + annotations: + fluxcd.io/ignore: "false" + fluxcd.io/automated: "true" + fluxcd.io/tag.radarr-exporter: semver:~v2.2 +spec: + replicas: 1 + revisionHistoryLimit: 3 + selector: + matchLabels: + app.kubernetes.io/name: radarr-exporter + app.kubernetes.io/instance: radarr-exporter + template: + metadata: + labels: + app.kubernetes.io/name: radarr-exporter + app.kubernetes.io/instance: radarr-exporter + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "monitoring" + spec: + containers: + - name: radarr-exporter + image: onedr0p/radarr-exporter:v2.2.2 + imagePullPolicy: IfNotPresent + env: + - name: RADARR_URL + value: "http://radarr.default.svc.cluster.local:7878" + - name: RADARR_APIKEY + valueFrom: + secretKeyRef: + name: radarr-exporter + key: api-key + ports: + - name: monitoring + containerPort: 9707 + livenessProbe: + httpGet: + path: /healthz + port: monitoring + failureThreshold: 5 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /healthz + port: monitoring + failureThreshold: 5 + periodSeconds: 10 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 500m + memory: 256Mi diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5a34a5b --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/onedr0p/radarr-exporter + +go 1.12 + +require ( + github.com/prometheus/client_golang v1.5.1 + github.com/sirupsen/logrus v1.4.2 + github.com/urfave/cli/v2 v2.2.0 + gitlab.com/onedr0p/radarr-exporter v1.0.0 // indirect + golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect + gopkg.in/yaml.v2 v2.2.7 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3e635d8 --- /dev/null +++ b/go.sum @@ -0,0 +1,351 @@ +cloud.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.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= +github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/SAP/go-hdb v0.14.1/go.mod h1:7fdQLVC2lER3urZLjZCm0AuMQfApof92n3aylBPEkMo= +github.com/SermoDigital/jose v0.9.1/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= +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/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +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 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apache/thrift v0.12.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-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/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/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +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/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.3+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +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/denisenkom/go-mssqldb v0.0.0-20190423183735-731ef375ac02/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= +github.com/dustin/go-humanize v1.0.0/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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= +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-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-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-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gocql/gocql v0.0.0-20190423091413-b99afaf3b163/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/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/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +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 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +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.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +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/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +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/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/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.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.9.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.0.1/go.mod h1:I6dKdmYhZqU0RJSheVEWgTNWdVQH5QvTgIUQ0t/t32M= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.0.0/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +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/golang-math-big v0.0.0-20180316142257-561262b71329/go.mod h1:eBwVNKMPVQvPzsL2kU1sgH+Wf3xcmgFCvFSyGDEUSgc= +github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= +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/hashicorp/vault v0.9.6/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/heetch/confita v0.9.0/go.mod h1:PQQ3Rx11s2c3+6hB/HIUpD/dZPCRtkXAULzM5w3xeNk= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jefferai/jsonx v1.0.0/go.mod h1:OGmqmi2tTeI/PS+qQfBDToLHHJIy/RMp24fPo8vFvoQ= +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/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/keybase/go-crypto v0.0.0-20190416182011-b785b22cc757/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/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/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +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/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab/go.mod h1:y1pL58r5z2VvAjeG1VLGc8zOQgSOzbKN7kMHPvFXJ+8= +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/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +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/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +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/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +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/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +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 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +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.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA= +github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= +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 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/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.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +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.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-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 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +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/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +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/ryanuber/go-glob v0.0.0-20160226084822-572520ed46db/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sethgrid/pester v0.0.0-20180227223404-ed9870dad317/go.mod h1:Ad7IjTpvzZO8Fl0vh9AzQ+j/jYZfyp2diGwI8m5q+ns= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +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 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= +github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +gitlab.com/onedr0p/radarr-exporter v1.0.0 h1:/La6UkpQNoTEZZLq6Kz2I8PvWqq+ez9DHANAgnwAn0o= +gitlab.com/onedr0p/radarr-exporter v1.0.0/go.mod h1:h+GBCNBRakBxCJV0MBieD9s1taZOIE9oukmJyMZtBRg= +gitlab.com/onedr0p/sonarr-exporter v1.0.0/go.mod h1:8YLpw4jl63MaM6xnYdZd8rCB/ybeeZc3XiRN8SGMnJU= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +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-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +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/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +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-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= +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/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-20190129075346-302c3dd5f1cc/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20190508220229-2d0786266e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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/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/tools v0.0.0-20180828015842-6cd1fcedba52/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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +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-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +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= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/ory-am/dockertest.v3 v3.3.4/go.mod h1:s9mmoLkaGeAh97qygnNj4xWkiN7e1SKekYC6CovU+ek= +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/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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +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 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +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= diff --git a/internal/client/client.go b/internal/client/client.go new file mode 100644 index 0000000..ff2e716 --- /dev/null +++ b/internal/client/client.go @@ -0,0 +1,63 @@ +package client + +import ( + "crypto/tls" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "net/http" + + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +// Client struct is a Radarr client to request an instance of a Radarr +type Client struct { + config *cli.Context + httpClient http.Client +} + +// NewClient method initializes a new Radarr client. +func NewClient(c *cli.Context) *Client { + return &Client{ + config: c, + httpClient: http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + }, + } +} + +// DoRequest - Take a HTTP Request and return Unmarshaled data +func (c *Client) DoRequest(endpoint string, target interface{}) error { + url := fmt.Sprintf("%s/api/v3/%s", c.config.String("url"), endpoint) + + log.Infof("Sending HTTP request to %s", url) + + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: c.config.Bool("disable-ssl-verify")} + req, err := http.NewRequest("GET", url, nil) + if c.config.Bool("basic-auth-enabled") && c.config.String("basic-auth-username") != "" && c.config.String("basic-auth-password") != "" { + req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(c.config.String("basic-auth-username")+":"+c.config.String("basic-auth-password")))) + } + req.Header.Add("X-Api-Key", c.config.String("api-key")) + + if err != nil { + log.Fatalf("An error has occurred when creating HTTP request %v", err) + return err + } + + resp, err := c.httpClient.Do(req) + if err != nil { + log.Fatalf("An error has occurred during retrieving statistics %v", err) + return err + } + if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { + errMsg := fmt.Sprintf("An error has occurred during retrieving statistics HTTP statuscode %d", resp.StatusCode) + log.Fatal(errMsg) + return errors.New(errMsg) + } + defer resp.Body.Close() + return json.NewDecoder(resp.Body).Decode(target) +} diff --git a/internal/collector/radarr/movie.go b/internal/collector/radarr/movie.go new file mode 100644 index 0000000..e8ac716 --- /dev/null +++ b/internal/collector/radarr/movie.go @@ -0,0 +1,136 @@ +package collector + +import ( + "github.com/onedr0p/exportarr/internal/client" + "github.com/onedr0p/exportarr/internal/model" + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +type movieCollector struct { + config *cli.Context + movieMetric *prometheus.Desc + downloadedMetric *prometheus.Desc + monitoredMetric *prometheus.Desc + unmonitoredMetric *prometheus.Desc + wantedMetric *prometheus.Desc + missingMetric *prometheus.Desc + filesizeMetric *prometheus.Desc + qualitiesMetric *prometheus.Desc +} + +func NewMovieCollector(c *cli.Context) *movieCollector { + return &movieCollector{ + config: c, + movieMetric: prometheus.NewDesc( + "radarr_movie_total", + "Total number of movies", + nil, + prometheus.Labels{"url": c.String("url")}, + ), + downloadedMetric: prometheus.NewDesc( + "radarr_movie_download_total", + "Total number of downloaded movies", + nil, + prometheus.Labels{"url": c.String("url")}, + ), + monitoredMetric: prometheus.NewDesc( + "radarr_movie_monitored_total", + "Total number of monitored movies", + nil, + prometheus.Labels{"url": c.String("url")}, + ), + unmonitoredMetric: prometheus.NewDesc( + "radarr_movie_unmonitored_total", + "Total number of unmonitored movies", + nil, + prometheus.Labels{"url": c.String("url")}, + ), + wantedMetric: prometheus.NewDesc( + "radarr_movie_wanted_total", + "Total number of wanted movies", + nil, + prometheus.Labels{"url": c.String("url")}, + ), + missingMetric: prometheus.NewDesc( + "radarr_movie_missing_total", + "Total number of missing movies", + nil, + prometheus.Labels{"url": c.String("url")}, + ), + filesizeMetric: prometheus.NewDesc( + "radarr_movie_filesize_total", + "Total filesize of all movies", + nil, + prometheus.Labels{"url": c.String("url")}, + ), + qualitiesMetric: prometheus.NewDesc( + "radarr_movie_quality_total", + "Total number of downloaded movies by quality", + []string{"quality"}, + prometheus.Labels{"url": c.String("url")}, + ), + } +} + +func (collector *movieCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- collector.movieMetric + ch <- collector.downloadedMetric + ch <- collector.monitoredMetric + ch <- collector.unmonitoredMetric + ch <- collector.wantedMetric + ch <- collector.missingMetric + ch <- collector.filesizeMetric + ch <- collector.qualitiesMetric +} + +func (collector *movieCollector) Collect(ch chan<- prometheus.Metric) { + c := client.NewClient(collector.config) + var fileSize int64 + var ( + downloaded = 0 + monitored = 0 + unmonitored = 0 + missing = 0 + wanted = 0 + qualities = map[string]int{} + ) + movies := model.Movie{} + if err := c.DoRequest("movie", &movies); err != nil { + log.Fatal(err) + } + for _, s := range movies { + if s.HasFile { + downloaded++ + } + if s.Monitored { + monitored++ + if !s.HasFile && s.Status == "released" { + missing++ + } else if !s.HasFile { + wanted++ + } + } else { + unmonitored++ + } + if s.MovieFile.Quality.Quality.Name != "" { + qualities[s.MovieFile.Quality.Quality.Name]++ + } + if s.MovieFile.Size != 0 { + fileSize += s.MovieFile.Size + } + } + ch <- prometheus.MustNewConstMetric(collector.movieMetric, prometheus.GaugeValue, float64(len(movies))) + ch <- prometheus.MustNewConstMetric(collector.downloadedMetric, prometheus.GaugeValue, float64(downloaded)) + ch <- prometheus.MustNewConstMetric(collector.monitoredMetric, prometheus.GaugeValue, float64(monitored)) + ch <- prometheus.MustNewConstMetric(collector.unmonitoredMetric, prometheus.GaugeValue, float64(unmonitored)) + ch <- prometheus.MustNewConstMetric(collector.wantedMetric, prometheus.GaugeValue, float64(wanted)) + ch <- prometheus.MustNewConstMetric(collector.missingMetric, prometheus.GaugeValue, float64(missing)) + ch <- prometheus.MustNewConstMetric(collector.filesizeMetric, prometheus.GaugeValue, float64(fileSize)) + for qualityName, count := range qualities { + ch <- prometheus.MustNewConstMetric(collector.qualitiesMetric, prometheus.GaugeValue, float64(count), + qualityName, + ) + } +} diff --git a/internal/collector/shared/health.go b/internal/collector/shared/health.go new file mode 100644 index 0000000..bd45ab0 --- /dev/null +++ b/internal/collector/shared/health.go @@ -0,0 +1,46 @@ +package collector + +import ( + "github.com/onedr0p/exportarr/internal/client" + "github.com/onedr0p/exportarr/internal/model" + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +type systemHealthCollector struct { + config *cli.Context + systemHealthMetric *prometheus.Desc +} + +func NewSystemHealthCollector(c *cli.Context) *systemHealthCollector { + return &systemHealthCollector{ + config: c, + systemHealthMetric: prometheus.NewDesc( + "radarr_system_health_issues", + "Total number of movies in the queue by status", + []string{"source", "type", "message", "wikiurl"}, + prometheus.Labels{"url": c.String("url")}, + ), + } +} + +func (collector *systemHealthCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- collector.systemHealthMetric +} + +func (collector *systemHealthCollector) Collect(ch chan<- prometheus.Metric) { + c := client.NewClient(collector.config) + systemHealth := model.SystemHealth{} + if err := c.DoRequest("health", &systemHealth); err != nil { + log.Fatal(err) + } + // Group metrics by source, type, message and wikiurl + if len(systemHealth) > 0 { + for _, s := range systemHealth { + ch <- prometheus.MustNewConstMetric(collector.systemHealthMetric, prometheus.GaugeValue, float64(1), + s.Source, s.Type, s.Message, s.WikiURL, + ) + } + } +} diff --git a/internal/collector/shared/history.go b/internal/collector/shared/history.go new file mode 100644 index 0000000..8c8cb03 --- /dev/null +++ b/internal/collector/shared/history.go @@ -0,0 +1,39 @@ +package collector + +import ( + "github.com/onedr0p/exportarr/internal/client" + "github.com/onedr0p/exportarr/internal/model" + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +type historyCollector struct { + config *cli.Context + historyMetric *prometheus.Desc +} + +func NewHistoryCollector(c *cli.Context) *historyCollector { + return &historyCollector{ + config: c, + historyMetric: prometheus.NewDesc( + "radarr_history_total", + "Total number of records in history", + nil, + prometheus.Labels{"url": c.String("url")}, + ), + } +} + +func (collector *historyCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- collector.historyMetric +} + +func (collector *historyCollector) Collect(ch chan<- prometheus.Metric) { + c := client.NewClient(collector.config) + history := model.History{} + if err := c.DoRequest("history", &history); err != nil { + log.Fatal(err) + } + ch <- prometheus.MustNewConstMetric(collector.historyMetric, prometheus.GaugeValue, float64(history.TotalRecords)) +} diff --git a/internal/collector/shared/queue.go b/internal/collector/shared/queue.go new file mode 100644 index 0000000..3f053a7 --- /dev/null +++ b/internal/collector/shared/queue.go @@ -0,0 +1,63 @@ +package collector + +import ( + "fmt" + + "github.com/onedr0p/exportarr/internal/client" + "github.com/onedr0p/exportarr/internal/model" + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +type queueCollector struct { + config *cli.Context + queueMetric *prometheus.Desc +} + +func NewQueueCollector(c *cli.Context) *queueCollector { + return &queueCollector{ + config: c, + queueMetric: prometheus.NewDesc( + "radarr_queue_total", + "Total number of movies in the queue by status", + []string{"status", "download_status", "download_state"}, + prometheus.Labels{"url": c.String("url")}, + ), + } +} + +func (collector *queueCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- collector.queueMetric +} + +func (collector *queueCollector) Collect(ch chan<- prometheus.Metric) { + c := client.NewClient(collector.config) + queue := model.Queue{} + if err := c.DoRequest("queue?page=1&includeUnknownMovieItems=true", &queue); err != nil { + log.Fatal(err) + } + // Calculate total pages + var totalPages = (queue.TotalRecords + queue.PageSize - 1) / queue.PageSize + // Paginate + var queueStatusAll = make([]model.QueueRecords, 0, queue.TotalRecords) + queueStatusAll = append(queueStatusAll, queue.Records...) + if totalPages > 1 { + for page := 2; page <= totalPages; page++ { + if err := client.DoRequest(fmt.Sprintf("%s?page=%d&includeUnknownMovieItems=true", "queue", page), &queue); err != nil { + log.Fatal(err) + } + queueStatusAll = append(queueStatusAll, queue.Records...) + } + } + // Group metrics by status, download_status and download_state + if len(queueStatusAll) > 0 { + var queueMetrics prometheus.Metric + for i, s := range queueStatusAll { + queueMetrics = prometheus.MustNewConstMetric(collector.queueMetric, prometheus.GaugeValue, float64(i+1), + s.Status, s.TrackedDownloadStatus, s.TrackedDownloadState, + ) + } + ch <- queueMetrics + } +} diff --git a/internal/collector/shared/rootfolder.go b/internal/collector/shared/rootfolder.go new file mode 100644 index 0000000..78123eb --- /dev/null +++ b/internal/collector/shared/rootfolder.go @@ -0,0 +1,46 @@ +package collector + +import ( + "github.com/onedr0p/exportarr/internal/client" + "github.com/onedr0p/exportarr/internal/model" + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +type rootFolderCollector struct { + config *cli.Context + rootFolderMetric *prometheus.Desc +} + +func NewRootFolderCollector(c *cli.Context) *rootFolderCollector { + return &rootFolderCollector{ + config: c, + rootFolderMetric: prometheus.NewDesc( + "radarr_rootfolder_freespace_bytes", + "Root folder space in bytes", + []string{"path"}, + prometheus.Labels{"url": c.String("url")}, + ), + } +} + +func (collector *rootFolderCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- collector.rootFolderMetric +} + +func (collector *rootFolderCollector) Collect(ch chan<- prometheus.Metric) { + c := client.NewClient(collector.config) + rootFolders := model.RootFolder{} + if err := c.DoRequest("rootfolder", &rootFolders); err != nil { + log.Fatal(err) + } + // Group metrics by path + if len(rootFolders) > 0 { + for _, rootFolder := range rootFolders { + ch <- prometheus.MustNewConstMetric(collector.rootFolderMetric, prometheus.GaugeValue, float64(rootFolder.FreeSpace), + rootFolder.Path, + ) + } + } +} diff --git a/internal/collector/shared/status.go b/internal/collector/shared/status.go new file mode 100644 index 0000000..1fefc39 --- /dev/null +++ b/internal/collector/shared/status.go @@ -0,0 +1,41 @@ +package collector + +import ( + "github.com/onedr0p/exportarr/internal/client" + "github.com/onedr0p/exportarr/internal/model" + "github.com/prometheus/client_golang/prometheus" + "github.com/urfave/cli/v2" +) + +type systemStatusCollector struct { + config *cli.Context + systemStatus *prometheus.Desc +} + +func NewSystemStatusCollector(c *cli.Context) *systemStatusCollector { + return &systemStatusCollector{ + config: c, + systemStatus: prometheus.NewDesc( + "radarr_system_status", + "System Status", + nil, + prometheus.Labels{"url": c.String("url")}, + ), + } +} + +func (collector *systemStatusCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- collector.systemStatus +} + +func (collector *systemStatusCollector) Collect(ch chan<- prometheus.Metric) { + c := client.NewClient(collector.config) + systemStatus := model.SystemStatus{} + if err := client.DoRequest("system/status", &systemStatus); err != nil { + ch <- prometheus.MustNewConstMetric(collector.systemStatus, prometheus.GaugeValue, float64(0.0)) + } else if (model.SystemStatus{}) == systemStatus { + ch <- prometheus.MustNewConstMetric(collector.systemStatus, prometheus.GaugeValue, float64(0.0)) + } else { + ch <- prometheus.MustNewConstMetric(collector.systemStatus, prometheus.GaugeValue, float64(1.0)) + } +} diff --git a/internal/handlers/healthz.go b/internal/handlers/healthz.go new file mode 100644 index 0000000..de8c8f6 --- /dev/null +++ b/internal/handlers/healthz.go @@ -0,0 +1,12 @@ +package handlers + +import ( + "fmt" + "net/http" +) + +func HealthzHandler(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + fmt.Fprint(w) +} diff --git a/internal/handlers/index.go b/internal/handlers/index.go new file mode 100644 index 0000000..73a301a --- /dev/null +++ b/internal/handlers/index.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "fmt" + "net/http" +) + +func IndexHandler(w http.ResponseWriter, _ *http.Request) { + response := `

Radarr Exporter

metrics

` + fmt.Fprintf(w, response) +} diff --git a/internal/model/radarr.go b/internal/model/radarr.go new file mode 100644 index 0000000..a0a22b1 --- /dev/null +++ b/internal/model/radarr.go @@ -0,0 +1,17 @@ +package model + +// Movie - Stores struct of JSON response +type Movie []struct { + Status string `json:"status"` + HasFile bool `json:"hasFile"` + Monitored bool `json:"monitored"` + MovieFile struct { + Size int64 `json:"size"` + Quality struct { + Quality struct { + Name string `json:"name"` + } `json:"quality"` + } `json:"quality"` + } `json:"movieFile"` + QualityProfileID int `json:"qualityProfileId"` +} diff --git a/internal/model/shared.go b/internal/model/shared.go new file mode 100644 index 0000000..859c356 --- /dev/null +++ b/internal/model/shared.go @@ -0,0 +1,51 @@ +package model + +// RootFolder - Stores struct of JSON response +type RootFolder []struct { + Path string `json:"path"` + FreeSpace int64 `json:"freeSpace"` +} + +// SystemStatus - Stores struct of JSON response +type SystemStatus struct { + Version string `json:"version"` + AppData string `json:"appData"` + Branch string `json:"branch"` +} + +// Queue - Stores struct of JSON response +type Queue struct { + Page int `json:"page"` + PageSize int `json:"pageSize"` + SortKey string `json:"sortKey"` + SortDirection string `json:"sortDirection"` + TotalRecords int `json:"totalRecords"` + Records []QueueRecords `json:"records"` +} + +// QueueRecords - Stores struct of JSON response +type QueueRecords struct { + Size float64 `json:"size"` + Title string `json:"title"` + Status string `json:"status"` + TrackedDownloadStatus string `json:"trackedDownloadStatus"` + TrackedDownloadState string `json:"trackedDownloadState"` + StatusMessages []struct { + Title string `json:"title"` + Messages []string `json:"messages"` + } `json:"statusMessages"` + ErrorMessage string `json:"errorMessage"` +} + +// History - Stores struct of JSON response +type History struct { + TotalRecords int `json:"totalRecords"` +} + +// SystemHealth - Stores struct of JSON response +type SystemHealth []struct { + Source string `json:"source"` + Type string `json:"type"` + Message string `json:"message"` + WikiURL string `json:"wikiUrl"` +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 0000000..bbad04a --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,21 @@ +package utils + +import ( + "net/url" + "regexp" +) + +// IsValidApikey - Check if the API Key is 32 characters and only a-z0-9 +func IsValidApikey(str string) bool { + found, err := regexp.MatchString("([a-z0-9]{32})", str) + if err != nil { + return false + } + return found +} + +// IsValidUrl - Checks if the URL is valid +func IsValidUrl(str string) bool { + u, err := url.Parse(str) + return err == nil && u.Scheme != "" && u.Host != "" +}