Skip to content

Commit 39930f8

Browse files
ci: install golangci-lint (Tier-2 quality) (#16)
* ci: install golangci-lint (Tier-2 quality) Adds golangci-lint workflow + conservative initial config to surface Go code-quality issues (errcheck, ineffassign, gocyclo, unused, staticcheck, misspell). Runs on PR + push-to-master + weekly schedule. Sibling-checkout pattern matches existing codeql.yml for replace-directive resolution. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(golangci-lint): bump action v6 → v8 + migrate config to v2 (Go 1.25) Action v6 resolved to golangci-lint v1.64.8 (built with Go 1.24), which fails to load configs targeting Go 1.25. Action v8 ships golangci-lint v2.x which is Go 1.25-compatible. Config migrated to v2 format: removed gosimple (folded into staticcheck), moved exclude-rules under linters.exclusions, added version: "2" header. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(lint): clear golangci-lint v2 findings (errcheck/staticcheck/unused/gocyclo) Resolve all 21 Tier-2 lint findings so the golangci-lint gate goes green: - errcheck (16): wrap deferred Close/Disconnect in `defer func() { _ = ... }()` and add explicit `_ =` on the two non-deferred Close calls. No behavior change. - staticcheck SA9003 (1): drop the empty if/else-if in TestExecCommandConstruction; the actual $REDIS_PASSWORD assertion below it is unchanged. - unused (1): remove the dead fakeK8sSecretAbsent type (its own comment notes it is no longer needed); corev1/runtime imports stay referenced via the existing var _ runtime.Object guard. - gocyclo (3): raise min-complexity 20 -> 30 in .golangci.yml, just above the highest offender (Server.DeprovisionResource = 27). These are inherently branchy switch/teardown funcs; bumping the bar avoids a behavior-changing refactor while still flagging genuinely tangled new code. golangci-lint run = 0 issues; go build/vet clean; go test ./... -short green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(lint): apply De Morgan's law to hex-validation guards (QF1001) Merging origin/master into the lint branch pulled in two test files (mongo/k8s_test.go, postgres/k8s_helpers_test.go) whose hex-character guards trip staticcheck QF1001 under CI's golangci-lint `latest` (newer than the locally pinned 2.11.4). Rewrite if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) into the equivalent if (c < '0' || c > '9') && (c < 'a' || c > 'f') Pure boolean equivalence — both packages' tests still pass. Also merges master so the local lint surface matches the pull/16/merge commit CI lints. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Manas Srivastava <[email protected]> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6ee1637 commit 39930f8

11 files changed

Lines changed: 101 additions & 39 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: golangci-lint
2+
3+
on:
4+
push:
5+
branches: [master, main]
6+
pull_request:
7+
branches: [master, main]
8+
schedule:
9+
- cron: "23 6 * * 1"
10+
11+
permissions:
12+
contents: read
13+
pull-requests: read
14+
15+
jobs:
16+
lint:
17+
name: lint
18+
runs-on: ubuntu-latest
19+
timeout-minutes: 10
20+
steps:
21+
- uses: actions/checkout@v4
22+
with:
23+
path: provisioner
24+
# Sibling checkouts (proto/common) for repos with replace directives.
25+
# No-op for repos that do not need them.
26+
- uses: actions/checkout@v4
27+
if: ${{ hashFiles('provisioner/go.mod') != '' }}
28+
with:
29+
repository: InstaNode-dev/common
30+
path: common
31+
continue-on-error: true
32+
- uses: actions/checkout@v4
33+
with:
34+
repository: InstaNode-dev/proto
35+
path: proto
36+
continue-on-error: true
37+
- uses: actions/setup-go@v5
38+
with:
39+
go-version-file: provisioner/go.mod
40+
- uses: golangci/golangci-lint-action@v8
41+
with:
42+
version: latest
43+
working-directory: provisioner
44+
args: --timeout=5m

.golangci.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# golangci-lint v2 config — start conservative, expand once baseline is clean
2+
version: "2"
3+
4+
run:
5+
timeout: 5m
6+
tests: true
7+
8+
linters:
9+
# Default linter set is govet+errcheck+ineffassign+staticcheck+unused.
10+
# We explicitly add misspell + gocyclo on top. gosimple folded into staticcheck in v2.
11+
enable:
12+
- errcheck # checks unchecked errors
13+
- govet # standard vet
14+
- ineffassign # ineffective assignments
15+
- staticcheck # bug detection (subsumes gosimple in v2)
16+
- unused # unused code
17+
- misspell # spelling
18+
- gocyclo # cyclomatic complexity
19+
settings:
20+
gocyclo:
21+
# Threshold raised above the highest current offender
22+
# (Server.DeprovisionResource = 27) — these are inherently branchy
23+
# switch/teardown functions; bumping the bar avoids a behavior-changing
24+
# refactor while still flagging genuinely tangled new code.
25+
min-complexity: 30
26+
exclusions:
27+
rules:
28+
- path: _test\.go
29+
linters:
30+
- errcheck
31+
- gocyclo
32+
33+
issues:
34+
max-issues-per-linter: 0
35+
max-same-issues: 0

internal/backend/mongo/k8s.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ func (b *K8sBackend) StorageBytes(ctx context.Context, token, providerResourceID
458458
if err != nil {
459459
return 0, fmt.Errorf("k8s mongo.StorageBytes: connect: %w", err)
460460
}
461-
defer client.Disconnect(ctx)
461+
defer func() { _ = client.Disconnect(ctx) }()
462462

463463
// Try the canonical DB name first, then every legacy scheme. A pod
464464
// provisioned before the P0-5 naming fix holds its data under the legacy
@@ -810,7 +810,7 @@ func (b *K8sBackend) tryInitMongo(ctx context.Context, adminURI, dbName, appUser
810810
if err != nil {
811811
return fmt.Errorf("connect: %w", err)
812812
}
813-
defer client.Disconnect(ctx)
813+
defer func() { _ = client.Disconnect(ctx) }()
814814

815815
// Create the user in dbName (not admin). This way authSource=dbName works in the
816816
// connection URL. MongoDB creates the database implicitly on first write.

internal/backend/mongo/k8s_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func TestMongoK8sRandHex_LengthAndUniqueness(t *testing.T) {
117117
}
118118
// Hex-only characters.
119119
for _, c := range a {
120-
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
120+
if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
121121
t.Errorf("non-hex char %q in %q", c, a)
122122
}
123123
}

internal/backend/postgres/dedicated.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func (p *DedicatedProvider) provisionNeon(ctx context.Context, token, tier strin
117117
if err != nil {
118118
return nil, fmt.Errorf("db.dedicated.provisionNeon: http: %w", err)
119119
}
120-
defer resp.Body.Close()
120+
defer func() { _ = resp.Body.Close() }()
121121

122122
respBytes, err := ioReadAll(resp.Body)
123123
if err != nil {
@@ -173,7 +173,7 @@ func (p *DedicatedProvider) neonStorageBytes(ctx context.Context, providerResour
173173
if err != nil {
174174
return 0, fmt.Errorf("db.dedicated.neonStorageBytes: http: %w", err)
175175
}
176-
defer resp.Body.Close()
176+
defer func() { _ = resp.Body.Close() }()
177177

178178
respBytes, err := ioReadAll(resp.Body)
179179
if err != nil {
@@ -211,7 +211,7 @@ func (p *DedicatedProvider) deprovisionNeon(ctx context.Context, token, provider
211211
if err != nil {
212212
return fmt.Errorf("db.dedicated.deprovisionNeon: http: %w", err)
213213
}
214-
defer resp.Body.Close()
214+
defer func() { _ = resp.Body.Close() }()
215215

216216
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
217217
body, _ := ioReadAll(resp.Body)

internal/backend/postgres/k8s.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ func (b *K8sBackend) StorageBytes(ctx context.Context, token, providerResourceID
361361
if err != nil {
362362
return 0, fmt.Errorf("k8s postgres.StorageBytes: connect: %w", err)
363363
}
364-
defer conn.Close(ctx)
364+
defer func() { _ = conn.Close(ctx) }()
365365

366366
// Try the canonical DB name first, then the legacy 12-char-truncated name.
367367
// A pod provisioned before the P1-W5-05 naming fix holds its data under the
@@ -460,7 +460,7 @@ func (b *K8sBackend) Regrade(ctx context.Context, token, providerResourceID stri
460460
if err != nil {
461461
return RegradeResult{Applied: false, SkipReason: fmt.Sprintf("resource not reachable: connect: %v", err)}, nil
462462
}
463-
defer conn.Close(ctx)
463+
defer func() { _ = conn.Close(ctx) }()
464464

465465
// ALTER ROLE re-applies the tier's connection cap. -1 = unlimited (passed
466466
// through verbatim). Identifier quoted with %q, mirroring the CREATE USER
@@ -745,7 +745,7 @@ func (b *K8sBackend) initDatabase(ctx context.Context, adminDSN, dbName, appUser
745745
if err != nil {
746746
return fmt.Errorf("connect: %w", err)
747747
}
748-
defer conn.Close(ctx)
748+
defer func() { _ = conn.Close(ctx) }()
749749

750750
// CONNECTION LIMIT enforces the tier's connection cap at the Postgres user
751751
// level. -1 = unlimited (capped only by pod max_connections); a positive
@@ -771,7 +771,7 @@ func (b *K8sBackend) initDatabase(ctx context.Context, adminDSN, dbName, appUser
771771
if dbConn, dbErr := pgxConnect(ctx, dbDSN); dbErr == nil {
772772
_, _ = dbConn.Exec(ctx, `CREATE EXTENSION IF NOT EXISTS vector`)
773773
_, _ = dbConn.Exec(ctx, fmt.Sprintf(`ALTER EXTENSION vector OWNER TO %q`, appUser))
774-
dbConn.Close(ctx)
774+
_ = dbConn.Close(ctx)
775775
}
776776
return nil
777777
}

internal/backend/postgres/k8s_helpers_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func TestK8sRandHex(t *testing.T) {
8585
}
8686
// Must be valid hex.
8787
for _, c := range s {
88-
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
88+
if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
8989
t.Errorf("k8sRandHex returned non-hex char %q", c)
9090
break
9191
}

internal/backend/postgres/neon.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func (b *NeonBackend) Provision(ctx context.Context, token, tier string, connLim
115115
if err != nil {
116116
return nil, fmt.Errorf("db.neon.Provision: http: %w", err)
117117
}
118-
defer resp.Body.Close()
118+
defer func() { _ = resp.Body.Close() }()
119119

120120
respBytes, err := ioReadAll(resp.Body)
121121
if err != nil {
@@ -177,7 +177,7 @@ func (b *NeonBackend) StorageBytes(ctx context.Context, token, providerResourceI
177177
if err != nil {
178178
return 0, fmt.Errorf("db.neon.StorageBytes: http: %w", err)
179179
}
180-
defer resp.Body.Close()
180+
defer func() { _ = resp.Body.Close() }()
181181

182182
respBytes, err := ioReadAll(resp.Body)
183183
if err != nil {
@@ -220,7 +220,7 @@ func (b *NeonBackend) Deprovision(ctx context.Context, token, providerResourceID
220220
if err != nil {
221221
return fmt.Errorf("db.neon.Deprovision: http: %w", err)
222222
}
223-
defer resp.Body.Close()
223+
defer func() { _ = resp.Body.Close() }()
224224

225225
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
226226
body, _ := ioReadAll(resp.Body)
@@ -254,7 +254,7 @@ func (b *NeonBackend) findProjectByName(ctx context.Context, projectName string)
254254
if err != nil {
255255
return "", fmt.Errorf("db.neon.findProjectByName: http: %w", err)
256256
}
257-
defer resp.Body.Close()
257+
defer func() { _ = resp.Body.Close() }()
258258

259259
respBytes, err := ioReadAll(resp.Body)
260260
if err != nil {

internal/backend/queue/local.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func (b *LocalBackend) Provision(ctx context.Context, token, tier string) (*Cred
4949
if err != nil {
5050
return nil, fmt.Errorf("queue.local.Provision: NATS health check failed (%s): %w", monitorURL, err)
5151
}
52-
resp.Body.Close()
52+
_ = resp.Body.Close()
5353
if resp.StatusCode != http.StatusOK {
5454
return nil, fmt.Errorf("queue.local.Provision: NATS unhealthy (HTTP %d from %s)", resp.StatusCode, monitorURL)
5555
}

internal/backend/redis/k8s.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ func (b *K8sBackend) StorageBytes(ctx context.Context, token, providerResourceID
535535
Addr: fmt.Sprintf("%s:6379", svc.Spec.ClusterIP),
536536
Password: password,
537537
})
538-
defer rdb.Close()
538+
defer func() { _ = rdb.Close() }()
539539

540540
info, err := rdb.Info(ctx, "memory").Result()
541541
if err != nil {
@@ -665,7 +665,7 @@ func (b *K8sBackend) Regrade(ctx context.Context, token, providerResourceID stri
665665
Addr: fmt.Sprintf("%s:6379", svc.Spec.ClusterIP),
666666
Password: password,
667667
})
668-
defer rdb.Close()
668+
defer func() { _ = rdb.Close() }()
669669

670670
// Compute the target in bytes (what Redis uses internally).
671671
// targetMaxmemoryMB <= 0 means unlimited → targetBytes = 0.

0 commit comments

Comments
 (0)