Skip to content

Commit f5540f3

Browse files
authored
Merge pull request #162 from cozystack/feat/strict-lint
ci(lint): adopt strict golangci-lint config + cross-platform CI gate (#153)
2 parents b57638a + 3960231 commit f5540f3

66 files changed

Lines changed: 4846 additions & 2083 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
* text=auto eol=lf
2+
3+
# Go source must keep LF endings; gofmt rejects CRLF.
4+
*.go text eol=lf
5+
6+
# Go module / sum / config files.
7+
go.mod text eol=lf
8+
go.sum text eol=lf
9+
*.yaml text eol=lf
10+
*.yml text eol=lf
11+
*.sh text eol=lf
12+
13+
# Binaries
14+
*.png binary
15+
*.jpg binary
16+
*.jpeg binary
17+
*.gif binary
18+
19+
# pkg/generated re-exports the embedded chart map produced by `go
20+
# embed`; the file is mechanical glue, not hand-edited. Marking it
21+
# generated keeps it out of GitHub language stats and collapses it
22+
# in PR diffs by default.
23+
pkg/generated/** linguist-generated

.github/workflows/pr.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,30 @@ jobs:
2222
- name: Run tests
2323
run: go test ./...
2424

25+
lint:
26+
# Run golangci-lint on the same OS matrix as test:. The Windows
27+
# runner is essential — secureperm_windows.go is build-tagged
28+
# (//go:build windows) and never gets evaluated on a Linux/macOS
29+
# host. Without a Windows lint pass, build-tagged files diverge
30+
# from the rest of the tree silently.
31+
strategy:
32+
fail-fast: false
33+
matrix:
34+
os: [ubuntu-latest, windows-latest]
35+
runs-on: ${{ matrix.os }}
36+
steps:
37+
- name: Checkout
38+
uses: actions/checkout@v6
39+
- name: Set up Go
40+
uses: actions/setup-go@v6
41+
with:
42+
go-version: stable
43+
- name: Run golangci-lint
44+
uses: golangci/golangci-lint-action@v7
45+
with:
46+
version: v2.12.2
47+
args: --timeout=5m
48+
2549
dco:
2650
runs-on: ubuntu-latest
2751
steps:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
talm
22
dist/
3+
.claude/

.golangci.yml

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
version: "2"
2+
3+
linters:
4+
default: all
5+
disable:
6+
- depguard
7+
- exhaustruct
8+
- gochecknoinits
9+
- wsl
10+
- lll
11+
- errchkjson
12+
- ireturn
13+
- gocheckcompilerdirectives
14+
# noinlineerr: this codebase wholesale uses 'if err := X(); err != nil' inline form
15+
# — 219+ occurrences across packages. Converting to plain-assignment style would
16+
# produce a noisy diff with zero correctness benefit AND create variable-scope leaks
17+
# in many places where err is locally scoped to the check.
18+
- noinlineerr
19+
# gomodguard: deprecated in v2.12+ in favour of gomodguard_v2; we don't use
20+
# either (no allow/blocklists configured), so disable to silence the warning.
21+
- gomodguard
22+
settings:
23+
dupl:
24+
threshold: 100
25+
goconst:
26+
min-len: 2
27+
min-occurrences: 2
28+
gocritic:
29+
disabled-checks:
30+
- dupImport
31+
- unnamedResult
32+
enabled-tags:
33+
- diagnostic
34+
- experimental
35+
- opinionated
36+
- performance
37+
- style
38+
funlen:
39+
lines: 60
40+
statements: 60
41+
gomoddirectives:
42+
# The cozystack fork of Talos carries a downstream-only patch
43+
# (siderolabs/talos#12652, --skip-verify) that upstream declined.
44+
# Until that flag lands upstream, the replace directive is the
45+
# only way to consume the fork — it is not generic dependency
46+
# rewriting and must stay.
47+
replace-allow-list:
48+
- github.com/siderolabs/talos
49+
- github.com/siderolabs/talos/pkg/machinery
50+
gocyclo:
51+
min-complexity: 15
52+
cyclop:
53+
max-complexity: 15
54+
mnd:
55+
ignored-numbers:
56+
- "10"
57+
- "100"
58+
- "1000"
59+
- "2"
60+
- "60"
61+
- "60.0"
62+
- "64"
63+
- "500"
64+
nolintlint:
65+
require-explanation: true
66+
require-specific: true
67+
allow-unused: false
68+
varnamelen:
69+
max-distance: 5
70+
min-name-length: 3
71+
check-receiver: false
72+
check-return: false
73+
ignore-type-assert-ok: false
74+
ignore-map-index-ok: false
75+
ignore-chan-recv-ok: false
76+
ignore-decls:
77+
- wg sync.WaitGroup
78+
- wg *sync.WaitGroup
79+
- mu sync.Mutex
80+
- ok bool
81+
ignore-names:
82+
- i
83+
- w
84+
- r
85+
- b
86+
- c
87+
- m
88+
- n
89+
- tt
90+
- rw
91+
exclusions:
92+
generated: lax
93+
presets:
94+
- comments
95+
- common-false-positives
96+
- legacy
97+
- std-error-handling
98+
paths:
99+
- third_party$
100+
- builtin$
101+
- generated\.go$
102+
- pkg/generated/
103+
- \.claude/
104+
rules:
105+
- linters:
106+
- funlen
107+
- dupl
108+
- gocognit
109+
- gocyclo
110+
- cyclop
111+
- errcheck
112+
- testableexamples
113+
- testpackage
114+
- forcetypeassert
115+
- gocritic
116+
- nlreturn
117+
- wsl_v5
118+
- varnamelen
119+
- unparam
120+
- modernize
121+
- gosec
122+
- testifylint
123+
- perfsprint
124+
- paralleltest
125+
- maintidx
126+
# goconst on _test.go: tests intentionally repeat literals
127+
# (IPs, CIDRs, MAC addresses, YAML keys) inside backtick raw
128+
# strings that are EXPECTED template outputs, alongside Go
129+
# string literals used as assertion values. Substituting the
130+
# Go literal into a const desynchronises it from the
131+
# backtick fixture, breaking the test silently. Empirically
132+
# observed during the strict-lint adoption pass: every
133+
# blanket goconst substitution in pkg/engine/contract_*.go
134+
# broke at least one TestRender* case. Sub-agent attempts
135+
# failed for the same reason. Disable goconst on test files.
136+
- goconst
137+
path: _test\.go
138+
139+
formatters:
140+
enable:
141+
- gofmt
142+
- gofumpt
143+
- goimports
144+
exclusions:
145+
generated: lax
146+
paths:
147+
- third_party$
148+
- builtin$
149+
- generated\.go$
150+
- pkg/generated/
151+
- \.claude/

charts/charts.go

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ import (
66
"path"
77
"regexp"
88
"strings"
9+
10+
"github.com/cockroachdb/errors"
911
)
1012

13+
const presetGenericName = "generic"
14+
1115
//go:embed all:cozystack all:generic all:talm
1216
var embeddedCharts embed.FS
1317

@@ -16,83 +20,87 @@ var embeddedCharts embed.FS
1620
func PresetFiles() (map[string]string, error) {
1721
filesMap := make(map[string]string)
1822
regex := regexp.MustCompile(`(name|version): \S+`)
19-
20-
err := fs.WalkDir(embeddedCharts, ".", func(filePath string, d fs.DirEntry, err error) error {
23+
24+
err := fs.WalkDir(embeddedCharts, ".", func(filePath string, entry fs.DirEntry, err error) error {
2125
if err != nil {
22-
return err
26+
// WalkDir surfaces a plain *fs.PathError on failure;
27+
// wrap with the offending path so a downstream caller
28+
// reading just the error message can locate the bad file
29+
// without re-running with extra logging.
30+
return errors.Wrapf(err, "walking embedded charts at %q", filePath)
2331
}
24-
25-
if d.IsDir() {
32+
33+
if entry.IsDir() {
2634
return nil
2735
}
28-
36+
2937
// Skip talm subdirectories in preset charts (cozystack/charts/talm, generic/charts/talm)
3038
// but include files from the main talm chart (talm/templates/_helpers.tpl, etc.)
31-
if strings.HasPrefix(filePath, "cozystack/charts/talm/") ||
32-
strings.HasPrefix(filePath, "generic/charts/talm/") {
39+
if strings.HasPrefix(filePath, "cozystack/charts/talm/") ||
40+
strings.HasPrefix(filePath, "generic/charts/talm/") {
3341
return nil
3442
}
35-
43+
3644
// Read file content
3745
data, err := embeddedCharts.ReadFile(filePath)
3846
if err != nil {
39-
return err
47+
return errors.Wrapf(err, "reading embedded chart file %q", filePath)
4048
}
41-
49+
4250
content := string(data)
43-
51+
4452
// For Chart.yaml files, replace name and version with %s
4553
if path.Base(filePath) == "Chart.yaml" {
4654
content = regex.ReplaceAllString(content, "$1: %s")
4755
}
48-
56+
4957
// Use the file path as-is (relative to charts directory)
5058
filesMap[filePath] = content
51-
59+
5260
return nil
5361
})
54-
5562
if err != nil {
56-
return nil, err
63+
return nil, errors.Wrap(err, "walking embedded charts")
5764
}
58-
65+
5966
return filesMap, nil
6067
}
6168

6269
// AvailablePresets returns a list of available preset chart names.
63-
// The "generic" preset is always first if it exists.
70+
// The presetGenericName preset is always first if it exists.
6471
func AvailablePresets() ([]string, error) {
65-
var presets []string
66-
var hasGeneric bool
67-
72+
var (
73+
presets []string
74+
hasGeneric bool
75+
)
76+
6877
entries, err := embeddedCharts.ReadDir(".")
6978
if err != nil {
70-
return nil, err
79+
return nil, errors.Wrap(err, "reading embedded charts root")
7180
}
72-
81+
7382
for _, entry := range entries {
7483
if !entry.IsDir() {
7584
continue
7685
}
77-
86+
7887
name := entry.Name()
7988
// Skip talm as it's a library chart, not a preset
8089
if name == "talm" {
8190
continue
8291
}
83-
84-
if name == "generic" {
92+
93+
if name == presetGenericName {
8594
hasGeneric = true
8695
} else {
8796
presets = append(presets, name)
8897
}
8998
}
90-
99+
91100
// Put generic first if it exists
92101
if hasGeneric {
93-
presets = append([]string{"generic"}, presets...)
102+
presets = append([]string{presetGenericName}, presets...)
94103
}
95-
104+
96105
return presets, nil
97106
}
98-

0 commit comments

Comments
 (0)