Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: golangci-lint

on:
push:
branches: [master, main]
pull_request:
branches: [master, main]
schedule:
- cron: "23 6 * * 1"

permissions:
contents: read
pull-requests: read

jobs:
lint:
name: lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
path: provisioner
# Sibling checkouts (proto/common) for repos with replace directives.
# No-op for repos that do not need them.
- uses: actions/checkout@v4
if: ${{ hashFiles('provisioner/go.mod') != '' }}
with:
repository: InstaNode-dev/common
path: common
continue-on-error: true
- uses: actions/checkout@v4
with:
repository: InstaNode-dev/proto
path: proto
continue-on-error: true
- uses: actions/setup-go@v5
with:
go-version-file: provisioner/go.mod
- uses: golangci/golangci-lint-action@v8
with:
version: latest
working-directory: provisioner
args: --timeout=5m
35 changes: 35 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# golangci-lint v2 config — start conservative, expand once baseline is clean
version: "2"

run:
timeout: 5m
tests: true

linters:
# Default linter set is govet+errcheck+ineffassign+staticcheck+unused.
# We explicitly add misspell + gocyclo on top. gosimple folded into staticcheck in v2.
enable:
- errcheck # checks unchecked errors
- govet # standard vet
- ineffassign # ineffective assignments
- staticcheck # bug detection (subsumes gosimple in v2)
- unused # unused code
- misspell # spelling
- gocyclo # cyclomatic complexity
settings:
gocyclo:
# Threshold raised above the highest current offender
# (Server.DeprovisionResource = 27) — these are inherently branchy
# switch/teardown functions; bumping the bar avoids a behavior-changing
# refactor while still flagging genuinely tangled new code.
min-complexity: 30
exclusions:
rules:
- path: _test\.go
linters:
- errcheck
- gocyclo

issues:
max-issues-per-linter: 0
max-same-issues: 0
4 changes: 2 additions & 2 deletions internal/backend/mongo/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ func (b *K8sBackend) StorageBytes(ctx context.Context, token, providerResourceID
if err != nil {
return 0, fmt.Errorf("k8s mongo.StorageBytes: connect: %w", err)
}
defer client.Disconnect(ctx)
defer func() { _ = client.Disconnect(ctx) }()

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

// Create the user in dbName (not admin). This way authSource=dbName works in the
// connection URL. MongoDB creates the database implicitly on first write.
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/mongo/k8s_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func TestMongoK8sRandHex_LengthAndUniqueness(t *testing.T) {
}
// Hex-only characters.
for _, c := range a {
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
t.Errorf("non-hex char %q in %q", c, a)
}
}
Expand Down
6 changes: 3 additions & 3 deletions internal/backend/postgres/dedicated.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func (p *DedicatedProvider) provisionNeon(ctx context.Context, token, tier strin
if err != nil {
return nil, fmt.Errorf("db.dedicated.provisionNeon: http: %w", err)
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()

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

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

if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := ioReadAll(resp.Body)
Expand Down
8 changes: 4 additions & 4 deletions internal/backend/postgres/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ func (b *K8sBackend) StorageBytes(ctx context.Context, token, providerResourceID
if err != nil {
return 0, fmt.Errorf("k8s postgres.StorageBytes: connect: %w", err)
}
defer conn.Close(ctx)
defer func() { _ = conn.Close(ctx) }()

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

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

// CONNECTION LIMIT enforces the tier's connection cap at the Postgres user
// level. -1 = unlimited (capped only by pod max_connections); a positive
Expand All @@ -771,7 +771,7 @@ func (b *K8sBackend) initDatabase(ctx context.Context, adminDSN, dbName, appUser
if dbConn, dbErr := pgxConnect(ctx, dbDSN); dbErr == nil {
_, _ = dbConn.Exec(ctx, `CREATE EXTENSION IF NOT EXISTS vector`)
_, _ = dbConn.Exec(ctx, fmt.Sprintf(`ALTER EXTENSION vector OWNER TO %q`, appUser))
dbConn.Close(ctx)
_ = dbConn.Close(ctx)
}
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/postgres/k8s_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func TestK8sRandHex(t *testing.T) {
}
// Must be valid hex.
for _, c := range s {
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
t.Errorf("k8sRandHex returned non-hex char %q", c)
break
}
Expand Down
8 changes: 4 additions & 4 deletions internal/backend/postgres/neon.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (b *NeonBackend) Provision(ctx context.Context, token, tier string, connLim
if err != nil {
return nil, fmt.Errorf("db.neon.Provision: http: %w", err)
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()

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

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

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

respBytes, err := ioReadAll(resp.Body)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/queue/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (b *LocalBackend) Provision(ctx context.Context, token, tier string) (*Cred
if err != nil {
return nil, fmt.Errorf("queue.local.Provision: NATS health check failed (%s): %w", monitorURL, err)
}
resp.Body.Close()
_ = resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("queue.local.Provision: NATS unhealthy (HTTP %d from %s)", resp.StatusCode, monitorURL)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/backend/redis/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ func (b *K8sBackend) StorageBytes(ctx context.Context, token, providerResourceID
Addr: fmt.Sprintf("%s:6379", svc.Spec.ClusterIP),
Password: password,
})
defer rdb.Close()
defer func() { _ = rdb.Close() }()

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

// Compute the target in bytes (what Redis uses internally).
// targetMaxmemoryMB <= 0 means unlimited → targetBytes = 0.
Expand Down
25 changes: 4 additions & 21 deletions internal/backend/redis/k8s_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,17 +422,11 @@ func TestExecCommandConstruction(t *testing.T) {
t.Errorf("call[0] should be CONFIG GET maxmemory, got: %v", getCall.cmd)
}
// PASSWORD must appear as $REDIS_PASSWORD (shell variable), never as a literal.
// The password variable itself is fine; what we forbid is any hardcoded
// secret string. Since we never inject one, this check ensures the pattern
// `$REDIS_PASSWORD` appears in every redis-cli command.
for i, call := range fe.calls {
cmdStr := strings.Join(call.cmd, " ")
if strings.Contains(cmdStr, "$REDIS_PASSWORD") {
// Good — variable reference.
} else if strings.Contains(cmdStr, "REDIS_PASSWORD") {
// Also acceptable if it's the env var name without $.
// But the full literal password must not appear.
}
// The password variable itself is fine; what we forbid is any
// hardcoded secret string. Since we never inject one, this check
// ensures the pattern `$REDIS_PASSWORD` appears in every redis-cli command.
if !strings.Contains(cmdStr, `$REDIS_PASSWORD`) {
t.Errorf("call[%d] cmd does not reference $REDIS_PASSWORD: %v", i, call.cmd)
}
Expand Down Expand Up @@ -753,16 +747,5 @@ func notFoundErr(resource, name string) error {
// Ensure notFoundErr is used (avoids "declared and not used" compile error).
var _ = notFoundErr

// fakeK8sSecretAbsent wraps fake.Clientset and returns NotFound for Secrets
// with a specific name. Used to simulate the legacy-resource scenario where
// the redis-auth Secret was never created.
//
// NOTE: This is no longer needed — fake.NewClientset without the secret
// object already returns NotFound. Kept as documentation of the pattern.
type fakeK8sSecretAbsent struct {
fake.Clientset
absentSecretName string
}

// Ensure it compiles.
// Ensure the corev1/runtime imports stay referenced.
var _ runtime.Object = (*corev1.Pod)(nil)
Loading