diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 00000000..312a5280 --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,28 @@ +name: golangci-lint +on: + push: + branches: + - main + - next + pull_request: + +permissions: + contents: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache: false + - name: golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + # Require: The version of golangci-lint to use. + # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. + # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. + version: v1.54 \ No newline at end of file diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 00000000..0ce59c53 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,11 @@ +linters: + # Enable specific linter + # https://golangci-lint.run/usage/linters/#enabled-by-default + enable: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused + - whitespace diff --git a/cmd/root.go b/cmd/root.go index 83584f6a..cb3d947a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,6 +1,7 @@ package cmd import ( + "errors" "fmt" tea "github.com/charmbracelet/bubbletea" "github.com/robinovitch61/wander/internal/dev" @@ -208,7 +209,7 @@ func init() { rootCmd.PersistentFlags().BoolP(cliLong, rootNameToArg[cliLong].cliShort, rootNameToArg[cliLong].defaultIfBool, rootNameToArg[cliLong].description) // colors, config or env var only - viper.BindPFlag("", rootCmd.PersistentFlags().Lookup(rootNameToArg["logo-color"].cfgFileEnvVar)) + _ = viper.BindPFlag("", rootCmd.PersistentFlags().Lookup(rootNameToArg["logo-color"].cfgFileEnvVar)) for _, cliLong = range []string{ "addr", @@ -247,7 +248,7 @@ func init() { } else { rootCmd.PersistentFlags().StringP(cliLong, c.cliShort, c.defaultString, c.description) } - viper.BindPFlag(cliLong, rootCmd.PersistentFlags().Lookup(c.cfgFileEnvVar)) + _ = viper.BindPFlag(cliLong, rootCmd.PersistentFlags().Lookup(c.cfgFileEnvVar)) } // serve @@ -265,7 +266,7 @@ func init() { } else { serveCmd.PersistentFlags().StringP(cliLong, c.cliShort, c.defaultString, c.description) } - viper.BindPFlag(cliLong, serveCmd.PersistentFlags().Lookup(c.cfgFileEnvVar)) + _ = viper.BindPFlag(cliLong, serveCmd.PersistentFlags().Lookup(c.cfgFileEnvVar)) } // exec @@ -305,9 +306,8 @@ func initConfig(cmd *cobra.Command, nameToArg map[string]arg) error { if err := viper.ReadInConfig(); err == nil { fmt.Println("Using config file:", viper.ConfigFileUsed()) } else { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { - // no config file found, that's ok - } else { + var configFileNotFoundError viper.ConfigFileNotFoundError + if !errors.As(err, &configFileNotFoundError) { fmt.Println(err) os.Exit(1) } @@ -333,14 +333,14 @@ func bindFlags(cmd *cobra.Command, nameToArg map[string]arg) { val := v.Get(viperName) err := cmd.Flags().Set(cliLong, fmt.Sprintf("%v", val)) if err != nil { - fmt.Println(fmt.Sprintf("error setting flag %s: %v", cliLong, err)) + fmt.Printf("error setting flag %s: %v\n", cliLong, err) os.Exit(1) } } }) } -func mainEntrypoint(cmd *cobra.Command, args []string) { +func mainEntrypoint(cmd *cobra.Command, _ []string) { dev.Debug("~STARTING UP~") rootOpts := getRootOpts(cmd) initialModel, options := setup(cmd, rootOpts, "") diff --git a/cmd/util.go b/cmd/util.go index 06bab048..06f1bb7a 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -44,10 +44,7 @@ func validateToken(token string) error { } func trueIfTrue(v string) bool { - if strings.ToLower(strings.TrimSpace(v)) == "true" { - return true - } - return false + return strings.ToLower(strings.TrimSpace(v)) == "true" } func retrieveLogoColor() string { diff --git a/internal/tui/components/app/app.go b/internal/tui/components/app/app.go index 7a790955..9bec7138 100644 --- a/internal/tui/components/app/app.go +++ b/internal/tui/components/app/app.go @@ -86,7 +86,6 @@ type Model struct { eventsStream nomad.EventsStream event string - meta map[string]string logsStream nomad.LogsStream lastLogFinished bool @@ -434,7 +433,6 @@ func (m *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { if !m.currentPageFilterFocused() && !m.currentPageViewportSaving() { switch { - case key.Matches(msg, keymap.KeyMap.Compact): m.toggleCompact() return nil @@ -516,9 +514,7 @@ func (m *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { m.getCurrentPageModel().SetLoading(true) return m.getCurrentPageCmd() } - } - if key.Matches(msg, keymap.KeyMap.Exec) { if selectedPageRow, err := m.getCurrentPageModel().GetSelectedPageRow(); err == nil { if m.currentPage.ShowsTasks() { @@ -636,7 +632,6 @@ func (m *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { } if m.currentPage == nomad.JobTasksPage { - taskInfo, err := nomad.TaskInfoFromKey(selectedPageRow.Key) if err != nil { m.err = err @@ -691,21 +686,6 @@ func (m *Model) getCurrentPageModel() *page.Model { return m.pageModels[m.currentPage] } -func (m *Model) appendToViewport(content string, startOnNewLine bool) { - stringRows := strings.Split(content, "\n") - var pageRows []page.Row - for _, row := range stringRows { - stripOS := formatter.StripOSCommandSequences(row) - stripped := formatter.StripANSI(stripOS) - // bell seems to mess with parent terminal - if stripped != "\a" { - pageRows = append(pageRows, page.Row{Row: stripped}) - } - } - m.getCurrentPageModel().AppendToViewport(pageRows, startOnNewLine) - m.getCurrentPageModel().ScrollViewportToBottom() -} - func (m *Model) updateKeyHelp() { newKeyHelp := nomad.GetPageKeyHelp(m.currentPage, m.currentPageFilterFocused(), m.currentPageFilterApplied(), m.currentPageViewportSaving(), m.logType, m.compact, m.inJobsMode) m.header.SetKeyHelp(newKeyHelp) diff --git a/internal/tui/components/toast/toast.go b/internal/tui/components/toast/toast.go index 312d7338..e1148078 100644 --- a/internal/tui/components/toast/toast.go +++ b/internal/tui/components/toast/toast.go @@ -20,7 +20,6 @@ type Model struct { ID int message string Timeout time.Duration - initialized bool Visible bool MessageStyle lipgloss.Style } @@ -63,10 +62,6 @@ type TimeoutMsg struct { ID int } -func (m Model) timeoutAfterDuration() tea.Cmd { - return tea.Tick(m.Timeout, func(t time.Time) tea.Msg { return TimeoutMsg{m.ID} }) -} - func nextID() int { idMtx.Lock() defer idMtx.Unlock() diff --git a/internal/tui/components/viewport/viewport.go b/internal/tui/components/viewport/viewport.go index d98ea76a..84083958 100644 --- a/internal/tui/components/viewport/viewport.go +++ b/internal/tui/components/viewport/viewport.go @@ -415,9 +415,7 @@ func (m *Model) updateWrappedHeader() { var allWrappedHeader []string for _, line := range m.header { wrappedLinesForLine := m.getWrappedLines(line) - for _, wrappedLine := range wrappedLinesForLine { - allWrappedHeader = append(allWrappedHeader, wrappedLine) - } + allWrappedHeader = append(allWrappedHeader, wrappedLinesForLine...) } m.wrappedHeader = allWrappedHeader } diff --git a/internal/tui/formatter/formatter.go b/internal/tui/formatter/formatter.go index fd975dbf..3ea205a7 100644 --- a/internal/tui/formatter/formatter.go +++ b/internal/tui/formatter/formatter.go @@ -46,10 +46,6 @@ type Table struct { HeaderRows, ContentRows []string } -func (t *Table) isEmpty() bool { - return len(t.HeaderRows) == 0 && len(t.ContentRows) == 0 -} - type tableConfig struct { writer *tablewriter.Table string *strings.Builder @@ -120,7 +116,7 @@ func pluralize(s string, q float64) string { func FormatTimeNsSinceNow(t int64) string { tm := time.Unix(0, t).UTC() - since := time.Now().Sub(tm) + since := time.Since(tm) if secs := since.Seconds(); secs > 0 && secs < 60 { val := math.Floor(secs) out := fmt.Sprintf("%.0f second", val) diff --git a/internal/tui/nomad/events.go b/internal/tui/nomad/events.go index 021dec52..06c8abc0 100644 --- a/internal/tui/nomad/events.go +++ b/internal/tui/nomad/events.go @@ -100,7 +100,7 @@ func getEventsFromJQQuery(event string, code *gojq.Code) ([]Event, error) { if err != nil { events = append(events, Event{event, fmt.Sprintf("events jq json error: %s", err)}) } - events = append(events, Event{event, fmt.Sprintf("%s", j)}) + events = append(events, Event{event, string(j)}) } return events, nil } diff --git a/internal/tui/nomad/exec.go b/internal/tui/nomad/exec.go index 4c2b03cc..6294c046 100644 --- a/internal/tui/nomad/exec.go +++ b/internal/tui/nomad/exec.go @@ -53,7 +53,7 @@ func AllocExec(client *api.Client, allocID, task string, args []string) (int, er if len(foundAllocs) > 0 { if len(foundAllocs) == 1 && len(maps.Values(foundAllocs)[0]) == 1 && maps.Values(foundAllocs)[0][0] != nil { // only one job with one allocation found, use that - alloc, _, err = client.Allocations().Info(maps.Values(foundAllocs)[0][0].ID, nil) + alloc, _, _ = client.Allocations().Info(maps.Values(foundAllocs)[0][0].ID, nil) } else { // multiple jobs and/or allocations found, print them and exit for job, jobAllocs := range foundAllocs { @@ -78,7 +78,7 @@ func AllocExec(client *api.Client, allocID, task string, args []string) (int, er } return 1, err } else if len(shortIDAllocs) == 1 { - alloc, _, err = client.Allocations().Info(shortIDAllocs[0].ID, nil) + alloc, _, _ = client.Allocations().Info(shortIDAllocs[0].ID, nil) } else { return 1, fmt.Errorf("no allocations found for alloc id %s", allocID) } @@ -108,14 +108,21 @@ func AllocExec(client *api.Client, allocID, task string, args []string) (int, er } // execImpl invokes the Alloc Exec api call, it also prepares and restores terminal states as necessary. -func execImpl(client *api.Client, alloc *api.Allocation, task string, - command []string, escapeChar string, stdin io.Reader, stdout, stderr io.WriteCloser) (int, error) { - +func execImpl( + client *api.Client, + alloc *api.Allocation, + task string, + command []string, + escapeChar string, + stdin io.Reader, + stdout, + stderr io.WriteCloser, +) (int, error) { // attempt to clear screen time.Sleep(10 * time.Millisecond) - os.Stdout.Write([]byte("\033c")) + _, _ = os.Stdout.Write([]byte("\033c")) - fmt.Println(fmt.Sprintf("Exec session for %s (%s), task %s", alloc.Name, formatter.ShortAllocID(alloc.ID), task)) + fmt.Printf("Exec session for %s (%s), task %s\n", alloc.Name, formatter.ShortAllocID(alloc.ID), task) sizeCh := make(chan api.TerminalSize, 1) @@ -183,7 +190,7 @@ func setRawTerminal(stream interface{}) (cleanup func(), err error) { } return func() { - term.RestoreTerminal(fd, state) + _ = term.RestoreTerminal(fd, state) }, nil } @@ -200,7 +207,7 @@ func setRawTerminalOutput(stream interface{}) (cleanup func(), err error) { } return func() { - term.RestoreTerminal(fd, state) + _ = term.RestoreTerminal(fd, state) }, nil } @@ -276,7 +283,7 @@ func (r *reader) pipe() { if n > 0 { state = r.processBuf(bw, rb, n, state) - bw.Flush() + _ = bw.Flush() if state == sLookChar { // terminated with ~ - let's read one more character n, err = r.impl.Read(rb[:1]) @@ -284,14 +291,14 @@ func (r *reader) pipe() { state = sLookNewLine if rb[0] == r.escapeChar { // only emit escape character once - bw.WriteByte(rb[0]) - bw.Flush() + _ = bw.WriteByte(rb[0]) + _ = bw.Flush() } else if r.handler(rb[0]) { // skip if handled } else { - bw.WriteByte(r.escapeChar) - bw.WriteByte(rb[0]) - bw.Flush() + _ = bw.WriteByte(r.escapeChar) + _ = bw.WriteByte(rb[0]) + _ = bw.Flush() if rb[0] == '\n' || rb[0] == '\r' { state = sLookEscapeChar } @@ -303,10 +310,10 @@ func (r *reader) pipe() { if err != nil { // write ~ if it's the last thing if state == sLookChar { - bw.WriteByte(r.escapeChar) + _ = bw.WriteByte(r.escapeChar) } - bw.Flush() - r.pw.CloseWithError(err) + _ = bw.Flush() + _ = r.pw.CloseWithError(err) break } } @@ -324,19 +331,19 @@ START: if s == sLookEscapeChar && buf[i] == r.escapeChar { if i+1 >= n { // buf terminates with ~ - write all before - bw.Write(buf[wi:i]) + _, _ = bw.Write(buf[wi:i]) return sLookChar } nc := buf[i+1] if nc == r.escapeChar { // skip one escape char - bw.Write(buf[wi:i]) + _, _ = bw.Write(buf[wi:i]) i++ wi = i } else if r.handler(nc) { // skip both characters - bw.Write(buf[wi:i]) + _, _ = bw.Write(buf[wi:i]) i = i + 2 wi = i } else if nc == '\n' || nc == '\r' { @@ -353,14 +360,14 @@ START: for { if i >= n { // got to end without new line, write and return - bw.Write(buf[wi:n]) + _, _ = bw.Write(buf[wi:n]) return sLookNewLine } if buf[i] == '\n' || buf[i] == '\r' { // buf terminated at new line if i+1 >= n { - bw.Write(buf[wi:n]) + _, _ = bw.Write(buf[wi:n]) return sLookEscapeChar } diff --git a/internal/tui/nomad/jobs.go b/internal/tui/nomad/jobs.go index feaae85b..161534ed 100644 --- a/internal/tui/nomad/jobs.go +++ b/internal/tui/nomad/jobs.go @@ -101,5 +101,4 @@ func toJobsKey(jobResponseEntry *api.JobListStub) string { func JobIDAndNamespaceFromKey(key string) (string, string) { split := strings.Split(key, " ") return split[0], split[1] - } diff --git a/internal/tui/nomad/pages.go b/internal/tui/nomad/pages.go index 10afc565..99636688 100644 --- a/internal/tui/nomad/pages.go +++ b/internal/tui/nomad/pages.go @@ -407,7 +407,7 @@ func (p Page) GetFilterPrefix(namespace, jobID, taskName, allocName, allocID str case AllEventsPage: return fmt.Sprintf("All Events in Namespace %s (%s)", eventNamespace, formatEventTopics(eventTopics)) case AllEventPage: - return fmt.Sprintf("Event") + return "Event" case JobTasksPage: return fmt.Sprintf("Tasks for Job %s", style.Bold.Render(jobID)) case ExecPage: @@ -530,7 +530,7 @@ func GetPageKeyHelp( changeKeyHelp(&keymap.KeyMap.Back, "remove filter") fourthRow = append(fourthRow, keymap.KeyMap.Back) } else if prevPage := currentPage.Backward(inJobsMode); prevPage != currentPage { - changeKeyHelp(&keymap.KeyMap.Back, fmt.Sprintf("%s", currentPage.Backward(inJobsMode).String())) + changeKeyHelp(&keymap.KeyMap.Back, currentPage.Backward(inJobsMode).String()) fourthRow = append(fourthRow, keymap.KeyMap.Back) } diff --git a/main.go b/main.go index fe2dfd7e..22ae6944 100644 --- a/main.go +++ b/main.go @@ -5,5 +5,8 @@ import ( ) func main() { - cmd.Execute() + err := cmd.Execute() + if err != nil { + panic(err) + } }