Compare commits

...

112 Commits

Author SHA1 Message Date
renovate[bot]
7a3db16074
fix(deps): update module github.com/knadh/koanf/providers/file to v1.2.1 (#408)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-19 08:40:22 -05:00
Boemeltrein
86a455425a
Fix instances from 'sabnzdb' to 'sabnzbd' (#406) 2025-12-11 20:04:30 -05:00
Gavin McFall
f4282b23cb
feat(sonarr): add series tag metrics (#405) 2025-12-09 08:15:32 -05:00
renovate[bot]
32206e0195
fix(deps): update module golang.org/x/sync to v0.19.0 (#404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 11:57:30 -05:00
renovate[bot]
d02dcfc638
fix(deps): update module github.com/spf13/cobra to v1.10.2 (#403)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 09:37:46 -05:00
renovate[bot]
e272e7d2d6
fix(deps): update module go.uber.org/zap to v1.27.1 (#399)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 08:00:55 -05:00
renovate[bot]
6f2cbc6b77
chore(deps): update actions/checkout action to v6 (#400)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 17:42:38 -05:00
renovate[bot]
0641a89f16
chore(deps): update golangci/golangci-lint-action action to v9 (#397)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-08 09:59:24 -05:00
renovate[bot]
6df7bec85d
fix(deps): update module golang.org/x/sync to v0.18.0 (#398)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-08 09:58:47 -05:00
Timo Jaspers
67e213dea8
datasource filter and fixes for dashboard2.json (#396) 2025-11-07 07:45:00 -05:00
renovate[bot]
3cd394508c
fix(deps): update module github.com/knadh/koanf/v2 to v2.3.0 (#391)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 06:19:54 -04:00
renovate[bot]
32cfa9e4ac
fix(deps): update module golang.org/x/sync to v0.17.0 (#388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-07 09:59:56 -04:00
renovate[bot]
b9f5074fbb
fix(deps): update module github.com/spf13/cobra to v1.10.1 (#386)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 20:11:21 -04:00
renovate[bot]
c983dc5d73
chore(deps): update actions/setup-go action to v6 (#387)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 20:10:57 -04:00
renovate[bot]
389d695698
fix(deps): update module github.com/prometheus/client_golang to v1.23.2 (#385)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 20:09:24 -04:00
renovate[bot]
eb2493be64
fix(deps): update module github.com/spf13/pflag to v1.0.10 (#382)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 20:08:48 -04:00
renovate[bot]
1e0d2f4e97
fix(deps): update module github.com/gookit/validate to v1.5.6 (#384)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 20:08:33 -04:00
Devin Buhl
b8b6aeac4b
Update README to reflect maintenance mode status
Clarified the maintenance status of Exportarr and its limitations regarding data collection from APIs.
2025-08-31 16:49:24 -04:00
renovate[bot]
3d6df3896b
fix(deps): update module github.com/stretchr/testify to v1.11.1 (#381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 16:42:15 -04:00
ShawnHardwick
367f6031df
Track total count of episodes with cutoff unmet in Sonarr (#375)
* Add sonarr_episode_cutoff_unmet_total metric

* Fix sonarr test fixture by adding missing json file

* Add radarr_movie_cutoff_unmet_total metric

* Remove compiled file

* Fix expected_metrics.txt for radarr using wrong HELP string

* Add missing end-of-line to test ficture JSON files

* Fix redeclare of CutoffUnmet type

* Fix EOL for radarr model

* Fix test fixture data for sonarr and radarr to match expectations
2025-08-12 00:14:47 -04:00
renovate[bot]
1c6341f0b1
chore(deps): update golang docker tag to v1.24.6 (#374)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 20:42:04 -04:00
renovate[bot]
9cc130410f
chore(deps): update dependency go to v1.24.6 (#373)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 20:41:51 -04:00
renovate[bot]
04561a3c3d
chore(deps): update actions/checkout action to v5 (#376)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 20:41:32 -04:00
renovate[bot]
305ecf665e
fix(deps): update module github.com/prometheus/client_golang to v1.23.0 (#372)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-01 09:45:07 -04:00
renovate[bot]
3eff0ca85f
fix(deps): update module github.com/knadh/koanf/v2 to v2.2.2 (#366)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-20 08:47:11 -04:00
renovate[bot]
9d7eaf4aa8
chore(deps): update golang docker tag to v1.24.5 (#369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-20 08:46:54 -04:00
renovate[bot]
fae1d714ce
fix(deps): update module github.com/spf13/pflag to v1.0.7 (#371)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-20 08:46:46 -04:00
renovate[bot]
0089def832
fix(deps): update module golang.org/x/sync to v0.16.0 (#370)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-20 08:46:39 -04:00
renovate[bot]
260ab02f5f
chore(deps): update dependency go to v1.24.5 (#368)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-20 08:46:29 -04:00
renovate[bot]
0225f50153
fix(deps): update module github.com/knadh/koanf/providers/posflag to v1.0.1 (#365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-20 08:46:19 -04:00
renovate[bot]
e9a64ee311
fix(deps): update module golang.org/x/sync to v0.15.0 (#363)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-06 10:11:05 -04:00
renovate[bot]
a9a21b8126
chore(deps): update golang docker tag to v1.24.4 (#362)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-06 10:09:32 -04:00
renovate[bot]
5f58308c3e
chore(deps): update dependency go to v1.24.4 (#361)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-06 10:09:26 -04:00
renovate[bot]
330f0b0abb
fix(deps): update module github.com/gookit/validate to v1.5.5 (#360)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-27 13:30:28 -04:00
renovate[bot]
1f39314323
chore(deps): update golang docker tag to v1.24.3 (#359)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-06 21:04:11 -04:00
renovate[bot]
c4597fb481
chore(deps): update dependency go to v1.24.3 (#358)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-06 21:04:05 -04:00
renovate[bot]
40225cedcf
chore(deps): update golangci/golangci-lint-action action to v8 (#356)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-06 11:38:47 -04:00
renovate[bot]
5d768b256d
fix(deps): update module golang.org/x/sync to v0.14.0 (#357)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-06 11:38:10 -04:00
Cyb3r Jak3
f86fe3bfc9
Build binaries for windows (#355) 2025-04-21 16:53:36 -04:00
Michał Sawicz
def78d0339
Add quality weights (#350)
Co-authored-by: Devin Buhl <onedr0p@users.noreply.github.com>
2025-04-20 09:06:35 -04:00
renovate[bot]
130eb4ce15
fix(deps): update module github.com/knadh/koanf/providers/posflag to v1 (#354)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 18:19:52 -04:00
Devin Buhl
baacf8df6e
fix: update golangci-lint and fix linting issues (#353) 2025-04-15 16:57:31 -04:00
renovate[bot]
4ace1abc7a
fix(deps): update module github.com/knadh/koanf/providers/confmap to v1 (#352)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 16:29:33 -04:00
renovate[bot]
72d0253efa
fix(deps): update module github.com/knadh/koanf/v2 to v2.2.0 (#351)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 16:29:15 -04:00
renovate[bot]
7b0c581020
chore(deps): update dependency go to v1.24.2 (#344)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 16:25:55 -04:00
renovate[bot]
acb2097c4b
chore(deps): update golang docker tag to v1.24.2 (#345)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 16:25:46 -04:00
renovate[bot]
7d73b433bb
fix(deps): update module golang.org/x/sync to v0.13.0 (#346)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 16:25:32 -04:00
renovate[bot]
e312a81d4b
fix(deps): update module github.com/prometheus/client_golang to v1.22.0 (#347)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 13:48:33 -04:00
renovate[bot]
7778f75502
fix(deps): update module github.com/knadh/koanf/providers/file to v1.2.0 (#349)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 13:36:06 -04:00
renovate[bot]
481a1686f9
fix(deps): update module github.com/knadh/koanf/providers/env to v1.1.0 (#348)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 13:30:35 -04:00
Devin Buhl
587c6a1a7c
fix: goreleaser 2025-03-29 07:29:27 -04:00
renovate[bot]
a5a595a650
fix(deps): update module github.com/prometheus/client_golang to v1.21.1 (#340)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-29 07:11:53 -04:00
renovate[bot]
091fa156db
fix(deps): update module github.com/spf13/cobra to v1.9.1 (#339)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-29 07:09:07 -04:00
Russell Troxel
168fac32f6
Update golangci-lint version (#342)
Signed-off-by: Russell Troxel <russell@troxel.io>
2025-03-22 19:16:56 -07:00
renovate[bot]
d3eb0e22be
fix(deps): update module golang.org/x/sync to v0.12.0 (#338)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 18:53:57 -07:00
renovate[bot]
2078395212
fix(deps): update module github.com/spf13/pflag to v1.0.6 (#337)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 18:53:47 -07:00
renovate[bot]
d064d5d4bb
chore(deps): update golang docker tag to v1.24.1 (#336)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 18:53:35 -07:00
Will Tekulve
4b1ee958a8
fix: resolve invalid port override (#334) 2025-03-22 18:50:40 -07:00
Devin Buhl
9173dbdca7
chore: update renovate config 2025-03-15 21:52:03 -04:00
renovate[bot]
dba01a7134
fix(deps): update module github.com/gookit/validate to v1.5.4 (#332)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-22 23:13:23 -05:00
renovate[bot]
c01ec8a7cb
fix(deps): update module github.com/gookit/validate to v1.5.3 (#331)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-11 13:03:45 -05:00
renovate[bot]
643a925701
fix(deps): update module golang.org/x/sync to v0.10.0 (#330)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-04 11:46:30 -05:00
renovate[bot]
cd2441e39f
chore(deps): update golang docker tag to v1.23.4 (#329)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 21:41:16 -05:00
renovate[bot]
42bdd7908f
fix(deps): update module github.com/stretchr/testify to v1.10.0 (#328)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-23 09:16:55 -05:00
Nik LaBelle
7d1af014c0
Trim whitespace in API key file (#327)
Many editors insert newlines automatically, so we need to trim the api key from the file before we try to use it.
2024-11-14 04:57:26 -05:00
renovate[bot]
17e1c08791
fix(deps): update module golang.org/x/sync to v0.9.0 (#326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-07 20:06:26 -05:00
renovate[bot]
5fd5008d91
fix(deps): update module github.com/knadh/koanf/v2 to v2.1.2 (#324)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-07 15:42:58 -05:00
renovate[bot]
07175a6b01
chore(deps): update golang docker tag to v1.23.3 (#325)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-07 15:42:51 -05:00
renovate[bot]
327c7a1dab
chore(deps): update golang docker tag to v1.23.2 (#318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-22 21:01:58 -04:00
renovate[bot]
dfc7028b96
fix(deps): update module github.com/prometheus/client_golang to v1.20.5 (#319)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-22 21:01:42 -04:00
renovate[bot]
cb96e5dca2
fix(deps): update module github.com/knadh/koanf/providers/file to v1.1.2 (#323)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-03 16:14:28 -04:00
renovate[bot]
c03e86b3b2
fix(deps): update module github.com/knadh/koanf/providers/file to v1.1.1 (#322)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-02 21:53:47 -04:00
renovate[bot]
ebe23a146f
fix(deps): update module github.com/knadh/koanf/providers/env to v1 (#321)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-27 12:37:55 -04:00
renovate[bot]
89ac18a844
fix(deps): update module github.com/knadh/koanf/providers/file to v1 (#311)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-09 08:41:54 -07:00
renovate[bot]
f40b71c1cf
fix(deps): update module golang.org/x/sync to v0.8.0 (#314)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-09 08:36:35 -07:00
renovate[bot]
6de6a2c23f
chore(deps): update golang docker tag to v1.22.6 (#315)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-09 08:36:25 -07:00
Flozza
af49a5d4fe
Fix prowlarr env var in docker-compose.yml (#310)
The example only uses a single underscore, but the parser expects two to create a `prowlarr.backfill` option.
2024-08-09 08:36:06 -07:00
renovate[bot]
72c2d6ce18
chore(deps): update golang docker tag to v1.22.5 (#312)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-03 08:22:33 -04:00
renovate[bot]
ddb0aa5a18
chore(deps): update golangci/golangci-lint-action action to v6 (#296)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 07:06:21 -04:00
renovate[bot]
dc97704375
chore(deps): update golang docker tag to v1.22.4 (#305)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 07:06:14 -04:00
renovate[bot]
ebafeb2b9e
chore(deps): update goreleaser/goreleaser-action action to v6 (#306)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 07:06:08 -04:00
renovate[bot]
d4dde1a55c
fix(deps): update module github.com/spf13/cobra to v1.8.1 (#307)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 07:05:54 -04:00
renovate[bot]
b1c1026505
chore(deps): update docker/build-push-action action to v6 (#308)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 06:59:46 -04:00
Michael J. Mitchell
bc0dd989b8
Update docker-compose with examples for bazarr and readarr (#301) 2024-05-17 21:40:05 -04:00
renovate[bot]
0b552eac7a
fix(deps): update module github.com/prometheus/client_golang to v1.19.1 (#300)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-10 06:54:56 -04:00
renovate[bot]
fc830f4cd7
chore(deps): update golang docker tag to v1.22.3 (#297)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-07 18:46:15 -04:00
renovate[bot]
3d11ef4c16
chore(deps): update golangci/golangci-lint-action action to v5 (#293)
* chore(deps): update golangci/golangci-lint-action action to v5

* Update action.yaml

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Devin Buhl <onedr0p@users.noreply.github.com>
2024-04-25 07:34:07 -04:00
Devin Buhl
862f5f4cde
fix: do not pin digest on GHA
Signed-off-by: Devin Buhl <devin@buhl.casa>
2024-04-22 14:04:40 -04:00
Devin Buhl
950cb75dbd
chore(renovate): ignore golang.org/x/exp 2024-04-15 11:50:19 -04:00
renovate[bot]
a5966c3d2c
fix(deps): update golang.org/x/exp digest to 93d18d7 (#288)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-09 19:40:19 -04:00
renovate[bot]
e8b408abb1
fix(deps): update golang.org/x/exp digest to c0f41cb (#287)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-05 06:42:14 -04:00
renovate[bot]
0ba1cd2d80
fix(deps): update module golang.org/x/sync to v0.7.0 (#286)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-04 12:02:15 -04:00
renovate[bot]
67335fdd3f
fix(deps): update module github.com/knadh/koanf/v2 to v2.1.1 (#284)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-03 14:48:06 -04:00
renovate[bot]
2ee793e8b4
chore(deps): update golang docker tag to v1.22.2 (#285)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-03 14:47:51 -04:00
renovate[bot]
3e6b595b84
fix(deps): update golang.org/x/exp digest to a685a6e (#282)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-28 15:32:44 -04:00
Devin Buhl
e401f676cf
don't prefix branch build with v 2024-03-28 12:19:05 -04:00
renovate[bot]
10597c440a
fix(deps): update golang.org/x/exp digest to a85f2c6 (#281)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-21 09:30:06 -04:00
Chris Yocum
0963316789
Minor fixes (#280) 2024-03-12 09:53:03 -04:00
Devin Buhl
86cd0a5681
chore: use the right discord link 2024-03-10 09:46:57 -04:00
Devin Buhl
1db0b074fc
chore: update issue template 2024-03-10 09:45:15 -04:00
renovate[bot]
63ea3fa3bf
chore(deps): update golang docker tag to v1.22.1 (#277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-05 17:22:07 -05:00
Russell Troxel
8161da029e
fix-263: remove hardcoded /sabnzbd from api path (#275)
Signed-off-by: Russell Troxel <russell@troxel.io>
2024-03-02 19:01:22 -05:00
Russell Troxel
793371b2a3
add missing valid api key tests (#274)
Signed-off-by: Russell Troxel <russell@troxel.io>
2024-03-02 12:45:35 -08:00
renovate[bot]
582c27972d
fix(deps): update module github.com/stretchr/testify to v1.9.0 (#272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 10:53:08 -05:00
Devin Buhl
0cb318682e
fix: make api-key less restrictive (#270)
* fix: make api-key less restrictive

Signed-off-by: Devin Buhl <devin@buhl.casa>

* fix: update tests

Signed-off-by: Devin Buhl <devin@buhl.casa>

---------

Signed-off-by: Devin Buhl <devin@buhl.casa>
2024-02-28 15:45:53 -05:00
renovate[bot]
f9543a2207
fix(deps): update module go.uber.org/zap to v1.27.0 (#267)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-28 15:35:30 -05:00
renovate[bot]
56c90ac0ec
chore(deps): update golangci/golangci-lint-action action to v4 (#265)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-28 15:34:35 -05:00
renovate[bot]
9439b4a634
chore(deps): update golang docker tag to v1.22.0 (#264)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-28 15:33:34 -05:00
renovate[bot]
f12eac158b
fix(deps): update golang.org/x/exp digest to 814bf88 (#261)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-28 15:33:02 -05:00
renovate[bot]
6e75f42405
fix(deps): update module github.com/prometheus/client_golang to v1.19.0 (#268)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-28 15:32:54 -05:00
renovate[bot]
933ed39eba
fix(deps): update module github.com/knadh/koanf/v2 to v2.1.0 (#262)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-06 08:59:16 -05:00
renovate[bot]
7523925fa6
fix(deps): update module github.com/knadh/koanf/v2 to v2.0.2 (#260)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-05 14:08:29 -08:00
63 changed files with 3422 additions and 4248 deletions

View File

@ -1,10 +1,6 @@
---
blank_issues_enabled: false
contact_links:
- name: Discuss on GitHub
url: https://github.com/k8s-at-home/organization/discussions
about: Ask and answer questions here
- name: Discuss on Discord
url: https://discord.com/invite/sTMX7Vh
about: Join our Discord community
- name: Discuss on Discord
url: https://discord.gg/home-operations
about: Join our Discord community

View File

@ -27,12 +27,11 @@ runs:
images: ghcr.io/${{ github.repository }}
flavor: |
latest=${{ fromJSON(inputs.latest) }}
prefix=v
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=semver,pattern={{version}},prefix=v
type=semver,pattern={{major}}.{{minor}},prefix=v
type=semver,pattern={{major}},prefix=v
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@ -51,7 +50,7 @@ runs:
- name: Build Docker Image
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile

View File

@ -11,14 +11,14 @@ runs:
using: composite
steps:
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ">=1.19"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --rm-dist
args: release
env:
GITHUB_TOKEN: ${{ inputs.token }}

View File

@ -6,13 +6,12 @@ runs:
using: composite
steps:
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ">=1.19"
go-version: ">=1.23"
# Run golangcilint before `go get` is ran
# https://github.com/golangci/golangci-lint-action/issues/23
- uses: golangci/golangci-lint-action@v3
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v1.55.1
args: --timeout 5m --config .github/lint/golangci.yaml
version: v2.1
args: --timeout=5m --config=.github/lint/golangci.yaml

View File

@ -1,18 +1,26 @@
---
run:
timeout: 3m
version: "2"
linters:
# https://golangci-lint.run/usage/linters/#enabled-by-default
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- typecheck
- unused
issues:
exclude-rules:
- path: '(.+)_test\.go'
linters:
- errcheck
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- errcheck
path: (.+)_test\.go
paths:
- third_party$
- builtin$
- examples$
formatters:
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

View File

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
uses: actions/checkout@v6
- name: Tests
uses: ./.github/actions/tests
@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
uses: actions/checkout@v6
- name: Lint
uses: ./.github/actions/lint
@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
uses: actions/checkout@v6
- name: Build
uses: ./.github/actions/docker-image

View File

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
uses: actions/checkout@v6
- name: Lint
uses: ./.github/actions/lint
@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
uses: actions/checkout@v6
- name: Tests
uses: ./.github/actions/tests

View File

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
uses: actions/checkout@v6
- name: Tests
uses: ./.github/actions/tests
@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
uses: actions/checkout@v6
- name: Lint
uses: ./.github/actions/lint
@ -32,7 +32,7 @@ jobs:
- lint
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
uses: actions/checkout@v6
- name: Build
uses: ./.github/actions/docker-image
@ -46,7 +46,7 @@ jobs:
- lint
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
uses: actions/checkout@v6
- name: Release
uses: ./.github/actions/go-release

View File

@ -1,3 +1,5 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
version: 2
project_name: "exportarr"
archives:
- id: main
@ -21,6 +23,7 @@ builds:
- "linux"
- "freebsd"
- "openbsd"
- "windows"
ignore:
- goos: darwin
goarch: arm
@ -32,5 +35,7 @@ builds:
goarch: arm64
- goos: openbsd
goarch: arm64
- goos: windows
goarch: arm
env:
- CGO_ENABLED=0

11
.renovaterc.json5 Normal file
View File

@ -0,0 +1,11 @@
{
$schema: "https://docs.renovatebot.com/renovate-schema.json",
extends: [
"config:recommended"
],
postUpdateOptions: [
"gomodTidy",
"gomodUpdateImportPaths"
],
ignoreDeps: ["golang.org/x/exp"]
}

View File

@ -1,4 +1,4 @@
FROM golang:1.21.6-alpine as builder
FROM golang:1.24.6-alpine as builder
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT=""

View File

@ -1,10 +1,13 @@
# exportarr
# Exportarr
> [!IMPORTANT]
> Exportarr is in maintenance mode, trying to collect metrics from these applications is not up to my standards — meaning gathering this data from the Sonarr, Radarr etc... APIs is not ideal. If these apps ever expose a stats API endpoint that can be use to collect data instead I might be willing on refactoring it to support pulling data from that instead.
AIO Prometheus Exporter for Sonarr, Radarr, Lidarr, Prowlarr, Readarr, Bazarr and Sabnzbd
[![Go Report Card](https://goreportcard.com/badge/github.com/onedr0p/exportarr)](https://goreportcard.com/report/github.com/onedr0p/exportarr)
Note: This exporter will not gather metrics from all apps at once, and instead you an `exportarr` instance for each app. Be sure to see the examples below for more information.
Note: This exporter will not gather metrics from all apps at once. You will need an `exportarr` instance for each app. Be sure to see the examples below for more information.
![image](.github/images/dashboard-2.png)
@ -23,7 +26,7 @@ See examples in the [examples/kubernetes](./examples/kubernetes/) directory.
_Replace `$app`, `$port` and `$apikey` with one of the support apps, port and api key_
```sh
# PORT must be unique across all exportarr instances
# PORT must be unique across all Exportarr instances
docker run --name exportarr_$app \
-e PORT=9707 \
-e URL="http://x.x.x.x:$port" \
@ -42,7 +45,7 @@ _Replace `$app`, `$port` and `$apikey` with one of the support apps, port and ap
```sh
./exportarr $app --help
# --port must be unique across all exportarr instances
# --port must be unique across all Exportarr instances
./exportarr $app \
--port 9707 \
--url "http://x.x.x.x:$port" \
@ -55,28 +58,28 @@ Visit http://127.0.0.1:9707/metrics to see the app metrics
| Environment Variable | CLI Flag | Description | Default | Required |
| :-----------------------------: | ------------------------------ | -------------------------------------------------------------- | -------------------- | :------: |
| `PORT` | `--port` or `-p` | The port exportarr will listen on | | ✅ |
| `URL` | `--url` or `-u` | The full URL to Sonarr, Radarr, or Lidarr | | ✅ |
| `API_KEY` | `--api-key` or `-a` | API Key for Sonarr, Radarr or Lidarr | | ❌ |
| `API_KEY_FILE` | `--api-key-file` | API Key file location for Sonarr, Radarr or Lidarr | | ❌ |
| `CONFIG` | `--config` or `-c` | Path to Sonarr, Radarr or Lidarr's `config.xml` (advanced) | | ❌ |
| `INTERFACE` | `--interface` or `-i` | The interface IP exportarr will listen on | `0.0.0.0` | ❌ |
| `LOG_LEVEL` | `--log-level` or `-l` | Set the default Log Level | `INFO` | ❌ |
| `DISABLE_SSL_VERIFY` | `--disable-ssl-verify` | Set to `true` to disable SSL verification | `false` | ❌ |
| `AUTH_PASSWORD` | `--auth-password` | Set to your basic or form auth password | | ❌ |
| `AUTH_USERNAME` | `--auth-username` | Set to your basic or form auth username | | ❌ |
| `FORM_AUTH` | `--form-auth` | Use Form Auth instead of basic auth | `false` | ❌ |
| `ENABLE_ADDITIONAL_METRICS` | `--enable-additional-metrics` | Set to `true` to enable gathering of additional metrics (slow) | `false` | ❌ |
| `ENABLE_UNKNOWN_QUEUE_ITEMS` | `--enable-unknown-queue-items` | Set to `true` to enable gathering unknown queue items | `false` | ❌ |
| `PROWLARR__BACKFILL` | `--backfill` | Set to `true` to enable backfill of historical metrics | `false` | ❌ |
| `PROWLARR__BACKFILL_SINCE_DATE` | `--backfill-since-date` | Set a date from which to start the backfill | `1970-01-01` (epoch) | ❌ |
| `PORT` | `--port` or `-p` | The port Exportarr will listen on | | ✅ |
| `URL` | `--url` or `-u` | The full URL to Sonarr, Radarr, or Lidarr | | ✅ |
| `API_KEY` | `--api-key` or `-a` | API Key for Sonarr, Radarr or Lidarr | | ❌ |
| `API_KEY_FILE` | `--api-key-file` | API Key file location for Sonarr, Radarr or Lidarr | | ❌ |
| `CONFIG` | `--config` or `-c` | Path to Sonarr, Radarr or Lidarr's `config.xml` (advanced) | | ❌ |
| `INTERFACE` | `--interface` or `-i` | The interface IP Exportarr will listen on | `0.0.0.0` | ❌ |
| `LOG_LEVEL` | `--log-level` or `-l` | Set the default Log Level | `INFO` | ❌ |
| `DISABLE_SSL_VERIFY` | `--disable-ssl-verify` | Set to `true` to disable SSL verification | `false` | ❌ |
| `AUTH_PASSWORD` | `--auth-password` | Set to your basic or form auth password | | ❌ |
| `AUTH_USERNAME` | `--auth-username` | Set to your basic or form auth username | | ❌ |
| `FORM_AUTH` | `--form-auth` | Use Form Auth instead of basic auth | `false` | ❌ |
| `ENABLE_ADDITIONAL_METRICS` | `--enable-additional-metrics` | Set to `true` to enable gathering of additional metrics (slow) | `false` | ❌ |
| `ENABLE_UNKNOWN_QUEUE_ITEMS` | `--enable-unknown-queue-items` | Set to `true` to enable gathering unknown queue items | `false` | ❌ |
| `PROWLARR__BACKFILL` | `--backfill` | Set to `true` to enable backfill of historical metrics | `false` | ❌ |
| `PROWLARR__BACKFILL_SINCE_DATE` | `--backfill-since-date` | Set a date from which to start the backfill | `1970-01-01` (epoch) | ❌ |
### Prowlarr Backfill
The prowlarr collector is a little different than other collectors as it's hitting an actual "stats" endpoint, collecting counters of events that happened in a small time window, rather than getting all-time statistics like the other collectors. This means that by default, when you start the prowlarr collector, collected stats will start from that moment (all counters will start from zero).
The Prowlarr collector is a little different than other collectors as it's hitting an actual "stats" endpoint, collecting counters of events that happened in a small time window, rather than getting all-time statistics like the other collectors. This means that by default, when you start the Prowlarr collector, collected stats will start from that moment (all counters will start from zero).
To backfill all Prowlarr Data, either use `PROWLARR__BACKFILL` or `--backfill`.
Note that the first request can be extremely slow, depending on how long your prowlarr instance has been running. You can also specify a start date to limit the backfill if the backfill is timing out:
Note that the first request can be extremely slow, depending on how long your Prowlarr instance has been running. You can also specify a start date to limit the backfill if the backfill is timing out:
`PROWLARR__BACKFILL_DATE_SINCE=2023-03-01` or `--backfill-date-since=2023-03-01`

View File

@ -2,7 +2,7 @@
version: "3.7"
services:
sonarr-exporter:
image: ghcr.io/onedr0p/exportarr:v1.5.3
image: ghcr.io/onedr0p/exportarr:v2.0
container_name: sonarr-exporter
command: ["sonarr"]
environment:
@ -15,7 +15,7 @@ services:
- "9707:9707"
restart: unless-stopped
radarr-exporter:
image: ghcr.io/onedr0p/exportarr:v1.5.3
image: ghcr.io/onedr0p/exportarr:v2.0
container_name: radarr-exporter
command: ["radarr"]
environment:
@ -28,7 +28,7 @@ services:
- "9708:9708"
restart: unless-stopped
lidarr-exporter:
image: ghcr.io/onedr0p/exportarr:v1.5.3
image: ghcr.io/onedr0p/exportarr:v2.0
container_name: lidarr-exporter
command: ["lidarr"]
environment:
@ -41,22 +41,22 @@ services:
- "9709:9709"
restart: unless-stopped
prowlarr-exporter:
image: ghcr.io/onedr0p/exportarr:v1.5.3
image: ghcr.io/onedr0p/exportarr:v2.0
container_name: prowlarr-exporter
command: ["prowlarr"]
environment:
PORT: 9710
URL: "http://x.x.x.x:9696" # or; http://prowlarr:8080
APIKEY: "abc"
# PROWLARR_BACKFILL: true # optional
# PROWLARR_BACKFILL_SINCE_DATE: "2023-03-01" # optional
# PROWLARR__BACKFILL: true # optional
# PROWLARR__BACKFILL_SINCE_DATE: "2023-03-01" # optional
# networks:
# - your_custom_network # optional
ports:
- "9710:9710"
restart: unless-stopped
sabnzbd-exporter:
image: ghcr.io/onedr0p/exportarr:v1.5.3
image: ghcr.io/onedr0p/exportarr:v2.0
container_name: sabnzbd-exporter
command: ["sabnzbd"]
environment:
@ -68,3 +68,29 @@ services:
ports:
- "9711:9711"
restart: unless-stopped
bazarr-exporter:
image: ghcr.io/onedr0p/exportarr:v2.0
container_name: bazarr-exporter
command: ["bazarr"]
environment:
PORT: 9712
URL: "http://x.x.x.x:6767" # or; http://bazarr:6767
APIKEY: "xxx"
# networks:
# - your_custom_network # optional
ports:
- "9712:9712"
restart: unless-stopped
readarr-exporter:
image: ghcr.io/onedr0p/exportarr:v2.0
container_name: readarr-exporter
command: ["readarr"]
environment:
PORT: 9713
URL: "http://x.x.x.x:8787" # or; http://readarr:8787
APIKEY: "xxx"
# networks:
# - your_custom_network # optional
ports:
- "9713:9713"
restart: unless-stopped

File diff suppressed because it is too large Load Diff

58
go.mod
View File

@ -1,43 +1,47 @@
module github.com/onedr0p/exportarr
go 1.19
go 1.24.0
toolchain go1.24.6
require (
github.com/gookit/validate v1.5.2
github.com/knadh/koanf/providers/confmap v0.1.0
github.com/knadh/koanf/providers/env v0.1.0
github.com/knadh/koanf/providers/file v0.1.0
github.com/knadh/koanf/providers/posflag v0.1.0
github.com/knadh/koanf/v2 v2.0.1
github.com/prometheus/client_golang v1.18.0
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
go.uber.org/zap v1.26.0
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
golang.org/x/sync v0.6.0
github.com/gookit/validate v1.5.6
github.com/knadh/koanf/providers/confmap v1.0.0
github.com/knadh/koanf/providers/env v1.1.0
github.com/knadh/koanf/providers/file v1.2.1
github.com/knadh/koanf/providers/posflag v1.0.1
github.com/knadh/koanf/v2 v2.3.0
github.com/prometheus/client_golang v1.23.2
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
go.uber.org/zap v1.27.1
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8
golang.org/x/sync v0.19.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gookit/filter v1.2.1 // indirect
github.com/gookit/goutil v0.6.15 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gookit/filter v1.2.3 // indirect
github.com/gookit/goutil v0.7.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

134
go.sum
View File

@ -1,85 +1,93 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
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/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/filter v1.2.1 h1:37XivkBm2E5qe1KaGdJ5ZfF5l9NYdGWfLEeQadJD8O4=
github.com/gookit/filter v1.2.1/go.mod h1:rxynQFr793x+XDwnRmJFEb53zDw0Zqx3OD7TXWoR9mQ=
github.com/gookit/goutil v0.6.15 h1:mMQ0ElojNZoyPD0eVROk5QXJPh2uKR4g06slgPDF5Jo=
github.com/gookit/goutil v0.6.15/go.mod h1:qdKdYEHQdEtyH+4fNdQNZfJHhI0jUZzHxQVAV3DaMDY=
github.com/gookit/validate v1.5.2 h1:i5I2OQ7WYHFRPRATGu9QarR9snnNHydvwSuHXaRWAV0=
github.com/gookit/validate v1.5.2/go.mod h1:yuPy2WwDlwGRa06fFJ5XIO8QEwhRnTC2LmxmBa5SE14=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/gookit/filter v1.2.3 h1:Zo7cBOtsVzAoa/jtf+Ury6zlsbJXqInFdUpbbnB2vMM=
github.com/gookit/filter v1.2.3/go.mod h1:nFLJcOV8dRgS1iiX23gUQgmHUhpuS40qCvAGgIvA1pM=
github.com/gookit/goutil v0.7.1 h1:AaFJPN9mrdeYBv8HOybri26EHGCC34WJVT7jUStGJsI=
github.com/gookit/goutil v0.7.1/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
github.com/gookit/validate v1.5.6 h1:D6vbSZzreuKYpeeXm5FDDEJy3K5E4lcWsQE4saSMZbU=
github.com/gookit/validate v1.5.6/go.mod h1:WYEHndRNepIIkM+6CtgEX9MQ9ToIQRhXxmz5oLHF/fc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU=
github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU=
github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg=
github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ=
github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
github.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHYujwipe7Ie3qW6U=
github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0=
github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g=
github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/providers/env v1.1.0 h1:U2VXPY0f+CsNDkvdsG8GcsnK4ah85WwWyJgef9oQMSc=
github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY=
github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP3AESOJYp9wM=
github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
github.com/knadh/koanf/providers/posflag v1.0.1 h1:EnMxHSrPkYCFnKgBUl5KBgrjed8gVFrcXDzaW4l/C6Y=
github.com/knadh/koanf/providers/posflag v1.0.1/go.mod h1:3Wn3+YG3f4ljzRyCUgIwH7G0sZ1pMjCOsNBovrbKmAk=
github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM=
github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -9,18 +9,17 @@ import (
"github.com/onedr0p/exportarr/internal/arr/config"
"github.com/onedr0p/exportarr/internal/client"
base_client "github.com/onedr0p/exportarr/internal/client"
)
type Client = base_client.Client
type QueryParams = base_client.QueryParams
type Client = client.Client
type QueryParams = client.QueryParams
func NewClient(config *config.ArrConfig) (*Client, error) {
auth, err := NewAuth(config)
if err != nil {
return nil, err
}
return base_client.NewClient(config.BaseURL(), config.DisableSSLVerify, auth)
return client.NewClient(config.BaseURL(), config.DisableSSLVerify, auth)
}
func NewAuth(config *config.ArrConfig) (client.Authenticator, error) {
@ -97,7 +96,7 @@ func (a *FormAuth) Auth(req *http.Request) error {
authReq, err := http.NewRequest("POST", u.String(), strings.NewReader(form.Encode()))
if err != nil {
return fmt.Errorf("Failed to renew FormAuth Cookie: %w", err)
return fmt.Errorf("failed to renew FormAuth Cookie: %w", err)
}
authReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
@ -105,18 +104,18 @@ func (a *FormAuth) Auth(req *http.Request) error {
client := &http.Client{Transport: a.Transport, CheckRedirect: func(req *http.Request, via []*http.Request) error {
if req.URL.Query().Get("loginFailed") == "true" {
return fmt.Errorf("Failed to renew FormAuth Cookie: Login Failed")
return fmt.Errorf("failed to renew FormAuth Cookie: Login Failed")
}
return http.ErrUseLastResponse
}}
authResp, err := client.Do(authReq)
if err != nil {
return fmt.Errorf("Failed to renew FormAuth Cookie: %w", err)
return fmt.Errorf("failed to renew FormAuth Cookie: %w", err)
}
if authResp.StatusCode != 302 {
return fmt.Errorf("Failed to renew FormAuth Cookie: Received Status Code %d", authResp.StatusCode)
return fmt.Errorf("failed to renew FormAuth Cookie: Received Status Code %d", authResp.StatusCode)
}
found := false
@ -129,7 +128,7 @@ func (a *FormAuth) Auth(req *http.Request) error {
}
}
if !found {
return fmt.Errorf("Failed to renew FormAuth Cookie: No Cookie with suffix 'arrAuth' found")
return fmt.Errorf("failed to renew FormAuth Cookie: No Cookie with suffix 'arrAuth' found")
}
}

View File

@ -44,7 +44,7 @@ func TestBazarrCollect(t *testing.T) {
b, err := os.ReadFile(bazarr_test_fixtures_path + "expected_metrics.txt")
require.NoError(err)
expected := strings.Replace(string(b), "SOMEURL", ts.URL, -1)
expected := strings.ReplaceAll(string(b), "SOMEURL", ts.URL)
f := strings.NewReader(expected)
collections := []string{
"bazarr_episode_subtitles_downloaded_total",
@ -97,17 +97,20 @@ func TestBazarrCollect_Concurrency(t *testing.T) {
require := require.New(t)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Contains(r.URL.Path, "/api/")
if r.URL.Path == "/api/series" {
switch r.URL.Path {
case "/api/series":
w.WriteHeader(http.StatusOK)
json, err := os.ReadFile(bazarr_test_fixtures_path + "concurrency/series.json")
require.NoError(err)
_, err = w.Write(json)
require.NoError(err)
} else if r.URL.Path == "/api/episodes" {
case "/api/episodes":
seriesIDs := r.URL.Query()["seriesid[]"]
require.Len(seriesIDs, 2)
if slices.Contains(seriesIDs, "944") && slices.Contains(seriesIDs, "945") {
w.WriteHeader(http.StatusOK)
json, err := os.ReadFile(bazarr_test_fixtures_path + "concurrency/episodes944_945.json")
@ -123,7 +126,8 @@ func TestBazarrCollect_Concurrency(t *testing.T) {
} else {
w.WriteHeader(http.StatusInternalServerError)
}
} else {
default:
ts2, err := newTestBazarrServer(t, func(w http.ResponseWriter, r *http.Request) {
require.Contains(r.URL.Path, "/api/")
})
@ -149,7 +153,7 @@ func TestBazarrCollect_Concurrency(t *testing.T) {
b, err := os.ReadFile(bazarr_test_fixtures_path + "concurrency/expected_metrics.txt")
require.NoError(err)
expected := strings.Replace(string(b), "SOMEURL", ts.URL, -1)
expected := strings.ReplaceAll(string(b), "SOMEURL", ts.URL)
f := strings.NewReader(expected)
collections := []string{
"bazarr_episode_subtitles_downloaded_total",

View File

@ -71,8 +71,8 @@ func TestSystemHealthCollect(t *testing.T) {
b, err := os.ReadFile(test_util.COMMON_FIXTURES_PATH + "expected_health_metrics.txt")
require.NoError(err)
expected := strings.Replace(string(b), "SOMEURL", ts.URL, -1)
expected = strings.Replace(expected, "APP", tt.config.App, -1)
expected := strings.ReplaceAll(string(b), "SOMEURL", ts.URL)
expected = strings.ReplaceAll(expected, "APP", tt.config.App)
f := strings.NewReader(expected)

View File

@ -71,8 +71,8 @@ func TestHistoryCollect(t *testing.T) {
b, err := os.ReadFile(test_util.COMMON_FIXTURES_PATH + "expected_history_metrics.txt")
require.NoError(err)
expected := strings.Replace(string(b), "SOMEURL", ts.URL, -1)
expected = strings.Replace(expected, "APP", tt.config.App, -1)
expected := strings.ReplaceAll(string(b), "SOMEURL", ts.URL)
expected = strings.ReplaceAll(expected, "APP", tt.config.App)
f := strings.NewReader(expected)

View File

@ -2,6 +2,7 @@ package collector
import (
"fmt"
"strconv"
"github.com/onedr0p/exportarr/internal/arr/client"
"github.com/onedr0p/exportarr/internal/arr/config"
@ -99,7 +100,7 @@ func NewLidarrCollector(c *config.ArrConfig) *lidarrCollector {
songsQualitiesMetric: prometheus.NewDesc(
"lidarr_songs_quality_total",
"Total number of downloaded songs by quality",
[]string{"quality"},
[]string{"quality", "weight"},
prometheus.Labels{"url": c.URL},
),
errorMetric: prometheus.NewDesc(
@ -144,6 +145,7 @@ func (collector *lidarrCollector) Collect(ch chan<- prometheus.Metric) {
songs = 0
songsDownloaded = 0
songsQualities = map[string]int{}
qualityWeights = map[string]string{}
)
artists := model.Artist{}
@ -197,6 +199,19 @@ func (collector *lidarrCollector) Collect(ch chan<- prometheus.Metric) {
albumGenres[genre]++
}
}
qualities := model.Qualities{}
if err := c.DoRequest("qualitydefinition", &qualities); err != nil {
log.Errorw("Error getting qualities",
"error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
for _, q := range qualities {
if q.Quality.Name != "" {
qualityWeights[q.Quality.Name] = strconv.Itoa(q.Weight)
}
}
}
}
@ -226,7 +241,7 @@ func (collector *lidarrCollector) Collect(ch chan<- prometheus.Metric) {
if len(songsQualities) > 0 {
for qualityName, count := range songsQualities {
ch <- prometheus.MustNewConstMetric(collector.songsQualitiesMetric, prometheus.GaugeValue, float64(count), qualityName)
ch <- prometheus.MustNewConstMetric(collector.songsQualitiesMetric, prometheus.GaugeValue, float64(count), qualityName, qualityWeights[qualityName])
}
}

View File

@ -0,0 +1,96 @@
package collector
import (
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"github.com/onedr0p/exportarr/internal/arr/config"
"github.com/onedr0p/exportarr/internal/test_util"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/require"
)
const lidarr_test_fixtures_path = "../test_fixtures/lidarr/"
func newTestLidarrServer(t *testing.T, fn func(http.ResponseWriter, *http.Request)) (*httptest.Server, error) {
return test_util.NewTestServer(t, lidarr_test_fixtures_path, fn)
}
func TestLidarrCollect(t *testing.T) {
tests := []struct {
name string
config *config.ArrConfig
expected_metrics_file string
}{
{
name: "basic",
config: &config.ArrConfig{
App: "lidarr",
ApiVersion: "v3",
},
expected_metrics_file: "expected_metrics.txt",
},
{
name: "additional_metrics",
config: &config.ArrConfig{
App: "lidarr",
ApiVersion: "v3",
EnableAdditionalMetrics: true,
},
expected_metrics_file: "expected_metrics_extended.txt",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)
ts, err := newTestLidarrServer(t, func(w http.ResponseWriter, r *http.Request) {
require.Contains(r.URL.Path, "/api/")
})
require.NoError(err)
defer ts.Close()
tt.config.URL = ts.URL
tt.config.ApiKey = test_util.API_KEY
collector := NewLidarrCollector(tt.config)
require.NoError(err)
b, err := os.ReadFile(lidarr_test_fixtures_path + tt.expected_metrics_file)
require.NoError(err)
expected := strings.ReplaceAll(string(b), "SOMEURL", ts.URL)
f := strings.NewReader(expected)
require.NotPanics(func() {
err = testutil.CollectAndCompare(collector, f)
})
require.NoError(err)
})
}
}
func TestLidarrCollect_FailureDoesntPanic(t *testing.T) {
require := require.New(t)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer ts.Close()
config := &config.ArrConfig{
URL: ts.URL,
ApiKey: test_util.API_KEY,
}
collector := NewRadarrCollector(config)
f := strings.NewReader("")
require.NotPanics(func() {
err := testutil.CollectAndCompare(collector, f)
require.Error(err)
}, "Collecting metrics should not panic on failure")
}

View File

@ -51,9 +51,10 @@ func (collector *queueCollector) Collect(ch chan<- prometheus.Metric) {
params := client.QueryParams{}
params.Add("page", "1")
if collector.config.EnableUnknownQueueItems {
if collector.config.App == "sonarr" {
switch collector.config.App {
case "sonarr":
params.Add("includeUnknownSeriesItems", "true")
} else if collector.config.App == "radarr" {
case "radarr":
params.Add("includeUnknownMovieItems", "true")
}
}

View File

@ -71,8 +71,8 @@ func TestQueueCollect(t *testing.T) {
b, err := os.ReadFile(test_util.COMMON_FIXTURES_PATH + "expected_queue_metrics.txt")
require.NoError(err)
expected := strings.Replace(string(b), "SOMEURL", ts.URL, -1)
expected = strings.Replace(expected, "APP", tt.config.App, -1)
expected := strings.ReplaceAll(string(b), "SOMEURL", ts.URL)
expected = strings.ReplaceAll(expected, "APP", tt.config.App)
f := strings.NewReader(expected)

View File

@ -1,6 +1,8 @@
package collector
import (
"strconv"
"github.com/onedr0p/exportarr/internal/arr/client"
"github.com/onedr0p/exportarr/internal/arr/config"
"github.com/onedr0p/exportarr/internal/arr/model"
@ -17,6 +19,7 @@ type radarrCollector struct {
movieUnmonitoredMetric *prometheus.Desc // Total number of unmonitored movies
movieWantedMetric *prometheus.Desc // Total number of wanted movies
movieMissingMetric *prometheus.Desc // Total number of missing movies
movieCutoffUnmetMetric *prometheus.Desc // Total number of movies with cutoff unmet
movieQualitiesMetric *prometheus.Desc // Total number of movies by quality
movieFileSizeMetric *prometheus.Desc // Total fizesize of all movies in bytes
errorMetric *prometheus.Desc // Error Description for use with InvalidMetric
@ -68,6 +71,12 @@ func NewRadarrCollector(c *config.ArrConfig) *radarrCollector {
nil,
prometheus.Labels{"url": c.URL},
),
movieCutoffUnmetMetric: prometheus.NewDesc(
"radarr_movie_cutoff_unmet_total",
"Total number of movies with cutoff unmet",
nil,
prometheus.Labels{"url": c.URL},
),
movieFileSizeMetric: prometheus.NewDesc(
"radarr_movie_filesize_total",
"Total filesize of all movies",
@ -77,7 +86,7 @@ func NewRadarrCollector(c *config.ArrConfig) *radarrCollector {
movieQualitiesMetric: prometheus.NewDesc(
"radarr_movie_quality_total",
"Total number of downloaded movies by quality",
[]string{"quality"},
[]string{"quality", "weight"},
prometheus.Labels{"url": c.URL},
),
movieTagsMetric: prometheus.NewDesc(
@ -103,6 +112,7 @@ func (collector *radarrCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- collector.movieUnmonitoredMetric
ch <- collector.movieWantedMetric
ch <- collector.movieMissingMetric
ch <- collector.movieCutoffUnmetMetric
ch <- collector.movieFileSizeMetric
ch <- collector.movieQualitiesMetric
ch <- collector.movieTagsMetric
@ -129,6 +139,7 @@ func (collector *radarrCollector) Collect(ch chan<- prometheus.Metric) {
Label string
Movies int
}{}
qualityWeights = map[string]string{}
)
movies := model.Movie{}
@ -186,6 +197,30 @@ func (collector *radarrCollector) Collect(ch chan<- prometheus.Metric) {
tags = append(tags, tag)
}
qualityDefs := model.Qualities{}
if err := c.DoRequest("qualitydefinition", &qualityDefs); err != nil {
log.Errorw("Error getting qualities",
"error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
for _, q := range qualityDefs {
if q.Quality.Name != "" {
qualityWeights[q.Quality.Name] = strconv.Itoa(q.Weight)
}
}
moviesCutoffUnmet := model.CutoffUnmetMovies{}
moviesCutoffUnmetParams := client.QueryParams{}
params.Add("sortKey", "airDateUtc")
if err := c.DoRequest("wanted/cutoff", &moviesCutoffUnmet, moviesCutoffUnmetParams); err != nil {
log.Errorw("Error getting cutoff unmet",
"error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
ch <- prometheus.MustNewConstMetric(collector.movieEdition, prometheus.GaugeValue, float64(editions))
ch <- prometheus.MustNewConstMetric(collector.movieMetric, prometheus.GaugeValue, float64(len(movies)))
ch <- prometheus.MustNewConstMetric(collector.movieDownloadedMetric, prometheus.GaugeValue, float64(downloaded))
@ -193,12 +228,13 @@ func (collector *radarrCollector) Collect(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(collector.movieUnmonitoredMetric, prometheus.GaugeValue, float64(unmonitored))
ch <- prometheus.MustNewConstMetric(collector.movieWantedMetric, prometheus.GaugeValue, float64(wanted))
ch <- prometheus.MustNewConstMetric(collector.movieMissingMetric, prometheus.GaugeValue, float64(missing))
ch <- prometheus.MustNewConstMetric(collector.movieCutoffUnmetMetric, prometheus.GaugeValue, float64(moviesCutoffUnmet.TotalRecords))
ch <- prometheus.MustNewConstMetric(collector.movieFileSizeMetric, prometheus.GaugeValue, float64(fileSize))
if len(qualities) > 0 {
for qualityName, count := range qualities {
ch <- prometheus.MustNewConstMetric(collector.movieQualitiesMetric, prometheus.GaugeValue, float64(count),
qualityName,
qualityName, qualityWeights[qualityName],
)
}
}

View File

@ -40,7 +40,7 @@ func TestRadarrCollect(t *testing.T) {
b, err := os.ReadFile(radarr_test_fixtures_path + "expected_metrics.txt")
require.NoError(err)
expected := strings.Replace(string(b), "SOMEURL", ts.URL, -1)
expected := strings.ReplaceAll(string(b), "SOMEURL", ts.URL)
f := strings.NewReader(expected)
require.NotPanics(func() {

View File

@ -40,7 +40,7 @@ func TestReadarrCollect(t *testing.T) {
b, err := os.ReadFile(readarr_test_fixtures_path + "expected_metrics.txt")
require.NoError(err)
expected := strings.Replace(string(b), "SOMEURL", ts.URL, -1)
expected := strings.ReplaceAll(string(b), "SOMEURL", ts.URL)
f := strings.NewReader(expected)
require.NotPanics(func() {

View File

@ -71,8 +71,8 @@ func TestRootFolderCollect(t *testing.T) {
b, err := os.ReadFile(test_util.COMMON_FIXTURES_PATH + "expected_rootfolder_metrics.txt")
require.NoError(err)
expected := strings.Replace(string(b), "SOMEURL", ts.URL, -1)
expected = strings.Replace(expected, "APP", tt.config.App, -1)
expected := strings.ReplaceAll(string(b), "SOMEURL", ts.URL)
expected = strings.ReplaceAll(expected, "APP", tt.config.App)
f := strings.NewReader(expected)

View File

@ -2,6 +2,7 @@ package collector
import (
"fmt"
"strconv"
"time"
"github.com/onedr0p/exportarr/internal/arr/client"
@ -18,6 +19,7 @@ type sonarrCollector struct {
seriesMonitoredMetric *prometheus.Desc // Total number of monitored series
seriesUnmonitoredMetric *prometheus.Desc // Total number of unmonitored series
seriesFileSizeMetric *prometheus.Desc // Total fizesize of all series in bytes
seriesTagsMetric *prometheus.Desc // Total number of series by tag
seasonMetric *prometheus.Desc // Total number of seasons
seasonDownloadedMetric *prometheus.Desc // Total number of downloaded seasons
seasonMonitoredMetric *prometheus.Desc // Total number of monitored seasons
@ -27,6 +29,7 @@ type sonarrCollector struct {
episodeUnmonitoredMetric *prometheus.Desc // Total number of unmonitored episodes
episodeDownloadedMetric *prometheus.Desc // Total number of downloaded episodes
episodeMissingMetric *prometheus.Desc // Total number of missing episodes
episodeCutoffUnmetMetric *prometheus.Desc // Total number of episodes with cutoff unmet
episodeQualitiesMetric *prometheus.Desc // Total number of episodes by quality
errorMetric *prometheus.Desc // Error Description for use with InvalidMetric
}
@ -64,6 +67,12 @@ func NewSonarrCollector(conf *config.ArrConfig) *sonarrCollector {
nil,
prometheus.Labels{"url": conf.URL},
),
seriesTagsMetric: prometheus.NewDesc(
"sonarr_series_tag_total",
"Total number of downloaded series by tag",
[]string{"tag"},
prometheus.Labels{"url": conf.URL},
),
seasonMetric: prometheus.NewDesc(
"sonarr_season_total",
"Total number of seasons",
@ -118,10 +127,16 @@ func NewSonarrCollector(conf *config.ArrConfig) *sonarrCollector {
nil,
prometheus.Labels{"url": conf.URL},
),
episodeCutoffUnmetMetric: prometheus.NewDesc(
"sonarr_episode_cutoff_unmet_total",
"Total number of episodes with cutoff unmet",
nil,
prometheus.Labels{"url": conf.URL},
),
episodeQualitiesMetric: prometheus.NewDesc(
"sonarr_episode_quality_total",
"Total number of downloaded episodes by quality",
[]string{"quality"},
[]string{"quality", "weight"},
prometheus.Labels{"url": conf.URL},
),
errorMetric: prometheus.NewDesc(
@ -139,6 +154,7 @@ func (collector *sonarrCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- collector.seriesMonitoredMetric
ch <- collector.seriesUnmonitoredMetric
ch <- collector.seriesFileSizeMetric
ch <- collector.seriesTagsMetric
ch <- collector.seasonMetric
ch <- collector.seasonDownloadedMetric
ch <- collector.seasonMonitoredMetric
@ -148,6 +164,7 @@ func (collector *sonarrCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- collector.episodeUnmonitoredMetric
ch <- collector.episodeDownloadedMetric
ch <- collector.episodeMissingMetric
ch <- collector.episodeCutoffUnmetMetric
ch <- collector.episodeQualitiesMetric
}
@ -175,6 +192,7 @@ func (collector *sonarrCollector) Collect(ch chan<- prometheus.Metric) {
episodesMonitored = 0
episodesUnmonitored = 0
episodesQualities = map[string]int{}
qualityWeights = map[string]string{}
)
cseries := []time.Duration{}
@ -249,6 +267,20 @@ func (collector *sonarrCollector) Collect(ch chan<- prometheus.Metric) {
episodesUnmonitored++
}
}
qualities := model.Qualities{}
if err := c.DoRequest("qualitydefinition", &qualities); err != nil {
log.Errorw("Error getting qualities",
"error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
for _, q := range qualities {
if q.Quality.Name != "" {
qualityWeights[q.Quality.Name] = strconv.Itoa(q.Weight)
}
}
log.Debugw("Extra options completed",
"duration", time.Since(textra))
}
@ -271,11 +303,35 @@ func (collector *sonarrCollector) Collect(ch chan<- prometheus.Metric) {
return
}
episodesCutoffUnmet := model.CutoffUnmet{}
// Cutoff unmet endpoint uses the same params as missing
if err := c.DoRequest("wanted/cutoff", &episodesCutoffUnmet, params); err != nil {
log.Errorw("Error getting cutoff unmet",
"error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
// Get tag details for series
tagObjects := model.TagSeries{}
if err := c.DoRequest("tag/detail", &tagObjects); err != nil {
log.Errorw("Error getting tags",
"error", err)
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
ch <- prometheus.MustNewConstMetric(collector.seriesMetric, prometheus.GaugeValue, float64(len(series)))
ch <- prometheus.MustNewConstMetric(collector.seriesDownloadedMetric, prometheus.GaugeValue, float64(seriesDownloaded))
ch <- prometheus.MustNewConstMetric(collector.seriesMonitoredMetric, prometheus.GaugeValue, float64(seriesMonitored))
ch <- prometheus.MustNewConstMetric(collector.seriesUnmonitoredMetric, prometheus.GaugeValue, float64(seriesUnmonitored))
ch <- prometheus.MustNewConstMetric(collector.seriesFileSizeMetric, prometheus.GaugeValue, float64(seriesFileSize))
for _, tag := range tagObjects {
ch <- prometheus.MustNewConstMetric(collector.seriesTagsMetric, prometheus.GaugeValue, float64(len(tag.SeriesIds)),
tag.Label,
)
}
ch <- prometheus.MustNewConstMetric(collector.seasonMetric, prometheus.GaugeValue, float64(seasons))
ch <- prometheus.MustNewConstMetric(collector.seasonDownloadedMetric, prometheus.GaugeValue, float64(seasonsDownloaded))
ch <- prometheus.MustNewConstMetric(collector.seasonMonitoredMetric, prometheus.GaugeValue, float64(seasonsMonitored))
@ -283,6 +339,7 @@ func (collector *sonarrCollector) Collect(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(collector.episodeMetric, prometheus.GaugeValue, float64(episodes))
ch <- prometheus.MustNewConstMetric(collector.episodeDownloadedMetric, prometheus.GaugeValue, float64(episodesDownloaded))
ch <- prometheus.MustNewConstMetric(collector.episodeMissingMetric, prometheus.GaugeValue, float64(episodesMissing.TotalRecords))
ch <- prometheus.MustNewConstMetric(collector.episodeCutoffUnmetMetric, prometheus.GaugeValue, float64(episodesCutoffUnmet.TotalRecords))
if collector.config.EnableAdditionalMetrics {
ch <- prometheus.MustNewConstMetric(collector.episodeMonitoredMetric, prometheus.GaugeValue, float64(episodesMonitored))
@ -291,7 +348,7 @@ func (collector *sonarrCollector) Collect(ch chan<- prometheus.Metric) {
if len(episodesQualities) > 0 {
for qualityName, count := range episodesQualities {
ch <- prometheus.MustNewConstMetric(collector.episodeQualitiesMetric, prometheus.GaugeValue, float64(count),
qualityName,
qualityName, qualityWeights[qualityName],
)
}
}

View File

@ -62,7 +62,7 @@ func TestSonarrCollect(t *testing.T) {
b, err := os.ReadFile(sonarr_test_fixtures_path + tt.expected_metrics_file)
require.NoError(err)
expected := strings.Replace(string(b), "SOMEURL", ts.URL, -1)
expected := strings.ReplaceAll(string(b), "SOMEURL", ts.URL)
f := strings.NewReader(expected)
require.NotPanics(func() {

View File

@ -71,8 +71,8 @@ func TestStatusCollect(t *testing.T) {
b, err := os.ReadFile(test_util.COMMON_FIXTURES_PATH + "expected_status_metrics.txt")
require.NoError(err)
expected := strings.Replace(string(b), "SOMEURL", ts.URL, -1)
expected = strings.Replace(expected, "APP", tt.config.App, -1)
expected := strings.ReplaceAll(string(b), "SOMEURL", ts.URL)
expected = strings.ReplaceAll(expected, "APP", tt.config.App)
f := strings.NewReader(expected)

View File

@ -39,9 +39,9 @@ type ArrConfig struct {
FormAuth bool `koanf:"form-auth"`
EnableUnknownQueueItems bool `koanf:"enable-unknown-queue-items"`
EnableAdditionalMetrics bool `koanf:"enable-additional-metrics"`
URL string `koanf:"url" validate:"required|url"` // stores rendered Arr URL (with api version)
ApiKey string `koanf:"api-key" validate:"required|regex:(^[a-z0-9]{32}$)"` // stores the API key
DisableSSLVerify bool `koanf:"disable-ssl-verify"` // stores the disable SSL verify flag
URL string `koanf:"url" validate:"required|url"` // stores rendered Arr URL (with api version)
ApiKey string `koanf:"api-key" validate:"required|regex:(^[a-zA-Z0-9]{20,32}$)"` // stores the API key
DisableSSLVerify bool `koanf:"disable-ssl-verify"` // stores the disable SSL verify flag
Prowlarr ProwlarrConfig `koanf:"prowlarr"`
Bazarr BazarrConfig `koanf:"bazarr"`
k *koanf.Koanf
@ -74,8 +74,8 @@ func LoadArrConfig(conf base_config.Config, flags *flag.FlagSet) (*ArrConfig, er
// Environment
err = k.Load(env.Provider("", ".", func(s string) string {
s = strings.ToLower(s)
s = strings.Replace(s, "__", ".", -1)
s = strings.Replace(s, "_", "-", -1)
s = strings.ReplaceAll(s, "__", ".")
s = strings.ReplaceAll(s, "_", "-")
return backwardsCompatibilityTransforms(s)
}), nil)
if err != nil {
@ -130,7 +130,7 @@ func (c *ArrConfig) Validate() error {
func (c ArrConfig) Messages() map[string]string {
return validate.MS{
"ApiKey.regex": "api-key must be a 32 character hex string",
"ApiKey.regex": "api-key must be a 20-32 character alphanumeric string",
"LogLevel.ValidateLogLevel": "log-level must be one of: debug, info, warn, error, dpanic, panic, fatal",
}
}

View File

@ -209,11 +209,29 @@ func TestValidate(t *testing.T) {
},
valid: true,
},
{
name: "good-api-key-32-len",
config: &ArrConfig{
URL: "http://localhost",
ApiKey: "abcdefABCDEF0123456789abcdef0123",
ApiVersion: "v3",
},
valid: true,
},
{
name: "good-api-key-32-len",
config: &ArrConfig{
URL: "http://localhost",
ApiKey: "abcdefABCDEF01234567",
ApiVersion: "v3",
},
valid: true,
},
{
name: "bad-api-key",
config: &ArrConfig{
URL: "http://localhost",
ApiKey: "abcdef0123456789abcdef01234567",
ApiKey: "abcdef0123456789abc",
ApiVersion: "v3",
},
valid: false,

View File

@ -25,10 +25,10 @@ func (b BazarrConfig) Validate() error {
return v.Errors
}
if b.SeriesBatchSize < 1 {
return fmt.Errorf("series-batch-size must be greater than zero.")
return fmt.Errorf("series-batch-size must be greater than zero")
}
if b.SeriesBatchConcurrency < 1 {
return fmt.Errorf("series-batch-concurrency must be greater than zero.")
return fmt.Errorf("series-batch-concurrency must be greater than zero")
}
return nil
}

View File

@ -39,7 +39,6 @@ func (p *XML) Marshal(o map[string]interface{}) ([]byte, error) {
func (p *XML) Merge(baseURL string) func(src, dest map[string]interface{}) error {
return func(src, dest map[string]interface{}) error {
if src["api-key"] != nil && src["api-key"].(string) != "" {
dest["api-key"] = src["api-key"]
}
@ -50,7 +49,18 @@ func (p *XML) Merge(baseURL string) func(src, dest map[string]interface{}) error
}
// Add or replace target port
u.Host = u.Hostname() + ":" + src["target-port"].(string)
target_port := src["target-port"].(string)
if len(target_port) > 0 {
// AKA Port is defined in the config xml so we use this value as the source
// of truth.
u.Host = u.Hostname() + ":" + target_port
// If Port is NOT defined in the XML it may be configured by the
// *ARR__SERVER__PORT environment varible passed directly to *arr services.
// In this case, we do not want to override whatever URL (that may include
// the port) passed to exportarr by other means.
}
u = u.JoinPath(src["url-base"].(string))
dest["url"] = u.String()
return nil

View File

@ -23,3 +23,9 @@ type TagMovies []struct {
Label string `json:"label"`
MovieIds []int `json:"movieIds"`
}
// CutoffUnmetMovies - Stores struct of JSON response
// https://radarr.video/docs/api/#/Cutoff/get_api_v3_wanted_cutoff
type CutoffUnmetMovies struct {
TotalRecords int `json:"totalRecords"`
}

View File

@ -51,3 +51,20 @@ type SystemHealthMessage struct {
Message string `json:"message"`
WikiURL string `json:"wikiUrl"`
}
// Qualities - Stores struct of JSON response
// https://wiki.servarr.com/en/sonarr/settings#quality-1
type Qualities []struct {
ID int `json:"id"`
Title string `json:"title"`
Weight int `json:"weight"`
MinSize float32 `json:"minSize"`
MaxSize float32 `json:"maxSize"`
PrefferredSize float32 `json:"preferredSize"`
Quality struct {
ID int `json:"id"`
Name string `json:"name"`
Source string `json:"source"`
Resolution int `json:"resolution"`
}
}

View File

@ -42,6 +42,12 @@ type Missing struct {
TotalRecords int `json:"totalRecords"`
}
// CutoffUnmet - Stores struct of JSON response
// https://sonarr.tv/docs/api/#/Cutoff/get_api_v3_wanted_cutoff
type CutoffUnmet struct {
TotalRecords int `json:"totalRecords"`
}
// EpisodeFile - Stores struct of JSON response
// https://github.com/Sonarr/Sonarr/wiki/EpisodeFile
type EpisodeFile []struct {
@ -64,3 +70,11 @@ type Episode []struct {
HasFile bool `json:"hasFile"`
Monitored bool `json:"monitored"`
}
// TagSeries - Stores struct of JSON response for tag details
// https://sonarr.tv/docs/api/#/TagDetails/get_api_v3_tag_detail
type TagSeries []struct {
ID int `json:"id"`
Label string `json:"label"`
SeriesIds []int `json:"seriesIds"`
}

View File

@ -0,0 +1,25 @@
# HELP lidarr_albums_missing_total Total number of missing albums
# TYPE lidarr_albums_missing_total gauge
lidarr_albums_missing_total{url="SOMEURL"} 321
# HELP lidarr_albums_total Total number of albums
# TYPE lidarr_albums_total gauge
lidarr_albums_total{url="SOMEURL"} 7
# HELP lidarr_artists_filesize_bytes Total fizesize of all artists in bytes
# TYPE lidarr_artists_filesize_bytes gauge
lidarr_artists_filesize_bytes{url="SOMEURL"} 105
# HELP lidarr_artists_genres_total Total number of artists by genre
# TYPE lidarr_artists_genres_total gauge
lidarr_artists_genres_total{genre="Prog",url="SOMEURL"} 1
lidarr_artists_genres_total{genre="Rock",url="SOMEURL"} 1
# HELP lidarr_artists_monitored_total Total number of monitored artists
# TYPE lidarr_artists_monitored_total gauge
lidarr_artists_monitored_total{url="SOMEURL"} 1
# HELP lidarr_artists_total Total number of artists
# TYPE lidarr_artists_total gauge
lidarr_artists_total{url="SOMEURL"} 2
# HELP lidarr_songs_downloaded_total Total number of downloaded songs
# TYPE lidarr_songs_downloaded_total gauge
lidarr_songs_downloaded_total{url="SOMEURL"} 9
# HELP lidarr_songs_total Total number of songs
# TYPE lidarr_songs_total gauge
lidarr_songs_total{url="SOMEURL"} 13

View File

@ -0,0 +1,36 @@
# HELP lidarr_albums_genres_total Total number of albums by genre
# TYPE lidarr_albums_genres_total gauge
lidarr_albums_genres_total{genre="Prog",url="SOMEURL"} 2
lidarr_albums_genres_total{genre="Rock",url="SOMEURL"} 2
# HELP lidarr_albums_missing_total Total number of missing albums
# TYPE lidarr_albums_missing_total gauge
lidarr_albums_missing_total{url="SOMEURL"} 321
# HELP lidarr_albums_monitored_total Total number of albums
# TYPE lidarr_albums_monitored_total gauge
lidarr_albums_monitored_total{url="SOMEURL"} 2
# HELP lidarr_albums_total Total number of albums
# TYPE lidarr_albums_total gauge
lidarr_albums_total{url="SOMEURL"} 7
# HELP lidarr_artists_filesize_bytes Total fizesize of all artists in bytes
# TYPE lidarr_artists_filesize_bytes gauge
lidarr_artists_filesize_bytes{url="SOMEURL"} 105
# HELP lidarr_artists_genres_total Total number of artists by genre
# TYPE lidarr_artists_genres_total gauge
lidarr_artists_genres_total{genre="Prog",url="SOMEURL"} 1
lidarr_artists_genres_total{genre="Rock",url="SOMEURL"} 1
# HELP lidarr_artists_monitored_total Total number of monitored artists
# TYPE lidarr_artists_monitored_total gauge
lidarr_artists_monitored_total{url="SOMEURL"} 1
# HELP lidarr_artists_total Total number of artists
# TYPE lidarr_artists_total gauge
lidarr_artists_total{url="SOMEURL"} 2
# HELP lidarr_songs_downloaded_total Total number of downloaded songs
# TYPE lidarr_songs_downloaded_total gauge
lidarr_songs_downloaded_total{url="SOMEURL"} 9
# HELP lidarr_songs_quality_total Total number of downloaded songs by quality
# TYPE lidarr_songs_quality_total gauge
lidarr_songs_quality_total{quality="High",url="SOMEURL",weight="10"} 2
lidarr_songs_quality_total{quality="Medium",url="SOMEURL",weight="5"} 2
# HELP lidarr_songs_total Total number of songs
# TYPE lidarr_songs_total gauge
lidarr_songs_total{url="SOMEURL"} 13

View File

@ -0,0 +1,8 @@
[
{
"monitored": true
},
{
"monitored": false
}
]

View File

@ -0,0 +1,27 @@
[
{
"monitored": true,
"statistics": {
"albumCount": 1,
"trackFileCount": 2,
"totalTrackCount": 4,
"sizeOnDisk": 5
},
"genres": [
"Rock"
]
},
{
"ended": true,
"monitored": false,
"statistics": {
"albumCount": 6,
"trackFileCount": 7,
"totalTrackCount": 9,
"sizeOnDisk": 100
},
"genres": [
"Prog"
]
}
]

View File

@ -0,0 +1,14 @@
[
{
"quality": {
"name": "Medium"
},
"weight": 5
},
{
"quality": {
"name": "High"
},
"weight": 10
}
]

View File

@ -0,0 +1,16 @@
[
{
"quality": {
"quality": {
"name": "High"
}
}
},
{
"quality": {
"quality": {
"name": "Medium"
}
}
}
]

View File

@ -0,0 +1,3 @@
{
"totalRecords": 321
}

View File

@ -10,14 +10,17 @@ radarr_movie_filesize_total{url="SOMEURL"} 1.47062956689e+11
# HELP radarr_movie_missing_total Total number of missing movies
# TYPE radarr_movie_missing_total gauge
radarr_movie_missing_total{url="SOMEURL"} 2
# HELP radarr_movie_cutoff_unmet_total Total number of movies with cutoff unmet
# TYPE radarr_movie_cutoff_unmet_total gauge
radarr_movie_cutoff_unmet_total{url="SOMEURL"} 1179
# HELP radarr_movie_monitored_total Total number of monitored movies
# TYPE radarr_movie_monitored_total gauge
radarr_movie_monitored_total{url="SOMEURL"} 7
# HELP radarr_movie_quality_total Total number of downloaded movies by quality
# TYPE radarr_movie_quality_total gauge
radarr_movie_quality_total{quality="Bluray-1080p",url="SOMEURL"} 1
radarr_movie_quality_total{quality="Bluray-2160p",url="SOMEURL"} 2
radarr_movie_quality_total{quality="Remux-2160p",url="SOMEURL"} 1
radarr_movie_quality_total{quality="Bluray-1080p",url="SOMEURL",weight="5"} 1
radarr_movie_quality_total{quality="Bluray-2160p",url="SOMEURL",weight="10"} 2
radarr_movie_quality_total{quality="Remux-2160p",url="SOMEURL",weight="15"} 1
# HELP radarr_movie_tag_total Total number of downloaded movies by tag
# TYPE radarr_movie_tag_total gauge
radarr_movie_tag_total{tag="somelabel",url="SOMEURL"} 3

View File

@ -0,0 +1,20 @@
[
{
"quality": {
"name": "Bluray-1080p"
},
"weight": 5
},
{
"quality": {
"name": "Bluray-2160p"
},
"weight": 10
},
{
"quality": {
"name": "Remux-2160p"
},
"weight": 15
}
]

View File

@ -0,0 +1,3 @@
{
"totalRecords": 1179
}

View File

@ -4,6 +4,9 @@ sonarr_episode_downloaded_total{url="SOMEURL"} 285
# HELP sonarr_episode_missing_total Total number of missing episodes
# TYPE sonarr_episode_missing_total gauge
sonarr_episode_missing_total{url="SOMEURL"} 1179
# HELP sonarr_episode_cutoff_unmet_total Total number of episodes with cutoff unmet
# TYPE sonarr_episode_cutoff_unmet_total gauge
sonarr_episode_cutoff_unmet_total{url="SOMEURL"} 1179
# HELP sonarr_episode_total Total number of episodes
# TYPE sonarr_episode_total gauge
sonarr_episode_total{url="SOMEURL"} 675
@ -25,6 +28,10 @@ sonarr_series_downloaded_total{url="SOMEURL"} 5
# HELP sonarr_series_filesize_bytes Total fizesize of all series in bytes
# TYPE sonarr_series_filesize_bytes gauge
sonarr_series_filesize_bytes{url="SOMEURL"} 7.91293980833e+11
# HELP sonarr_series_tag_total Total number of downloaded series by tag
# TYPE sonarr_series_tag_total gauge
sonarr_series_tag_total{tag="comedy",url="SOMEURL"} 3
sonarr_series_tag_total{tag="drama",url="SOMEURL"} 2
# HELP sonarr_series_monitored_total Total number of monitored series
# TYPE sonarr_series_monitored_total gauge
sonarr_series_monitored_total{url="SOMEURL"} 5

View File

@ -4,13 +4,16 @@ sonarr_episode_downloaded_total{url="SOMEURL"} 285
# HELP sonarr_episode_missing_total Total number of missing episodes
# TYPE sonarr_episode_missing_total gauge
sonarr_episode_missing_total{url="SOMEURL"} 1179
# HELP sonarr_episode_cutoff_unmet_total Total number of episodes with cutoff unmet
# TYPE sonarr_episode_cutoff_unmet_total gauge
sonarr_episode_cutoff_unmet_total{url="SOMEURL"} 1179
# HELP sonarr_episode_monitored_total Total number of monitored episodes
# TYPE sonarr_episode_monitored_total gauge
sonarr_episode_monitored_total{url="SOMEURL"} 12
# HELP sonarr_episode_quality_total Total number of downloaded episodes by quality
# TYPE sonarr_episode_quality_total gauge
sonarr_episode_quality_total{quality="WEBDL-1080p",url="SOMEURL"} 12
sonarr_episode_quality_total{quality="WEBRip-1080p",url="SOMEURL"} 6
sonarr_episode_quality_total{quality="WEBDL-1080p",url="SOMEURL",weight="10"} 12
sonarr_episode_quality_total{quality="WEBRip-1080p",url="SOMEURL",weight="5"} 6
# HELP sonarr_episode_total Total number of episodes
# TYPE sonarr_episode_total gauge
sonarr_episode_total{url="SOMEURL"} 675
@ -35,6 +38,10 @@ sonarr_series_downloaded_total{url="SOMEURL"} 5
# HELP sonarr_series_filesize_bytes Total fizesize of all series in bytes
# TYPE sonarr_series_filesize_bytes gauge
sonarr_series_filesize_bytes{url="SOMEURL"} 7.91293980833e+11
# HELP sonarr_series_tag_total Total number of downloaded series by tag
# TYPE sonarr_series_tag_total gauge
sonarr_series_tag_total{tag="comedy",url="SOMEURL"} 3
sonarr_series_tag_total{tag="drama",url="SOMEURL"} 2
# HELP sonarr_series_monitored_total Total number of monitored series
# TYPE sonarr_series_monitored_total gauge
sonarr_series_monitored_total{url="SOMEURL"} 5

View File

@ -0,0 +1,14 @@
[
{
"quality": {
"name": "WEBRip-1080p"
},
"weight": 5
},
{
"quality": {
"name": "WEBDL-1080p"
},
"weight": 10
}
]

View File

@ -0,0 +1,33 @@
[
{
"label": "comedy",
"delayProfileIds": [],
"importListIds": [],
"notificationIds": [],
"restrictionIds": [],
"indexerIds": [],
"downloadClientIds": [],
"autoTagIds": [],
"seriesIds": [
37,
21,
46
],
"id": 14
},
{
"label": "drama",
"delayProfileIds": [],
"importListIds": [],
"notificationIds": [],
"restrictionIds": [],
"indexerIds": [],
"downloadClientIds": [],
"autoTagIds": [],
"seriesIds": [
371,
464
],
"id": 196
}
]

View File

@ -0,0 +1,3 @@
{
"totalRecords": 1179
}

View File

@ -1,3 +1,3 @@
{
"totalRecords": 1179
}
}

View File

@ -25,7 +25,7 @@ func NewClient(baseURL string, insecureSkipVerify bool, auth Authenticator) (*Cl
u, err := url.Parse(baseURL)
if err != nil {
return nil, fmt.Errorf("Failed to parse URL(%s): %w", baseURL, err)
return nil, fmt.Errorf("failed to parse URL(%s): %w", baseURL, err)
}
return &Client{
@ -43,7 +43,7 @@ func (c *Client) unmarshalBody(b io.Reader, target interface{}) (err error) {
defer func() {
if r := recover(); r != nil {
// return recovered panic as error
err = fmt.Errorf("Recovered from panic: %s", r)
err = fmt.Errorf("recovered from panic: %s", r)
log := zap.S()
if zap.S().Level() == zap.DebugLevel {
@ -83,11 +83,11 @@ func (c *Client) DoRequest(endpoint string, target interface{}, queryParams ...Q
req, err := http.NewRequest("GET", url.String(), nil)
if err != nil {
return fmt.Errorf("Failed to create HTTP Request(%s): %w", url, err)
return fmt.Errorf("failed to create HTTP Request(%s): %w", url, err)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("Failed to execute HTTP Request(%s): %w", url, err)
return fmt.Errorf("failed to execute HTTP Request(%s): %w", url, err)
}
defer resp.Body.Close()
return c.unmarshalBody(resp.Body, target)

View File

@ -26,7 +26,7 @@ func (t *ExportarrTransport) RoundTrip(req *http.Request) (*http.Response, error
if t.auth != nil {
err := t.auth.Auth(req)
if err != nil {
return nil, fmt.Errorf("Error authenticating request: %w", err)
return nil, fmt.Errorf("error authenticating request: %w", err)
}
}
@ -40,19 +40,19 @@ func (t *ExportarrTransport) RoundTrip(req *http.Request) (*http.Response, error
}
}
if err != nil {
return nil, fmt.Errorf("Error sending HTTP Request: %w", err)
return nil, fmt.Errorf("error sending HTTP Request: %w", err)
} else {
return nil, fmt.Errorf("Received Server Error Status Code: %d", resp.StatusCode)
return nil, fmt.Errorf("received Server Error Status Code: %d", resp.StatusCode)
}
}
if resp.StatusCode >= 400 && resp.StatusCode <= 499 {
return nil, fmt.Errorf("Received Client Error Status Code: %d", resp.StatusCode)
return nil, fmt.Errorf("received Client Error Status Code: %d", resp.StatusCode)
}
if resp.StatusCode >= 300 && resp.StatusCode <= 399 {
if location, err := resp.Location(); err == nil {
return nil, fmt.Errorf("Received Redirect Status Code: %d, Location: %s", resp.StatusCode, location.String())
return nil, fmt.Errorf("received Redirect Status Code: %d, Location: %s", resp.StatusCode, location.String())
} else {
return nil, fmt.Errorf("Received Redirect Status Code: %d, ", resp.StatusCode)
return nil, fmt.Errorf("received Redirect Status Code: %d, ", resp.StatusCode)
}
}
return resp, nil

View File

@ -57,8 +57,8 @@ func LoadConfig(flags *flag.FlagSet) (*Config, error) {
// Environment
err = k.Load(env.Provider("", ".", func(s string) string {
s = strings.ToLower(s)
s = strings.Replace(s, "__", ".", -1)
s = strings.Replace(s, "_", "-", -1)
s = strings.ReplaceAll(s, "__", ".")
s = strings.ReplaceAll(s, "_", "-")
return backwardsCompatibilityTransforms(s)
}), nil)
if err != nil {
@ -75,11 +75,11 @@ func LoadConfig(flags *flag.FlagSet) (*Config, error) {
if apiKeyFile != "" {
data, err := os.ReadFile(apiKeyFile)
if err != nil {
return nil, fmt.Errorf("Couldn't Read API Key file %w", err)
return nil, fmt.Errorf("couldn't Read API Key file %w", err)
}
if err := k.Set("api-key", string(data)); err != nil {
return nil, fmt.Errorf("Couldn't merge api-key into config: %w", err)
if err := k.Set("api-key", strings.TrimSpace(string(data))); err != nil {
return nil, fmt.Errorf("couldn't merge api-key into config: %w", err)
}
}
@ -111,7 +111,7 @@ func (c *Config) Validate() error {
func (c Config) Messages() map[string]string {
return validate.MS{
"ApiKey.regex": "api-key must be a 32 character hex string",
"ApiKey.regex": "api-key must be a 20-32 character alphanumeric string",
"LogLevel.ValidateLogLevel": "log-level must be one of: debug, info, warn, error, dpanic, panic, fatal",
}
}

View File

@ -27,7 +27,7 @@ func (s serverStatCache) Update(stat model.ServerStat) (ServerStats, error) {
if stat.DayParsed == "" && s.todayKey != "" {
// If the day parsed is empty, it means there are no server side stats.
// If we have exportarr stats, something likely went wrong,
return s, errors.New("No Parsed Dates from Server, but cache is not empty")
return s, errors.New("no Parsed Dates from Server, but cache is not empty")
}
s.total = stat.Total

View File

@ -176,7 +176,7 @@ func NewSabnzbdCollector(config *config.SabnzbdConfig) (*SabnzbdCollector, error
auther := auth.ApiKeyAuth{ApiKey: config.ApiKey}
client, err := client.NewClient(config.URL, config.DisableSSLVerify, auther)
if err != nil {
return nil, fmt.Errorf("Failed to build client: %w", err)
return nil, fmt.Errorf("failed to build client: %w", err)
}
return &SabnzbdCollector{
@ -189,7 +189,7 @@ func NewSabnzbdCollector(config *config.SabnzbdConfig) (*SabnzbdCollector, error
func (s *SabnzbdCollector) doRequest(mode string, target interface{}) error {
params := client.QueryParams{}
params.Add("mode", mode)
return s.client.DoRequest("/sabnzbd/api", target, params)
return s.client.DoRequest("/api", target, params)
}
func (s *SabnzbdCollector) getQueueStats() (*model.QueueStats, error) {
@ -197,7 +197,7 @@ func (s *SabnzbdCollector) getQueueStats() (*model.QueueStats, error) {
err := s.doRequest("queue", stats)
if err != nil {
return nil, fmt.Errorf("Failed to get queue stats: %w", err)
return nil, fmt.Errorf("failed to get queue stats: %w", err)
}
return stats, nil
@ -207,7 +207,7 @@ func (s *SabnzbdCollector) getServerStats() (*model.ServerStats, error) {
var stats = &model.ServerStats{}
err := s.doRequest("server_stats", stats)
if err != nil {
return nil, fmt.Errorf("Failed to get server stats: %w", err)
return nil, fmt.Errorf("failed to get server stats: %w", err)
}
return stats, nil
}

View File

@ -39,7 +39,7 @@ func newTestServer(t *testing.T, fn func(http.ResponseWriter, *http.Request)) (*
func TestCollect(t *testing.T) {
require := require.New(t)
ts, err := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
require.Equal("/sabnzbd/api", r.URL.Path)
require.Equal("/api", r.URL.Path)
require.Equal(API_KEY, r.URL.Query().Get("apikey"))
require.Equal("json", r.URL.Query().Get("output"))
})
@ -57,7 +57,7 @@ func TestCollect(t *testing.T) {
b, err := os.ReadFile("../test_fixtures/expected_metrics.txt")
require.NoError(err)
expected := strings.Replace(string(b), "http://127.0.0.1:39965", ts.URL, -1)
expected := strings.ReplaceAll(string(b), "http://127.0.0.1:39965", ts.URL)
f := strings.NewReader(expected)
require.NotPanics(func() {

View File

@ -149,7 +149,7 @@ func (q *QueueStats) UnmarshalJSON(data []byte) error {
q.TimeEstimate, err = parseDuration(queue["timeleft"], err)
if err != nil {
return fmt.Errorf("Error parsing queue stats: %w", err)
return fmt.Errorf("error parsing queue stats: %w", err)
}
q.DownloadDirDiskspaceTotal *= GB
@ -193,7 +193,7 @@ func parseFloat(s interface{}, prevErr error) (float64, error) {
f, ok := s.(string)
if !ok {
return 0, fmt.Errorf("Invalid float: %v", s)
return 0, fmt.Errorf("invalid float: %v", s)
}
if f == "" {
@ -220,7 +220,7 @@ func parseSize(s interface{}, prevErr error) (float64, error) {
sz, ok := s.(string)
if !ok {
return 0, fmt.Errorf("Invalid float: %v", s)
return 0, fmt.Errorf("invalid float: %v", s)
}
fields := strings.Fields(strings.TrimSpace(sz))
@ -229,7 +229,7 @@ func parseSize(s interface{}, prevErr error) (float64, error) {
}
if len(fields) > 2 {
return 0, fmt.Errorf("Invalid size: %s", sz)
return 0, fmt.Errorf("invalid size: %s", sz)
}
ret, err := strconv.ParseFloat(fields[0], 64)
@ -255,7 +255,7 @@ func parseSize(s interface{}, prevErr error) (float64, error) {
case "PB", "P":
return ret * 1024 * 1024 * 1024 * 1024 * 1024, nil
default:
return 0, fmt.Errorf("Invalid size suffix: %s", sz)
return 0, fmt.Errorf("invalid size suffix: %s", sz)
}
}
@ -271,7 +271,7 @@ func parseDuration(sd interface{}, prevErr error) (time.Duration, error) {
s, ok := sd.(string)
if !ok {
return 0, fmt.Errorf("Invalid float: %v", sd)
return 0, fmt.Errorf("invalid float: %v", sd)
}
if s == "" {
@ -280,7 +280,7 @@ func parseDuration(sd interface{}, prevErr error) (time.Duration, error) {
fields := strings.Split(strings.TrimSpace(s), ":")
if len(fields) < 1 || len(fields) > 4 {
return 0, fmt.Errorf("Invalid duration: %s", s)
return 0, fmt.Errorf("invalid duration: %s", s)
}
intFields := make([]int, len(fields))
@ -290,7 +290,7 @@ func parseDuration(sd interface{}, prevErr error) (time.Duration, error) {
// Reverse the order of the fields
intFields[len(intFields)-1-i], err = strconv.Atoi(f)
if err != nil {
return 0, fmt.Errorf("Invalid integer in duration: %s: %w", f, err)
return 0, fmt.Errorf("invalid integer in duration: %s: %w", f, err)
}
}

View File

@ -17,7 +17,7 @@ func NewTestServer(t *testing.T, fixture_dir string, fn func(http.ResponseWriter
fn(w, r)
require.NotEmpty(t, r.URL.Path)
// turns /api/some/path into some_path
endpoint := strings.Replace(strings.Replace(r.URL.Path, "/api/", "", -1), "/", "_", -1)
endpoint := strings.ReplaceAll(strings.ReplaceAll(r.URL.Path, "/api/", ""), "/", "_")
w.WriteHeader(http.StatusOK)
// NOTE: this assumes there is a file that matches the some_path
json, err := os.ReadFile(filepath.Join(fixture_dir, endpoint+".json"))

View File

@ -1,9 +0,0 @@
{
"extends": [
"config:base"
],
"postUpdateOptions": [
"gomodTidy",
"gomodUpdateImportPaths"
]
}