diff --git a/.github/actions/tests/action.yaml b/.github/actions/tests/action.yaml index e4cff31..709478a 100644 --- a/.github/actions/tests/action.yaml +++ b/.github/actions/tests/action.yaml @@ -10,6 +10,25 @@ runs: with: go-version: ">=1.19" + - name: Check Go Fmt + shell: bash + run: | + go version + go fmt ./... + git diff --exit-code + + - name: Check Go Mod + shell: bash + run: | + go version + go mod tidy + git diff --exit-code + + - uses: golangci/golangci-lint-action@v2 + with: + version: v1.51.2 + args: --timeout 5m --config .github/lint/golangci.yaml + - name: Run Unit tests shell: bash run: | diff --git a/.github/lint/golangci.yaml b/.github/lint/golangci.yaml new file mode 100644 index 0000000..6e7dfb2 --- /dev/null +++ b/.github/lint/golangci.yaml @@ -0,0 +1,18 @@ +--- +run: + timeout: 3m +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 \ No newline at end of file diff --git a/makefile b/Makefile similarity index 68% rename from makefile rename to Makefile index 4bb935a..172c5de 100644 --- a/makefile +++ b/Makefile @@ -1,7 +1,10 @@ -include .env +.PHONY: build run check fmt tidy lint test + build: docker build . -t exportarr:local + run: docker rm --force exportarr || echo "" docker run --name exportarr \ @@ -11,7 +14,17 @@ run: -e LOG_LEVEL="debug" \ -p 9707:9707 \ -d exportarr:local ${APP_NAME} -test: - go test -v -race -covermode atomic -coverprofile=covprofile ./... + +check: fmt tidy lint test + +fmt: + go fmt ./... + tidy: - go mod tidy \ No newline at end of file + go mod tidy + +lint: + golangci-lint run -c .github/lint/golangci.yaml + +test: + go test -v -race -covermode atomic -coverprofile=covprofile ./... \ No newline at end of file diff --git a/cmd/exportarr/main.go b/cmd/exportarr/main.go index 04cfa29..ca23741 100644 --- a/cmd/exportarr/main.go +++ b/cmd/exportarr/main.go @@ -1,6 +1,8 @@ package main -import "github.com/onedr0p/exportarr/internal/commands" +import ( + "github.com/onedr0p/exportarr/internal/commands" +) var ( appName = "exportarr" @@ -10,10 +12,13 @@ var ( ) func main() { - commands.Execute(commands.AppInfo{ + err := commands.Execute(commands.AppInfo{ Name: appName, Version: version, BuildTime: buildTime, Revision: revision, }) + if err != nil { + panic(err) + } } diff --git a/internal/arr/client/auth.go b/internal/arr/client/auth.go index 1758c5d..5a5d6fd 100644 --- a/internal/arr/client/auth.go +++ b/internal/arr/client/auth.go @@ -91,7 +91,9 @@ func (a *FormAuth) Auth(req *http.Request) error { } u := a.AuthBaseURL.JoinPath("login") - u.Query().Add("ReturnUrl", "/general/settings") + vals := u.Query() + vals.Add("ReturnUrl", "/general/settings") + u.RawQuery = vals.Encode() authReq, err := http.NewRequest("POST", u.String(), strings.NewReader(form.Encode())) if err != nil { @@ -117,12 +119,16 @@ func (a *FormAuth) Auth(req *http.Request) error { return fmt.Errorf("Failed to renew FormAuth Cookie: Received Status Code %d", authResp.StatusCode) } + found := false for _, cookie := range authResp.Cookies() { if strings.HasSuffix(cookie.Name, "arrAuth") { copy := *cookie a.cookie = © + found = true break } + } + if !found { return fmt.Errorf("Failed to renew FormAuth Cookie: No Cookie with suffix 'arrAuth' found") } } diff --git a/internal/arr/client/auth_test.go b/internal/arr/client/auth_test.go index cba2869..8fdd014 100644 --- a/internal/arr/client/auth_test.go +++ b/internal/arr/client/auth_test.go @@ -183,7 +183,7 @@ func TestRoundTrip_Retries(t *testing.T) { { name: "Err", testFunc: func(req *http.Request) (*http.Response, error) { - return nil, &http.ProtocolError{} + return nil, http.ErrNotSupported }, }, } diff --git a/internal/arr/collector/radarr.go b/internal/arr/collector/radarr.go index d70a1ac..b9a4d8d 100644 --- a/internal/arr/collector/radarr.go +++ b/internal/arr/collector/radarr.go @@ -117,15 +117,15 @@ func (collector *radarrCollector) Collect(ch chan<- prometheus.Metric) { } var fileSize int64 var ( - editions = 0 + editions = 0 downloaded = 0 monitored = 0 unmonitored = 0 missing = 0 wanted = 0 qualities = map[string]int{} - tags = []struct { - Label string + tags = []struct { + Label string Movies int }{} ) @@ -166,22 +166,20 @@ func (collector *radarrCollector) Collect(ch chan<- prometheus.Metric) { tagObjects := model.TagMovies{} // https://radarr.video/docs/api/#/TagDetails/get_api_v3_tag_detail if err := c.DoRequest("tag/detail", &tagObjects); err != nil { - log.Errorw("Error getting Tags", "error", err) - ch <- prometheus.NewInvalidMetric(collector.errorMetric, err) - return - } + log.Errorw("Error getting Tags", "error", err) + ch <- prometheus.NewInvalidMetric(collector.errorMetric, err) + return + } for _, s := range tagObjects { tag := struct { - Label string + Label string Movies int }{ - Label: s.Label, + Label: s.Label, Movies: len(s.MovieIds), } tags = append(tags, tag) } - - ch <- prometheus.MustNewConstMetric(collector.movieEdition, prometheus.GaugeValue, float64(editions)) ch <- prometheus.MustNewConstMetric(collector.movieMetric, prometheus.GaugeValue, float64(len(movies))) diff --git a/internal/arr/collector/status.go b/internal/arr/collector/status.go index 5cc83f0..e5547ff 100644 --- a/internal/arr/collector/status.go +++ b/internal/arr/collector/status.go @@ -12,7 +12,6 @@ import ( type systemStatusCollector struct { config *config.ArrConfig // App configuration - configFile *model.Config // *arr configuration from config.xml systemStatus *prometheus.Desc // Total number of system statuses errorMetric *prometheus.Desc // Error Description for use with InvalidMetric } diff --git a/internal/arr/model/radarr.go b/internal/arr/model/radarr.go index 94b9c50..f26fb86 100644 --- a/internal/arr/model/radarr.go +++ b/internal/arr/model/radarr.go @@ -6,9 +6,9 @@ type Movie []struct { HasFile bool `json:"hasFile"` Available bool `json:"isAvailable"` Monitored bool `json:"monitored"` - MovieFile struct { - Edition string `json:"edition"` - Size int64 `json:"size"` + MovieFile struct { + Edition string `json:"edition"` + Size int64 `json:"size"` Quality struct { Quality struct { Name string `json:"name"` @@ -19,7 +19,7 @@ type Movie []struct { } type TagMovies []struct { - ID int `json:"id"` - Label string `json:"label"` - MovieIds []int `json:"movieIds"` + ID int `json:"id"` + Label string `json:"label"` + MovieIds []int `json:"movieIds"` } diff --git a/internal/client/client_test.go b/internal/client/client_test.go index e768fe8..6b77283 100644 --- a/internal/client/client_test.go +++ b/internal/client/client_test.go @@ -95,7 +95,6 @@ func TestDoRequest_PanicRecovery(t *testing.T) { require.NoError(err) w.Write(s) w.WriteHeader(http.StatusOK) - return })) defer ts.Close() diff --git a/internal/commands/arr.go b/internal/commands/arr.go index 6d71401..75a2b84 100644 --- a/internal/commands/arr.go +++ b/internal/commands/arr.go @@ -33,7 +33,9 @@ func init() { func UsageOnError(cmd *cobra.Command, err error) { if err != nil { fmt.Fprintln(os.Stderr, err) - cmd.Usage() + if err := cmd.Usage(); err != nil { + panic(err) + } os.Exit(1) } } @@ -178,7 +180,9 @@ var prowlarrCmd = &cobra.Command{ return err } c.ApiVersion = "v1" - c.LoadProwlarrConfig(cmd.PersistentFlags()) + if err := c.LoadProwlarrConfig(cmd.PersistentFlags()); err != nil { + return err + } if err := c.Prowlarr.Validate(); err != nil { return err } diff --git a/internal/commands/root.go b/internal/commands/root.go index e239baf..d24b577 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -60,13 +60,17 @@ func initConfig() { conf, err = config.LoadConfig(rootCmd.PersistentFlags()) if err != nil { fmt.Fprintln(os.Stderr, err) - rootCmd.Usage() + if err := rootCmd.Usage(); err != nil { + panic(err) + } os.Exit(1) } if err := conf.Validate(); err != nil { fmt.Fprintln(os.Stderr, err) - rootCmd.Usage() + if err := rootCmd.Usage(); err != nil { + panic(err) + } os.Exit(1) } } @@ -102,7 +106,7 @@ func initLogger() { func finalizeLogger() { // Flushes buffered log messages - zap.S().Sync() + zap.S().Sync() //nolint:errcheck } type registerFunc func(registry prometheus.Registerer) diff --git a/internal/config/config.go b/internal/config/config.go index 2bf82ad..cd193c7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -78,7 +78,9 @@ func LoadConfig(flags *flag.FlagSet) (*Config, error) { return nil, fmt.Errorf("Couldn't Read API Key file %w", err) } - k.Set("api-key", string(data)) + if err := k.Set("api-key", string(data)); err != nil { + return nil, fmt.Errorf("Couldn't merge api-key into config: %w", err) + } } var out Config diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 451325c..e47849a 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -44,7 +44,7 @@ func TestLoadConfig_Flags(t *testing.T) { require.True(config.DisableSSLVerify) flags.Set("form-auth", "false") - config, err = LoadConfig(flags) + _, err = LoadConfig(flags) require.NoError(err) } diff --git a/internal/handlers/healthz.go b/internal/handlers/healthz.go index de8c8f6..4c9bbd0 100644 --- a/internal/handlers/healthz.go +++ b/internal/handlers/healthz.go @@ -7,6 +7,6 @@ import ( func HealthzHandler(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) - w.Write([]byte("OK")) + w.Write([]byte("OK")) //nolint:errcheck fmt.Fprint(w) } diff --git a/internal/handlers/index.go b/internal/handlers/index.go index bd6c27f..0b3b9d0 100644 --- a/internal/handlers/index.go +++ b/internal/handlers/index.go @@ -7,5 +7,5 @@ import ( func IndexHandler(w http.ResponseWriter, _ *http.Request) { response := `