diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 5d73696..855b9d9 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -24,5 +24,5 @@ jobs: - name: Golangci lint uses: golangci/golangci-lint-action@v9 with: - version: v2.7.1 + version: v2.15.0 install-mode: goinstall diff --git a/.golangci.yaml b/.golangci.yaml index 0aeff3a..fbaf3a7 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -21,6 +21,7 @@ linters: - govet - ineffassign - misspell + - modernize - nakedret - reassign - revive @@ -111,6 +112,7 @@ linters: - dupl - errcheck - funlen + - goconst - gocyclo - gosec path: _test\.go diff --git a/AGENTS.md b/AGENTS.md index a8b499c..91e8ad6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,7 +27,7 @@ This guide helps agentic coding agents work effectively with the chartify codeba ### Linting and Formatting - **Format code**: `go fmt ./...` - **Vet code**: `go vet ./...` (42s, set timeout 120s+) -- **NOTE**: golangci-lint config (.golangci.yaml) has compatibility issues with recent versions. Use `go fmt` and `go vet` for validation. +- **Lint**: `golangci-lint run` (uses .golangci.yaml; CI runs v2.15.0). If local golangci-lint fails with "export data version" errors, reinstall it with the same Go toolchain as go.mod: `GOTOOLCHAIN=go1.27.2 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.15.0` ### Running the CLI - Basic usage: `./chartify -o OUTPUT_DIR RELEASE_NAME CHART_OR_MANIFEST_PATH` @@ -122,11 +122,12 @@ Run these commands to ensure CI passes: ```bash go fmt ./... go vet ./... +golangci-lint run CGO_ENABLED=0 go test ./... ``` ### Prerequisites -- Go 1.26.0+ +- Go 1.27.2+ - Helm v4.3.1 (helm command) - Kustomize v5.8.0+ (kustomize command) diff --git a/README.md b/README.md index 755ede5..d5e859f 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ go build -o chartreposerver ./cmd/chartreposerver ### Prerequisites -- Go 1.26.0+ +- Go 1.27.2+ - Helm v4.3.1 (helm command) - Kustomize v5.8.0+ (kustomize command, optional, for kustomize integration) diff --git a/chartify.go b/chartify.go index c310bb3..3726a33 100644 --- a/chartify.go +++ b/chartify.go @@ -135,7 +135,7 @@ type ChartifyOpts struct { // TemplateFuncs is the FuncMap used while rendering .gotmpl files in the target directory TemplateFuncs template.FuncMap // TemplateData is the data available via {{ . }} within .gotmpl files - TemplateData interface{} + TemplateData any // TemplateArgs to pass Flags to helm template TemplateArgs string diff --git a/chartify_test.go b/chartify_test.go index f69abed..6d92c2a 100644 --- a/chartify_test.go +++ b/chartify_test.go @@ -179,7 +179,7 @@ func TestDepCommandSelection(t *testing.T) { ReadDir: os.ReadDir, Walk: filepath.Walk, Exists: exists, - Logf: func(string, ...interface{}) {}, + Logf: func(string, ...any) {}, MakeTempDir: func(release, chart string, opts *ChartifyOpts) string { return "" }, diff --git a/command.go b/command.go index dc95fef..45a8435 100644 --- a/command.go +++ b/command.go @@ -2,6 +2,7 @@ package chartify import ( "io" + "maps" "os" "os/exec" "strings" @@ -18,9 +19,7 @@ func RunCommand(cmd string, args []string, dir string, stdout, stderr io.Writer, func mergeEnv(orig []string, new map[string]string) []string { wanted := env2map(orig) - for k, v := range new { - wanted[k] = v - } + maps.Copy(wanted, new) return map2env(wanted) } diff --git a/go.mod b/go.mod index 7fdc110..a84a566 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/helmfile/chartify -go 1.26.0 +go 1.27.2 require ( github.com/Masterminds/semver/v3 v3.5.0 diff --git a/inject.go b/inject.go index e4f7868..511c83c 100644 --- a/inject.go +++ b/inject.go @@ -11,7 +11,7 @@ type InjectOpts struct { } func (r *Runner) Inject(files []string, o InjectOpts) error { - var flagsTemplate string + var flagsTemplate strings.Builder for _, inj := range o.injectors { tokens := strings.Split(inj, ",") injector := tokens[0] @@ -20,16 +20,16 @@ func (r *Runner) Inject(files []string, o InjectOpts) error { flagSplit := strings.Split(flag, "=") switch len(flagSplit) { case 1: - flagsTemplate += flagSplit[0] + flagsTemplate.WriteString(flagSplit[0]) case 2: key, val := flagSplit[0], flagSplit[1] - flagsTemplate += createFlagChain(key, []string{val}) + flagsTemplate.WriteString(createFlagChain(key, []string{val})) default: return fmt.Errorf("inject-flags must be in the form of key1=value1[,key2=value2,...]: %v", flag) } } for _, file := range files { - flags := strings.Replace(flagsTemplate, "FILE", file, 1) + flags := strings.Replace(flagsTemplate.String(), "FILE", file, 1) command := fmt.Sprintf("%s %s", injector, flags) stdout, err := r.runBytes(nil, "", command) if err != nil { diff --git a/kustomize_extra_args_test.go b/kustomize_extra_args_test.go index 1c7c65e..49db5f8 100644 --- a/kustomize_extra_args_test.go +++ b/kustomize_extra_args_test.go @@ -4,6 +4,7 @@ import ( "io" "os" "path/filepath" + "slices" "testing" "github.com/stretchr/testify/require" @@ -47,10 +48,8 @@ func newSpyRunner(t *testing.T, versionOut string) (*Runner, *[]spyCall) { // buildCall returns the first recorded invocation that runs a kustomize "build". func buildCall(calls []spyCall) (spyCall, bool) { for _, c := range calls { - for _, a := range c.args { - if a == "build" { - return c, true - } + if slices.Contains(c.args, "build") { + return c, true } } return spyCall{}, false diff --git a/patch.go b/patch.go index 27ccb8c..4e2a1bd 100644 --- a/patch.go +++ b/patch.go @@ -512,9 +512,9 @@ func (r *Runner) resolveTransformerFileRefs(transformerBytes []byte, transformer // A transformer file can be a single document, a list of documents, or multi-document YAML. decoder := yaml.NewDecoder(bytes.NewReader(transformerBytes)) - var docs []interface{} + var docs []any for { - var doc interface{} + var doc any if err := decoder.Decode(&doc); err != nil { if err == io.EOF { break @@ -532,14 +532,14 @@ func (r *Runner) resolveTransformerFileRefs(transformerBytes []byte, transformer // Collect all transformer map nodes. This handles both single-document and // list-of-documents formats. Maps are reference types in Go, so modifying // them below will be reflected when re-encoding docs. - var allMaps []map[string]interface{} + var allMaps []map[string]any for _, doc := range docs { switch v := doc.(type) { - case map[string]interface{}: + case map[string]any: allMaps = append(allMaps, v) - case []interface{}: + case []any: for _, item := range v { - if m, ok := item.(map[string]interface{}); ok { + if m, ok := item.(map[string]any); ok { allMaps = append(allMaps, m) } } @@ -655,13 +655,13 @@ func (r *Runner) resolveTransformerPath(pathStr string, transformerFileDir strin // kind: Deployment // // See https://github.com/kubernetes-sigs/kustomize/blob/master/examples/inlinePatch.md -func parsePatchDocuments(content []byte) ([]map[string]interface{}, error) { +func parsePatchDocuments(content []byte) ([]map[string]any, error) { decoder := yaml.NewDecoder(bytes.NewReader(content)) - var entries []map[string]interface{} + var entries []map[string]any for { - var doc interface{} + var doc any if err := decoder.Decode(&doc); err != nil { if err == io.EOF { break @@ -670,18 +670,18 @@ func parsePatchDocuments(content []byte) ([]map[string]interface{}, error) { } switch v := doc.(type) { - case map[string]interface{}: + case map[string]any: if v == nil { // Skip null documents (e.g. from empty "---" separators). continue } entries = append(entries, v) - case []interface{}: + case []any: if len(v) == 0 { return nil, fmt.Errorf("patches list is empty") } for i, item := range v { - m, ok := item.(map[string]interface{}) + m, ok := item.(map[string]any) if !ok { return nil, fmt.Errorf("patches list item %d must be a YAML mapping, got %T", i, item) } @@ -705,7 +705,7 @@ func parsePatchDocuments(content []byte) ([]map[string]interface{}, error) { // marshalPatchEntry encodes a single kustomize patches entry as YAML suitable for // embedding as a list item under the "patches:" field of kustomization.yaml. // The returned string has no leading or trailing whitespace lines. -func marshalPatchEntry(entry map[string]interface{}) (string, error) { +func marshalPatchEntry(entry map[string]any) (string, error) { buf := &bytes.Buffer{} encoder := yaml.NewEncoder(buf) encoder.SetIndent(2) diff --git a/patch_test.go b/patch_test.go index 40f7c1a..7134113 100644 --- a/patch_test.go +++ b/patch_test.go @@ -330,9 +330,9 @@ func TestParsePatchDocuments(t *testing.T) { // TestMarshalPatchEntry verifies YAML encoding of a patch entry. func TestMarshalPatchEntry(t *testing.T) { t.Run("preserves inline block scalar patch", func(t *testing.T) { - entry := map[string]interface{}{ + entry := map[string]any{ "patch": "apiVersion: v1\nkind: ConfigMap\ndata:\n key: val\n", - "target": map[string]interface{}{ + "target": map[string]any{ "kind": "ConfigMap", }, } @@ -347,9 +347,9 @@ func TestMarshalPatchEntry(t *testing.T) { }) t.Run("preserves path field", func(t *testing.T) { - entry := map[string]interface{}{ + entry := map[string]any{ "path": "some-file.yaml", - "target": map[string]interface{}{ + "target": map[string]any{ "kind": "Deployment", }, } diff --git a/replace.go b/replace.go index bbdbb7f..6181f50 100644 --- a/replace.go +++ b/replace.go @@ -201,8 +201,8 @@ func (r *Runner) ReplaceWithRendered(name, chartName, chartPath string, o Replac } } - lines := strings.Split(stdout, "\n") - for _, line := range lines { + lines := strings.SplitSeq(stdout, "\n") + for line := range lines { if strings.HasPrefix(line, "wrote ") { file := strings.Split(line, "wrote ")[1] @@ -245,8 +245,8 @@ func (r *Runner) ReplaceWithRendered(name, chartName, chartPath string, o Replac // should have been downloaded by `helm fetch` that run in an even earlier phase of chartify. if r.IsHelm3() || r.IsHelm4() { type ChartMeta struct { - Dependencies []Dependency `yaml:"dependencies,omitempty"` - Data map[string]interface{} `yaml:",inline"` + Dependencies []Dependency `yaml:"dependencies,omitempty"` + Data map[string]any `yaml:",inline"` } var chartMeta ChartMeta diff --git a/requirements.go b/requirements.go index ef80a2b..a45f06f 100644 --- a/requirements.go +++ b/requirements.go @@ -20,7 +20,7 @@ type Dependency struct { Version string `yaml:"version,omitempty"` // ImportValues holds the mapping of source values to parent key to be imported. Each item can be a // string or pair of child/parent sublist items. - ImportValues []interface{} `yaml:"import-values,omitempty"` + ImportValues []any `yaml:"import-values,omitempty"` } type ChartDependency struct { @@ -64,8 +64,8 @@ func (r *Runner) UpdateRequirements(replace bool, chartYamlPath, tempDir string, if r.IsHelm3() || r.IsHelm4() { type ChartMeta struct { - Dependencies []Dependency `yaml:"dependencies,omitempty"` - Data map[string]interface{} `yaml:",inline"` + Dependencies []Dependency `yaml:"dependencies,omitempty"` + Data map[string]any `yaml:",inline"` } var chartMeta ChartMeta diff --git a/runner.go b/runner.go index 8bf0827..f426203 100644 --- a/runner.go +++ b/runner.go @@ -35,7 +35,7 @@ type Runner struct { Exists func(path string) (bool, error) // Logf is the alternative log function used by chartify - Logf func(string, ...interface{}) + Logf func(string, ...any) } type Option func(*Runner) error @@ -61,7 +61,7 @@ func UseHelm4(u bool) Option { } } -func WithLogf(logf func(string, ...interface{})) Option { +func WithLogf(logf func(string, ...any)) Option { return func(r *Runner) error { r.Logf = logf return nil @@ -259,6 +259,6 @@ func (r *Runner) captureBytes(binary string, args []string, dir string, envs map return stdout.Bytes(), stderr.Bytes(), err } -func printf(format string, vars ...interface{}) { +func printf(format string, vars ...any) { fmt.Fprintf(os.Stderr, format+"\n", vars...) } diff --git a/tempdir.go b/tempdir.go index da3746c..7ba1136 100644 --- a/tempdir.go +++ b/tempdir.go @@ -42,7 +42,7 @@ func makeTempDir(release, chart string, opts *ChartifyOpts) string { d := filepath.Join(workDir, id) if os.Getenv(EnvVarDebug) != "" { - bs, _ := json.Marshal(map[string]interface{}{ + bs, _ := json.Marshal(map[string]any{ "Release": release, "Chart": chart, "Options": opts, @@ -74,7 +74,7 @@ func GenerateID(release, chart string, opts *ChartifyOpts) (string, error) { id = append(id, release) - hash, err := HashObject([]interface{}{release, chart, opts}) + hash, err := HashObject([]any{release, chart, opts}) if err != nil { return "", err } @@ -84,7 +84,7 @@ func GenerateID(release, chart string, opts *ChartifyOpts) (string, error) { return strings.Join(id, "-"), nil } -func HashObject(obj interface{}) (string, error) { +func HashObject(obj any) (string, error) { hash := fnv.New32a() hash.Reset()