Skip to content

Commit ecd566c

Browse files
authored
Merge pull request #29 from aaearon/feat/go-best-practices
feat: add golangci-lint config and apply Go best practices
2 parents a305da6 + 5352abd commit ecd566c

35 files changed

+490
-301
lines changed

.golangci.yml

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# golangci-lint v1 configuration for grant-cli
2+
# golangci-lint v1.64.8 | Go 1.25+
3+
# https://golangci-lint.run/usage/configuration/
4+
5+
run:
6+
timeout: 3m
7+
tests: true
8+
9+
linters:
10+
disable-all: true
11+
enable:
12+
# --- Default linters ---
13+
- errcheck
14+
- gosimple
15+
- govet
16+
- ineffassign
17+
- staticcheck
18+
- unused
19+
# --- Bug detection ---
20+
- bodyclose # unclosed HTTP response bodies
21+
- errorlint # error wrapping correctness (Go 1.13+)
22+
- noctx # HTTP requests without context.Context
23+
# --- Security ---
24+
- gosec # security issue detection
25+
# --- Style & naming ---
26+
- errname # sentinel error naming (ErrFoo)
27+
- gocritic # diagnostic/style/performance checks
28+
- misspell # common English typos
29+
- revive # configurable Go linter (golint replacement)
30+
# --- Complexity ---
31+
- gocognit # cognitive complexity
32+
# --- Performance ---
33+
- perfsprint # fmt.Sprintf replaceable with concatenation
34+
- unconvert # unnecessary type conversions
35+
# --- Testing ---
36+
- usetesting # modern test helpers (t.Context, etc.)
37+
38+
linters-settings:
39+
gocognit:
40+
min-complexity: 40
41+
42+
gocritic:
43+
enabled-tags:
44+
- diagnostic
45+
- style
46+
- performance
47+
disabled-checks:
48+
- hugeParam # small structs in a CLI are fine
49+
- rangeValCopy # same: small value copies are fine
50+
51+
gosec:
52+
excludes:
53+
# G101 "hardcoded credentials" false-positives on test JWT tokens
54+
- G101
55+
56+
revive:
57+
severity: warning
58+
enable-all-rules: false
59+
rules:
60+
- name: blank-imports
61+
- name: context-as-argument
62+
- name: context-keys-type
63+
- name: dot-imports
64+
- name: empty-block
65+
- name: error-naming
66+
- name: error-return
67+
- name: error-strings
68+
- name: errorf
69+
# exported: disabled because stuttering names (cache.CacheDir,
70+
# config.ConfigDir, sca.SCAAccessService) are established API.
71+
- name: exported
72+
disabled: true
73+
- name: increment-decrement
74+
- name: indent-error-flow
75+
- name: range
76+
- name: receiver-naming
77+
- name: redefines-builtin-id
78+
- name: superfluous-else
79+
- name: time-naming
80+
- name: unexported-return
81+
- name: unreachable-code
82+
- name: var-declaration
83+
- name: var-naming
84+
# unused-parameter: disabled because Cobra RunE signatures require
85+
# func(cmd *cobra.Command, args []string) and test mocks implement
86+
# interfaces with necessarily unused parameters.
87+
- name: unused-parameter
88+
disabled: true
89+
90+
misspell:
91+
locale: US
92+
93+
issues:
94+
max-issues-per-linter: 0
95+
max-same-issues: 0
96+
97+
exclude-rules:
98+
# Test files: relax security checks (G306 WriteFile perms in temp dirs)
99+
- path: _test\.go
100+
linters:
101+
- gosec
102+
103+
# Test files: relax complexity checks (table-driven tests are long)
104+
- path: _test\.go
105+
linters:
106+
- gocognit
107+
108+
# Test files: bodyclose on mock HTTP responses
109+
- path: _test\.go
110+
linters:
111+
- bodyclose

.goreleaser.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ version: 2
22

33
builds:
44
- binary: grant
5+
flags:
6+
- -trimpath
7+
mod_timestamp: "{{ .CommitTimestamp }}"
58
ldflags:
69
- -s -w
710
- -X github.com/aaearon/grant-cli/cmd.version={{.Version}}
811
- -X github.com/aaearon/grant-cli/cmd.commit={{.ShortCommit}}
9-
- -X github.com/aaearon/grant-cli/cmd.buildDate={{.Date}}
12+
- -X github.com/aaearon/grant-cli/cmd.buildDate={{.CommitDate}}
1013
goos:
1114
- linux
1215
- darwin

CLAUDE.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,25 @@ Custom `SCAAccessService` follows SDK conventions:
9494
- Skill definition: `.claude/skills/grant-login/SKILL.md`
9595
- Requires `.env` at project root with `GRANT_PASSWORD` and `TOTP_SECRET`
9696

97+
## Lint
98+
- Config: `.golangci.yml` (golangci-lint v1 format)
99+
- 19 linters enabled: defaults (errcheck, gosimple, govet, ineffassign, staticcheck, unused) + bodyclose, errorlint, noctx, gosec (G101 excluded), errname, gocritic, misspell, revive, gocognit (threshold 40), perfsprint, unconvert, usetesting
100+
- Test files excluded from gosec, gocognit, bodyclose
101+
- `revive/unused-parameter` and `revive/exported` disabled (Cobra signatures, established API names)
102+
- Use `errors.New` for static error strings (perfsprint enforced); `fmt.Errorf` only with `%` verbs
103+
- Use `t.Context()` instead of `context.Background()` in tests (usetesting enforced)
104+
97105
## Build
98106
```bash
99-
make build # Build binary with ldflags
107+
make build # Build binary with -trimpath and ldflags
100108
make test # Run unit tests
101109
make test-integration # Run integration tests (builds binary)
102110
make test-all # Run all tests
103-
make lint # Run linter
111+
make lint # Run linter (golangci-lint)
104112
make clean # Clean build artifacts
105113
```
114+
- `-trimpath` used in both `Makefile` and `.goreleaser.yaml` for reproducible builds
115+
- `.goreleaser.yaml` uses `CommitDate` (not build date) and `mod_timestamp` for reproducibility
106116

107117
## Git
108118
- Feature branches, conventional commits

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ LDFLAGS := -s -w \
1010
.PHONY: build test test-race test-integration test-all test-coverage lint clean
1111

1212
build:
13-
go build -ldflags "$(LDFLAGS)" -o $(BINARY_NAME) .
13+
go build -trimpath -ldflags "$(LDFLAGS)" -o $(BINARY_NAME) .
1414

1515
test:
1616
go test ./... -v

cmd/configure.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"errors"
45
"fmt"
56
"net/url"
67
"os"
@@ -83,7 +84,7 @@ func runConfigure(cmd *cobra.Command, saver profileSaver, tenantURL, username st
8384
}
8485

8586
if strings.TrimSpace(username) == "" {
86-
return fmt.Errorf("username is required")
87+
return errors.New("username is required")
8788
}
8889

8990
// Create SDK profile
@@ -144,7 +145,7 @@ func runConfigure(cmd *cobra.Command, saver profileSaver, tenantURL, username st
144145
// validateTenantURL validates that the tenant URL is a valid HTTPS URL
145146
func validateTenantURL(tenantURL string) error {
146147
if strings.TrimSpace(tenantURL) == "" {
147-
return fmt.Errorf("invalid tenant URL: cannot be empty")
148+
return errors.New("invalid tenant URL: cannot be empty")
148149
}
149150

150151
u, err := url.Parse(tenantURL)
@@ -153,11 +154,11 @@ func validateTenantURL(tenantURL string) error {
153154
}
154155

155156
if u.Scheme != "https" {
156-
return fmt.Errorf("invalid tenant URL: must use HTTPS scheme")
157+
return errors.New("invalid tenant URL: must use HTTPS scheme")
157158
}
158159

159160
if u.Host == "" {
160-
return fmt.Errorf("invalid tenant URL: must have a host")
161+
return errors.New("invalid tenant URL: must have a host")
161162
}
162163

163164
return nil

cmd/env.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"errors"
45
"fmt"
56

67
"github.com/aaearon/grant-cli/internal/config"
@@ -92,7 +93,7 @@ func runEnvWithDeps(
9293
}
9394

9495
if res.result.AccessCredentials == nil {
95-
return fmt.Errorf("no credentials returned; grant env is only supported for AWS elevations")
96+
return errors.New("no credentials returned; grant env is only supported for AWS elevations")
9697
}
9798

9899
awsCreds, err := models.ParseAWSCredentials(*res.result.AccessCredentials)

0 commit comments

Comments
 (0)