diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..162bbf7 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,69 @@ +# Derived from https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml + +--- + +run: + # timeout for analysis, e.g. 30s, 5m, default is 1m + timeout: 5m + # Force readonly modules usage for checking + modules-download-mode: readonly + +output: + format: tab + +linters-settings: + forbidigo: + forbid: + - 'fmt\.Errorf' # Should use github.com/pkg/errors + + funlen: + lines: 100 + statements: 60 + + gocyclo: + # minimal code complexity to report, 30 by default (but we recommend 10-20) + min-complexity: 15 + + gomnd: + settings: + mnd: + ignored-functions: 'strconv.(?:Format|Parse)\B+' + +linters: + disable-all: true + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false] + - bodyclose # checks whether HTTP response body is closed successfully [fast: true, auto-fix: false] + - deadcode # Finds unused code [fast: true, auto-fix: false] + - depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false] + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false] + - exportloopref # checks for pointers to enclosing loop variables [fast: true, auto-fix: false] + - forbidigo # Forbids identifiers [fast: true, auto-fix: false] + - funlen # Tool for detection of long functions [fast: true, auto-fix: false] + - gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false] + - goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false] + - gocritic # The most opinionated Go source code linter [fast: true, auto-fix: false] + - gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false] + - godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false] + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true] + - gofumpt # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true] + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true] + - gomnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false] + - gosec # Inspects source code for security problems [fast: true, auto-fix: false] + - gosimple # Linter for Go source code that specializes in simplifying a code [fast: true, auto-fix: false] + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true, auto-fix: false] + - ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false] + - misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true] + - nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false] + - noctx # noctx finds sending http request without context.Context [fast: true, auto-fix: false] + - nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false] + - revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false] + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false] + - structcheck # Finds unused struct fields [fast: true, auto-fix: false] + - stylecheck # Stylecheck is a replacement for golint [fast: true, auto-fix: false] + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false] + - unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false] + - unused # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false] + - varcheck # Finds unused global variables and constants [fast: true, auto-fix: false] + +... diff --git a/.repo-runner.yaml b/.repo-runner.yaml new file mode 100644 index 0000000..dc0d533 --- /dev/null +++ b/.repo-runner.yaml @@ -0,0 +1,14 @@ +--- + +image: "reporunner/archlinux" +checkout_dir: /go/src/github.com/Luzifer/go-latestver + +commands: + # Dependencies for downloading libraries + - echo -e "[luzifer]\nSigLevel = Optional TrustAll\nServer = https://s3-eu-west-1.amazonaws.com/arch-luzifer-io/repo/\$arch" >>/etc/pacman.conf + - pacman -Syy --noconfirm base-devel git go golangci-lint-bin sqlite3 + - make go_test + +environment: + CGO_ENABLED: 1 + GOPATH: /go diff --git a/Makefile b/Makefile index a61f75d..d56e455 100644 --- a/Makefile +++ b/Makefile @@ -11,3 +11,7 @@ lint: node_modules node_modules: npm ci + +go_test: + go test -cover -v ./... + golangci-lint run diff --git a/api.go b/api.go index 3e1ffba..0384532 100644 --- a/api.go +++ b/api.go @@ -40,7 +40,7 @@ func catalogEntryToAPICatalogEntry(ce database.CatalogEntry) (APICatalogEntry, e return APICatalogEntry{}, errors.Wrap(err, "fetching catalog meta") } - for _, l := range fetcher.Get(ce.Fetcher).Links(&ce.FetcherConfig) { + for _, l := range fetcher.Get(ce.Fetcher).Links(ce.FetcherConfig) { var found bool for _, el := range ce.Links { if l.Name == el.Name { @@ -239,6 +239,7 @@ func prepareLogForRequest(r *http.Request) ([]database.LogEntry, error) { num, page = 25, 0 + ce database.CatalogEntry err error logs []database.LogEntry ) @@ -254,7 +255,7 @@ func prepareLogForRequest(r *http.Request) ([]database.LogEntry, error) { if name == "" && tag == "" { logs, err = storage.Logs.List(num, page) } else { - ce, err := configFile.CatalogEntryByTag(name, tag) + ce, err = configFile.CatalogEntryByTag(name, tag) if errors.Is(err, config.ErrCatalogEntryNotFound) { return nil, config.ErrCatalogEntryNotFound } diff --git a/frontend_dist.go b/frontend_dist.go index 2ebace4..fccf3a7 100644 --- a/frontend_dist.go +++ b/frontend_dist.go @@ -11,5 +11,5 @@ var ( //go:embed frontend/** embeddedFrontend embed.FS - frontendFS http.FileSystem = http.FS(embeddedFrontend) + frontendFS = http.FS(embeddedFrontend) ) diff --git a/internal/config/config.go b/internal/config/config.go index 5fb4f3a..0ab3dc8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -57,7 +57,7 @@ func (f File) ValidateCatalog() error { return errors.Errorf("catalog entry %d has unknown fetcher", i) } - if err := f.Validate(&ce.FetcherConfig); err != nil { + if err := f.Validate(ce.FetcherConfig); err != nil { return errors.Wrapf(err, "catalog entry %d has invalid fetcher config", i) } } diff --git a/internal/database/store.go b/internal/database/store.go index a286a8a..eff8429 100644 --- a/internal/database/store.go +++ b/internal/database/store.go @@ -1,7 +1,6 @@ package database import ( - _ "embed" "strings" "time" @@ -17,8 +16,8 @@ type ( Name string `json:"name" yaml:"name"` Tag string `json:"tag" yaml:"tag"` - Fetcher string `json:"-" yaml:"fetcher"` - FetcherConfig fieldcollection.FieldCollection `json:"-" yaml:"fetcher_config"` + Fetcher string `json:"-" yaml:"fetcher"` + FetcherConfig *fieldcollection.FieldCollection `json:"-" yaml:"fetcher_config"` Links []CatalogLink `json:"links" yaml:"links"` } diff --git a/internal/database/store_test.go b/internal/database/store_test.go index 13fb2da..65b8503 100644 --- a/internal/database/store_test.go +++ b/internal/database/store_test.go @@ -101,6 +101,7 @@ func Test_LogStorage(t *testing.T) { {CatalogName: "anotherapp", CatalogTag: ce.Tag, Timestamp: rt.Add(-2 * time.Hour), VersionFrom: "5.2.0", VersionTo: "5.2.1"}, {CatalogName: "anotherapp", CatalogTag: ce.Tag, Timestamp: rt.Add(-1 * time.Hour), VersionFrom: "5.2.1", VersionTo: "6.0.0"}, } { + //#nosec G601 // Acceptable for test usage if err = dbc.Logs.Add(&le); err != nil { t.Fatalf("unable to add log entry: %s", err) } diff --git a/internal/fetcher/html.go b/internal/fetcher/html.go index 851c107..fc3861b 100644 --- a/internal/fetcher/html.go +++ b/internal/fetcher/html.go @@ -46,7 +46,7 @@ func (h HTMLFetcher) FetchVersion(ctx context.Context, attrs *fieldcollection.Fi } match := regexp.MustCompile(attrs.MustString("regex", &htmlFetcherDefaultRegex)).FindStringSubmatch(node.Data) - if len(match) < 2 { + if len(match) < 2 { //nolint:gomnd // Simple count of fields, no need for constant return "", time.Time{}, errors.New("regular expression did not yield version") } diff --git a/internal/fetcher/interface.go b/internal/fetcher/interface.go index da25ced..1370a2a 100644 --- a/internal/fetcher/interface.go +++ b/internal/fetcher/interface.go @@ -17,17 +17,17 @@ type ( Links(attrs *fieldcollection.FieldCollection) []database.CatalogLink Validate(attrs *fieldcollection.FieldCollection) error } - FetcherCreate func() Fetcher + Create func() Fetcher ) var ( ErrNoVersionFound = errors.New("no version found") - availableFetchers = map[string]FetcherCreate{} + availableFetchers = map[string]Create{} availableFetchersLock sync.RWMutex ) -func registerFetcher(name string, fn FetcherCreate) { +func registerFetcher(name string, fn Create) { availableFetchersLock.Lock() defer availableFetchersLock.Unlock() diff --git a/internal/fetcher/json.go b/internal/fetcher/json.go index f98860f..c8c7595 100644 --- a/internal/fetcher/json.go +++ b/internal/fetcher/json.go @@ -35,18 +35,23 @@ func (JSONFetcher) FetchVersion(ctx context.Context, attrs *fieldcollection.Fiel ) if attrs.MustBool("jsonp", ptrBoolFalse) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, attrs.MustString("url", nil), nil) + var ( + body []byte + req *http.Request + resp *http.Response + ) + req, err = http.NewRequestWithContext(ctx, http.MethodGet, attrs.MustString("url", nil), nil) if err != nil { return "", time.Time{}, errors.Wrap(err, "creating request") } - resp, err := http.DefaultClient.Do(req) + resp, err = http.DefaultClient.Do(req) if err != nil { return "", time.Time{}, errors.Wrap(err, "executing request") } defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) + body, err = ioutil.ReadAll(resp.Body) if err != nil { return "", time.Time{}, errors.Wrap(err, "reading response body") } @@ -83,7 +88,7 @@ func (JSONFetcher) FetchVersion(ctx context.Context, attrs *fieldcollection.Fiel } match := regexp.MustCompile(attrs.MustString("regex", &jsonFetcherDefaultRegex)).FindStringSubmatch(node.Data) - if len(match) < 2 { + if len(match) < 2 { //nolint:gomnd // Simple count of fields, no need for constant return "", time.Time{}, errors.New("regular expression did not yield version") } diff --git a/main.go b/main.go index 4948098..87f559b 100644 --- a/main.go +++ b/main.go @@ -33,8 +33,6 @@ var ( router *mux.Router storage *database.Client - processStart = time.Now() - version = "dev" ) diff --git a/scheduler.go b/scheduler.go index b1e55db..8fc072b 100644 --- a/scheduler.go +++ b/scheduler.go @@ -2,7 +2,7 @@ package main import ( "context" - "crypto/md5" + "crypto/md5" //#nosec G501 // Used to derive a static jitter checksum, not cryptographically "fmt" "math" "strings" @@ -26,7 +26,8 @@ func schedulerRun() { schedulerRunActive = true defer func() { schedulerRunActive = false }() - for _, ce := range configFile.Catalog { + for i := range configFile.Catalog { + ce := configFile.Catalog[i] if err := checkForUpdates(&ce); err != nil { log.WithField("entry", ce.Key()).WithError(err).Error("Unable to update entry") } @@ -54,7 +55,7 @@ func checkForUpdates(ce *database.CatalogEntry) error { logger.Debug("Checking for updates") - ver, vertime, err := fetcher.Get(ce.Fetcher).FetchVersion(context.Background(), &ce.FetcherConfig) + ver, vertime, err := fetcher.Get(ce.Fetcher).FetchVersion(context.Background(), ce.FetcherConfig) ver = strings.TrimPrefix(ver, "v") vertime = vertime.Truncate(time.Second).UTC() @@ -100,19 +101,19 @@ func nextCheckTime(ce *database.CatalogEntry, lastCheck *time.Time) time.Time { return time.Now() } - hash := md5.New() + hash := md5.New() //#nosec G401 // Used to derive a static jitter checksum, not cryptographically fmt.Fprint(hash, ce.Key()) var jitter int64 for i, c := range hash.Sum(nil) { - jitter += int64(c) * int64(math.Pow(10, float64(i))) + jitter += int64(c) * int64(math.Pow(10, float64(i))) //nolint:gomnd // No need for constant here } next := lastCheck. Truncate(cfg.CheckDistribution). Add(time.Duration(jitter) % cfg.CheckDistribution) - if next.Before((*lastCheck).Add(schedulerInterval)) { + if next.Before(lastCheck.Add(schedulerInterval)) { next = next.Add(cfg.CheckDistribution) }