Compare commits

1 Commits

Author SHA1 Message Date
924cc6f6b1 chore(deps): update docker/login-action action to v3.5.0
Some checks failed
Golang Tests / unittest (stable, ubuntu-latest-arm64) (push) Failing after 14s
Golang CI lint / golangci (stable, ubuntu-latest-arm64) (push) Failing after 1m36s
Markdown linter / markdown-lint (push) Successful in 10s
Golang Tests / unittest (stable, ubuntu-latest-arm64) (pull_request) Failing after 14s
Golang CI lint / golangci (stable, ubuntu-latest-arm64) (pull_request) Failing after 1m20s
Markdown linter / markdown-lint (pull_request) Successful in 10s
Golang Tests / unittest (stable, ubuntu-latest-amd64) (push) Failing after 16s
Golang CI lint / golangci (stable, ubuntu-latest-amd64) (push) Failing after 1m37s
Golang Tests / unittest (stable, ubuntu-latest-amd64) (pull_request) Failing after 17s
Golang CI lint / golangci (stable, ubuntu-latest-amd64) (pull_request) Failing after 1m12s
2025-08-04 16:17:15 +00:00
13 changed files with 63 additions and 126 deletions

View File

@ -1,42 +1,24 @@
# name: "Run Golang tests" name: Golang Tests
# on: on:
# pull_request: pull_request:
# types: [ "opened", "reopened", "synchronize" ] types: [ "opened", "reopened", "synchronize" ]
# push: push:
# branches: [ '**' ] branches: [ '**' ]
# tags-ignore: [ '**' ] tags-ignore: [ '**' ]
# workflow_dispatch: {}
# jobs: jobs:
# integration-test: unittest:
# name: "Run integration tests" runs-on: ${{ matrix.os }}
# runs-on: ${{ matrix.os }} strategy:
# strategy: matrix:
# matrix: go: [ stable ]
# go: [ stable ] os: [ ubuntu-latest-amd64, ubuntu-latest-arm64 ]
# os: [ ubuntu-latest-amd64, ubuntu-latest-arm64 ] steps:
# steps: - uses: actions/checkout@v4.2.2
# - uses: actions/checkout@v4.3.0 - uses: actions/setup-go@v5.5.0
# - uses: actions/setup-go@v5.5.0 with:
# with: go-version: ${{ matrix.go }}
# go-version: ${{ matrix.go }} - env:
# - env: GOPROXY: ${{ vars.GOPROXY }}
# GOPROXY: ${{ vars.GOPROXY }} run: make test/unit
# run: make test/integration
# unit-test:
# name: "Run unit tests"
# runs-on: ${{ matrix.os }}
# strategy:
# matrix:
# go: [ stable ]
# os: [ ubuntu-latest-amd64, ubuntu-latest-arm64 ]
# steps:
# - uses: actions/checkout@v4.3.0
# - uses: actions/setup-go@v5.5.0
# with:
# go-version: ${{ matrix.go }}
# - env:
# GOPROXY: ${{ vars.GOPROXY }}
# run: make test/unit

View File

@ -1,4 +1,4 @@
name: "Lint Golang files" name: Golang CI lint
on: on:
pull_request: pull_request:
@ -6,24 +6,22 @@ on:
push: push:
branches: [ '**' ] branches: [ '**' ]
tags-ignore: [ '**' ] tags-ignore: [ '**' ]
workflow_dispatch: {}
permissions: permissions:
contents: read contents: read
jobs: jobs:
golangci: golangci:
name: "Run golang CI linter"
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go: [ stable ] go: [ stable ]
os: [ ubuntu-latest-amd64, ubuntu-latest-arm64 ] os: [ ubuntu-latest-amd64, ubuntu-latest-arm64 ]
steps: steps:
- uses: actions/checkout@v4.3.0 - uses: actions/checkout@v4.2.2
- uses: actions/setup-go@v5.5.0 - uses: actions/setup-go@v5.5.0
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
- uses: golangci/golangci-lint-action@v8.0.0 - uses: golangci/golangci-lint-action@v8.0.0
with: with:
version: v2.4.0 # renovate: datasource=github-releases depName=golangci/golangci-lint version: v2.1

View File

@ -1,19 +1,19 @@
name: "Lint Markdown files" name: Markdown linter
on: on:
pull_request: pull_request:
types: [ "opened", "reopened", "synchronize" ] types: [ "opened", "reopened", "synchronize" ]
push: push:
branches: [ '*' ] branches: [ '**' ]
tags-ignore: [ '*' ] tags-ignore: [ '**' ]
workflow_dispatch: {} workflow_dispatch: {}
jobs: jobs:
markdown-lint: markdown-lint:
name: "Run markdown linter" runs-on:
runs-on: ubuntu-latest - ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.3.0 - uses: actions/checkout@v4.2.2
- uses: DavidAnson/markdownlint-cli2-action@v20.0.0 - uses: DavidAnson/markdownlint-cli2-action@v20.0.0
with: with:
globs: '**/*.md' globs: '**/*.md'

View File

@ -1,4 +1,4 @@
name: "Release" name: Release
on: on:
push: push:
@ -9,10 +9,10 @@ permissions:
jobs: jobs:
release: release:
name: "Release application" runs-on:
runs-on: ubuntu-latest - ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.3.0 - uses: actions/checkout@v4.2.2
- uses: docker/setup-qemu-action@v3.6.0 - uses: docker/setup-qemu-action@v3.6.0
- uses: actions/setup-go@v5.5.0 - uses: actions/setup-go@v5.5.0
with: with:
@ -26,13 +26,12 @@ jobs:
GITEA_TOKEN: ${{ secrets.GIT_CRYPTIC_SYSTEMS_PACKAGE_REGISTRY_TOKEN }} GITEA_TOKEN: ${{ secrets.GIT_CRYPTIC_SYSTEMS_PACKAGE_REGISTRY_TOKEN }}
GONOSUMDB: ${{ vars.GONOSUMDB }} GONOSUMDB: ${{ vars.GONOSUMDB }}
GOPROXY: ${{ vars.GOPROXY }} GOPROXY: ${{ vars.GOPROXY }}
uses: goreleaser/goreleaser-action@v6.4.0 uses: goreleaser/goreleaser-action@v6.3.0
with: with:
version: v2.11.2 # renovate: datasource=github-releases depName=goreleaser/goreleaser version: "~> v2"
args: release --clean args: release --clean
sync-to-hub-docker-io: sync-to-hub-docker-io:
name: "Upload Images to docker.io"
needs: needs:
- release - release
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -11,10 +11,10 @@ jobs:
runs-on: runs-on:
- ubuntu-latest - ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.3.0 - uses: actions/checkout@v4.2.2
- uses: peter-evans/dockerhub-description@v4.0.2 - uses: peter-evans/dockerhub-description@v4.0.2
with: with:
username: ${{ secrets.DOCKER_IO_USERNAME }} username: ${{ secrets.DOCKER_IO_USERNAME }}
password: ${{ secrets.DOCKER_IO_PASSWORD }} password: ${{ secrets.DOCKER_IO_PASSWORD }}
repository: volkerraschek/tarr repository: volkerraschek/tarr
readme-filepath: README.md readme-filepath: README.md

View File

@ -1,13 +0,0 @@
version: "2"
linters:
default: standard
enable:
- errname
- gosec
exclusions:
rules: []
warn-unused: true
run:
tests: true

View File

@ -2,7 +2,6 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
@ -27,9 +26,7 @@ autharr /etc/lidarr/config.xml`,
} }
rootCmd.Flags().Bool("watch", false, "Listens for changes to the configuration and writes the token continuously to the output") rootCmd.Flags().Bool("watch", false, "Listens for changes to the configuration and writes the token continuously to the output")
if err := rootCmd.Execute(); err != nil { rootCmd.Execute()
log.Fatalln(err)
}
} }
func runE(cmd *cobra.Command, args []string) error { func runE(cmd *cobra.Command, args []string) error {
@ -87,16 +84,10 @@ func runWatch(cmd *cobra.Command, args []string) error {
select { select {
case <-cmd.Context().Done(): case <-cmd.Context().Done():
return nil return nil
case err, open := <-errorChannel: case err := <-errorChannel:
if !open { logrus.WithError(err).Errorln("Received from config watcher")
return fmt.Errorf("error channel has been closed")
}
logrus.WithError(err).Errorln("received from config watcher")
case <-timer.C: case <-timer.C:
err = writeConfig(cachedConfig, dest) writeConfig(cachedConfig, dest)
if err != nil {
logrus.WithError(err).Errorln("failed to write config")
}
case config := <-configChannel: case config := <-configChannel:
cachedConfig = config cachedConfig = config
timer.Reset(waitFor) timer.Reset(waitFor)
@ -114,23 +105,18 @@ func writeConfig(config *domain.Config, dest string) error {
case len(dest) > 0: case len(dest) > 0:
dirname := filepath.Dir(dest) dirname := filepath.Dir(dest)
// #nosec G301
err := os.MkdirAll(dirname, 0755) err := os.MkdirAll(dirname, 0755)
if err != nil { if err != nil {
return err return err
} }
// #nosec G304
f, err := os.Create(dest) f, err := os.Create(dest)
if err != nil { if err != nil {
return err return err
} }
defer func() { _ = f.Close() }() defer func() { _ = f.Close() }()
_, err = f.WriteString(config.API.Token) f.WriteString(config.API.Token)
if err != nil {
return err
}
} }
return nil return nil

View File

@ -3,7 +3,6 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"time" "time"
"git.cryptic.systems/volker.raschek/tarr/pkg/config" "git.cryptic.systems/volker.raschek/tarr/pkg/config"
@ -96,10 +95,7 @@ healarr sonarr https://sonarr.example.com:8443 --api-token my-token`,
rootCmd.PersistentFlags().Bool("insecure", false, "Trust insecure TLS certificates") rootCmd.PersistentFlags().Bool("insecure", false, "Trust insecure TLS certificates")
rootCmd.PersistentFlags().Duration("timeout", time.Minute, "Timeout") rootCmd.PersistentFlags().Duration("timeout", time.Minute, "Timeout")
err := rootCmd.Execute() rootCmd.Execute()
if err != nil {
log.Fatalln(err)
}
} }
func runBazarrE(cmd *cobra.Command, args []string) error { func runBazarrE(cmd *cobra.Command, args []string) error {
@ -156,7 +152,7 @@ func runE(cmd *cobra.Command, args []string, queryKey string) error {
switch { switch {
case len(apiToken) <= 0 && len(configPath) <= 0: case len(apiToken) <= 0 && len(configPath) <= 0:
return fmt.Errorf("at least --api-token oder --config must be defined") return fmt.Errorf("At least --api-token oder --config must be defined")
case len(apiToken) > 0 && len(configPath) <= 0: case len(apiToken) > 0 && len(configPath) <= 0:
err = health.NewReadinessProbe(args[0]). err = health.NewReadinessProbe(args[0]).
QueryAdd(queryKey, apiToken). QueryAdd(queryKey, apiToken).
@ -179,7 +175,7 @@ func runE(cmd *cobra.Command, args []string, queryKey string) error {
return err return err
} }
default: default:
return fmt.Errorf("neither --api-token nor --config can be used at the same time") return fmt.Errorf("Neither --api-token nor --config can be used at the same time.")
} }
return nil return nil

2
go.mod
View File

@ -6,7 +6,7 @@ require (
github.com/fsnotify/fsnotify v1.9.0 github.com/fsnotify/fsnotify v1.9.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )

4
go.sum
View File

@ -17,8 +17,8 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -46,12 +46,11 @@ type YAMLConfig struct {
// Read reads the config struct from a file. The decoding format will be determined by the file extension like // Read reads the config struct from a file. The decoding format will be determined by the file extension like
// `xml` or `yaml`. // `xml` or `yaml`.
func ReadConfig(name string) (*domain.Config, error) { func ReadConfig(name string) (*domain.Config, error) {
// #nosec G304
f, err := os.Open(name) f, err := os.Open(name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() { _ = f.Close() }() defer f.Close()
switch { switch {
case strings.HasSuffix(name, "xml"): case strings.HasSuffix(name, "xml"):
@ -59,7 +58,7 @@ func ReadConfig(name string) (*domain.Config, error) {
case strings.HasSuffix(name, "yml") || strings.HasSuffix(name, "yaml"): case strings.HasSuffix(name, "yml") || strings.HasSuffix(name, "yaml"):
return readYAMLConfig(f) return readYAMLConfig(f)
default: default:
return nil, fmt.Errorf("unsupported file extension") return nil, fmt.Errorf("Unsupported file extension")
} }
} }
@ -98,8 +97,8 @@ func readYAMLConfig(r io.Reader) (*domain.Config, error) {
} }
func WatchConfig(ctx context.Context, name string) (<-chan *domain.Config, <-chan error) { func WatchConfig(ctx context.Context, name string) (<-chan *domain.Config, <-chan error) {
configChannel := make(chan *domain.Config) configChannel := make(chan *domain.Config, 0)
errorChannel := make(chan error) errorChannel := make(chan error, 0)
go func() { go func() {
wait := time.Second * 3 wait := time.Second * 3
@ -111,18 +110,14 @@ func WatchConfig(ctx context.Context, name string) (<-chan *domain.Config, <-cha
errorChannel <- err errorChannel <- err
return return
} }
err = watcher.Add(name) watcher.Add(name)
if err != nil {
errorChannel <- err
return
}
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
close(configChannel) close(configChannel)
close(errorChannel) close(errorChannel)
return break
case event, open := <-watcher.Events: case event, open := <-watcher.Events:
if !open { if !open {
errorChannel <- fmt.Errorf("FSWatcher closed channel: %w", err) errorChannel <- fmt.Errorf("FSWatcher closed channel: %w", err)
@ -150,12 +145,11 @@ func WatchConfig(ctx context.Context, name string) (<-chan *domain.Config, <-cha
// WriteConfig writes the config struct into the file. The encoding format will be determined by the file extension like // WriteConfig writes the config struct into the file. The encoding format will be determined by the file extension like
// `xml` or `yaml`. // `xml` or `yaml`.
func WriteConfig(name string, config *domain.Config) error { func WriteConfig(name string, config *domain.Config) error {
// #nosec G304
f, err := os.Create(name) f, err := os.Create(name)
if err != nil { if err != nil {
return err return err
} }
defer func() { _ = f.Close() }() defer f.Close()
switch { switch {
case strings.HasSuffix(name, "xml"): case strings.HasSuffix(name, "xml"):
@ -163,13 +157,13 @@ func WriteConfig(name string, config *domain.Config) error {
case strings.HasSuffix(name, "yml") || strings.HasSuffix(name, "yaml"): case strings.HasSuffix(name, "yml") || strings.HasSuffix(name, "yaml"):
return writeYAMLConfig(f, config) return writeYAMLConfig(f, config)
default: default:
return fmt.Errorf("unsupported file extension") return fmt.Errorf("Unsupported file extension")
} }
} }
func writeXMLConfig(w io.Writer, config *domain.Config) error { func writeXMLConfig(w io.Writer, config *domain.Config) error {
xmlEncoder := xml.NewEncoder(w) xmlEncoder := xml.NewEncoder(w)
defer func() { _ = xmlEncoder.Close() }() defer xmlEncoder.Close()
xmlConfig := &XMLConfig{ xmlConfig := &XMLConfig{
APIToken: config.API.Token, APIToken: config.API.Token,
@ -186,7 +180,7 @@ func writeXMLConfig(w io.Writer, config *domain.Config) error {
func writeYAMLConfig(w io.Writer, config *domain.Config) error { func writeYAMLConfig(w io.Writer, config *domain.Config) error {
yamlEncoder := yaml.NewEncoder(w) yamlEncoder := yaml.NewEncoder(w)
defer func() { _ = yamlEncoder.Close() }() defer yamlEncoder.Close()
yamlConfig := &YAMLConfig{ yamlConfig := &YAMLConfig{
Auth: YAMLConfigAuth{ Auth: YAMLConfigAuth{

View File

@ -24,7 +24,6 @@ func TestReadWriteConfig_XML(t *testing.T) {
t.Cleanup(func() { _ = os.RemoveAll(tmpDir) }) t.Cleanup(func() { _ = os.RemoveAll(tmpDir) })
expectedXMLConfigName := filepath.Join(tmpDir, "expected_config.xml") expectedXMLConfigName := filepath.Join(tmpDir, "expected_config.xml")
// #nosec G304
f, err := os.Create(expectedXMLConfigName) f, err := os.Create(expectedXMLConfigName)
require.NoError(err) require.NoError(err)
@ -39,7 +38,6 @@ func TestReadWriteConfig_XML(t *testing.T) {
err = config.WriteConfig(actualXMLConfigName, actualConfig) err = config.WriteConfig(actualXMLConfigName, actualConfig)
require.NoError(err) require.NoError(err)
// #nosec G304
b, err := os.ReadFile(actualXMLConfigName) b, err := os.ReadFile(actualXMLConfigName)
require.NoError(err) require.NoError(err)
require.Equal(expectedXMLConfig, string(b)) require.Equal(expectedXMLConfig, string(b))
@ -53,7 +51,6 @@ func TestReadWriteConfig_YAML(t *testing.T) {
t.Cleanup(func() { _ = os.RemoveAll(tmpDir) }) t.Cleanup(func() { _ = os.RemoveAll(tmpDir) })
expectedYAMLConfigName := filepath.Join(tmpDir, "expected_config.yaml") expectedYAMLConfigName := filepath.Join(tmpDir, "expected_config.yaml")
// #nosec G304
f, err := os.Create(expectedYAMLConfigName) f, err := os.Create(expectedYAMLConfigName)
require.NoError(err) require.NoError(err)
@ -68,7 +65,6 @@ func TestReadWriteConfig_YAML(t *testing.T) {
err = config.WriteConfig(actualYAMLConfigName, actualConfig) err = config.WriteConfig(actualYAMLConfigName, actualConfig)
require.NoError(err) require.NoError(err)
// #nosec G304
b, err := os.ReadFile(actualYAMLConfigName) b, err := os.ReadFile(actualYAMLConfigName)
require.NoError(err) require.NoError(err)
require.Equal(expectedYAMLConfig, string(b)) require.Equal(expectedYAMLConfig, string(b))

View File

@ -9,8 +9,8 @@ import (
"net/url" "net/url"
) )
var ErrNoAPIToken error = errors.New("no API token defined") var ErrNoAPIToken error = errors.New("No API token defined")
var ErrNoURL error = errors.New("no API token defined") var ErrNoURL error = errors.New("No API token defined")
type ReadinessProbe struct { type ReadinessProbe struct {
additionalQueryValues url.Values additionalQueryValues url.Values
@ -40,7 +40,6 @@ func (rp *ReadinessProbe) Run(ctx context.Context) error {
httpClient := &http.Client{ httpClient := &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
// #nosec G402
InsecureSkipVerify: rp.insecure, InsecureSkipVerify: rp.insecure,
}, },
}, },
@ -65,7 +64,7 @@ func (rp *ReadinessProbe) Run(ctx context.Context) error {
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return fmt.Errorf("received unexpected HTTP status code %v", resp.StatusCode) return fmt.Errorf("Received unexpected HTTP status code %v", resp.StatusCode)
} }
return nil return nil