initial commit

This commit is contained in:
Devin Buhl 2020-04-12 08:21:46 -04:00
commit fe0019c383
23 changed files with 2161 additions and 0 deletions

7
.dockerignore Normal file
View File

@ -0,0 +1,7 @@
.git
.github/
.gitignore
Dockerfile
LICENSE
README.md
k8s/

32
.github/workflows/main.yml vendored Normal file
View File

@ -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 .

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
./radarr-exporter
/vendor/
/.idea/

21
LICENSE Normal file
View File

@ -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.

120
README.md Normal file
View File

@ -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
```

25
build/package/Dockerfile Normal file
View File

@ -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"]

298
cmd/exportarr/main.go Normal file
View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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

12
go.mod Normal file
View File

@ -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
)

351
go.sum Normal file
View File

@ -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=

63
internal/client/client.go Normal file
View File

@ -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)
}

View File

@ -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,
)
}
}

View File

@ -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,
)
}
}
}

View File

@ -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))
}

View File

@ -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
}
}

View File

@ -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,
)
}
}
}

View File

@ -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))
}
}

View File

@ -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)
}

View File

@ -0,0 +1,11 @@
package handlers
import (
"fmt"
"net/http"
)
func IndexHandler(w http.ResponseWriter, _ *http.Request) {
response := `<h1>Radarr Exporter</h1><p><a href='/metrics'>metrics</a></p>`
fmt.Fprintf(w, response)
}

17
internal/model/radarr.go Normal file
View File

@ -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"`
}

51
internal/model/shared.go Normal file
View File

@ -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"`
}

21
internal/utils/utils.go Normal file
View File

@ -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 != ""
}