diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..046c726 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,30 @@ +name: Lint + +permissions: + contents: read + +on: + - push + - pull_request + +jobs: + golangci-lint: + strategy: + fail-fast: false + matrix: + module: [".", "api"] + runs-on: ubuntu-latest + name: golangci-lint (${{ matrix.module }}) + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: ${{ matrix.module }}/go.mod + + - name: golangci-lint + uses: golangci/golangci-lint-action@v9 + with: + version: v2.12 + working-directory: ${{ matrix.module }} diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..9641910 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,7 @@ +version: "2" + +run: + timeout: 5m + +linters: + default: standard diff --git a/Makefile b/Makefile index 33fd99f..b42918b 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ CFGDIR=$(PREFIX)/etc/thalos DOCKER_IMAGE_REPO ?= ghcr.io/eosswedenorg/thalos DOCKER_IMAGE_TAG ?= $(PROGRAM_VERSION) -.PHONY: build build/$(PROGRAM) build/thalos-tools test docker-image docker-publish +.PHONY: build build/$(PROGRAM) build/thalos-tools test lint docker-image docker-publish build: build/$(PROGRAM) @@ -42,5 +42,10 @@ build-deb: test: $(GO) test -v ./... cd api; $(GO) test -v ./... + +lint: + golangci-lint run ./... + cd api; golangci-lint run ./... + clean : $(RM) -fr build diff --git a/cmd/thalos/server.go b/cmd/thalos/server.go index bb550db..6f1ef98 100644 --- a/cmd/thalos/server.go +++ b/cmd/thalos/server.go @@ -25,8 +25,8 @@ import ( "github.com/eosswedenorg/thalos/internal/config" driver "github.com/eosswedenorg/thalos/internal/driver/redis" "github.com/eosswedenorg/thalos/internal/filter" - . "github.com/eosswedenorg/thalos/internal/log" - . "github.com/eosswedenorg/thalos/internal/server" + internallog "github.com/eosswedenorg/thalos/internal/log" + shipserver "github.com/eosswedenorg/thalos/internal/server" "github.com/nikoksr/notify" "github.com/nikoksr/notify/service/telegram" "github.com/redis/go-redis/v9" @@ -36,7 +36,7 @@ import ( "github.com/spf13/pflag" ) -func readerLoop(conf *config.Config, running *bool, shClient *shipclient.Stream, processor *ShipProcessor) { +func readerLoop(conf *config.Config, running *bool, shClient *shipclient.Stream, processor *shipserver.ShipProcessor) { recon_cnt := 0 exp := &backoff.ExponentialBackOff{ @@ -123,7 +123,7 @@ func readerLoop(conf *config.Config, running *bool, shClient *shipclient.Stream, } } -func run(conf *config.Config, shClient *shipclient.Stream, processor *ShipProcessor) { +func run(conf *config.Config, shClient *shipclient.Stream, processor *shipserver.ShipProcessor) { running := true // Spawn reader loop in another thread. @@ -160,8 +160,8 @@ func initAbiManager(cfg *config.AbiCache, api *antelopeapi.Client, store cache.S return abi.NewAbiManager(cfg, cache, api) } -func stateLoader(conf *config.Config, start_block_flag *pflag.Flag, chainInfo func() *antelopeapi.Info, cache *cache.Cache, current_block_no_cache bool) StateLoader { - return func(state *State) { +func stateLoader(conf *config.Config, start_block_flag *pflag.Flag, chainInfo func() *antelopeapi.Info, cache *cache.Cache, current_block_no_cache bool) shipserver.StateLoader { + return func(state *shipserver.State) { var source string // Load state from cache. @@ -203,8 +203,8 @@ func stateLoader(conf *config.Config, start_block_flag *pflag.Flag, chainInfo fu } } -func stateSaver(cache *cache.Cache) StateSaver { - return func(state State) error { +func stateSaver(cache *cache.Cache) shipserver.StateSaver { + return func(state shipserver.State) error { ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500) defer cancel() return cache.Set(ctx, "state", state, 0) @@ -304,12 +304,12 @@ func serverCmd(cmd *cobra.Command, args []string) { } if len(conf.Log.Filename) > 0 { - stdWriter, err := NewRotatingFileFromConfig(conf.Log, "info") + stdWriter, err := internallog.NewRotatingFileFromConfig(conf.Log, "info") if err != nil { log.WithError(err).Fatal("Failed to set standard log file") return } - errWriter, err := NewRotatingFileFromConfig(conf.Log, "error") + errWriter, err := internallog.NewRotatingFileFromConfig(conf.Log, "error") if err != nil { log.WithError(err).Fatal("Failed to set error log file") return @@ -324,8 +324,8 @@ func serverCmd(cmd *cobra.Command, args []string) { }).Info("Logging to file") log.SetOutput(io.Discard) - log.AddHook(MakeStdHook(stdWriter)) - log.AddHook(MakeErrorHook(errWriter)) + log.AddHook(internallog.MakeStdHook(stdWriter)) + log.AddHook(internallog.MakeErrorHook(errWriter)) } // Init telegram notification service @@ -384,7 +384,7 @@ func serverCmd(cmd *cobra.Command, args []string) { chain_id = chainInfo().ChainID } - processor := SpawnProccessor( + processor := shipserver.SpawnProccessor( shClient, stateLoader(conf, cmd.Flags().Lookup("start-block"), chainInfo, cache, skip_currentblock_cache), stateSaver(cache), @@ -404,5 +404,7 @@ func serverCmd(cmd *cobra.Command, args []string) { run(conf, shClient, processor) // Close the processor properly - processor.Close() + if err := processor.Close(); err != nil { + log.WithError(err).Error("Failed to close processor") + } } diff --git a/cmd/tools/bench.go b/cmd/tools/bench.go index 067ee90..6026ce9 100644 --- a/cmd/tools/bench.go +++ b/cmd/tools/bench.go @@ -98,7 +98,9 @@ func CreateBenchCmd() *cobra.Command { select { case <-sig: fmt.Println("Got interrupt") - client.Close() + if err := client.Close(); err != nil { + log.WithError(err).Warn("Failed to close client") + } return case now := <-time.After(interval): elapsed := now.Sub(t) diff --git a/cmd/tools/mock_publisher.go b/cmd/tools/mock_publisher.go index 6a9e028..1b8707e 100644 --- a/cmd/tools/mock_publisher.go +++ b/cmd/tools/mock_publisher.go @@ -109,8 +109,14 @@ func CreateMockPublisherCmd() *cobra.Command { channel := api.ActionChannel{}.Channel() for { - _ = publisher.Write(channel, payload) - publisher.Flush() + if err := publisher.Write(channel, payload); err != nil { + log.WithError(err).Error("Failed to queue message") + continue + } + + if err := publisher.Flush(); err != nil { + log.WithError(err).Error("Failed to flush publisher") + } } }, } diff --git a/cmd/tools/redis-acl.go b/cmd/tools/redis-acl.go index ad15036..df3abe7 100644 --- a/cmd/tools/redis-acl.go +++ b/cmd/tools/redis-acl.go @@ -144,7 +144,11 @@ func CreateRedisACLCmd() *cobra.Command { log.WithError(err).Fatal("Failed to create output file") return } - defer out.Close() + defer func() { + if closeErr := out.Close(); closeErr != nil { + log.WithError(closeErr).Error("Failed to close output file") + } + }() } else if !cleartext && atleastOneGeneratedPw { fmt.Println() } diff --git a/cmd/tools/validate.go b/cmd/tools/validate.go index 310c8c1..71f949b 100644 --- a/cmd/tools/validate.go +++ b/cmd/tools/validate.go @@ -104,7 +104,9 @@ func CreateValidateCmd() *cobra.Command { select { case <-sig: fmt.Println("Got interrupt") - client.Close() + if err := client.Close(); err != nil { + log.WithError(err).Warn("Failed to close client") + } return case <-timer.C: log.WithField("duration", timeout). diff --git a/internal/cache/factory.go b/internal/cache/factory.go index bdde3a9..51d688f 100644 --- a/internal/cache/factory.go +++ b/internal/cache/factory.go @@ -22,5 +22,5 @@ func Make(driver string, opts typed.Typed) (Store, error) { if factory, ok := factories[driver]; ok { return factory(opts) } - return nil, fmt.Errorf("Invalid cache storage: %s", driver) + return nil, fmt.Errorf("invalid cache storage: %s", driver) } diff --git a/internal/config/builder.go b/internal/config/builder.go index 0f9d1c6..0bc8874 100644 --- a/internal/config/builder.go +++ b/internal/config/builder.go @@ -92,7 +92,7 @@ func (b *Builder) AddFlag(flag *pflag.Flag) *Builder { // Build the config object from file, cli-flags func (b *Builder) Build() (*Config, error) { if b.in == nil { - return nil, errors.New("Config not set") + return nil, errors.New("config not set") } conf := Config{} @@ -172,7 +172,7 @@ func decodeIntoFilter(in any) (*filter.List, error) { return list.SetMode(filter.Exclude), nil } - return nil, fmt.Errorf("Must be a string slice") + return nil, fmt.Errorf("must be a string slice") } // Filter map parser @@ -199,7 +199,7 @@ func filterParseValue(list *filter.List, contract string, in any) error { for nested, nestedValue := range value { nestedKey, ok := nested.(string) if !ok { - return fmt.Errorf("Must be a string slice") + return fmt.Errorf("must be a string slice") } if err := filterParseValue(list, contract+"."+nestedKey, nestedValue); err != nil { return err @@ -249,12 +249,12 @@ func toStringSlice(in any) ([]string, error) { for _, item := range value { str, ok := item.(string) if !ok { - return nil, fmt.Errorf("Must be a string slice") + return nil, fmt.Errorf("must be a string slice") } out = append(out, str) } return out, nil default: - return nil, fmt.Errorf("Must be a string slice") + return nil, fmt.Errorf("must be a string slice") } } diff --git a/internal/config/builder_test.go b/internal/config/builder_test.go index cec7073..670eac4 100644 --- a/internal/config/builder_test.go +++ b/internal/config/builder_test.go @@ -158,7 +158,7 @@ func TestBuilder_WithDefaultConfig(t *testing.T) { func TestBuilder_NilSource(t *testing.T) { cfg, err := NewBuilder().Build() require.Nil(t, cfg) - require.EqualError(t, err, "Config not set") + require.EqualError(t, err, "config not set") } func TestBuilder_Flags(t *testing.T) { diff --git a/internal/driver/redis/publisher.go b/internal/driver/redis/publisher.go index 623d67b..24c14a1 100644 --- a/internal/driver/redis/publisher.go +++ b/internal/driver/redis/publisher.go @@ -4,7 +4,7 @@ import ( "context" "github.com/eosswedenorg/thalos/api" - . "github.com/eosswedenorg/thalos/api/redis" + api_redis "github.com/eosswedenorg/thalos/api/redis" "github.com/redis/go-redis/v9" ) @@ -12,10 +12,10 @@ import ( type Publisher struct { pipeline redis.Pipeliner ctx context.Context - ns Namespace + ns api_redis.Namespace } -func NewPublisher(ctx context.Context, client *redis.Client, ns Namespace) *Publisher { +func NewPublisher(ctx context.Context, client *redis.Client, ns api_redis.Namespace) *Publisher { return &Publisher{ pipeline: client.Pipeline(), ctx: ctx, diff --git a/internal/log/RotatingFile.go b/internal/log/RotatingFile.go index 540a783..ffb260e 100644 --- a/internal/log/RotatingFile.go +++ b/internal/log/RotatingFile.go @@ -82,12 +82,16 @@ func (w RotatingFile) GetFilename() string { } // Rotate the file. -func (w *RotatingFile) Rotate() error { +func (w *RotatingFile) Rotate() (err error) { dst, err := os.OpenFile(w.newFilename(w.fd.Name()), os.O_CREATE|os.O_WRONLY, 0o666) if err != nil { return err } - defer dst.Close() + defer func() { + if closeErr := dst.Close(); err == nil && closeErr != nil { + err = closeErr + } + }() // Seek to the beginning of file if _, err = w.fd.Seek(0, io.SeekStart); err != nil { diff --git a/internal/server/ship_processor.go b/internal/server/ship_processor.go index 9823630..fb37163 100644 --- a/internal/server/ship_processor.go +++ b/internal/server/ship_processor.go @@ -203,7 +203,9 @@ func (processor *ShipProcessor) processTransactionTrace(log *log.Entry, blockNum actMsg.BlockNum = blockNumber actMsg.Timestamp = timestamp - processor.queue.PostAction(*actMsg) + if err := processor.queue.PostAction(*actMsg); err != nil { + logger.WithError(err).Error("Failed to post action trace") + } transaction.ActionTraces = append(transaction.ActionTraces, *actMsg) } @@ -311,16 +313,16 @@ func (processor *ShipProcessor) proccessDeltaRow(row ship.Row, table_name string } if processor.shipABI == nil { - return msg, errors.New("No SHIP ABI present") + return msg, errors.New("no SHIP ABI present") } v, err := processor.shipABI.Decode(bytes.NewReader(row.Data), table_name) if err != nil { - return msg, errors.New("Failed to decode table delta") + return msg, errors.New("failed to decode table delta") } data, err := ship_helper.ParseTableDeltaData(v) if err != nil { - return msg, errors.New("Failed to parse table delta data") + return msg, errors.New("failed to parse table delta data") } msg.Data = data @@ -329,7 +331,7 @@ func (processor *ShipProcessor) proccessDeltaRow(row ship.Row, table_name string if table_name == "contract_row" { dec, err := ship_helper.DecodeContractRow(processor.abi, data) if err != nil { - return msg, errors.New("Failed to decode contract row") + return msg, errors.New("failed to decode contract row") } msg.Data["value"] = dec } @@ -339,7 +341,10 @@ func (processor *ShipProcessor) proccessDeltaRow(row ship.Row, table_name string // Callback function called by shipclient.Stream when a new block arrives. func (processor *ShipProcessor) processBlock(blockResult *ship.GetBlocksResultV0) { block := ship.SignedBlock{} - blockResult.Block.Unpack(&block) + if err := blockResult.Block.Unpack(&block); err != nil { + log.WithError(err).Error("Failed to unpack block") + return + } timestamp := block.BlockHeader.Timestamp.Time().UTC() blockNumber := blockResult.ThisBlock.BlockNum