diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..f41d5bc --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,63 @@ +--- +run: +linters: + enable: + - asasalint + - asciicheck + - bodyclose + - dupword + - durationcheck + - errcheck + - errname + - errorlint + - exhaustive + - gocheckcompilerdirectives + - gocritic + - godot + - gofmt + - goimports + - goprintffuncname + - gosec + - gosimple + - govet + - grouper + - ineffassign + - makezero + - nilerr + - nilnil + - nolintlint + - nosprintfhostport + - prealloc + - predeclared + - reassign + - staticcheck + - stylecheck + - tenv + - testableexamples + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + - wastedassign + - whitespace + - zerologlint + fast: false + +# Issues configuration +issues: + exclude: [] + exclude-case-sensitive: false + exclude-rules: + - path: '(.+)_test\.go' + linters: + - goconst + - path: '(.+)_test\.go' + linters: + - govet + text: 'fieldalignment: .*' + fix: false + max-issues-per-linter: 0 + max-same-issues: 0 diff --git a/cmd/salt-exporter/config.go b/cmd/salt-exporter/config.go index 3d38f91..8e86c2f 100644 --- a/cmd/salt-exporter/config.go +++ b/cmd/salt-exporter/config.go @@ -128,7 +128,7 @@ func getConfig(configFileName string, healthMinions bool) (Config, error) { err := viper.ReadInConfig() if err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { //nolint: errorlint // ConfigFileNotFoundError not implementing error return Config{}, fmt.Errorf("invalid config file: %w", err) } } diff --git a/cmd/salt-exporter/main.go b/cmd/salt-exporter/main.go index 04a55d4..a91509a 100644 --- a/cmd/salt-exporter/main.go +++ b/cmd/salt-exporter/main.go @@ -7,6 +7,7 @@ import ( "os" "os/signal" "syscall" + "time" "github.com/kpetremann/salt-exporter/internal/logging" "github.com/kpetremann/salt-exporter/internal/metrics" @@ -65,7 +66,7 @@ func start(config Config) { if config.Metrics.HealthMinions { pkiWatcher, err := listener.NewPKIWatcher(ctx, config.PKIDir, watchChan) if err != nil { - log.Fatal().Msgf("unable to watch PKI for minions change: %v", err) + log.Fatal().Msgf("unable to watch PKI for minions change: %v", err) //nolint:gocritic // force exit } go pkiWatcher.StartWatching() @@ -78,7 +79,7 @@ func start(config Config) { mux := http.NewServeMux() mux.Handle("/metrics", promhttp.Handler()) - httpServer := http.Server{Addr: listenSocket, Handler: mux} + httpServer := http.Server{Addr: listenSocket, Handler: mux, ReadHeaderTimeout: 2 * time.Second} go func() { var err error @@ -108,7 +109,7 @@ func main() { config, err := ReadConfig(configFileName) if err != nil { - log.Fatal().Err(err).Msg("failed to load settings during initialization") + log.Fatal().Err(err).Msg("failed to load settings during initialization") //nolint:gocritic // force exit } logging.SetLevel(config.LogLevel) diff --git a/cmd/salt-live/main.go b/cmd/salt-live/main.go index bf07c35..2d23d5e 100644 --- a/cmd/salt-live/main.go +++ b/cmd/salt-live/main.go @@ -66,6 +66,6 @@ func main() { p := tea.NewProgram(tui.NewModel(eventChan, *maxItems, *filter), tea.WithMouseCellMotion()) if _, err := p.Run(); err != nil { fmt.Printf("Alas, there's been an error: %v", err) - os.Exit(1) + os.Exit(1) //nolint:gocritic // force exit } } diff --git a/internal/filters/filters.go b/internal/filters/filters.go index abce20e..f40a421 100644 --- a/internal/filters/filters.go +++ b/internal/filters/filters.go @@ -41,7 +41,6 @@ func matchTerm(s string, pattern string) bool { // - Match("foo", []string{"*o*"}) -> true func Match(value string, filters []string) bool { for _, pattern := range filters { - if matchTerm(value, pattern) { return true } diff --git a/internal/logging/logging.go b/internal/logging/logging.go index 85bbda5..27cd9bf 100644 --- a/internal/logging/logging.go +++ b/internal/logging/logging.go @@ -16,7 +16,7 @@ func Configure() { // SetLogLevel configures the loglevel. // -// logLevel: The log level to use, in zerolog format +// logLevel: The log level to use, in zerolog format. func SetLevel(logLevel string) { level, err := zerolog.ParseLevel(logLevel) fmt.Println(logLevel) diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index a2c1c90..32d1420 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -4,7 +4,6 @@ import ( "context" "github.com/kpetremann/salt-exporter/pkg/event" - evt "github.com/kpetremann/salt-exporter/pkg/event" "github.com/rs/zerolog/log" ) @@ -15,26 +14,26 @@ func boolToFloat64(b bool) float64 { return 0.0 } -func eventToMetrics(event event.SaltEvent, r *Registry) { - if event.Module == evt.BeaconModule { - if event.Type != "status" { +func eventToMetrics(e event.SaltEvent, r *Registry) { + if e.Module == event.BeaconModule { + if e.Type != "status" { return } - r.UpdateLastHeartbeat(event.Data.Id) + r.UpdateLastHeartbeat(e.Data.ID) return } - switch event.Type { + switch e.Type { case "new": - state := event.ExtractState() - r.IncreaseNewJobTotal(event.Data.Fun, state) - r.IncreaseExpectedResponsesTotal(event.Data.Fun, state, float64(event.TargetNumber)) + state := e.ExtractState() + r.IncreaseNewJobTotal(e.Data.Fun, state) + r.IncreaseExpectedResponsesTotal(e.Data.Fun, state, float64(e.TargetNumber)) case "ret": - state := event.ExtractState() - success := event.Data.Success + state := e.ExtractState() + success := e.Data.Success - if event.IsScheduleJob { + if e.IsScheduleJob { // for scheduled job, when the states in the job actually failed // - the global "success" value is always true // - the state module success is false, but the global retcode is > 0 @@ -43,17 +42,17 @@ func eventToMetrics(event event.SaltEvent, r *Registry) { // // using retcode and state module success could be enough, but we combine all values // in case there are other corner cases. - success = event.Data.Success && (event.Data.Retcode == 0) - if event.StateModuleSuccess != nil { - success = success && *event.StateModuleSuccess + success = e.Data.Success && (e.Data.Retcode == 0) + if e.StateModuleSuccess != nil { + success = success && *e.StateModuleSuccess } - r.IncreaseScheduledJobReturnTotal(event.Data.Fun, state, event.Data.Id, success) + r.IncreaseScheduledJobReturnTotal(e.Data.Fun, state, e.Data.ID, success) } else { - r.IncreaseFunctionResponsesTotal(event.Data.Fun, state, event.Data.Id, success) + r.IncreaseFunctionResponsesTotal(e.Data.Fun, state, e.Data.ID, success) } - r.IncreaseResponseTotal(event.Data.Id, success) - r.SetFunctionStatus(event.Data.Id, event.Data.Fun, state, success) + r.IncreaseResponseTotal(e.Data.ID, success) + r.SetFunctionStatus(e.Data.ID, e.Data.Fun, state, success) } } @@ -65,22 +64,22 @@ func ExposeMetrics(ctx context.Context, eventChan <-chan event.SaltEvent, watchC case <-ctx.Done(): log.Info().Msg("stopping event listener") return - case event := <-watchChan: - if event.Op == evt.Accepted { - registry.AddObservableMinion(event.MinionName) + case e := <-watchChan: + if e.Op == event.Accepted { + registry.AddObservableMinion(e.MinionName) } - if event.Op == evt.Removed { - registry.DeleteObservableMinion(event.MinionName) + if e.Op == event.Removed { + registry.DeleteObservableMinion(e.MinionName) } - case event := <-eventChan: - if config.Global.Filters.IgnoreTest && event.IsTest { + case e := <-eventChan: + if config.Global.Filters.IgnoreTest && e.IsTest { return } - if config.Global.Filters.IgnoreMock && event.IsMock { + if config.Global.Filters.IgnoreMock && e.IsMock { return } - eventToMetrics(event, ®istry) + eventToMetrics(e, ®istry) } } } diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 245f7ab..6c8a7bb 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -103,9 +103,9 @@ func watchEvent(m model) tea.Cmd { return func() tea.Msg { for { e := <-m.eventChan - var sender string = "master" - if e.Data.Id != "" { - sender = e.Data.Id + sender := "master" + if e.Data.ID != "" { + sender = e.Data.ID } eventJSON, err := e.RawToJSON(true) if err != nil { @@ -234,7 +234,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.updateTitle() return m, tea.Batch(cmds...) - } func (m *model) updateSideInfos() { diff --git a/pkg/event/event.go b/pkg/event/event.go index cc8561a..99f5f66 100644 --- a/pkg/event/event.go +++ b/pkg/event/event.go @@ -35,7 +35,7 @@ type EventData struct { Cmd string `msgpack:"cmd"` Fun string `msgpack:"fun"` FunArgs []interface{} `msgpack:"fun_args"` - Id string `msgpack:"id"` + ID string `msgpack:"id"` Jid string `msgpack:"jid"` JidStamp string `msgpack:"jid_stamp"` Minions []string `msgpack:"minions"` @@ -66,7 +66,7 @@ type SaltEvent struct { // RawToJSON converts raw body to JSON // -// If indent is true, the JSON will be indented +// If indent is true, the JSON will be indented. func (e SaltEvent) RawToJSON(indent bool) ([]byte, error) { if e.RawBody == nil { return nil, errors.New("raw body not registered") @@ -83,7 +83,7 @@ func (e SaltEvent) RawToJSON(indent bool) ([]byte, error) { } } -// RawToYAML converts raw body to YAML +// RawToYAML converts raw body to YAML. func (e SaltEvent) RawToYAML() ([]byte, error) { if e.RawBody == nil { return nil, errors.New("raw body not registered") @@ -114,7 +114,7 @@ func GetEventModule(tag string) EventModule { } } -// extractStateFromArgs extracts embedded state info +// extractStateFromArgs extracts embedded state info. func extractStateFromArgs(args interface{}, key string) string { // args only if v, ok := args.(string); ok { @@ -134,15 +134,16 @@ func extractStateFromArgs(args interface{}, key string) string { return "" } -// Extract state info from event +// Extract state info from event. func (e *SaltEvent) ExtractState() string { switch e.Data.Fun { case "state.sls", "state.apply": - if len(e.Data.Arg) > 0 { + switch { + case len(e.Data.Arg) > 0: return extractStateFromArgs(e.Data.Arg[0], "mods") - } else if len(e.Data.FunArgs) > 0 { + case len(e.Data.FunArgs) > 0: return extractStateFromArgs(e.Data.FunArgs[0], "mods") - } else if e.Data.Fun == "state.apply" { + case e.Data.Fun == "state.apply": return "highstate" } case "state.single": diff --git a/pkg/event/event_test.go b/pkg/event/event_test.go index aaf8d93..d057c12 100644 --- a/pkg/event/event_test.go +++ b/pkg/event/event_test.go @@ -96,5 +96,4 @@ func TestExtractState(t *testing.T) { t.Errorf("Mismatch for '%s', wants '%s' got '%s' ", test.name, test.want, res) } } - } diff --git a/pkg/listener/listener.go b/pkg/listener/listener.go index 45d0faa..0c0c3ce 100644 --- a/pkg/listener/listener.go +++ b/pkg/listener/listener.go @@ -17,7 +17,7 @@ type eventParser interface { const DefaultIPCFilepath = "/var/run/salt/master/master_event_pub.ipc" -// EventListener listens to the salt-master event bus and sends events to the event channel +// EventListener listens to the salt-master event bus and sends events to the event channel. type EventListener struct { // ctx specificies the context used mainly for cancellation ctx context.Context @@ -37,7 +37,7 @@ type EventListener struct { eventParser eventParser } -// Open opens the salt-master event bus +// Open opens the salt-master event bus. func (e *EventListener) Open() { log.Info().Str("file", e.iPCFilepath).Msg("connecting to salt-master event bus") var err error @@ -61,7 +61,7 @@ func (e *EventListener) Open() { } } -// Close closes the salt-master event bus +// Close closes the salt-master event bus. func (e *EventListener) Close() error { log.Info().Msg("disconnecting from salt-master event bus") if e.saltEventBus != nil { @@ -71,7 +71,7 @@ func (e *EventListener) Close() error { } } -// Reconnect reconnects to the salt-master event bus +// Reconnect reconnects to the salt-master event bus. func (e *EventListener) Reconnect() { select { case <-e.ctx.Done(): @@ -99,12 +99,12 @@ func NewEventListener(ctx context.Context, eventParser eventParser, eventChan ch // // The IPC file must be readable by the user running the exporter. // -// Default: /var/run/salt/master/master_event_pub.ipc +// Default: /var/run/salt/master/master_event_pub.ipc. func (e *EventListener) SetIPCFilepath(filepath string) { e.iPCFilepath = filepath } -// ListenEvents listens to the salt-master event bus and sends events to the event channel +// ListenEvents listens to the salt-master event bus and sends events to the event channel. func (e *EventListener) ListenEvents() { e.Open() diff --git a/pkg/listener/pkiwatcher.go b/pkg/listener/pkiwatcher.go index 8400d38..7fa5c69 100644 --- a/pkg/listener/pkiwatcher.go +++ b/pkg/listener/pkiwatcher.go @@ -15,19 +15,14 @@ import ( const DefaultPKIDirpath = "/etc/salt/pki/master" type PKIWatcher struct { - ctx context.Context - + ctx context.Context pkiDirPath string - - watcher *fsnotify.Watcher - - eventChan chan<- event.WatchEvent - - lock sync.RWMutex + watcher *fsnotify.Watcher + eventChan chan<- event.WatchEvent + lock sync.RWMutex } func NewPKIWatcher(ctx context.Context, pkiDirPath string, eventChan chan event.WatchEvent) (*PKIWatcher, error) { - watcher, err := fsnotify.NewWatcher() if err != nil { return nil, err @@ -48,13 +43,12 @@ func NewPKIWatcher(ctx context.Context, pkiDirPath string, eventChan chan event. // // The directory must be readable by the user running the exporter (usually salt). // -// Default: /etc/salt/pki +// Default: /etc/salt/pki. func (w *PKIWatcher) SetPKIDirectory(filepath string) { w.pkiDirPath = filepath } func (w *PKIWatcher) open() { - for { select { case <-w.ctx.Done(): diff --git a/pkg/parser/fake_beacon_data_test.go b/pkg/parser/fake_beacon_data_test.go index ee8a2e2..da569a7 100644 --- a/pkg/parser/fake_beacon_data_test.go +++ b/pkg/parser/fake_beacon_data_test.go @@ -31,7 +31,7 @@ var expectedBeacon = event.SaltEvent{ TargetNumber: 0, Data: event.EventData{ Timestamp: "2023-10-09T11:36:02.205686", - Id: "host1.example.com", + ID: "host1.example.com", Minions: []string{}, }, IsScheduleJob: false, @@ -42,7 +42,7 @@ func fakeBeaconEvent() []byte { fake := FakeData{ Timestamp: "2023-10-09T11:36:02.205686", Minions: []string{}, - Id: "host1.example.com", + ID: "host1.example.com", } fakeBody, err := msgpack.Marshal(fake) diff --git a/pkg/parser/fake_data_test.go b/pkg/parser/fake_data_test.go index 93a0880..d933d81 100644 --- a/pkg/parser/fake_data_test.go +++ b/pkg/parser/fake_data_test.go @@ -11,7 +11,7 @@ type FakeData struct { Cmd string `msgpack:"cmd"` Fun string `msgpack:"fun"` FunArgs []interface{} `msgpack:"fun_args"` - Id string `msgpack:"id"` + ID string `msgpack:"id"` Jid string `msgpack:"jid"` Minions []string `msgpack:"minions"` Missing []string `msgpack:"missing"` diff --git a/pkg/parser/fake_exec_data_test.go b/pkg/parser/fake_exec_data_test.go index f1b8ebd..3cac8c4 100644 --- a/pkg/parser/fake_exec_data_test.go +++ b/pkg/parser/fake_exec_data_test.go @@ -91,7 +91,7 @@ var expectedReturnJob = event.SaltEvent{ Timestamp: "2022-06-30T00:00:00.000000", Cmd: "_return", Fun: "test.ping", - Id: "localhost", + ID: "localhost", Jid: "20220630000000000000", Retcode: 0, Return: true, @@ -106,7 +106,7 @@ func fakeRetJobEvent() []byte { Timestamp: "2022-06-30T00:00:00.000000", Cmd: "_return", Fun: "test.ping", - Id: "localhost", + ID: "localhost", Jid: "20220630000000000000", Retcode: 0, Return: true, @@ -216,7 +216,7 @@ var expectedAckScheduleJob = event.SaltEvent{ Cmd: "_return", Fun: "schedule.run_job", FunArgs: []interface{}{"sync_all"}, - Id: "localhost", + ID: "localhost", Jid: "20220630000000000000", Retcode: 0, Return: map[string]interface{}{ @@ -235,7 +235,7 @@ func fakeAckScheduleJobEvent() []byte { Cmd: "_return", Fun: "schedule.run_job", FunArgs: []interface{}{"sync_all"}, - Id: "localhost", + ID: "localhost", Jid: "20220630000000000000", Retcode: 0, Return: map[string]interface{}{ @@ -304,7 +304,7 @@ var expectedScheduleJobReturn = event.SaltEvent{ Timestamp: "2022-06-30T00:00:00.000000", Cmd: "_return", Fun: "saltutil.sync_all", - Id: "localhost", + ID: "localhost", Jid: "20220630000000000000", Retcode: 0, Return: map[string]interface{}{ @@ -340,7 +340,7 @@ func fakeScheduleJobReturnEvent() []byte { Timestamp: "2022-06-30T00:00:00.000000", Cmd: "_return", Fun: "saltutil.sync_all", - Id: "localhost", + ID: "localhost", Jid: "20220630000000000000", Retcode: 0, Return: map[string]interface{}{ diff --git a/pkg/parser/fake_state_data_test.go b/pkg/parser/fake_state_data_test.go index cb3d51d..2e5ef0f 100644 --- a/pkg/parser/fake_state_data_test.go +++ b/pkg/parser/fake_state_data_test.go @@ -118,7 +118,7 @@ var expectedStateSlsReturn = event.SaltEvent{ Cmd: "_return", Fun: "state.sls", FunArgs: []interface{}{"test"}, - Id: "node1", + ID: "node1", Jid: "20220630000000000000", Out: "highstate", Retcode: 0, @@ -150,7 +150,7 @@ func fakeStateSlsReturnEvent() []byte { Cmd: "_return", Fun: "state.sls", FunArgs: []interface{}{"test"}, - Id: "node1", + ID: "node1", Out: "highstate", Jid: "20220630000000000000", Retcode: 0, @@ -316,7 +316,7 @@ var expectedStateSingleReturn = event.SaltEvent{ "name": "toto", }, }, - Id: "node1", + ID: "node1", Jid: "20220630000000000000", Out: "highstate", Retcode: 0, @@ -353,7 +353,7 @@ func fakeStateSingleReturnEvent() []byte { "name": "toto", }, }, - Id: "node1", + ID: "node1", Jid: "20220630000000000000", Out: "highstate", Retcode: 0, @@ -535,7 +535,7 @@ var expectedTestMockStateSlsReturn = event.SaltEvent{ "mock": true, }, }, - Id: "node1", + ID: "node1", Jid: "20220630000000000000", Out: "highstate", Retcode: 1, @@ -584,7 +584,7 @@ func fakeTestMockStateSlsReturnEvent() []byte { "mock": true, }, }, - Id: "node1", + ID: "node1", Out: "highstate", Jid: "20220630000000000000", Retcode: 1, diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index 9395b24..3450c5b 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -13,11 +13,11 @@ const testArg = "test" const mockArg = "mock" type Event struct { - KeepRawBody bool + KeepRewBody bool } -func NewEventParser(KeepRawBody bool) Event { - return Event{KeepRawBody: KeepRawBody} +func NewEventParser(keepRawBody bool) Event { + return Event{KeepRewBody: keepRawBody} } // isDryRun checks if an event is run with test=True @@ -93,7 +93,7 @@ func statemoduleResult(event event.SaltEvent) *bool { return &success } -// ParseEvent parses a salt event +// ParseEvent parses a salt event. func (e Event) Parse(message map[string]interface{}) (event.SaltEvent, error) { body := string(message["body"].([]byte)) lines := strings.SplitN(body, "\n\n", 2) @@ -110,20 +110,20 @@ func (e Event) Parse(message map[string]interface{}) (event.SaltEvent, error) { return event.SaltEvent{}, errors.New("tag not supported") } - event_module := event.GetEventModule(tag) + eventModule := event.GetEventModule(tag) - if event_module == event.UnknownModule { + if eventModule == event.UnknownModule { return event.SaltEvent{}, errors.New("tag not supported. Module unknown") } // Extract job type from the tag - job_type := strings.Split(tag, "/")[3] + jobType := strings.Split(tag, "/")[3] // Parse message body byteResult := []byte(lines[1]) - ev := event.SaltEvent{Tag: tag, Type: job_type, Module: event_module} + ev := event.SaltEvent{Tag: tag, Type: jobType, Module: eventModule} - if e.KeepRawBody { + if e.KeepRewBody { ev.RawBody = byteResult } @@ -140,8 +140,8 @@ func (e Event) Parse(message map[string]interface{}) (event.SaltEvent, error) { ev.StateModuleSuccess = statemoduleResult(ev) // A runner are executed on the master but they do not provide their ID in the event - if strings.HasPrefix(tag, "salt/run") && ev.Data.Id == "" { - ev.Data.Id = "master" + if strings.HasPrefix(tag, "salt/run") && ev.Data.ID == "" { + ev.Data.ID = "master" } return ev, nil