diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index df3fb5d1..e7fa1ad2 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ['1.21', '1.22'] + go: ['1.23'] steps: - uses: actions/checkout@v3 @@ -27,7 +27,7 @@ jobs: - name: check clean vendors run: go mod vendor - name: Report coverage - if: ${{ matrix.go == '1.22' }} + if: ${{ matrix.go == '1.23' }} uses: codecov/codecov-action@v4 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/pull_request_e2e.yml b/.github/workflows/pull_request_e2e.yml index d7540122..b7467a11 100644 --- a/.github/workflows/pull_request_e2e.yml +++ b/.github/workflows/pull_request_e2e.yml @@ -23,7 +23,7 @@ jobs: - name: set up go 1.x uses: actions/setup-go@v3 with: - go-version: '1.22' + go-version: '1.23' - name: checkout uses: actions/checkout@v3 - name: get kernel version diff --git a/.github/workflows/push_image.yml b/.github/workflows/push_image.yml index fb7b8da2..698f7f64 100644 --- a/.github/workflows/push_image.yml +++ b/.github/workflows/push_image.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ['1.22'] + go: ['1.23'] steps: - name: install make run: sudo apt-get install make @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ['1.22'] + go: ['1.23'] steps: - name: install make run: sudo apt-get install make diff --git a/.github/workflows/push_image_pr.yml b/.github/workflows/push_image_pr.yml index 3d968814..d8d4de92 100644 --- a/.github/workflows/push_image_pr.yml +++ b/.github/workflows/push_image_pr.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ['1.22'] + go: ['1.23'] steps: - name: install make run: sudo apt-get install make diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dcf13f56..c6d70ed5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ['1.22'] + go: ['1.23'] steps: - name: checkout uses: actions/checkout@v3 diff --git a/.golangci.yml b/.golangci.yml index c607321f..c2a7bc15 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,7 @@ linters: - cyclop - errname - exhaustive - - exportloopref + - copyloopvar - gocritic - gofmt - gosimple @@ -16,15 +16,17 @@ linters: - stylecheck - typecheck - unused +run: + go: "1.22" linters-settings: - stylecheck: - go: "1.22" gocritic: enabled-checks: - hugeParam - rangeExprCopy - rangeValCopy - indexAlloc - - deprecatedComment + settings: + ifElseChain: + minThreshold: 3 cyclop: - max-complexity: 150 # TODO: reduce that to 20 \ No newline at end of file + max-complexity: 150 # TODO: reduce that to 20 diff --git a/Dockerfile b/Dockerfile index 6281035b..aa678586 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ ARG TARGETARCH=amd64 # Build the manager binary -FROM docker.io/library/golang:1.22 as builder +FROM docker.io/library/golang:1.23 as builder ARG TARGETARCH ARG LDFLAGS diff --git a/Dockerfile.downstream b/Dockerfile.downstream index 66b3a08f..aadd69fc 100644 --- a/Dockerfile.downstream +++ b/Dockerfile.downstream @@ -6,7 +6,7 @@ ARG COMMIT FROM registry.redhat.io/openshift4/ose-cli-rhel9:v4.17.0-202412032103.p0.g13001b0.assembly.stream.el9 as ose-cli # Build the manager binary -FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:v1.22.5-202407301806.g4c8b32d.el9 as builder +FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:v1.23 as builder ARG TARGETARCH ARG TARGETPLATFORM diff --git a/Makefile b/Makefile index 25bb43a5..af9db146 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ ifneq ($(CLEAN_BUILD),) LDFLAGS ?= -X 'main.buildVersion=${VERSION}-${BUILD_SHA}' -X 'main.buildDate=${BUILD_DATE}' endif -GOLANGCI_LINT_VERSION = v1.54.2 +GOLANGCI_LINT_VERSION = v1.61.0 YQ_VERSION = v4.43.1 # build a single arch target provided as argument diff --git a/cmd/flow_capture.go b/cmd/flow_capture.go index dca0dfc8..2a0920a5 100644 --- a/cmd/flow_capture.go +++ b/cmd/flow_capture.go @@ -58,7 +58,13 @@ var ( ) func runFlowCapture(_ *cobra.Command, _ []string) { - go scanner() + go func() { + if !scanner() { + return + } + // scanner returns on exit request + os.Exit(0) + }() captureType = "Flow" wg := sync.WaitGroup{} @@ -66,20 +72,24 @@ func runFlowCapture(_ *cobra.Command, _ []string) { for i := range ports { go func(idx int) { defer wg.Done() - runFlowCaptureOnAddr(ports[idx], nodes[idx]) + err := runFlowCaptureOnAddr(ports[idx], nodes[idx]) + if err != nil { + // Only fatal errors are returned here + log.Fatal(err) + } }(i) } wg.Wait() } -func runFlowCaptureOnAddr(port int, filename string) { +func runFlowCaptureOnAddr(port int, filename string) error { if len(filename) > 0 { log.Infof("Starting Flow Capture for %s...", filename) } else { log.Infof("Starting Flow Capture...") - filename = strings.Replace( + filename = strings.ReplaceAll( currentTime().UTC().Format(time.RFC3339), - ":", "", -1) // get rid of offensive colons + ":", "") // get rid of offensive colons } var f *os.File @@ -105,8 +115,7 @@ func runFlowCaptureOnAddr(port int, filename string) { flowPackets := make(chan *genericmap.Flow, 100) collector, err := grpc.StartCollector(port, flowPackets) if err != nil { - log.Error("StartCollector failed:", err.Error()) - log.Fatal(err) + return fmt.Errorf("StartCollector failed: %w", err) } log.Trace("Started collector") collectorStarted = true @@ -128,7 +137,7 @@ func runFlowCaptureOnAddr(port int, filename string) { if stopReceived { log.Trace("Stop received") - return + return nil } // parse and display flow async go parseGenericMapAndDisplay(fp.GenericMap.Value) @@ -145,18 +154,18 @@ func runFlowCaptureOnAddr(port int, filename string) { // append new line between each record to read file easilly bytes, err := f.Write(append(fp.GenericMap.Value, []byte(",\n")...)) if err != nil { - log.Fatal(err) + return err } if !captureStarted { log.Trace("Wrote flows to json") } // terminate capture if max bytes reached - totalBytes = totalBytes + int64(bytes) + totalBytes += int64(bytes) if totalBytes > maxBytes { if exit := onLimitReached(); exit { log.Infof("Capture reached %s, exiting now...", sizestr.ToString(maxBytes)) - return + return nil } } @@ -166,12 +175,13 @@ func runFlowCaptureOnAddr(port int, filename string) { if int(duration) > int(maxTime) { if exit := onLimitReached(); exit { log.Infof("Capture reached %s, exiting now...", maxTime) - return + return nil } } captureStarted = true } + return nil } func parseGenericMapAndDisplay(bytes []byte) { @@ -202,7 +212,7 @@ func manageFlowsDisplay(genericMap config.GenericMap) { if len(regexes) > 0 { // regexes may change during the render so we make a copy first rCopy := make([]string, len(regexes)) - copy(rCopy[:], regexes) + copy(rCopy, regexes) filtered := []config.GenericMap{} for _, flow := range lastFlows { match := true @@ -380,10 +390,11 @@ func cycleOption(selection []string, exclusiveOptions []string, options []string return selection } -func scanner() { +// scanner returns true in case of normal exit (end of program execution) or false in case of error +func scanner() bool { if err := keyboard.Open(); err != nil { keyboardError = fmt.Sprintf("Keyboard not supported %v", err) - return + return false } defer func() { _ = keyboard.Close() @@ -394,26 +405,26 @@ func scanner() { if err != nil { panic(err) } - if key == keyboard.KeyCtrlC || stopReceived { + switch { + case key == keyboard.KeyCtrlC, stopReceived: log.Info("Ctrl-C pressed, exiting program.") - // exit program - os.Exit(0) - } else if key == keyboard.KeyArrowUp { - flowsToShow = flowsToShow + 1 - } else if key == keyboard.KeyArrowDown { + return true + case key == keyboard.KeyArrowUp: + flowsToShow++ + case key == keyboard.KeyArrowDown: if flowsToShow > 10 { - flowsToShow = flowsToShow - 1 + flowsToShow-- } - } else if key == keyboard.KeyArrowRight { + case key == keyboard.KeyArrowRight: display = cycleOption(display, exclusiveDisplays, displays, 1) - } else if key == keyboard.KeyArrowLeft { + case key == keyboard.KeyArrowLeft: display = cycleOption(display, exclusiveDisplays, displays, -1) - } else if key == keyboard.KeyPgup { + case key == keyboard.KeyPgup: enrichment = cycleOption(enrichment, exclusiveEnrichments, enrichments, 1) - } else if key == keyboard.KeyPgdn { + case key == keyboard.KeyPgdn: enrichment = cycleOption(enrichment, exclusiveEnrichments, enrichments, -1) - } else if key == keyboard.KeyBackspace || key == keyboard.KeyBackspace2 { + case key == keyboard.KeyBackspace || key == keyboard.KeyBackspace2: if len(regexes) > 0 { lastIndex := len(regexes) - 1 if len(regexes[lastIndex]) > 0 { @@ -422,14 +433,14 @@ func scanner() { regexes = regexes[:lastIndex] } } - } else if key == keyboard.KeyEnter { + case key == keyboard.KeyEnter: regexes = append(regexes, "") - } else { + default: if len(regexes) == 0 { regexes = []string{string(char)} } else { lastIndex := len(regexes) - 1 - regexes[lastIndex] = regexes[lastIndex] + string(char) + regexes[lastIndex] += string(char) } } lastRefresh = startupTime diff --git a/cmd/flow_capture_test.go b/cmd/flow_capture_test.go index 2e81991a..6088294b 100644 --- a/cmd/flow_capture_test.go +++ b/cmd/flow_capture_test.go @@ -68,8 +68,8 @@ func TestFlowTableMultipleFlows(t *testing.T) { buf.Reset() // update time and bytes for next flow - flowTime = flowTime + 1000 - bytes = bytes + 1000 + flowTime += 1000 + bytes += 1000 // add flow to table parseGenericMapAndDisplay([]byte(fmt.Sprintf(`{ diff --git a/cmd/flow_db.go b/cmd/flow_db.go index 3e28fe9b..b5760a81 100644 --- a/cmd/flow_db.go +++ b/cmd/flow_db.go @@ -96,16 +96,17 @@ func insertFlowToDB(db *sql.DB, buf []byte) error { } // Insert message into database var flowSQL string - if flow["PktDropPackets"] != 0 && flow["DnsId"] != 0 { + switch { + case flow["PktDropPackets"] != 0 && flow["DnsId"] != 0: flowSQL = `INSERT INTO flow(DnsErrno, Dscp, DstAddr, DstPort, Interface, Proto, SrcAddr, SrcPort, Bytes, Packets, PktDropLatestDropCause, PktDropBytes, PktDropPackets, DnsId, DnsFlagsResponseCode, DnsLatencyMs, TimeFlowRttNs) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` - } else if flow["PktDropPackets"] != 0 { + case flow["PktDropPackets"] != 0: flowSQL = `INSERT INTO flow(DnsErrno, Dscp, DstAddr, DstPort, Interface, Proto, SrcAddr, SrcPort, Bytes, Packets, PktDropLatestDropCause, PktDropBytes, PktDropPackets, TimeFlowRttNs) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` - } else if flow["DnsId"] != 0 { + case flow["DnsId"] != 0: flowSQL = `INSERT INTO flow(DnsErrno, Dscp, DstAddr, DstPort, Interface, Proto, SrcAddr, SrcPort, Bytes, Packets, DnsId, DnsFlagsResponseCode, DnsLatencyMs, TimeFlowRttNs) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` - } else { + default: flowSQL = `INSERT INTO flow(DnsErrno, Dscp, DstAddr, DstPort, Interface, Proto, SrcAddr, SrcPort, Bytes, Packets, TimeFlowRttNs) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` } @@ -116,26 +117,27 @@ func insertFlowToDB(db *sql.DB, buf []byte) error { return fmt.Errorf("error preparing SQL: %v", err.Error()) } - if flow["PktDropLatestDropCause"] != 0 && flow["DnsId"] != 0 { + switch { + case flow["PktDropLatestDropCause"] != 0 && flow["DnsId"] != 0: _, err = statement.Exec( flow["DNSErrno"], flow["Dscp"], flow["DstAddr"], flow["DstPort"], flow["Interface"], flow["Proto"], flow["SrcAddr"], flow["SrcPort"], flow["Bytes"], flow["Packets"], flow["PktDropLatestDropCause"], flow["PktDropBytes"], flow["PktDropPackets"], flow["DnsId"], flow["DnsFlagsResponseCode"], flow["DnsLatencyMs"], flow["TimeFlowRttNs"]) - } else if flow["PktDropLatestDropCause"] != 0 { + case flow["PktDropLatestDropCause"] != 0: _, err = statement.Exec( flow["DNSErrno"], flow["Dscp"], flow["DstAddr"], flow["DstPort"], flow["Interface"], flow["Proto"], flow["SrcAddr"], flow["SrcPort"], flow["Bytes"], flow["Packets"], flow["PktDropLatestDropCause"], flow["PktDropBytes"], flow["PktDropPackets"], flow["TimeFlowRttNs"]) - } else if flow["DnsId"] != 0 { + case flow["DnsId"] != 0: _, err = statement.Exec( flow["DNSErrno"], flow["Dscp"], flow["DstAddr"], flow["DstPort"], flow["Interface"], flow["Proto"], flow["SrcAddr"], flow["SrcPort"], flow["Bytes"], flow["Packets"], flow["DnsId"], flow["DnsFlagsResponseCode"], flow["DnsLatencyMs"], flow["TimeFlowRttNs"]) - } else { + default: _, err = statement.Exec( flow["DNSErrno"], flow["Dscp"], flow["DstAddr"], flow["DstPort"], flow["Interface"], flow["Proto"], flow["SrcAddr"], flow["SrcPort"], flow["Bytes"], flow["Packets"], diff --git a/cmd/packet_capture.go b/cmd/packet_capture.go index 320954c1..569f43d2 100644 --- a/cmd/packet_capture.go +++ b/cmd/packet_capture.go @@ -36,20 +36,24 @@ func runPacketCapture(_ *cobra.Command, _ []string) { for i := range ports { go func(idx int) { defer wg.Done() - runPacketCaptureOnAddr(ports[idx], nodes[idx]) + err := runPacketCaptureOnAddr(ports[idx], nodes[idx]) + if err != nil { + // Only fatal error are returned + log.Fatal(err) + } }(i) } wg.Wait() } -func runPacketCaptureOnAddr(port int, filename string) { +func runPacketCaptureOnAddr(port int, filename string) error { if len(filename) > 0 { log.Infof("Starting Packet Capture for %s...", filename) } else { log.Infof("Starting Packet Capture...") - filename = strings.Replace( + filename = strings.ReplaceAll( currentTime().UTC().Format(time.RFC3339), - ":", "", -1) // get rid of offensive colons + ":", "") // get rid of offensive colons } var f *os.File @@ -82,8 +86,7 @@ func runPacketCaptureOnAddr(port int, filename string) { flowPackets := make(chan *genericmap.Flow, 100) collector, err := grpc.StartCollector(port, flowPackets) if err != nil { - log.Error("StartCollector failed:", err.Error()) - log.Fatal(err) + return fmt.Errorf("StartCollector failed: %w", err) } log.Trace("Started collector") collectorStarted = true @@ -104,14 +107,14 @@ func runPacketCaptureOnAddr(port int, filename string) { if stopReceived { log.Trace("Stop received") - return + return nil } genericMap := config.GenericMap{} err := json.Unmarshal(fp.GenericMap.Value, &genericMap) if err != nil { log.Error("Error while parsing json", err) - return + return nil } if !captureStarted { log.Tracef("Parsed genericMap %v", genericMap) @@ -135,7 +138,7 @@ func runPacketCaptureOnAddr(port int, filename string) { b, err := base64.StdEncoding.DecodeString(data.(string)) if err != nil { log.Error("Error while decoding data", err) - return + return nil } // write enriched data as interface @@ -144,7 +147,7 @@ func runPacketCaptureOnAddr(port int, filename string) { // then append packet to file using totalPackets as unique id err = pw.WriteEnhancedPacketBlock(totalPackets, ts, b, types.EnhancedPacketOptions{}) if err != nil { - log.Fatal(err) + return err } } else { if !captureStarted { @@ -156,14 +159,14 @@ func runPacketCaptureOnAddr(port int, filename string) { } // terminate capture if max bytes reached - totalBytes = totalBytes + int64(len(fp.GenericMap.Value)) + totalBytes += int64(len(fp.GenericMap.Value)) if totalBytes > maxBytes { if exit := onLimitReached(); exit { log.Infof("Capture reached %s, exiting now...", sizestr.ToString(maxBytes)) - return + return nil } } - totalPackets = totalPackets + 1 + totalPackets++ // terminate capture if max time reached now := currentTime() @@ -171,12 +174,13 @@ func runPacketCaptureOnAddr(port int, filename string) { if int(duration) > int(maxTime) { if exit := onLimitReached(); exit { log.Infof("Capture reached %s, exiting now...", maxTime) - return + return nil } } captureStarted = true } + return nil } func writeEnrichedData(pw *pcapng.FileWriter, genericMap *config.GenericMap) { diff --git a/cmd/root.go b/cmd/root.go index 8af2efc7..2a79ef6e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -28,9 +28,7 @@ var ( maxTime time.Duration maxBytes int64 - currentTime = func() time.Time { - return time.Now() - } + currentTime = time.Now startupTime = currentTime() lastRefresh = startupTime totalBytes = int64(0) @@ -49,7 +47,7 @@ var ( Use: "network-observability-cli", Short: "network-observability-cli is an interactive Flow and Packet visualizer", Long: `An interactive Flow / PCAP collector and visualization tool`, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { }, } diff --git a/e2e/cluster/kind.go b/e2e/cluster/kind.go index 56d732ca..227722ec 100644 --- a/e2e/cluster/kind.go +++ b/e2e/cluster/kind.go @@ -85,14 +85,14 @@ func (k *Kind) GetLogsDir() string { // export logs into the e2e-logs folder of the base directory. func (k *Kind) exportLogs() env.Func { - return func(ctx context.Context, config *envconf.Config) (context.Context, error) { + return func(ctx context.Context, _ *envconf.Config) (context.Context, error) { logsDir := k.GetLogsDir() klog.WithField("directory", logsDir).Info("exporting cluster logs") exe := gexe.New() out := exe.Run("kind export logs " + logsDir + " --name " + k.clusterName) klog.WithField("out", out).Info("exported cluster logs") - //move output files to cluster logs folder + // move output files to cluster logs folder err := os.Rename(path.Join(k.baseDir, "e2e", "tmp"), path.Join(logsDir, "output")) if err != nil { klog.Error(err) @@ -112,7 +112,7 @@ func (k *Kind) GetAgentLogs() string { // delete netobserv-cli namespace func (k *Kind) deleteNamespace() env.Func { - return func(ctx context.Context, config *envconf.Config) (context.Context, error) { + return func(ctx context.Context, _ *envconf.Config) (context.Context, error) { exe := gexe.New() out := exe.Run("kubectl delete namespace netobserv-cli") klog.WithField("out", out).Info("deleted namespace")