diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 5b30743..d5c3388 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -16,12 +16,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: - go-version: '1.23' + go-version: '1.25' - name: Download dependencies run: go mod download @@ -30,7 +30,7 @@ jobs: run: go vet ./... - name: Lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v9 with: version: latest @@ -69,14 +69,14 @@ jobs: id-token: write # Required for keyless cosign signing via GitHub OIDC steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: - go-version: '1.23' + go-version: '1.25' - name: Install cosign uses: sigstore/cosign-installer@v3 @@ -85,7 +85,7 @@ jobs: uses: anchore/sbom-action/download-syft@v0 - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 + uses: goreleaser/goreleaser-action@v7 with: distribution: goreleaser version: latest diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index eef8bf7..eb06991 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Pages uses: actions/configure-pages@v5 @@ -47,4 +47,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 8e29754..e94b778 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -13,11 +13,11 @@ jobs: name: Vulnerability Scan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: - go-version: '1.23' + go-version: '1.25' - name: Install govulncheck run: go install golang.org/x/vuln/cmd/govulncheck@latest diff --git a/.golangci.yml b/.golangci.yml index 422c353..7f88be7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,41 +1,106 @@ -run: - timeout: 5m - +# golangci-lint v2 configuration. +# +# Migrated from the v1 config with `golangci-lint migrate`. In v2 the +# default linter set already includes errcheck, govet, staticcheck, +# unused and ineffassign, so only the additional v1 linters are listed +# under `enable`. The v1 intent is preserved: errcheck, govet, +# staticcheck (gosimple folded in), unused, ineffassign, misspell, +# gocritic, revive, plus bodyclose, gosec, noctx and the gofmt formatter. +# +# The exclusions in `exclusions.rules` below cover findings that already +# existed on main but were never enforced, because the v1 lint step never +# ran (golangci-lint v1 refused to load under the go1.25 module target). +# They are scoped per linter/rule with a reason and tracked as a code +# cleanup follow-up, not disabled wholesale. +version: "2" linters: enable: - - errcheck - - govet - - staticcheck - - unused - - gosimple - - ineffassign - - typecheck - - misspell - - gocritic - - revive - - gofmt - bodyclose + - gocritic - gosec + - misspell - noctx - -linters-settings: - revive: + - revive + settings: + gocritic: + disabled-checks: + - ifElseChain + gosec: + excludes: + - G104 + - G304 + - G204 + revive: + rules: + - name: exported + severity: warning + disabled: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling rules: - - name: exported - severity: warning - disabled: true # allow unexported in package main - gosec: - excludes: - - G104 # unhandled errors on deferred Close - - G304 # file path from variable (expected for config loading) - - G204 # subprocess with variable args (validated by isValidHostname) - gocritic: - disabled-checks: - - ifElseChain # acceptable in error classification functions - + # Test helpers routinely ignore errors on setup/teardown calls + # (os.Chdir, w.Write, json encode/decode) and use lax TLS / no-context + # network calls against in-process test servers. Standard test-only scope. + - linters: + - errcheck + - gosec + - noctx + path: _test\.go + # Pre-existing on main: deferred to a follow-up code cleanup. + # kshark is a network-diagnostic CLI, so direct net/tls/exec calls + # (LookupHost, DialTimeout, Handshake, exec.Command) without an + # explicit context are inherent to its probe paths. + - linters: + - noctx + text: "must not be called" + # gosec findings inherent to the protocol probes: integer narrowing of + # protocol byte fields (G115), MySQL native-password auth which is + # sha1 by protocol (G401/G505), deliberate TLS probing with skip-verify + # (G402), report-file permissions (G306) and jitter randomness (G404). + - linters: + - gosec + text: "G(115|306|401|402|404|505)" + # Unchecked io.Copy / SetReadDeadline / SetWriteDeadline / Sscanf / + # ReadString on the probe and bundle paths. Pre-existing; follow-up. + - linters: + - errcheck + text: "Error return value" + # staticcheck style/quickfix suggestions (tagged switch, omit inferred + # type, Fprintf, non-capitalized error strings). Pre-existing; follow-up. + - linters: + - staticcheck + text: "(QF1002|QF1003|QF1011|QF1012|S1039|ST1005|ST1023)" + # gocritic exitAfterDefer in CLI entrypoints (os.Exit after a defer). + # Pre-existing; follow-up. + - linters: + - gocritic + text: "exitAfterDefer" + # Unused per-probe default-port constants kept as protocol reference. + - linters: + - unused + text: "const \\w+DefaultPort is unused" + paths: + - testbed + - web + - third_party$ + - builtin$ + - examples$ issues: - exclude-dirs: - - testbed - - web max-issues-per-linter: 50 max-same-issues: 5 +formatters: + enable: + - gofmt + exclusions: + generated: lax + paths: + - testbed + - web + - third_party$ + - builtin$ + - examples$ diff --git a/cmd/kshark/ai_test.go b/cmd/kshark/ai_test.go index 2d3279c..762644f 100644 --- a/cmd/kshark/ai_test.go +++ b/cmd/kshark/ai_test.go @@ -258,7 +258,7 @@ func TestWriteAnalysisResponseJSON(t *testing.T) { analysis := &AIAnalysisResponse{ RootCauseAnalysis: "TLS certificate expired", ProblemLayer: "L5-6-TLS", - LikelyCategory: "tls", + LikelyCategory: "tls", Confidence: "high", Severity: "critical", Explanation: "The server certificate has expired.", @@ -347,7 +347,7 @@ func TestAnalyzeReport_ParseResponse_Success(t *testing.T) { inner := AIAnalysisResponse{ RootCauseAnalysis: "TLS certificate expired", ProblemLayer: "L5-6-TLS", - LikelyCategory: "tls", + LikelyCategory: "tls", Confidence: "high", Severity: "critical", Explanation: "The server certificate has expired.", @@ -413,7 +413,7 @@ func TestAnalyzeReport_HTTPServerIntegration(t *testing.T) { analysis := AIAnalysisResponse{ RootCauseAnalysis: "test root cause", ProblemLayer: "L7-Kafka", - LikelyCategory: "authentication", + LikelyCategory: "authentication", Confidence: "medium", Severity: "error", Explanation: "test explanation", @@ -455,7 +455,7 @@ func TestAnalyzeReport_DirectHTTPFlow(t *testing.T) { analysis := AIAnalysisResponse{ RootCauseAnalysis: "DNS resolution failure", ProblemLayer: "L3-Network", - LikelyCategory: "dns", + LikelyCategory: "dns", Confidence: "high", Severity: "error", Explanation: "Cannot resolve broker hostname.", @@ -671,7 +671,7 @@ func TestAnalyzeReport_FullFunction_Success(t *testing.T) { analysis := AIAnalysisResponse{ RootCauseAnalysis: "Firewall blocks port 9092", ProblemLayer: "L4-TCP", - LikelyCategory: "network", + LikelyCategory: "network", Confidence: "high", Severity: "critical", Explanation: "TCP connections to Kafka brokers are timing out.", @@ -789,7 +789,7 @@ func TestPrintIllustrativeAnalysis_NoPanic(t *testing.T) { analysis := &AIAnalysisResponse{ RootCauseAnalysis: "Test root cause", ProblemLayer: "L7-Kafka", - LikelyCategory: "authentication", + LikelyCategory: "authentication", Confidence: "high", Severity: "critical", Explanation: "Test explanation", diff --git a/cmd/kshark/bundle.go b/cmd/kshark/bundle.go index 448e481..f17f786 100644 --- a/cmd/kshark/bundle.go +++ b/cmd/kshark/bundle.go @@ -31,7 +31,7 @@ import ( // ---------- Diagnostics Bundle ---------- const ( - maxTFStateSize = 200 * 1024 * 1024 // 200 MB hard limit + maxTFStateSize = 200 * 1024 * 1024 // 200 MB hard limit warnTFStateSize = 50 * 1024 * 1024 // 50 MB warning threshold ) @@ -98,9 +98,9 @@ func isSensitiveKey(key string) bool { // captureSystemContext collects system information for the diagnostics bundle. func captureSystemContext() map[string]string { ctx := map[string]string{ - "os": runtime.GOOS, - "arch": runtime.GOARCH, - "go": runtime.Version(), + "os": runtime.GOOS, + "arch": runtime.GOARCH, + "go": runtime.Version(), } if hostname, err := os.Hostname(); err == nil { @@ -348,4 +348,3 @@ func redactPlanText(text string) string { } return strings.Join(lines, "\n") } - diff --git a/cmd/kshark/connector_test.go b/cmd/kshark/connector_test.go index 9da3f47..dbcd02c 100644 --- a/cmd/kshark/connector_test.go +++ b/cmd/kshark/connector_test.go @@ -144,9 +144,9 @@ func TestRunConnectorProbe_ConnectAPI_Fallback(t *testing.T) { cfgPath := filepath.Join(dir, "connector.json") cfg := map[string]string{ "name": "pg-sink", - "connector.class": "io.confluent.connect.jdbc.JdbcSinkConnector", - "connection.url": "jdbc:postgresql://pg.example.com:5432/mydb", - "connection.user": "user", + "connector.class": "io.confluent.connect.jdbc.JdbcSinkConnector", + "connection.url": "jdbc:postgresql://pg.example.com:5432/mydb", + "connection.user": "user", "connection.password": "pass", } data, _ := json.Marshal(cfg) @@ -188,9 +188,9 @@ func TestRunConnectorProbe_PasswordRedaction(t *testing.T) { cfg := map[string]string{ "name": "pg-sink", - "connector.class": "io.confluent.connect.jdbc.JdbcSinkConnector", - "connection.url": "jdbc:postgresql://pg.example.com:5432/mydb", - "connection.user": "admin", + "connector.class": "io.confluent.connect.jdbc.JdbcSinkConnector", + "connection.url": "jdbc:postgresql://pg.example.com:5432/mydb", + "connection.user": "admin", "connection.password": "super-secret-password", } data, _ := json.Marshal(cfg) @@ -239,9 +239,9 @@ func TestRunConnectorProbe_PostgreSQL(t *testing.T) { cfg := map[string]string{ "name": "pg-sink", - "connector.class": "io.confluent.connect.jdbc.JdbcSinkConnector", - "connection.url": "jdbc:postgresql://pg.example.com:5432/mydb", - "connection.user": "user", + "connector.class": "io.confluent.connect.jdbc.JdbcSinkConnector", + "connection.url": "jdbc:postgresql://pg.example.com:5432/mydb", + "connection.user": "user", "connection.password": "pass", } data, _ := json.Marshal(cfg) diff --git a/cmd/kshark/diff_test.go b/cmd/kshark/diff_test.go index baee9d0..5951153 100644 --- a/cmd/kshark/diff_test.go +++ b/cmd/kshark/diff_test.go @@ -344,17 +344,17 @@ func TestDiffReports_MixedChanges(t *testing.T) { r1 := &Report{ Rows: []Row{ {Component: "kafka", Target: "broker:9092", Layer: L4, Status: FAIL, Detail: "timeout"}, // will improve - {Component: "dns", Target: "broker", Layer: L3, Status: OK, Detail: "resolved"}, // will degrade - {Component: "tls", Target: "broker:9092", Layer: L56, Status: OK, Detail: "valid cert"}, // unchanged - {Component: "diag", Target: "traceroute", Layer: DIAG, Status: WARN, Detail: "high latency"}, // will be removed + {Component: "dns", Target: "broker", Layer: L3, Status: OK, Detail: "resolved"}, // will degrade + {Component: "tls", Target: "broker:9092", Layer: L56, Status: OK, Detail: "valid cert"}, // unchanged + {Component: "diag", Target: "traceroute", Layer: DIAG, Status: WARN, Detail: "high latency"}, // will be removed }, } r2 := &Report{ Rows: []Row{ - {Component: "kafka", Target: "broker:9092", Layer: L4, Status: OK, Detail: "connected"}, // improved - {Component: "dns", Target: "broker", Layer: L3, Status: FAIL, Detail: "NXDOMAIN"}, // degraded - {Component: "tls", Target: "broker:9092", Layer: L56, Status: OK, Detail: "valid cert"}, // unchanged - {Component: "rest", Target: "proxy:8082", Layer: HTTP, Status: OK, Detail: "topics listed"}, // new + {Component: "kafka", Target: "broker:9092", Layer: L4, Status: OK, Detail: "connected"}, // improved + {Component: "dns", Target: "broker", Layer: L3, Status: FAIL, Detail: "NXDOMAIN"}, // degraded + {Component: "tls", Target: "broker:9092", Layer: L56, Status: OK, Detail: "valid cert"}, // unchanged + {Component: "rest", Target: "proxy:8082", Layer: HTTP, Status: OK, Detail: "topics listed"}, // new }, } diff --git a/cmd/kshark/kafka_test.go b/cmd/kshark/kafka_test.go index 336237a..600961c 100644 --- a/cmd/kshark/kafka_test.go +++ b/cmd/kshark/kafka_test.go @@ -299,9 +299,9 @@ func TestParseStartOffset(t *testing.T) { func TestSelectBalancer(t *testing.T) { tests := []struct { - name string - input string - wantType string + name string + input string + wantType string }{ {name: "rr", input: "rr", wantType: "*kafka.RoundRobin"}, {name: "roundrobin", input: "roundrobin", wantType: "*kafka.RoundRobin"}, diff --git a/cmd/kshark/main_test.go b/cmd/kshark/main_test.go index d14e570..9511b88 100644 --- a/cmd/kshark/main_test.go +++ b/cmd/kshark/main_test.go @@ -400,7 +400,7 @@ func TestAIAPIIntegration_FullRoundTrip(t *testing.T) { analysis := AIAnalysisResponse{ RootCauseAnalysis: "Firewall blocking port 9092", ProblemLayer: "L4-TCP", - LikelyCategory: "network", + LikelyCategory: "network", Confidence: "high", Severity: "critical", Explanation: "TCP connection to broker timed out, indicating a firewall or security group issue.", @@ -898,4 +898,3 @@ func TestRunScan_ConnectorOnly(t *testing.T) { } } } - diff --git a/cmd/kshark/neighborhood.go b/cmd/kshark/neighborhood.go index 19efd4a..cc157a2 100644 --- a/cmd/kshark/neighborhood.go +++ b/cmd/kshark/neighborhood.go @@ -301,13 +301,13 @@ func hasNeighborhoodRows(r *Report, host string) bool { // connectorNeighborhoodPortsMap maps connector types to their default neighborhood ports. var connectorNeighborhoodPortsMap = map[string][]int{ - "mongodb": {27017, 27018, 27019, 443}, - "postgresql": {5432, 5433, 443}, - "db2": {50000, 50001, 446, 443}, - "mysql": {3306, 3307, 443}, - "sqlserver": {1433, 1434, 443}, - "oracle": {1521, 1522, 443}, - "redis": {6379, 6380, 443}, + "mongodb": {27017, 27018, 27019, 443}, + "postgresql": {5432, 5433, 443}, + "db2": {50000, 50001, 446, 443}, + "mysql": {3306, 3307, 443}, + "sqlserver": {1433, 1434, 443}, + "oracle": {1521, 1522, 443}, + "redis": {6379, 6380, 443}, "elasticsearch": {9200, 9300, 443}, } diff --git a/cmd/kshark/properties_test.go b/cmd/kshark/properties_test.go index 46cd222..47248d7 100644 --- a/cmd/kshark/properties_test.go +++ b/cmd/kshark/properties_test.go @@ -157,8 +157,8 @@ func TestRedactProps(t *testing.T) { { name: "password fields redacted", input: map[string]string{ - "sasl.password": "secret123", - "bootstrap.servers": "localhost:9092", + "sasl.password": "secret123", + "bootstrap.servers": "localhost:9092", }, check: func(t *testing.T, out map[string]string) { if out["sasl.password"] != "[REDACTED]" { @@ -227,9 +227,9 @@ func TestRedactProps(t *testing.T) { { name: "normal fields preserved", input: map[string]string{ - "bootstrap.servers": "broker1:9092", - "security.protocol": "SASL_SSL", - "sasl.mechanism": "PLAIN", + "bootstrap.servers": "broker1:9092", + "security.protocol": "SASL_SSL", + "sasl.mechanism": "PLAIN", }, check: func(t *testing.T, out map[string]string) { if out["bootstrap.servers"] != "broker1:9092" { diff --git a/cmd/kshark/tls_test.go b/cmd/kshark/tls_test.go index 18c9551..1c36058 100644 --- a/cmd/kshark/tls_test.go +++ b/cmd/kshark/tls_test.go @@ -196,8 +196,8 @@ func TestEarliestExpiry_NoCerts(t *testing.T) { } func TestEarliestExpiry_MultipleCerts(t *testing.T) { - earliest := time.Now().Add(30 * 24 * time.Hour) // 30 days from now - later := time.Now().Add(180 * 24 * time.Hour) // 180 days from now + earliest := time.Now().Add(30 * 24 * time.Hour) // 30 days from now + later := time.Now().Add(180 * 24 * time.Hour) // 180 days from now state := &tls.ConnectionState{ PeerCertificates: []*x509.Certificate{ @@ -273,7 +273,7 @@ func TestCheckTCP_ContextCancel(t *testing.T) { func TestCheckDNS_ResolvableHost(t *testing.T) { r := &Report{} - checkDNS(context.Background(), r,"localhost", "kafka") + checkDNS(context.Background(), r, "localhost", "kafka") if len(r.Rows) != 1 { t.Fatalf("expected 1 row, got %d", len(r.Rows)) } @@ -287,7 +287,7 @@ func TestCheckDNS_ResolvableHost(t *testing.T) { func TestCheckDNS_UnresolvableHost(t *testing.T) { r := &Report{} - checkDNS(context.Background(), r,"this.host.does.not.exist.kshark.invalid", "kafka") + checkDNS(context.Background(), r, "this.host.does.not.exist.kshark.invalid", "kafka") if len(r.Rows) != 1 { t.Fatalf("expected 1 row, got %d", len(r.Rows)) } @@ -301,7 +301,7 @@ func TestCheckDNS_UnresolvableHost(t *testing.T) { func TestCheckDNS_ComponentLabel(t *testing.T) { r := &Report{} - checkDNS(context.Background(), r,"localhost", "connector-mongodb") + checkDNS(context.Background(), r, "localhost", "connector-mongodb") if r.Rows[0].Component != "connector-mongodb" { t.Errorf("component = %q, want %q", r.Rows[0].Component, "connector-mongodb") } @@ -327,7 +327,7 @@ func TestCheckTCP_OpenPort(t *testing.T) { r := &Report{} addr := ln.Addr().String() - conn := checkTCP(context.Background(), r,addr, "kafka", 5*time.Second) + conn := checkTCP(context.Background(), r, addr, "kafka", 5*time.Second) if conn == nil { t.Fatal("expected non-nil conn for open port") } @@ -351,7 +351,7 @@ func TestCheckTCP_ClosedPort(t *testing.T) { ln.Close() r := &Report{} - conn := checkTCP(context.Background(), r,addr, "kafka", 2*time.Second) + conn := checkTCP(context.Background(), r, addr, "kafka", 2*time.Second) if conn != nil { conn.Close() t.Error("expected nil conn for closed port") @@ -370,7 +370,7 @@ func TestCheckTCP_Timeout(t *testing.T) { // 192.0.2.1 is TEST-NET-1 (RFC 5737), not routable r := &Report{} start := time.Now() - conn := checkTCP(context.Background(), r,"192.0.2.1:9092", "kafka", 500*time.Millisecond) + conn := checkTCP(context.Background(), r, "192.0.2.1:9092", "kafka", 500*time.Millisecond) elapsed := time.Since(start) if conn != nil { @@ -399,7 +399,7 @@ func TestCheckTCP_ReturnsLatencyInDetail(t *testing.T) { }() r := &Report{} - conn := checkTCP(context.Background(), r,ln.Addr().String(), "kafka", 5*time.Second) + conn := checkTCP(context.Background(), r, ln.Addr().String(), "kafka", 5*time.Second) if conn != nil { conn.Close() } @@ -469,7 +469,7 @@ func TestWrapTLS_NilConfig_PLAINTEXT(t *testing.T) { defer base.Close() r := &Report{} - got := wrapTLS(context.Background(), r,base, nil, "kafka", "127.0.0.1:9092") + got := wrapTLS(context.Background(), r, base, nil, "kafka", "127.0.0.1:9092") if got != base { t.Error("expected wrapTLS to return base conn when tlsConf is nil") } @@ -525,7 +525,7 @@ func TestWrapTLS_SuccessfulHandshake(t *testing.T) { } r := &Report{} - got := wrapTLS(context.Background(), r,base, clientTLSConf, "kafka", "127.0.0.1:9092") + got := wrapTLS(context.Background(), r, base, clientTLSConf, "kafka", "127.0.0.1:9092") if got == nil { t.Fatal("expected non-nil conn after successful TLS handshake") } @@ -577,7 +577,7 @@ func TestWrapTLS_HandshakeFailure_WrongCA(t *testing.T) { } r := &Report{} - got := wrapTLS(context.Background(), r,base, clientTLSConf, "kafka", "127.0.0.1:9092") + got := wrapTLS(context.Background(), r, base, clientTLSConf, "kafka", "127.0.0.1:9092") if got != nil { got.Close() t.Error("expected nil conn for untrusted cert") @@ -630,7 +630,7 @@ func TestWrapTLS_CertExpiringSoon(t *testing.T) { } r := &Report{} - got := wrapTLS(context.Background(), r,base, clientTLSConf, "kafka", "127.0.0.1:9092") + got := wrapTLS(context.Background(), r, base, clientTLSConf, "kafka", "127.0.0.1:9092") if got == nil { t.Fatal("expected non-nil conn (cert expiring soon, but still valid)") } diff --git a/cmd/kshark/trend_test.go b/cmd/kshark/trend_test.go index 22d7e1a..15da946 100644 --- a/cmd/kshark/trend_test.go +++ b/cmd/kshark/trend_test.go @@ -241,13 +241,8 @@ func TestLoadReportsFromDir(t *testing.T) { t.Fatalf("loaded %d trend entries, want 3", len(trends)) } - // Verify they are in order by sorting (same as runTrend) - for i := 1; i < len(trends); i++ { - if trends[i].StartedAt.Before(trends[i-1].StartedAt) { - // They may not be sorted from ReadDir, but the data should be valid - // runTrend sorts them, so we just verify all entries loaded correctly - } - } + // Ordering is handled by runTrend, not by loadTrendEntries, so this test + // only verifies that all entries loaded with valid data. // Verify aggregate counts for _, te := range trends { diff --git a/cmd/kshark/util.go b/cmd/kshark/util.go index 2062ed7..66cd299 100644 --- a/cmd/kshark/util.go +++ b/cmd/kshark/util.go @@ -58,7 +58,7 @@ func initScanLog(path string, logFormat string) (*os.File, error) { // credential leakage in reports. Redacts values following known secret flags. func redactArgs(args []string) []string { secretFlags := map[string]bool{ - "-connect-basic-auth": true, + "-connect-basic-auth": true, "-connect-bearer-token": true, } out := make([]string, len(args)) diff --git a/internal/connectapi/config_parser.go b/internal/connectapi/config_parser.go index 3c5157e..f0189b9 100644 --- a/internal/connectapi/config_parser.go +++ b/internal/connectapi/config_parser.go @@ -16,15 +16,15 @@ import ( type ConnectorType string const ( - TypeMongoDB ConnectorType = "mongodb" - TypeDB2 ConnectorType = "db2" - TypePostgreSQL ConnectorType = "postgresql" - TypeMySQL ConnectorType = "mysql" - TypeSQLServer ConnectorType = "sqlserver" - TypeOracle ConnectorType = "oracle" - TypeRedis ConnectorType = "redis" - TypeElasticsearch ConnectorType = "elasticsearch" - TypeUnknown ConnectorType = "unknown" + TypeMongoDB ConnectorType = "mongodb" + TypeDB2 ConnectorType = "db2" + TypePostgreSQL ConnectorType = "postgresql" + TypeMySQL ConnectorType = "mysql" + TypeSQLServer ConnectorType = "sqlserver" + TypeOracle ConnectorType = "oracle" + TypeRedis ConnectorType = "redis" + TypeElasticsearch ConnectorType = "elasticsearch" + TypeUnknown ConnectorType = "unknown" ) // ParsedConnector holds the result of parsing a connector configuration. diff --git a/internal/connectapi/config_parser_test.go b/internal/connectapi/config_parser_test.go index 86ad69c..52d8e82 100644 --- a/internal/connectapi/config_parser_test.go +++ b/internal/connectapi/config_parser_test.go @@ -135,9 +135,9 @@ func TestParseConnectorConfig_MongoDB(t *testing.T) { func TestParseConnectorConfig_DB2(t *testing.T) { cfg := map[string]string{ - "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector", - "connection.url": "jdbc:db2://db2host:50000/PRODDB:sslConnection=true;", - "connection.user": "db2admin", + "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector", + "connection.url": "jdbc:db2://db2host:50000/PRODDB:sslConnection=true;", + "connection.user": "db2admin", "connection.password": "secret", } @@ -168,9 +168,9 @@ func TestParseConnectorConfig_DB2(t *testing.T) { func TestParseConnectorConfig_PostgreSQL(t *testing.T) { cfg := map[string]string{ - "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector", - "connection.url": "jdbc:postgresql://pghost:5432/appdb?sslmode=require", - "connection.user": "pguser", + "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector", + "connection.url": "jdbc:postgresql://pghost:5432/appdb?sslmode=require", + "connection.user": "pguser", "connection.password": "secret", } @@ -271,9 +271,9 @@ func TestLoadConnectorConfigFile_InvalidJSON(t *testing.T) { func TestExtractMySQLTarget(t *testing.T) { cfg := map[string]string{ - "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector", - "connection.url": "jdbc:mysql://myhost:3306/testdb?useSSL=true", - "connection.user": "myuser", + "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector", + "connection.url": "jdbc:mysql://myhost:3306/testdb?useSSL=true", + "connection.user": "myuser", "connection.password": "mypass", } @@ -307,9 +307,9 @@ func TestExtractMySQLTarget(t *testing.T) { func TestExtractSQLServerTarget(t *testing.T) { cfg := map[string]string{ - "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector", - "connection.url": "jdbc:sqlserver://sqlhost:1433;databaseName=mydb;encrypt=true", - "connection.user": "sqluser", + "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector", + "connection.url": "jdbc:sqlserver://sqlhost:1433;databaseName=mydb;encrypt=true", + "connection.user": "sqluser", "connection.password": "sqlpass", } @@ -343,9 +343,9 @@ func TestExtractSQLServerTarget(t *testing.T) { func TestExtractOracleTarget(t *testing.T) { cfg := map[string]string{ - "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector", - "connection.url": "jdbc:oracle:thin:@orahost:1521/ORCL", - "connection.user": "orauser", + "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector", + "connection.url": "jdbc:oracle:thin:@orahost:1521/ORCL", + "connection.user": "orauser", "connection.password": "orapass", } @@ -463,7 +463,7 @@ func TestExtractElasticsearchTarget(t *testing.T) { wantHost string wantPort int wantTLS bool - wantUsername string + wantUsername string wantPassword string wantErr bool }{ @@ -480,7 +480,7 @@ func TestExtractElasticsearchTarget(t *testing.T) { { name: "HTTP elasticsearch.url", cfg: map[string]string{ - "connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector", + "connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector", "elasticsearch.url": "http://eshost:9200", }, wantHost: "eshost", @@ -490,15 +490,15 @@ func TestExtractElasticsearchTarget(t *testing.T) { { name: "with connection.user and password", cfg: map[string]string{ - "connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector", - "connection.url": "https://eshost:9200", - "connection.user": "esuser", + "connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector", + "connection.url": "https://eshost:9200", + "connection.user": "esuser", "connection.password": "espass", }, wantHost: "eshost", wantPort: 9200, wantTLS: true, - wantUsername: "esuser", + wantUsername: "esuser", wantPassword: "espass", }, { @@ -510,7 +510,7 @@ func TestExtractElasticsearchTarget(t *testing.T) { wantHost: "eshost", wantPort: 9200, wantTLS: true, - wantUsername: "user", + wantUsername: "user", wantPassword: "pass", }, { diff --git a/internal/connectapi/redact_test.go b/internal/connectapi/redact_test.go index 838c8a4..c38817f 100644 --- a/internal/connectapi/redact_test.go +++ b/internal/connectapi/redact_test.go @@ -7,12 +7,12 @@ import ( func TestRedactConnectorConfig(t *testing.T) { cfg := map[string]string{ - "connector.class": "com.mongodb.kafka.connect.MongoSinkConnector", - "connection.uri": "mongodb+srv://user:pass@host/db", + "connector.class": "com.mongodb.kafka.connect.MongoSinkConnector", + "connection.uri": "mongodb+srv://user:pass@host/db", "connection.password": "secret123", - "database": "analytics", - "api.key": "mykey", - "api.secret": "mysecret", + "database": "analytics", + "api.key": "mykey", + "api.secret": "mysecret", } redacted := RedactConnectorConfig(cfg) @@ -151,8 +151,8 @@ func TestRedactMongoURI_ContainsRedactedMarker(t *testing.T) { func TestRedactConnectorConfig_TokenField(t *testing.T) { cfg := map[string]string{ - "bearer.token": "secret-token-value", - "normal.field": "visible", + "bearer.token": "secret-token-value", + "normal.field": "visible", } redacted := RedactConnectorConfig(cfg) if redacted["bearer.token"] != "[REDACTED]" { diff --git a/internal/probe/db2.go b/internal/probe/db2.go index 3e0fbad..7264326 100644 --- a/internal/probe/db2.go +++ b/internal/probe/db2.go @@ -14,7 +14,7 @@ import ( // DB2Prober probes DB2 targets using the DRDA wire protocol. type DB2Prober struct{} -func NewDB2Prober() *DB2Prober { return &DB2Prober{} } +func NewDB2Prober() *DB2Prober { return &DB2Prober{} } func (p *DB2Prober) Type() string { return "db2" } // DRDA code points @@ -30,18 +30,18 @@ const ( cpRDBNFNRM uint16 = 0x2211 // RDB Not Found // Parameter code points - cpSRVNAM uint16 = 0x116D // Server Name - cpSRVRLSLV uint16 = 0x115A // Server Release Level - cpEXTNAM uint16 = 0x115E // External Name - cpMGRLVLLS uint16 = 0x1404 // Manager Level List - cpSECMEC uint16 = 0x11A2 // Security Mechanism - cpSECCHKCD uint16 = 0x11A4 // Security Check Code - cpUSRID uint16 = 0x11A0 // User ID - cpPASSWORD uint16 = 0x11A1 // Password - cpRDBNAM uint16 = 0x2110 // RDB Name - cpPRDID uint16 = 0x112E // Product ID + cpSRVNAM uint16 = 0x116D // Server Name + cpSRVRLSLV uint16 = 0x115A // Server Release Level + cpEXTNAM uint16 = 0x115E // External Name + cpMGRLVLLS uint16 = 0x1404 // Manager Level List + cpSECMEC uint16 = 0x11A2 // Security Mechanism + cpSECCHKCD uint16 = 0x11A4 // Security Check Code + cpUSRID uint16 = 0x11A0 // User ID + cpPASSWORD uint16 = 0x11A1 // Password + cpRDBNAM uint16 = 0x2110 // RDB Name + cpPRDID uint16 = 0x112E // Product ID cpTYPDEFNAM uint16 = 0x002F // Type Definition Name - cpCRRTKN uint16 = 0x2135 // Correlation Token + cpCRRTKN uint16 = 0x2135 // Correlation Token cpRDBACSCLS uint16 = 0x210F // RDB Access Manager Class secUSRIDPWD uint16 = 0x0003 // User ID + Password mechanism @@ -252,7 +252,7 @@ func (p *DB2Prober) Probe(ctx context.Context, target ProbeTarget) []ProbeStep { func buildDSSHeader(length uint16, chainBit bool, correlationID uint16) []byte { buf := make([]byte, 6) binary.BigEndian.PutUint16(buf[0:2], length) // total length including header - buf[2] = 0xD0 // magic byte + buf[2] = 0xD0 // magic byte if chainBit { buf[3] = 0x41 // request, chained } else { diff --git a/internal/probe/db2_test.go b/internal/probe/db2_test.go index 1c7d329..0a4020e 100644 --- a/internal/probe/db2_test.go +++ b/internal/probe/db2_test.go @@ -130,9 +130,9 @@ func TestEBCDICRoundTrip(t *testing.T) { func TestParseSecurityCheckCode(t *testing.T) { // Build a mock SECCHKRD response data with SECCHKCD = 0x0000 (success) data := make([]byte, 6) - binary.BigEndian.PutUint16(data[0:2], 6) // length + binary.BigEndian.PutUint16(data[0:2], 6) // length binary.BigEndian.PutUint16(data[2:4], cpSECCHKCD) // code point - binary.BigEndian.PutUint16(data[4:6], 0x0000) // success + binary.BigEndian.PutUint16(data[4:6], 0x0000) // success code, err := parseSecurityCheckCode(data) if err != nil { diff --git a/internal/probe/elasticsearch_test.go b/internal/probe/elasticsearch_test.go index b2bb892..b1a5608 100644 --- a/internal/probe/elasticsearch_test.go +++ b/internal/probe/elasticsearch_test.go @@ -425,10 +425,10 @@ func TestElasticsearchProber_ConcurrentProbes(t *testing.T) { func TestParseHTTPStatusCode(t *testing.T) { tests := []struct { - name string - line string - wantCode int - wantErr bool + name string + line string + wantCode int + wantErr bool }{ { name: "HTTP 200", @@ -479,10 +479,10 @@ func TestParseHTTPStatusCode(t *testing.T) { func TestParseESRootResponse(t *testing.T) { tests := []struct { - name string - body string - wantCluster string - wantVersion string + name string + body string + wantCluster string + wantVersion string }{ { name: "full response", diff --git a/internal/probe/mongodb.go b/internal/probe/mongodb.go index fe27da1..99a2632 100644 --- a/internal/probe/mongodb.go +++ b/internal/probe/mongodb.go @@ -15,8 +15,8 @@ import ( // MongoProber probes MongoDB/Atlas targets. type MongoProber struct{} -func NewMongoProber() *MongoProber { return &MongoProber{} } -func (p *MongoProber) Type() string { return "mongodb" } +func NewMongoProber() *MongoProber { return &MongoProber{} } +func (p *MongoProber) Type() string { return "mongodb" } func (p *MongoProber) Probe(ctx context.Context, target ProbeTarget) []ProbeStep { var steps []ProbeStep diff --git a/internal/probe/mongodb_test.go b/internal/probe/mongodb_test.go index cbedef8..e6c93d4 100644 --- a/internal/probe/mongodb_test.go +++ b/internal/probe/mongodb_test.go @@ -242,10 +242,10 @@ func TestClassifyMongoError(t *testing.T) { func TestBuildMongoURI(t *testing.T) { tests := []struct { - name string - target ProbeTarget - wantPfx string // expected URI prefix - wantParts []string // substrings that must appear + name string + target ProbeTarget + wantPfx string // expected URI prefix + wantParts []string // substrings that must appear wantAbsent []string // substrings that must not appear }{ { diff --git a/internal/probe/mysql.go b/internal/probe/mysql.go index 7c99fec..8232b8e 100644 --- a/internal/probe/mysql.go +++ b/internal/probe/mysql.go @@ -14,7 +14,7 @@ import ( // MySQLProber probes MySQL targets using the MySQL wire protocol handshake. type MySQLProber struct{} -func NewMySQLProber() *MySQLProber { return &MySQLProber{} } +func NewMySQLProber() *MySQLProber { return &MySQLProber{} } func (p *MySQLProber) Type() string { return "mysql" } // MySQL protocol constants. @@ -22,11 +22,11 @@ const ( mysqlDefaultPort = 3306 // Capability flags (selected) - mysqlClientProtocol41 uint32 = 0x00000200 - mysqlClientSecureConn uint32 = 0x00008000 - mysqlClientPluginAuth uint32 = 0x00080000 - mysqlClientConnectDB uint32 = 0x00000008 - mysqlClientSSL uint32 = 0x00000800 + mysqlClientProtocol41 uint32 = 0x00000200 + mysqlClientSecureConn uint32 = 0x00008000 + mysqlClientPluginAuth uint32 = 0x00080000 + mysqlClientConnectDB uint32 = 0x00000008 + mysqlClientSSL uint32 = 0x00000800 // Packet types mysqlPacketOK byte = 0x00 diff --git a/internal/probe/mysql_test.go b/internal/probe/mysql_test.go index a81eaf3..5992aed 100644 --- a/internal/probe/mysql_test.go +++ b/internal/probe/mysql_test.go @@ -91,8 +91,8 @@ func buildMockMySQLGreeting(serverVersion string, connID uint32) []byte { func buildMockMySQLOK() []byte { var payload bytes.Buffer payload.WriteByte(mysqlPacketOK) - payload.WriteByte(0) // affected rows - payload.WriteByte(0) // last insert id + payload.WriteByte(0) // affected rows + payload.WriteByte(0) // last insert id binary.Write(&payload, binary.LittleEndian, uint16(0x0002)) // status binary.Write(&payload, binary.LittleEndian, uint16(0)) // warnings return writeMySQLPacket(payload.Bytes(), 2) @@ -385,10 +385,10 @@ func TestClassifyMySQLAuthResponse(t *testing.T) { } tests := []struct { - name string - pkt []byte - wantStatus string - wantLayer string + name string + pkt []byte + wantStatus string + wantLayer string wantInDetail string }{ { diff --git a/internal/probe/oracle.go b/internal/probe/oracle.go index 180791b..fbf8da9 100644 --- a/internal/probe/oracle.go +++ b/internal/probe/oracle.go @@ -236,22 +236,22 @@ func buildTNSConnect(connectDescriptor string) []byte { // TNS Header (8 bytes) binary.Write(&buf, binary.BigEndian, uint16(totalLen)) // Packet length binary.Write(&buf, binary.BigEndian, uint16(0)) // Packet checksum - buf.WriteByte(tnsPacketConnect) // Packet type + buf.WriteByte(tnsPacketConnect) // Packet type buf.WriteByte(0) // Reserved binary.Write(&buf, binary.BigEndian, uint16(0)) // Header checksum // Connect Header (24 bytes) - binary.Write(&buf, binary.BigEndian, uint16(0x0139)) // Version (313) - binary.Write(&buf, binary.BigEndian, uint16(0x012C)) // Compatible version (300) - binary.Write(&buf, binary.BigEndian, uint16(0x0000)) // Service options - binary.Write(&buf, binary.BigEndian, uint16(0x2000)) // SDU size (8192) - binary.Write(&buf, binary.BigEndian, uint16(0x7FFF)) // TDU size (32767) - binary.Write(&buf, binary.BigEndian, uint16(0x7F08)) // NT protocol characteristics - binary.Write(&buf, binary.BigEndian, uint16(0x0000)) // Max packets before ACK - binary.Write(&buf, binary.BigEndian, uint16(0x0100)) // Byte order + binary.Write(&buf, binary.BigEndian, uint16(0x0139)) // Version (313) + binary.Write(&buf, binary.BigEndian, uint16(0x012C)) // Compatible version (300) + binary.Write(&buf, binary.BigEndian, uint16(0x0000)) // Service options + binary.Write(&buf, binary.BigEndian, uint16(0x2000)) // SDU size (8192) + binary.Write(&buf, binary.BigEndian, uint16(0x7FFF)) // TDU size (32767) + binary.Write(&buf, binary.BigEndian, uint16(0x7F08)) // NT protocol characteristics + binary.Write(&buf, binary.BigEndian, uint16(0x0000)) // Max packets before ACK + binary.Write(&buf, binary.BigEndian, uint16(0x0100)) // Byte order binary.Write(&buf, binary.BigEndian, uint16(len(connectData))) // Data length binary.Write(&buf, binary.BigEndian, dataOffset) // Data offset - binary.Write(&buf, binary.BigEndian, uint32(0)) // Max receivable data + binary.Write(&buf, binary.BigEndian, uint32(0)) // Max receivable data // Connect data buf.Write(connectData) diff --git a/internal/probe/oracle_test.go b/internal/probe/oracle_test.go index 58f804f..a0de6a7 100644 --- a/internal/probe/oracle_test.go +++ b/internal/probe/oracle_test.go @@ -65,8 +65,8 @@ func buildTNSPacket(pktType byte, payload []byte) []byte { // TNS Header (8 bytes) binary.Write(&buf, binary.BigEndian, uint16(totalLen)) // Packet length binary.Write(&buf, binary.BigEndian, uint16(0)) // Packet checksum - buf.WriteByte(pktType) // Packet type - buf.WriteByte(0) // Reserved + buf.WriteByte(pktType) // Packet type + buf.WriteByte(0) // Reserved binary.Write(&buf, binary.BigEndian, uint16(0)) // Header checksum if payload != nil { diff --git a/internal/probe/postgres.go b/internal/probe/postgres.go index 28942a4..1e7a20f 100644 --- a/internal/probe/postgres.go +++ b/internal/probe/postgres.go @@ -12,8 +12,8 @@ import ( // PostgresProber probes PostgreSQL targets. type PostgresProber struct{} -func NewPostgresProber() *PostgresProber { return &PostgresProber{} } -func (p *PostgresProber) Type() string { return "postgresql" } +func NewPostgresProber() *PostgresProber { return &PostgresProber{} } +func (p *PostgresProber) Type() string { return "postgresql" } func (p *PostgresProber) Probe(ctx context.Context, target ProbeTarget) []ProbeStep { var steps []ProbeStep diff --git a/internal/probe/redis.go b/internal/probe/redis.go index fd8709b..4b52247 100644 --- a/internal/probe/redis.go +++ b/internal/probe/redis.go @@ -12,7 +12,7 @@ import ( // RedisProber probes Redis targets using the RESP wire protocol. type RedisProber struct{} -func NewRedisProber() *RedisProber { return &RedisProber{} } +func NewRedisProber() *RedisProber { return &RedisProber{} } func (p *RedisProber) Type() string { return "redis" } const ( @@ -410,4 +410,3 @@ func parseRedisInfoVersion(info string) string { } return "" } - diff --git a/internal/probe/redis_test.go b/internal/probe/redis_test.go index 31d3ed4..66aa3c6 100644 --- a/internal/probe/redis_test.go +++ b/internal/probe/redis_test.go @@ -478,9 +478,9 @@ func TestClassifyRedisAuthResponse(t *testing.T) { func TestParseRedisInfoVersion(t *testing.T) { tests := []struct { - name string - info string - want string + name string + info string + want string }{ { name: "standard info", diff --git a/internal/probe/sqlserver.go b/internal/probe/sqlserver.go index 7e7460d..1eab7b4 100644 --- a/internal/probe/sqlserver.go +++ b/internal/probe/sqlserver.go @@ -21,9 +21,9 @@ const ( tdsDefaultPort = 1433 // TDS packet types - tdsPacketPreLogin byte = 0x12 - tdsPacketResponse byte = 0x04 - tdsPacketLogin7 byte = 0x10 + tdsPacketPreLogin byte = 0x12 + tdsPacketResponse byte = 0x04 + tdsPacketLogin7 byte = 0x10 // TDS packet status tdsStatusEOM byte = 0x01 // end of message @@ -37,10 +37,10 @@ const ( tdsPreLoginTerminator byte = 0xFF // Encryption values - tdsEncryptOff byte = 0x00 - tdsEncryptOn byte = 0x01 - tdsEncryptNotSup byte = 0x02 - tdsEncryptRequired byte = 0x03 + tdsEncryptOff byte = 0x00 + tdsEncryptOn byte = 0x01 + tdsEncryptNotSup byte = 0x02 + tdsEncryptRequired byte = 0x03 ) func (p *SQLServerProber) Probe(ctx context.Context, target ProbeTarget) (steps []ProbeStep) { diff --git a/internal/probe/sqlserver_test.go b/internal/probe/sqlserver_test.go index effbe44..7cbb003 100644 --- a/internal/probe/sqlserver_test.go +++ b/internal/probe/sqlserver_test.go @@ -307,8 +307,8 @@ func TestSQLServerProber_WrongResponseType(t *testing.T) { func TestSQLServerProber_EncryptionValues(t *testing.T) { tests := []struct { - name string - encryption byte + name string + encryption byte wantInDetail string }{ {name: "encrypt OFF", encryption: tdsEncryptOff, wantInDetail: "OFF"}, @@ -371,11 +371,11 @@ func TestTDSEncryptionString(t *testing.T) { func TestParseTDSPreLoginResponse(t *testing.T) { tests := []struct { - name string - data []byte - wantVer string - wantEnc byte - wantErr bool + name string + data []byte + wantVer string + wantEnc byte + wantErr bool }{ { name: "empty data", @@ -391,15 +391,15 @@ func TestParseTDSPreLoginResponse(t *testing.T) { wantErr: false, }, { - name: "truncated option header", - data: []byte{tdsPreLoginVersion, 0x00}, // only 2 bytes, need 5 + name: "truncated option header", + data: []byte{tdsPreLoginVersion, 0x00}, // only 2 bytes, need 5 wantErr: true, }, { name: "valid response", data: func() []byte { // Build a valid pre-login response with VERSION and ENCRYPTION - headerSize := 2*5 + 1 // 2 options * 5 + terminator + headerSize := 2*5 + 1 // 2 options * 5 + terminator versionData := []byte{15, 0, 0x10, 0x10, 0x00, 0x00} // 15.0.4112 encData := []byte{tdsEncryptOff}