diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d4d9f5228..37e3e2048 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # pinv4.37.8 + uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # pinv4.37.9 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # pinv4.37.8 + uses: github/codeql-action/autobuild@cdf488f595d80d6e07e03d4674febd5ab45fa938 # pinv4.37.9 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # pinv4.37.8 + uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # pinv4.37.9 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 6d5367952..469178ced 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 with: sarif_file: results.sarif diff --git a/.golangci.yml b/.golangci.yml index 9daa85ad5..ac8a81f0f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,7 +13,8 @@ formatters: gofumpt: module-path: helm.sh/helm/v4 - extra-rules: false + extra: + group-params: true goimports: local-prefixes: @@ -95,7 +96,6 @@ linters: - exposedSyncMutex - hugeParam - importShadow - - paramTypeCombine - ptrToRefParam - rangeValCopy - todoCommentWithoutDetail @@ -119,6 +119,7 @@ linters: enable-default-rules: true max-open-files: 2048 rules: + - name: bare-return - name: early-return arguments: - "preserve-scope" diff --git a/go.mod b/go.mod index bf8bb7228..9da20b09a 100644 --- a/go.mod +++ b/go.mod @@ -19,8 +19,8 @@ require ( github.com/fatih/color v1.19.0 github.com/fluxcd/cli-utils v1.2.2 github.com/foxcpp/go-mockdns v1.2.0 - github.com/gobwas/glob v0.2.3 - github.com/gofrs/flock v0.13.0 + github.com/gobwas/glob v1.0.0 + github.com/gofrs/flock v0.13.1 github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.12.3 diff --git a/go.sum b/go.sum index b9d8e51fc..c4497aa58 100644 --- a/go.sum +++ b/go.sum @@ -128,10 +128,10 @@ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqw github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= -github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= +github.com/gobwas/glob v1.0.0 h1:p+FKbLEIsK1yZ39/OINwFvqNb5oyPY4H8xcy6uYu8dg= +github.com/gobwas/glob v1.0.0/go.mod h1:oWCdo522i2P1n/hMXGNWs7yoV4wy/ciZuUIbvKj5rkc= +github.com/gofrs/flock v0.13.1 h1:jjREztyBeSKBZYAC+mgc1laB+xsgy4kYMf3FbKF2UBo= +github.com/gofrs/flock v0.13.1/go.mod h1:sf4BFiHwnvgxa25DlQoDqXQnwRMEOwqxRq37P6MzzmE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/internal/chart/v3/chart.go b/internal/chart/v3/chart.go index 71a90c855..e8e03b17e 100644 --- a/internal/chart/v3/chart.go +++ b/internal/chart/v3/chart.go @@ -141,7 +141,7 @@ func (ch *Chart) AppVersion() string { // CRDs returns a list of File objects in the 'crds/' directory of a Helm chart. // -// Deprecated: use CRDObjects() +// Deprecated: Use CRDObjects instead. func (ch *Chart) CRDs() []*common.File { files := []*common.File{} // Find all resources in the crds/ directory diff --git a/internal/chart/v3/loader/directory.go b/internal/chart/v3/loader/directory.go index 7cf343568..535468dd1 100644 --- a/internal/chart/v3/loader/directory.go +++ b/internal/chart/v3/loader/directory.go @@ -43,6 +43,10 @@ func (l DirLoader) Load() (*chart.Chart, error) { // // This loads charts only from directories. func LoadDir(dir string) (*chart.Chart, error) { + return loadDir(dir, archive.MaxDecompressedChartSize) +} + +func loadDir(dir string, budget int64) (*chart.Chart, error) { topdir, err := filepath.Abs(dir) if err != nil { return nil, err @@ -64,6 +68,7 @@ func LoadDir(dir string) (*chart.Chart, error) { files := []*archive.BufferedFile{} topdir += string(filepath.Separator) + budgetReader := archive.NewBudgetedReader(budget) walk := func(name string, fi os.FileInfo, err error) error { n := strings.TrimPrefix(name, topdir) @@ -100,11 +105,7 @@ func LoadDir(dir string) (*chart.Chart, error) { return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) } - if fi.Size() > archive.MaxDecompressedFileSize { - return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), archive.MaxDecompressedFileSize) - } - - data, err := os.ReadFile(name) + data, err := budgetReader.ReadFileWithBudget(name, fi.Size()) if err != nil { return fmt.Errorf("error reading %s: %w", n, err) } diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index e34364f39..2163cf4e2 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -50,6 +50,16 @@ func TestLoadDir(t *testing.T) { verifyDependenciesLock(t, c) } +func TestLoadDirExceedsBudget(t *testing.T) { + _, err := loadDir("testdata/frobnitz", 1) + if err == nil { + t.Fatal("expected error when chart directory exceeds budget") + } + if !strings.Contains(err.Error(), "chart exceeds maximum decompressed size") { + t.Fatalf("unexpected error: %v", err) + } +} + func TestLoadDirWithDevNull(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("test only works on unix systems with /dev/null present") diff --git a/internal/chart/v3/util/create.go b/internal/chart/v3/util/create.go index dcb5bbb39..0c143615e 100644 --- a/internal/chart/v3/util/create.go +++ b/internal/chart/v3/util/create.go @@ -18,7 +18,7 @@ package util import ( "fmt" - "io" + "log/slog" "os" "path/filepath" "regexp" @@ -642,12 +642,6 @@ spec: restartPolicy: Never ` -// Stderr is an io.Writer to which error messages can be written -// -// In Helm 4, this will be replaced. It is needed in Helm 3 to preserve API backward -// compatibility. -var Stderr io.Writer = os.Stderr - // CreateFrom creates a new chart, but scaffolds it from the src chart. func CreateFrom(chartfile *chart.Metadata, dest, src string) error { schart, err := loader.Load(src) @@ -795,8 +789,7 @@ func Create(name, dir string) (string, error) { for _, file := range files { if _, err := os.Stat(file.path); err == nil { - // There is no handle to a preferred output stream here. - fmt.Fprintf(Stderr, "WARNING: File %q already exists. Overwriting.\n", file.path) + slog.Warn("Overwriting existing file", slog.String("file", file.path)) } if err := writeFile(file.path, file.content); err != nil { return cdir, err diff --git a/internal/chart/v3/util/create_test.go b/internal/chart/v3/util/create_test.go index ddbbf6216..a227e4e7c 100644 --- a/internal/chart/v3/util/create_test.go +++ b/internal/chart/v3/util/create_test.go @@ -18,6 +18,7 @@ package util import ( "bytes" + "log/slog" "os" "path/filepath" "testing" @@ -98,7 +99,11 @@ func TestCreateFrom(t *testing.T) { func TestCreate_Overwrite(t *testing.T) { tdir := t.TempDir() - var errlog bytes.Buffer + prev := slog.Default() + t.Cleanup(func() { slog.SetDefault(prev) }) + + var logBuf bytes.Buffer + slog.SetDefault(slog.New(slog.NewJSONHandler(&logBuf, nil))) _, err := Create("foo", tdir) require.NoError(t, err) @@ -109,7 +114,6 @@ func TestCreate_Overwrite(t *testing.T) { writeFile(tplname, []byte("FOO")) // Now re-run the create - Stderr = &errlog _, err = Create("foo", tdir) require.NoError(t, err) @@ -118,7 +122,7 @@ func TestCreate_Overwrite(t *testing.T) { require.NotEqual(t, "FOO", string(data), "File that should have been modified was not.") - assert.NotEqual(t, 0, errlog.Len(), "Expected warnings about overwriting files.") + assert.NotEqual(t, 0, logBuf.Len(), "Expected warnings about overwriting files.") } func TestValidateChartName(t *testing.T) { diff --git a/internal/chart/v3/util/validate_name.go b/internal/chart/v3/util/validate_name.go deleted file mode 100644 index 7d85a5b88..000000000 --- a/internal/chart/v3/util/validate_name.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "errors" - "fmt" - "regexp" -) - -// validName is a regular expression for resource names. -// -// According to the Kubernetes help text, the regular expression it uses is: -// -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// -// This follows the above regular expression (but requires a full string match, not partial). -// -// The Kubernetes documentation is here, though it is not entirely correct: -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) - -var ( - // errMissingName indicates that a release (name) was not provided. - errMissingName = errors.New("no name provided") - - // errInvalidName indicates that an invalid release name was provided - errInvalidName = fmt.Errorf( - "invalid release name, must match regex %s and the length must not be longer than 53", - validName.String()) - - // errInvalidKubernetesName indicates that the name does not meet the Kubernetes - // restrictions on metadata names. - errInvalidKubernetesName = fmt.Errorf( - "invalid metadata name, must match regex %s and the length must not be longer than 253", - validName.String()) -) - -const ( - // According to the Kubernetes docs (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names) - // some resource names have a max length of 63 characters while others have a max - // length of 253 characters. As we cannot be sure the resources used in a chart, we - // therefore need to limit it to 63 chars and reserve 10 chars for additional part to name - // of the resource. The reason is that chart maintainers can use release name as part of - // the resource name (and some additional chars). - maxReleaseNameLen = 53 - // maxMetadataNameLen is the maximum length Kubernetes allows for any name. - maxMetadataNameLen = 253 -) - -// ValidateReleaseName performs checks for an entry for a Helm release name -// -// For Helm to allow a name, it must be below a certain character count (53) and also match -// a regular expression. -// -// According to the Kubernetes help text, the regular expression it uses is: -// -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// -// This follows the above regular expression (but requires a full string match, not partial). -// -// The Kubernetes documentation is here, though it is not entirely correct: -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -func ValidateReleaseName(name string) error { - // This case is preserved for backwards compatibility - if name == "" { - return errMissingName - } - if len(name) > maxReleaseNameLen || !validName.MatchString(name) { - return errInvalidName - } - return nil -} - -// ValidateMetadataName validates the name field of a Kubernetes metadata object. -// -// Empty strings, strings longer than 253 chars, or strings that don't match the regexp -// will fail. -// -// According to the Kubernetes help text, the regular expression it uses is: -// -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// -// This follows the above regular expression (but requires a full string match, not partial). -// -// The Kubernetes documentation is here, though it is not entirely correct: -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -// -// Deprecated: remove in Helm 4. Name validation now uses rules defined in -// pkg/lint/rules.validateMetadataNameFunc() -func ValidateMetadataName(name string) error { - if name == "" || len(name) > maxMetadataNameLen || !validName.MatchString(name) { - return errInvalidKubernetesName - } - return nil -} diff --git a/internal/chart/v3/util/validate_name_test.go b/internal/chart/v3/util/validate_name_test.go deleted file mode 100644 index 3ea768db2..000000000 --- a/internal/chart/v3/util/validate_name_test.go +++ /dev/null @@ -1,93 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// TestValidateReleaseName is a regression test for ValidateName -// -// Kubernetes has strict naming conventions for resource names. This test represents -// those conventions. -// -// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -// -// NOTE: At the time of this writing, the docs above say that names cannot begin with -// digits. However, `kubectl`'s regular expression explicit allows this, and -// Kubernetes (at least as of 1.18) also accepts resources whose names begin with digits. -func TestValidateReleaseName(t *testing.T) { - names := map[string]bool{ - "": false, - "foo": true, - "foo.bar1234baz.seventyone": true, - "FOO": false, - "123baz": true, - "foo.BAR.baz": false, - "one-two": true, - "-two": false, - "one_two": false, - "a..b": false, - "%^&#$%*@^*@&#^": false, - "example:com": false, - "example%%com": false, - "a1111111111111111111111111111111111111111111111111111111111z": false, - } - for input, expectPass := range names { - err := ValidateReleaseName(input) - if expectPass { - assert.NoErrorf(t, err, "Expected release name %q to pass validation", input) - } else { - assert.Errorf(t, err, "Expected release name %q to fail validation, but it passed", input) - } - } -} - -func TestValidateMetadataName(t *testing.T) { - names := map[string]bool{ - "": false, - "foo": true, - "foo.bar1234baz.seventyone": true, - "FOO": false, - "123baz": true, - "foo.BAR.baz": false, - "one-two": true, - "-two": false, - "one_two": false, - "a..b": false, - "%^&#$%*@^*@&#^": false, - "example:com": false, - "example%%com": false, - "a1111111111111111111111111111111111111111111111111111111111z": true, - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z": false, - } - for input, expectPass := range names { - err := ValidateMetadataName(input) - if expectPass { - assert.NoError(t, err, "Expected %q to succeed", input) - } else { - assert.Error(t, err, "Expected %q to fail", input) - } - } -} diff --git a/internal/monocular/search.go b/internal/monocular/search.go index 29cc7f1a4..d64d6397a 100644 --- a/internal/monocular/search.go +++ b/internal/monocular/search.go @@ -101,11 +101,15 @@ type ChartVersion struct { // Search performs a search against the monocular search API // // Deprecated: Use SearchWithContext instead. +// +//go:fix inline func (c *Client) Search(term string) ([]SearchResult, error) { return c.SearchWithContext(context.Background(), term) } // SearchWithContext performs a search against the monocular search API +// +// TODO Helm v5: Rename this to Search (remove the current deprecated Search method) and 'go:fix inline' SearchWithContext method to call Search. func (c *Client) SearchWithContext(ctx context.Context, term string) ([]SearchResult, error) { // Create the URL to the search endpoint // Note, this is currently an internal API for the Hub. This should be diff --git a/internal/plugin/installer/extractor.go b/internal/plugin/installer/extractor.go index 6cd45e2d8..4588976c4 100644 --- a/internal/plugin/installer/extractor.go +++ b/internal/plugin/installer/extractor.go @@ -48,7 +48,7 @@ var Extractors = map[string]Extractor{ // Convert a media type to an extractor extension. // -// This should be refactored in Helm 4, combined with the extension-based mechanism. +// TODO Helm v5: refactor this, combined with the extension-based mechanism. func mediaTypeToExtension(mt string) (string, bool) { switch strings.ToLower(mt) { case "application/gzip", "application/x-gzip", "application/x-tgz", "application/x-gtar": diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index 4728879f5..aa43992db 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -153,7 +153,7 @@ func NewForSource(source, version string) (installer Installer, err error) { return installer, fmt.Errorf("cannot get information about plugin source %q (if it's a local directory, does it exist?), last error was: %w", source, err) } - return + return installer, err } // FindSource determines the correct Installer for the given source. diff --git a/internal/plugin/installer/plugin_structure.go b/internal/plugin/installer/plugin_structure.go index 10647141e..ca418ac30 100644 --- a/internal/plugin/installer/plugin_structure.go +++ b/internal/plugin/installer/plugin_structure.go @@ -53,7 +53,7 @@ func detectPluginRoot(extractDir string) (string, error) { // validatePluginName checks if the plugin directory name matches the plugin name // from plugin.yaml when the plugin is in a subdirectory. -func validatePluginName(pluginRoot string, expectedName string) error { +func validatePluginName(pluginRoot, expectedName string) error { // Only validate if plugin is in a subdirectory dirName := filepath.Base(pluginRoot) if dirName == expectedName { diff --git a/internal/plugin/runtime_extismv1.go b/internal/plugin/runtime_extismv1.go index ffa108a08..678fbb564 100644 --- a/internal/plugin/runtime_extismv1.go +++ b/internal/plugin/runtime_extismv1.go @@ -212,7 +212,7 @@ func (p *ExtismV1PluginRuntime) Invoke(ctx context.Context, input *Input) (*Outp return output, nil } -func buildManifest(pluginDir string, tmpDir string, rc *RuntimeConfigExtismV1) (extism.Manifest, error) { +func buildManifest(pluginDir, tmpDir string, rc *RuntimeConfigExtismV1) (extism.Manifest, error) { wasmFile := filepath.Join(pluginDir, ExtismV1WasmBinaryFilename) allowedHosts := rc.AllowedHosts diff --git a/internal/plugin/runtime_subprocess.go b/internal/plugin/runtime_subprocess.go index 9ea69e116..2fcbcfb33 100644 --- a/internal/plugin/runtime_subprocess.go +++ b/internal/plugin/runtime_subprocess.go @@ -47,7 +47,7 @@ type RuntimeConfigSubprocess struct { PlatformHooks PlatformHooks `yaml:"platformHooks"` // ProtocolCommands allows the plugin to specify protocol specific commands // - // Obsolete/deprecated: This is a compatibility hangover from the old plugin downloader mechanism, which was extended + // Deprecated: This is an obsolete compatibility hangover from the old plugin downloader mechanism, which was extended // to support multiple protocols in a given plugin. The command supplied in PlatformCommand should implement protocol // specific logic by inspecting the download URL ProtocolCommands []SubprocessProtocolCommand `yaml:"protocolCommands,omitempty"` @@ -112,7 +112,7 @@ func (r *SubprocessPluginRuntime) Invoke(ctx context.Context, input *Input) (*Ou // InvokeWithEnv executes a plugin command with custom environment and I/O streams // This method allows execution with different command/args than the plugin's default -func (r *SubprocessPluginRuntime) InvokeWithEnv(main string, argv []string, env []string, stdin io.Reader, stdout, stderr io.Writer) error { +func (r *SubprocessPluginRuntime) InvokeWithEnv(main string, argv, env []string, stdin io.Reader, stdout, stderr io.Writer) error { mainCmdExp := os.ExpandEnv(main) cmd := exec.CommandContext(context.Background(), mainCmdExp, argv...) cmd.Env = slices.Clone(os.Environ()) diff --git a/internal/plugin/signing_info.go b/internal/plugin/signing_info.go index ff27cc02d..8077eb09a 100644 --- a/internal/plugin/signing_info.go +++ b/internal/plugin/signing_info.go @@ -111,7 +111,7 @@ func GetPluginSigningInfo(metadata Metadata) (*SigningInfo, error) { }, nil } -func validateProvenanceHash(blockContent string, tarballPath string) bool { +func validateProvenanceHash(blockContent, tarballPath string) bool { // Parse provenance to get the expected hash _, sums, err := parsePluginMessageBlock([]byte(blockContent)) if err != nil { diff --git a/internal/release/v2/util/filter.go b/internal/release/v2/util/filter.go index 0daf06014..6ad5cce9f 100644 --- a/internal/release/v2/util/filter.go +++ b/internal/release/v2/util/filter.go @@ -41,7 +41,7 @@ func (fn FilterFunc) Filter(rels []*v2.Release) (rets []*v2.Release) { rets = append(rets, rel) } } - return + return rets } // Any returns a FilterFunc that filters a list of releases diff --git a/internal/release/v2/util/kind_sorter.go b/internal/release/v2/util/kind_sorter.go index 950e836fa..4ad1f7fb3 100644 --- a/internal/release/v2/util/kind_sorter.go +++ b/internal/release/v2/util/kind_sorter.go @@ -137,7 +137,7 @@ func sortHooksByKind(hooks []*release.Hook, ordering KindSortOrder) []*release.H return h } -func lessByKind(_ any, _ any, kindA string, kindB string, o KindSortOrder) bool { +func lessByKind(_, _ any, kindA, kindB string, o KindSortOrder) bool { ordering := make(map[string]int, len(o)) for v, k := range o { ordering[k] = v diff --git a/internal/test/test.go b/internal/test/test.go index ba4ea41e7..ceb91ba3a 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -49,7 +49,7 @@ func AssertGoldenString(t TestingT, actual, filename string) { } // AssertGoldenFile asserts that the content of the actual file matches the contents of the expected file -func AssertGoldenFile(t TestingT, actualFileName string, expectedFilename string) { +func AssertGoldenFile(t TestingT, actualFileName, expectedFilename string) { t.Helper() actual, err := os.ReadFile(actualFileName) diff --git a/internal/version/version.go b/internal/version/version.go index 572938c96..c40e101de 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -34,7 +34,7 @@ var ( // // Increment major number for new feature additions and behavioral changes. // Increment minor number for bug fixes and performance enhancements. - version = "v4.2" + version = "v4.3" // metadata is extra build time data metadata = "" diff --git a/pkg/action/action.go b/pkg/action/action.go index bcc91816c..e93d6181f 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -508,8 +508,8 @@ func (cfg *Configuration) renderResources(ctx context.Context, ch *chart.Chart, newDir = filepath.Join(outputDir, releaseName) } // NOTE: We do not have to worry about the post-renderer because - // output dir is only used by `helm template`. In the next major - // release, we should move this logic to template only as it is not + // output dir is only used by `helm template`. + // TODO Helm v5: move this logic to template only as it is not // used by install or upgrade err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name]) if err != nil { diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go index 895c11e63..49f3b5f14 100644 --- a/pkg/action/dependency.go +++ b/pkg/action/dependency.go @@ -77,12 +77,14 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*") // If a chart is unpacked, this will check the unpacked chart's `charts/` directory for tarballs. - // Technically, this is COMPLETELY unnecessary, and should be removed in Helm 4. It is here - // to preserved backward compatibility. In Helm 2/3, there is a "difference" between + // + // Technically, this is COMPLETELY unnecessary. It is here to preserve backward + // compatibility. In Helm 2/3, there is a "difference" between // the tgz version (which outputs "ok" if it unpacks) and the loaded version (which outputs // "unpacked"). Early in Helm 2's history, this would have made a difference. But it no - // longer does. However, since this code shipped with Helm 3, the output must remain stable - // until Helm 4. + // longer does. However, since this code shipped with Helm 3, the output must remain stable. + // + // TODO Helm v5: remove this. switch archives, err := filepath.Glob(filepath.Join(chartpath, "charts", filename)); { case err != nil: return "bad pattern" @@ -156,7 +158,9 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p // stat an archive and return a message if the stat is successful // // This is a refactor of the code originally in dependencyStatus. It is here to -// support legacy behavior, and should be removed in Helm 4. +// support legacy behavior. +// +// TODO Helm v5: remove this. func statArchiveForStatus(archive string, dep *chart.Dependency) string { if _, err := os.Stat(archive); err == nil { c, err := loader.Load(archive) diff --git a/pkg/action/install.go b/pkg/action/install.go index 48e949719..436a972c3 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -481,7 +481,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st return rel, err } -func (i *Install) performInstallCtx(ctx context.Context, rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) (*release.Release, error) { +func (i *Install) performInstallCtx(ctx context.Context, rel *release.Release, toBeAdopted, resources kube.ResourceList) (*release.Release, error) { type Msg struct { r *release.Release e error @@ -508,7 +508,7 @@ func (i *Install) getGoroutineCount() int32 { return i.goroutineCount.Load() } -func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) (*release.Release, error) { +func (i *Install) performInstall(rel *release.Release, toBeAdopted, resources kube.ResourceList) (*release.Release, error) { var err error // pre-install hooks if !i.DisableHooks { @@ -722,7 +722,7 @@ func (i *Install) replaceRelease(rel *release.Release) error { } // write the to /. controls if the file is created or content will be appended -func writeToFile(outputDir string, name string, data string, appendData bool) error { +func writeToFile(outputDir, name, data string, appendData bool) error { outfileName := outputDir + string(filepath.Separator) + name err := ensureDirectoryForFile(outfileName) diff --git a/pkg/action/push.go b/pkg/action/push.go index 0c7148f65..805fdb2dc 100644 --- a/pkg/action/push.go +++ b/pkg/action/push.go @@ -90,7 +90,7 @@ func NewPushWithOpts(opts ...PushOpt) *Push { } // Run executes 'helm push' against the given chart archive. -func (p *Push) Run(chartRef string, remote string) (string, error) { +func (p *Push) Run(chartRef, remote string) (string, error) { var out strings.Builder c := uploader.ChartUploader{ diff --git a/pkg/action/registry_login.go b/pkg/action/registry_login.go index fd9d4bfc6..23bbfd02f 100644 --- a/pkg/action/registry_login.go +++ b/pkg/action/registry_login.go @@ -82,7 +82,7 @@ func NewRegistryLogin(cfg *Configuration) *RegistryLogin { } // Run executes the registry login operation -func (a *RegistryLogin) Run(_ io.Writer, hostname string, username string, password string, opts ...RegistryLoginOpt) error { +func (a *RegistryLogin) Run(_ io.Writer, hostname, username, password string, opts ...RegistryLoginOpt) error { for _, opt := range opts { if err := opt(a); err != nil { return err diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 7f66ceefb..85904124b 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -453,7 +453,7 @@ func isReleaseApplyMethodClientSideApply(applyMethod string) bool { return applyMethod == "" || applyMethod == string(release.ApplyMethodClientSideApply) } -func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *release.Release, current kube.ResourceList, target kube.ResourceList, originalRelease *release.Release, serverSideApply bool) { +func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *release.Release, current, target kube.ResourceList, originalRelease *release.Release, serverSideApply bool) { // pre-upgrade hooks if !u.DisableHooks { @@ -664,7 +664,7 @@ func mergeCustomLabels(current, desired map[string]string) map[string]string { return labels } -func getUpgradeServerSideValue(serverSideOption string, releaseApplyMethod string) (bool, error) { +func getUpgradeServerSideValue(serverSideOption, releaseApplyMethod string) (bool, error) { switch serverSideOption { case "auto": return releaseApplyMethod == "ssa", nil diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index 8bd9f1916..d33ee683d 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -75,7 +75,7 @@ func newMissingDeployment(name, namespace string) *resource.Info { return info } -func newDeploymentWithOwner(name, namespace string, labels map[string]string, annotations map[string]string) *resource.Info { +func newDeploymentWithOwner(name, namespace string, labels, annotations map[string]string) *resource.Info { obj := &appsv1.Deployment{ ObjectMeta: v1.ObjectMeta{ Name: name, diff --git a/pkg/chart/common/capabilities.go b/pkg/chart/common/capabilities.go index a9374a86f..c7217a503 100644 --- a/pkg/chart/common/capabilities.go +++ b/pkg/chart/common/capabilities.go @@ -87,7 +87,9 @@ func (kv *KubeVersion) String() string { // GitVersion returns the full Kubernetes version string. // -// Deprecated: use KubeVersion.Version. +// Deprecated: Use KubeVersion.Version. +// +//go:fix inline func (kv *KubeVersion) GitVersion() string { return kv.Version } // ParseKubeVersion parses kubernetes version from string diff --git a/pkg/chart/common/capabilities_test.go b/pkg/chart/common/capabilities_test.go index b099e3193..18ff08fe9 100644 --- a/pkg/chart/common/capabilities_test.go +++ b/pkg/chart/common/capabilities_test.go @@ -46,7 +46,7 @@ func TestDefaultCapabilities(t *testing.T) { assert.Equalf(t, "20", kv.Minor, "Expected default KubeVersion.Minor to be 20, got %q", kv.Minor) hv := caps.HelmVersion - assert.Equalf(t, "v4.2", hv.Version, "Expected default HelmVersion to be v4.2, got %q", hv.Version) + assert.Equalf(t, "v4.3", hv.Version, "Expected default HelmVersion to be v4.3, got %q", hv.Version) } func TestParseKubeVersion(t *testing.T) { diff --git a/pkg/chart/loader/archive/archive.go b/pkg/chart/loader/archive/archive.go index a35c0152d..633871bcc 100644 --- a/pkg/chart/loader/archive/archive.go +++ b/pkg/chart/loader/archive/archive.go @@ -37,8 +37,10 @@ import ( // The default value is 100 MiB. var MaxDecompressedChartSize int64 = 100 * 1024 * 1024 // Default 100 MiB -// MaxDecompressedFileSize is the size of the largest file that Helm will attempt to load. -// The size of the file is the decompressed version of it when it is stored in an archive. +// MaxDecompressedFileSize was the per-file size limit enforced during chart loading. +// It is no longer used internally; aggregate chart size is enforced via MaxDecompressedChartSize. +// +// Deprecated: Retained for backward compatibility with external callers. Will be removed in Helm v5. var MaxDecompressedFileSize int64 = 5 * 1024 * 1024 // Default 5 MiB var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`) @@ -128,10 +130,6 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) } - if hd.Size > MaxDecompressedFileSize { - return nil, fmt.Errorf("decompressed chart file %q is larger than the maximum file size %d", hd.Name, MaxDecompressedFileSize) - } - limitedReader := io.LimitReader(tr, remainingSize) bytesWritten, err := io.Copy(b, limitedReader) diff --git a/pkg/chart/loader/archive/budget.go b/pkg/chart/loader/archive/budget.go new file mode 100644 index 000000000..3f9b90e4a --- /dev/null +++ b/pkg/chart/loader/archive/budget.go @@ -0,0 +1,74 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package archive + +import ( + "fmt" + "io" + "math" + "os" +) + +// BudgetedReader tracks cumulative file reads against a size limit. +type BudgetedReader struct { + max int64 + remaining int64 +} + +// NewBudgetedReader creates a BudgetedReader with the given maximum total size. +// The remaining budget is initialized to the maximum. +func NewBudgetedReader(limit int64) *BudgetedReader { + return &BudgetedReader{ + max: limit, + remaining: limit, + } +} + +// ReadFileWithBudget reads a file and decrements the remaining budget by the bytes read. +// It returns an error if the total would exceed the configured maximum. +// The read is capped via io.LimitReader so a file that grows between stat +// and read cannot cause unbounded memory allocation. +func (r *BudgetedReader) ReadFileWithBudget(path string, size int64) ([]byte, error) { + if size > r.remaining { + return nil, fmt.Errorf("chart exceeds maximum decompressed size of %d bytes", r.max) + } + + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + // Read at most r.remaining+1 bytes so we can detect over-budget without + // allocating unbounded memory if the file grew since stat. + // Clamp to avoid int64 overflow when r.remaining is near math.MaxInt64. + limit := r.remaining + if limit < math.MaxInt64 { + limit++ + } + data, err := io.ReadAll(io.LimitReader(f, limit)) + if err != nil { + return nil, err + } + + if int64(len(data)) > r.remaining { + return nil, fmt.Errorf("chart exceeds maximum decompressed size of %d bytes", r.max) + } + + r.remaining -= int64(len(data)) + return data, nil +} diff --git a/pkg/chart/loader/archive/budget_test.go b/pkg/chart/loader/archive/budget_test.go new file mode 100644 index 000000000..f8fc6776f --- /dev/null +++ b/pkg/chart/loader/archive/budget_test.go @@ -0,0 +1,128 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package archive + +import ( + "fmt" + "os" + "path/filepath" + "testing" +) + +func TestReadFileWithBudget(t *testing.T) { + dir := t.TempDir() + + writeFile := func(t *testing.T, name string, size int) string { + t.Helper() + p := filepath.Join(dir, name) + if err := os.WriteFile(p, make([]byte, size), 0o644); err != nil { + t.Fatal(err) + } + return p + } + + tcs := []struct { + name string + check func(t *testing.T) + }{ + { + name: "reads file and decrements budget", + check: func(t *testing.T) { + t.Helper() + p := writeFile(t, "small.txt", 100) + fi, err := os.Stat(p) + if err != nil { + t.Fatalf("failed to stat %s: %v", p, err) + } + limit := int64(1000) + + br := NewBudgetedReader(limit) + data, err := br.ReadFileWithBudget(p, fi.Size()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(data) != 100 { + t.Fatalf("expected 100 bytes, got %d", len(data)) + } + if br.remaining != 900 { + t.Fatalf("expected remaining=900, got %d", br.remaining) + } + }, + }, + { + name: "rejects file exceeding budget", + check: func(t *testing.T) { + t.Helper() + p := writeFile(t, "big.txt", 500) + fi, err := os.Stat(p) + if err != nil { + t.Fatalf("failed to stat %s: %v", p, err) + } + limit := int64(100) + + br := NewBudgetedReader(limit) + _, err = br.ReadFileWithBudget(p, fi.Size()) + if err == nil { + t.Fatal("expected error for file exceeding budget") + } + expectedErr := fmt.Sprintf("chart exceeds maximum decompressed size of %d bytes", limit) + if err.Error() != expectedErr { + t.Fatalf("expected %q, got %q", expectedErr, err.Error()) + } + if br.remaining != 100 { + t.Fatalf("budget should not change on rejection, got %d", br.remaining) + } + }, + }, + { + name: "tracks budget across multiple reads", + check: func(t *testing.T) { + t.Helper() + remaining := int64(250) + + br := NewBudgetedReader(remaining) + for i := range 3 { + p := writeFile(t, fmt.Sprintf("f%d.txt", i), 80) + fi, err := os.Stat(p) + if err != nil { + t.Fatalf("failed to stat %s: %v", p, err) + } + if _, err := br.ReadFileWithBudget(p, fi.Size()); err != nil { + t.Fatalf("read %d: unexpected error: %v", i, err) + } + } + if br.remaining != 10 { + t.Fatalf("expected remaining=10, got %d", br.remaining) + } + + p := writeFile(t, "over.txt", 20) + fi, err := os.Stat(p) + if err != nil { + t.Fatalf("failed to stat %s: %v", p, err) + } + _, err = br.ReadFileWithBudget(p, fi.Size()) + if err == nil { + t.Fatal("expected error when cumulative reads exceed budget") + } + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, tc.check) + } +} diff --git a/pkg/chart/v2/chart.go b/pkg/chart/v2/chart.go index ccfe84f51..114667001 100644 --- a/pkg/chart/v2/chart.go +++ b/pkg/chart/v2/chart.go @@ -144,7 +144,7 @@ func (ch *Chart) AppVersion() string { // CRDs returns a list of File objects in the 'crds/' directory of a Helm chart. // -// Deprecated: use CRDObjects() +// Deprecated: Use CRDObjects instead. func (ch *Chart) CRDs() []*common.File { files := []*common.File{} // Find all resources in the crds/ directory diff --git a/pkg/chart/v2/loader/directory.go b/pkg/chart/v2/loader/directory.go index a609ecbbf..bfc649f2a 100644 --- a/pkg/chart/v2/loader/directory.go +++ b/pkg/chart/v2/loader/directory.go @@ -43,6 +43,10 @@ func (l DirLoader) Load() (*chart.Chart, error) { // // This loads charts only from directories. func LoadDir(dir string) (*chart.Chart, error) { + return loadDir(dir, archive.MaxDecompressedChartSize) +} + +func loadDir(dir string, budget int64) (*chart.Chart, error) { topdir, err := filepath.Abs(dir) if err != nil { return nil, err @@ -64,6 +68,7 @@ func LoadDir(dir string) (*chart.Chart, error) { files := []*archive.BufferedFile{} topdir += string(filepath.Separator) + budgetReader := archive.NewBudgetedReader(budget) walk := func(name string, fi os.FileInfo, err error) error { n := strings.TrimPrefix(name, topdir) @@ -100,11 +105,7 @@ func LoadDir(dir string) (*chart.Chart, error) { return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) } - if fi.Size() > archive.MaxDecompressedFileSize { - return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), archive.MaxDecompressedFileSize) - } - - data, err := os.ReadFile(name) + data, err := budgetReader.ReadFileWithBudget(name, fi.Size()) if err != nil { return fmt.Errorf("error reading %s: %w", n, err) } diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index feaa28f12..fc57190ec 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -113,8 +113,8 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { c.Schema = f.Data c.SchemaModTime = f.ModTime - // Deprecated: requirements.yaml is deprecated use Chart.yaml. - // We will handle it for you because we are nice people + // requirements.yaml is deprecated; dependencies are declared in Chart.yaml since + // apiVersion v2. Handled here for backwards compatibility. case f.Name == "requirements.yaml": if c.Metadata == nil { c.Metadata = new(chart.Metadata) @@ -128,7 +128,8 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { if c.Metadata.APIVersion == chart.APIVersionV1 { c.Files = append(c.Files, &common.File{Name: f.Name, ModTime: f.ModTime, Data: f.Data}) } - // Deprecated: requirements.lock is deprecated use Chart.lock. + // requirements.lock is deprecated; use Chart.lock. Handled here for backwards + // compatibility. case f.Name == "requirements.lock": c.Lock = new(chart.Lock) if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil { diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index ae1da1130..5af88b341 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -50,6 +50,16 @@ func TestLoadDir(t *testing.T) { verifyDependenciesLock(t, c) } +func TestLoadDirExceedsBudget(t *testing.T) { + _, err := loadDir("testdata/frobnitz", 1) + if err == nil { + t.Fatal("expected error when chart directory exceeds budget") + } + if !strings.Contains(err.Error(), "chart exceeds maximum decompressed size") { + t.Fatalf("unexpected error: %v", err) + } +} + func TestLoadDirWithDevNull(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("test only works on unix systems with /dev/null present") diff --git a/pkg/chart/v2/util/create.go b/pkg/chart/v2/util/create.go index 82e32d90d..65546e21b 100644 --- a/pkg/chart/v2/util/create.go +++ b/pkg/chart/v2/util/create.go @@ -643,7 +643,7 @@ spec: // Stderr is an io.Writer to which error messages can be written // -// In Helm 4, this will be replaced. It is needed in Helm 3 to preserve API backward +// TODO Helm v5: replace this. It is needed in Helm 3 to preserve API backward // compatibility. var Stderr io.Writer = os.Stderr diff --git a/pkg/chart/v2/util/save.go b/pkg/chart/v2/util/save.go index 53c952421..3dd570e1c 100644 --- a/pkg/chart/v2/util/save.go +++ b/pkg/chart/v2/util/save.go @@ -183,7 +183,7 @@ func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { } // Save Chart.lock - // TODO: remove the APIVersion check when APIVersionV1 is not used anymore + // TODO Helm v5: remove the APIVersion check when APIVersionV1 is not used anymore if c.Metadata.APIVersion == chart.APIVersionV2 { if c.Lock != nil { ldata, err := yaml.Marshal(c.Lock) diff --git a/pkg/chart/v2/util/validate_name.go b/pkg/chart/v2/util/validate_name.go index 7d85a5b88..1304923dd 100644 --- a/pkg/chart/v2/util/validate_name.go +++ b/pkg/chart/v2/util/validate_name.go @@ -100,8 +100,11 @@ func ValidateReleaseName(name string) error { // The Kubernetes documentation is here, though it is not entirely correct: // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names // -// Deprecated: remove in Helm 4. Name validation now uses rules defined in -// pkg/lint/rules.validateMetadataNameFunc() +// Deprecated: ValidateMetadataName is no longer used by Helm. Metadata name +// validation is now performed by the lint rule validateMetadataNameFunc (see +// pkg/chart/v2/lint/rules). +// +// TODO Helm v5: remove this function. func ValidateMetadataName(name string) error { if name == "" || len(name) > maxMetadataNameLen || !validName.MatchString(name) { return errInvalidKubernetesName diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 45d773eb4..b0463d01f 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -219,7 +219,7 @@ func envCSV(name string) (ls []string) { if trimmed != "" { ls = strings.Split(trimmed, ",") } - return + return ls } func envColorMode() string { diff --git a/pkg/cli/output/output.go b/pkg/cli/output/output.go index a9bd846fe..bc4635647 100644 --- a/pkg/cli/output/output.go +++ b/pkg/cli/output/output.go @@ -85,7 +85,7 @@ func ParseFormat(s string) (out Format, err error) { default: out, err = "", ErrInvalidFormatType } - return + return out, err } // Writer is an interface that any type can implement to write supported formats diff --git a/pkg/cmd/create.go b/pkg/cmd/create.go index b38ce6a12..f7abb20dc 100644 --- a/pkg/cmd/create.go +++ b/pkg/cmd/create.go @@ -104,7 +104,7 @@ func (o *createOptions) run(out io.Writer) error { if !gates.ChartV3.IsEnabled() { return gates.ChartV3.Error() } - return o.createV3Chart(out) + return o.createV3Chart() default: return fmt.Errorf("unsupported chart API version: %s (supported: v2, v3)", o.chartAPIVersion) } @@ -136,7 +136,7 @@ func (o *createOptions) createV2Chart(out io.Writer) error { return err } -func (o *createOptions) createV3Chart(out io.Writer) error { +func (o *createOptions) createV3Chart() error { chartname := filepath.Base(o.name) cfile := &chartv3.Metadata{ Name: chartname, @@ -157,7 +157,6 @@ func (o *createOptions) createV3Chart(out io.Writer) error { return chartutilv3.CreateFrom(cfile, filepath.Dir(o.name), lstarter) } - chartutilv3.Stderr = out _, err := chartutilv3.Create(chartname, filepath.Dir(o.name)) return err } diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index 45da90d0e..61db07883 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -268,7 +268,7 @@ func (p *postRendererArgsSlice) GetSlice() []string { return p.options.args } -func compVersionFlag(chartRef string, _ string) ([]string, cobra.ShellCompDirective) { +func compVersionFlag(chartRef, _ string) ([]string, cobra.ShellCompDirective) { chartInfo := strings.Split(chartRef, "/") if len(chartInfo) != 2 { return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index d82ab910e..bed66beb6 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -215,7 +215,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal // For `helm template`, these notes flags are legacy, unused, and should not show in help, but // must remain accepted for backwards compatibility in Helm 4. Deprecate and hide them for now - // TODO remove these from template command in Helm 5 + // TODO Helm v5: remove these from template command if cmd.Name() == "template" { if err := cmd.Flags().MarkDeprecated("hide-notes", "this flag has no effect for 'helm template' and will be removed in Helm 5"); err != nil { log.Fatal(err) diff --git a/pkg/cmd/list.go b/pkg/cmd/list.go index 54becddca..00465d232 100644 --- a/pkg/cmd/list.go +++ b/pkg/cmd/list.go @@ -155,7 +155,7 @@ type releaseListWriter struct { noColor bool } -func newReleaseListWriter(releases []*release.Release, timeFormat string, noHeaders bool, noColor bool) *releaseListWriter { +func newReleaseListWriter(releases []*release.Release, timeFormat string, noHeaders, noColor bool) *releaseListWriter { // Initialize the array so no results returns an empty array instead of null elements := make([]releaseElement, 0, len(releases)) for _, r := range releases { diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index a62d6b1b4..aadb63db6 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -268,7 +268,7 @@ func runPullTests(t *testing.T, tests []struct { wantErrorMsg string expectFile string expectDir bool -}, outdir string, additionalFlags string, +}, outdir, additionalFlags string, ) { t.Helper() for _, tt := range tests { diff --git a/pkg/cmd/registry_login.go b/pkg/cmd/registry_login.go index bffc56445..605717e22 100644 --- a/pkg/cmd/registry_login.go +++ b/pkg/cmd/registry_login.go @@ -91,7 +91,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman } // Adapted from https://github.com/oras-project/oras -func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStdinOpt bool) (string, string, error) { +func getUsernamePassword(usernameOpt, passwordOpt string, passwordFromStdinOpt bool) (string, string, error) { var err error username := usernameOpt password := passwordOpt diff --git a/pkg/cmd/registry_output_test.go b/pkg/cmd/registry_output_test.go new file mode 100644 index 000000000..77682a4c4 --- /dev/null +++ b/pkg/cmd/registry_output_test.go @@ -0,0 +1,109 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "bytes" + "fmt" + "io" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + "helm.sh/helm/v4/pkg/repo/v1/repotest" +) + +func TestTemplateOCIRegistryMessagesNotOnStdout(t *testing.T) { + defer resetEnv()() + + stdout, stderr := runOCIChartCommand(t, func(ref, registryConfig, contentCache string) []string { + return []string{ + "template", "release-name", ref, + "--version", "0.1.0", + "--plain-http", + "--registry-config", registryConfig, + "--content-cache", contentCache, + } + }) + + require.NotEmpty(t, stdout) + require.NotContains(t, stdout, "Pulled:") + require.NotContains(t, stdout, "Digest:") + require.Contains(t, stderr, "Pulled:") + require.Contains(t, stderr, "Digest:") +} + +func TestShowOCIRegistryMessagesNotOnStdout(t *testing.T) { + defer resetEnv()() + + stdout, stderr := runOCIChartCommand(t, func(ref, registryConfig, contentCache string) []string { + return []string{ + "show", "chart", ref, + "--version", "0.1.0", + "--plain-http", + "--registry-config", registryConfig, + "--content-cache", contentCache, + } + }) + + require.NotEmpty(t, stdout) + require.Contains(t, stdout, "name: oci-dependent-chart") + require.NotContains(t, stdout, "Pulled:") + require.NotContains(t, stdout, "Digest:") + require.Contains(t, stderr, "Pulled:") + require.Contains(t, stderr, "Digest:") +} + +func runOCIChartCommand(t *testing.T, argsFn func(ref, registryConfig, contentCache string) []string) (string, string) { + t.Helper() + + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testcharts/*.tgz*"), + ) + t.Cleanup(func() { srv.Stop() }) + + ociSrv, err := repotest.NewOCIServer(t, srv.Root()) + require.NoError(t, err) + ociSrv.Run(t) + + ref := fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart", ociSrv.RegistryURL) + registryConfig := filepath.Join(srv.Root(), "config.json") + contentCache := t.TempDir() + args := argsFn(ref, registryConfig, contentCache) + + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + actionConfig := &action.Configuration{ + Releases: storageFixture(), + KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, + Capabilities: common.DefaultCapabilities, + } + + root, err := newRootCmdWithConfig(actionConfig, stdout, args, SetupLogging) + require.NoError(t, err) + root.SetOut(stdout) + root.SetErr(stderr) + root.SetArgs(args) + + require.NoError(t, root.Execute(), "stdout:\n%s\nstderr:\n%s", stdout.String(), stderr.String()) + return stdout.String(), stderr.String() +} diff --git a/pkg/cmd/repo_remove_test.go b/pkg/cmd/repo_remove_test.go index f58bc91d9..2672713a2 100644 --- a/pkg/cmd/repo_remove_test.go +++ b/pkg/cmd/repo_remove_test.go @@ -119,7 +119,7 @@ func TestRepoRemove(t *testing.T) { } } -func createCacheFiles(rootDir string, repoName string) (cacheIndexFile string, cacheChartsFile string) { +func createCacheFiles(rootDir, repoName string) (cacheIndexFile, cacheChartsFile string) { cacheIndexFile = filepath.Join(rootDir, helmpath.CacheIndexFile(repoName)) mf, _ := os.Create(cacheIndexFile) mf.Close() @@ -131,7 +131,7 @@ func createCacheFiles(rootDir string, repoName string) (cacheIndexFile string, c return cacheIndexFile, cacheChartsFile } -func testCacheFiles(t *testing.T, cacheIndexFile string, cacheChartsFile string, repoName string) { +func testCacheFiles(t *testing.T, cacheIndexFile, cacheChartsFile, repoName string) { t.Helper() _, err := os.Stat(cacheIndexFile) require.Errorf(t, err, "Error cache index file was not removed for repository %s", repoName) diff --git a/pkg/cmd/search_repo.go b/pkg/cmd/search_repo.go index 348ca12b6..2d9d967f8 100644 --- a/pkg/cmd/search_repo.go +++ b/pkg/cmd/search_repo.go @@ -268,7 +268,7 @@ func (r *repoSearchWriter) encodeByFormat(out io.Writer, format output.Format) e } // Provides the list of charts that are part of the specified repo, and that starts with 'prefix'. -func compListChartsOfRepo(repoName string, prefix string) []string { +func compListChartsOfRepo(repoName, prefix string) []string { var charts []string path := filepath.Join(settings.RepositoryCache, helmpath.CacheChartsFile(repoName)) diff --git a/pkg/cmd/show.go b/pkg/cmd/show.go index 8252d933e..161a38723 100644 --- a/pkg/cmd/show.go +++ b/pkg/cmd/show.go @@ -82,9 +82,9 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: showAllDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowAll - err := addRegistryClient(out, client) + err := addRegistryClient(cmd.ErrOrStderr(), client) if err != nil { return err } @@ -103,9 +103,9 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: showValuesDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowValues - err := addRegistryClient(out, client) + err := addRegistryClient(cmd.ErrOrStderr(), client) if err != nil { return err } @@ -124,9 +124,9 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: showChartDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowChart - err := addRegistryClient(out, client) + err := addRegistryClient(cmd.ErrOrStderr(), client) if err != nil { return err } @@ -145,9 +145,9 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: readmeChartDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowReadme - err := addRegistryClient(out, client) + err := addRegistryClient(cmd.ErrOrStderr(), client) if err != nil { return err } @@ -166,9 +166,9 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: showCRDsDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowCRDs - err := addRegistryClient(out, client) + err := addRegistryClient(cmd.ErrOrStderr(), client) if err != nil { return err } @@ -224,8 +224,8 @@ func runShow(args []string, client *action.Show) (string, error) { return client.Run(cp) } -func addRegistryClient(out io.Writer, client *action.Show) error { - registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, +func addRegistryClient(registryOut io.Writer, client *action.Show) error { + registryClient, err := newRegistryClient(registryOut, client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index 29839b871..5645ab33d 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -85,7 +85,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.KubeVersion = parsedKubeVersion } - registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(cmd.ErrOrStderr(), client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) @@ -238,7 +238,7 @@ func isTestHook(h *release.Hook) bool { // bug introduced by #8156. As part of the todo to refactor renderResources // this duplicate code should be removed. It is added here so that the API // surface area is as minimally impacted as possible in fixing the issue. -func writeToFile(outputDir string, name string, data string, appendData bool) error { +func writeToFile(outputDir, name, data string, appendData bool) error { outfileName := outputDir + string(filepath.Separator) + name err := ensureDirectoryForFile(outfileName) diff --git a/pkg/cmd/testdata/output/version-short.txt b/pkg/cmd/testdata/output/version-short.txt index 2fa2c5705..b570a0ac2 100644 --- a/pkg/cmd/testdata/output/version-short.txt +++ b/pkg/cmd/testdata/output/version-short.txt @@ -1 +1 @@ -v4.2 +v4.3 diff --git a/pkg/cmd/testdata/output/version-template.txt b/pkg/cmd/testdata/output/version-template.txt index 8f2491e54..06a6787a8 100644 --- a/pkg/cmd/testdata/output/version-template.txt +++ b/pkg/cmd/testdata/output/version-template.txt @@ -1 +1 @@ -Version: v4.2 \ No newline at end of file +Version: v4.3 \ No newline at end of file diff --git a/pkg/cmd/testdata/output/version.txt b/pkg/cmd/testdata/output/version.txt index 331c6d5e9..4b5678a7f 100644 --- a/pkg/cmd/testdata/output/version.txt +++ b/pkg/cmd/testdata/output/version.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v4.2", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v1.20"} +version.BuildInfo{Version:"v4.3", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v1.20"} diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index a1fa0ddbd..2e94d26ae 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -177,10 +177,11 @@ func (m *Manager) Update() error { // For the repositories Helm is not configured to know about, ensure Helm // has some information about them and, when possible, the index files // locally. - // TODO(mattfarina): Repositories should be explicitly added by end users - // rather than automatic. In Helm v4 require users to add repositories. They - // should have to add them in order to make sure they are aware of the - // repositories and opt-in to any locations, for security. + // + // TODO Helm v5: require users to add repositories explicitly rather than + // adding them automatically. They should have to add them in order to make + // sure they are aware of the repositories and opt-in to any locations, for + // security. repoNames, err = m.ensureMissingRepos(repoNames, req) if err != nil { return err @@ -733,22 +734,17 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]* var entry repo.ChartVersions entry, err = findEntryByName(name, cr) if err != nil { - // TODO: Where linting is skipped in this function we should - // refactor to remove naked returns while ensuring the same - // behavior - //nolint:nakedret - return + // TODO: Consider refactoring this function to reduce the number of returned values while preserving behavior. + return url, username, password, insecureSkipTLSVerify, passCredentialsAll, caFile, certFile, keyFile, err } var ve *repo.ChartVersion ve, err = findVersionedEntry(version, entry) if err != nil { - //nolint:nakedret - return + return url, username, password, insecureSkipTLSVerify, passCredentialsAll, caFile, certFile, keyFile, err } url, err = repo.ResolveReferenceURL(repoURL, ve.URLs[0]) if err != nil { - //nolint:nakedret - return + return url, username, password, insecureSkipTLSVerify, passCredentialsAll, caFile, certFile, keyFile, err } username = cr.Config.Username password = cr.Config.Password @@ -757,8 +753,8 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]* caFile = cr.Config.CAFile certFile = cr.Config.CertFile keyFile = cr.Config.KeyFile - //nolint:nakedret - return + + return url, username, password, insecureSkipTLSVerify, passCredentialsAll, caFile, certFile, keyFile, err } url, err = repo.FindChartInRepoURL(repoURL, name, m.Getters, repo.WithChartVersion(version), repo.WithClientTLS(certFile, keyFile, caFile)) if err == nil { diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 00bb4be9d..9763bc005 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -79,6 +79,8 @@ func New(config *rest.Config) Engine { // bar chart during render time. // // Deprecated: Use RenderWithContext instead. +// +//go:fix inline func (e Engine) Render(chrt ci.Charter, values common.Values) (map[string]string, error) { return e.RenderWithContext(context.Background(), chrt, values) } @@ -102,6 +104,8 @@ func (e Engine) Render(chrt ci.Charter, values common.Values) (map[string]string // that section of the values will be passed into the "foo" chart. And if that // section contains a value named "bar", that value will be passed on to the // bar chart during render time. +// +// TODO Helm v5: Rename this to Render (remove the current deprecated Render method) and 'go:fix inline' RenderWithContext method to call Render. func (e Engine) RenderWithContext(ctx context.Context, chrt ci.Charter, values common.Values) (map[string]string, error) { tmap := allTemplates(chrt, values) return e.render(ctx, tmap) @@ -109,28 +113,38 @@ func (e Engine) RenderWithContext(ctx context.Context, chrt ci.Charter, values c // Render takes a chart, optional values, and value overrides, and attempts to // render the Go templates using the default options. +// +// Deprecated: Instantiate an Engine and call RenderWithContext instead. +// +// TODO Helm v5: Replace with a NewEngine function. +// +//go:fix inline func Render(chrt ci.Charter, values common.Values) (map[string]string, error) { - return new(Engine).Render(chrt, values) + return new(Engine).RenderWithContext(context.Background(), chrt, values) } // RenderWithClient takes a chart, optional values, and value overrides, and attempts to // render the Go templates using the default options. This engine is client aware and so can have template // functions that interact with the client. +// +// TODO Helm v5: Replace with a NewEngine function that accepts a rest.Config option func RenderWithClient(chrt ci.Charter, values common.Values, config *rest.Config) (map[string]string, error) { var clientProvider ClientProvider = clientProviderFromConfig{config} return Engine{ clientProvider: &clientProvider, - }.Render(chrt, values) + }.RenderWithContext(context.Background(), chrt, values) } // RenderWithClientProvider takes a chart, optional values, and value overrides, and attempts to // render the Go templates using the default options. This engine is client aware and so can have template // functions that interact with the client. // This function differs from RenderWithClient in that it lets you customize the way a dynamic client is constructed. +// +// TODO Helm v5: Replace with a NewEngine function that accepts a ClientProvider option func RenderWithClientProvider(chrt ci.Charter, values common.Values, clientProvider ClientProvider) (map[string]string, error) { return Engine{ clientProvider: &clientProvider, - }.Render(chrt, values) + }.RenderWithContext(context.Background(), chrt, values) } // renderable is an object that can be rendered. @@ -413,7 +427,7 @@ func parseTemplateExecErrorString(s string) (TraceableError, bool) { // Special case: "template: no template %q associated with template %q" // Matches https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=191 -func parseTemplateNoTemplateError(s string, remainder string) (TraceableError, bool) { +func parseTemplateNoTemplateError(s, remainder string) (TraceableError, bool) { if strings.HasPrefix(remainder, "no template ") { return TraceableError{message: s}, true } diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go index 04a48cad3..e876df6c0 100644 --- a/pkg/engine/funcs.go +++ b/pkg/engine/funcs.go @@ -447,7 +447,7 @@ func durationWeeks(v any) float64 { // // v and m accept the same forms as asDuration (e.g. "2h13m", "30s"). // On error, it returns time.Duration(0). If m is invalid, it returns v. -func durationRoundTo(v any, m any) time.Duration { +func durationRoundTo(v, m any) time.Duration { d, err := asDuration(v) if err != nil { return 0 @@ -463,7 +463,7 @@ func durationRoundTo(v any, m any) time.Duration { // Returns a time.Duration. // // On error, it returns time.Duration(0). If m is invalid, it returns v. -func durationTruncateTo(v any, m any) time.Duration { +func durationTruncateTo(v, m any) time.Duration { d, err := asDuration(v) if err != nil { return 0 diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go index b0d0224be..df4925f05 100644 --- a/pkg/engine/lookup_func.go +++ b/pkg/engine/lookup_func.go @@ -30,7 +30,7 @@ import ( "k8s.io/client-go/rest" ) -type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]any, error) +type lookupFunc = func(apiversion, resource, namespace, name string) (map[string]any, error) // NewLookupFunction returns a function for looking up objects in the cluster. // @@ -55,7 +55,7 @@ func (c clientProviderFromConfig) GetClientFor(apiVersion, kind string) (dynamic } func newLookupFunction(ctx context.Context, clientProvider ClientProvider) lookupFunc { - return func(apiversion string, kind string, namespace string, name string) (map[string]any, error) { + return func(apiversion, kind, namespace, name string) (map[string]any, error) { var client dynamic.ResourceInterface c, namespaced, err := clientProvider.GetClientFor(apiversion, kind) if err != nil { @@ -105,7 +105,7 @@ func newLookupFunction(ctx context.Context, clientProvider ClientProvider) looku } // getDynamicClientOnKind returns a dynamic client on an Unstructured type. This client can be further namespaced. -func getDynamicClientOnKind(apiversion string, kind string, config *rest.Config) (dynamic.NamespaceableResourceInterface, bool, error) { +func getDynamicClientOnKind(apiversion, kind string, config *rest.Config) (dynamic.NamespaceableResourceInterface, bool, error) { gvk := schema.FromAPIVersionAndKind(apiversion, kind) apiRes, err := getAPIResourceForGVK(gvk, config) if err != nil { diff --git a/pkg/kube/client.go b/pkg/kube/client.go index ba881c560..03a2761f3 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -1237,8 +1237,8 @@ func upgradeClientSideFieldManager(info *resource.Info, dryRun bool, fieldValida return patched, err } -// Patch reource using server-side apply -func patchResourceServerSide(target *resource.Info, dryRun bool, forceConflicts bool, fieldValidationDirective FieldValidationDirective) error { +// Patch resource using server-side apply +func patchResourceServerSide(target *resource.Info, dryRun, forceConflicts bool, fieldValidationDirective FieldValidationDirective) error { helper := resource.NewHelper( target.Client, target.Mapping). diff --git a/pkg/kube/interface.go b/pkg/kube/interface.go index 63c784751..c9a6a0184 100644 --- a/pkg/kube/interface.go +++ b/pkg/kube/interface.go @@ -73,7 +73,7 @@ type Interface interface { // by "\n---\n") // // Validates against OpenAPI schema if validate is true. - // TODO Helm 4: Integrate into Build with an argument + // TODO Helm v5: Integrate into Build with an argument BuildTable(reader io.Reader, validate bool) (ResourceList, error) } @@ -103,7 +103,7 @@ type Waiter interface { // InterfaceWaitOptions defines an interface that extends Interface with // methods that accept wait options. // -// TODO Helm 5: Remove InterfaceWaitOptions and integrate its method(s) into the Interface. +// TODO Helm v5: Remove InterfaceWaitOptions and integrate its method(s) into the Interface. type InterfaceWaitOptions interface { // GetWaiter gets the Kube.Waiter with options. GetWaiterWithOptions(ws WaitStrategy, opts ...WaitOption) (Waiter, error) diff --git a/pkg/kube/ready_test.go b/pkg/kube/ready_test.go index c40ad1d4d..f160db75a 100644 --- a/pkg/kube/ready_test.go +++ b/pkg/kube/ready_test.go @@ -1444,7 +1444,7 @@ func newReplicationController(name string, generationInSync bool) *corev1.Replic } } -func newReplicaSet(name string, replicas int, readyReplicas int, generationInSync bool) *appsv1.ReplicaSet { +func newReplicaSet(name string, replicas, readyReplicas int, generationInSync bool) *appsv1.ReplicaSet { d := newDeployment(name, replicas, 0, 0, generationInSync) return &appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ @@ -1503,7 +1503,7 @@ func newPersistentVolumeClaim(name string, phase corev1.PersistentVolumeClaimPha } } -func newJob(name string, backoffLimit int, completions *int32, succeeded int, failed int) *batchv1.Job { +func newJob(name string, backoffLimit int, completions *int32, succeeded, failed int) *batchv1.Job { return &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: name, diff --git a/pkg/registry/chart.go b/pkg/registry/chart.go index b8b0d88b6..d0f138b18 100644 --- a/pkg/registry/chart.go +++ b/pkg/registry/chart.go @@ -108,7 +108,7 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[ } // addToMap takes an existing map and adds an item if the value is not empty -func addToMap(inputMap map[string]string, newKey string, newValue string) map[string]string { +func addToMap(inputMap map[string]string, newKey, newValue string) map[string]string { // Add item to map if its if strings.TrimSpace(newValue) != "" { inputMap[newKey] = newValue diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 672f5603a..786342c07 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -276,7 +276,7 @@ func (c *Client) Login(host string, options ...LoginOption) error { } // LoginOptBasicAuth returns a function that sets the username/password settings on login -func LoginOptBasicAuth(username string, password string) LoginOption { +func LoginOptBasicAuth(username, password string) LoginOption { return func(o *loginOperation) { o.client.username = username o.client.password = password diff --git a/pkg/registry/plugin.go b/pkg/registry/plugin.go index 959faaa7d..fa33de60a 100644 --- a/pkg/registry/plugin.go +++ b/pkg/registry/plugin.go @@ -49,7 +49,7 @@ type PluginPullResult struct { } // PullPlugin downloads a plugin from an OCI registry using artifact type -func (c *Client) PullPlugin(ref string, pluginName string, options ...PluginPullOption) (*PluginPullResult, error) { +func (c *Client) PullPlugin(ref, pluginName string, options ...PluginPullOption) (*PluginPullResult, error) { operation := &pluginPullOperation{ pluginName: pluginName, } diff --git a/pkg/release/v1/util/filter.go b/pkg/release/v1/util/filter.go index 88ac995d7..a746a9712 100644 --- a/pkg/release/v1/util/filter.go +++ b/pkg/release/v1/util/filter.go @@ -41,7 +41,7 @@ func (fn FilterFunc) Filter(rels []*rspb.Release) (rets []*rspb.Release) { rets = append(rets, rel) } } - return + return rets } // Any returns a FilterFunc that filters a list of releases diff --git a/pkg/release/v1/util/kind_sorter.go b/pkg/release/v1/util/kind_sorter.go index 01f1f801e..f112fb16e 100644 --- a/pkg/release/v1/util/kind_sorter.go +++ b/pkg/release/v1/util/kind_sorter.go @@ -137,7 +137,7 @@ func sortHooksByKind(hooks []*release.Hook, ordering KindSortOrder) []*release.H return h } -func lessByKind(_ any, _ any, kindA string, kindB string, o KindSortOrder) bool { +func lessByKind(_, _ any, kindA, kindB string, o KindSortOrder) bool { ordering := make(map[string]int, len(o)) for v, k := range o { ordering[k] = v diff --git a/pkg/repo/v1/chartrepo.go b/pkg/repo/v1/chartrepo.go index 42ed52f75..da42128bf 100644 --- a/pkg/repo/v1/chartrepo.go +++ b/pkg/repo/v1/chartrepo.go @@ -104,14 +104,15 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) { } // Create the chart list file in the cache directory - var charts strings.Builder + var charts bytes.Buffer for name := range indexFile.Entries { - fmt.Fprintln(&charts, name) + charts.WriteString(name) + charts.WriteByte('\n') // Terminate each entry with a newline } chartsFile := filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) os.MkdirAll(filepath.Dir(chartsFile), 0o755) - fileutil.AtomicWriteFile(chartsFile, bytes.NewReader([]byte(charts.String())), 0o644) + fileutil.AtomicWriteFile(chartsFile, &charts, 0o644) // Create the index file in the cache directory fname := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)) @@ -172,7 +173,7 @@ func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) FindChartInRepoURLOpt // FindChartInRepoURL finds chart in chart repository pointed by repoURL // without adding repo to repositories -func FindChartInRepoURL(repoURL string, chartName string, getters getter.Providers, options ...FindChartInRepoURLOption) (string, error) { +func FindChartInRepoURL(repoURL, chartName string, getters getter.Providers, options ...FindChartInRepoURLOption) (string, error) { opts := findChartInRepoURLOptions{} for _, option := range options { option(&opts) diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 1c1497c5e..99670eac8 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -151,7 +151,7 @@ func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string) // Add adds a file to the index and logs an error. // -// Deprecated: Use index.MustAdd instead. +// Deprecated: Use IndexFile.MustAdd instead. Add logs errors; MustAdd returns them. func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) { if err := i.MustAdd(md, filename, baseURL, digest); err != nil { slog.Error("skipping loading invalid entry for chart", "name", md.Name, "version", md.Version, "file", filename, "error", err) @@ -290,21 +290,22 @@ type ChartVersion struct { Removed bool `json:"removed,omitempty"` Digest string `json:"digest,omitempty"` - // ChecksumDeprecated is deprecated in Helm 3, and therefore ignored. Helm 3 replaced - // this with Digest. However, with a strict YAML parser enabled, a field must be - // present on the struct for backwards compatibility. + // Deprecated: ChecksumDeprecated is ignored (Helm 3 replaced it with Digest). It is + // retained only so a strict YAML parser accepts the "checksum" field for backwards + // compatibility; do not use it. ChecksumDeprecated string `json:"checksum,omitempty"` - // EngineDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict - // YAML parser enabled, this field must be present. + // Deprecated: EngineDeprecated is ignored (removed in Helm 3). It is retained only so a + // strict YAML parser accepts the "engine" field for backwards compatibility; do not use it. EngineDeprecated string `json:"engine,omitempty"` - // TillerVersionDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict - // YAML parser enabled, this field must be present. + // Deprecated: TillerVersionDeprecated is ignored (removed in Helm 3). It is retained only + // so a strict YAML parser accepts the "tillerVersion" field for backwards compatibility; + // do not use it. TillerVersionDeprecated string `json:"tillerVersion,omitempty"` - // URLDeprecated is deprecated in Helm 3, superseded by URLs. It is ignored. However, - // with a strict YAML parser enabled, this must be present on the struct. + // Deprecated: URLDeprecated is ignored (superseded by URLs). It is retained only so a + // strict YAML parser accepts the "url" field for backwards compatibility; do not use it. URLDeprecated string `json:"url,omitempty"` } diff --git a/pkg/storage/driver/labels.go b/pkg/storage/driver/labels.go index eb7118fe5..be8262b81 100644 --- a/pkg/storage/driver/labels.go +++ b/pkg/storage/driver/labels.go @@ -27,7 +27,7 @@ func (lbs labels) keys() (ls []string) { for key := range lbs { ls = append(ls, key) } - return + return ls } func (lbs labels) match(set labels) bool { diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index db076da98..6602f2ee3 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -281,7 +281,7 @@ type SQLReleaseCustomLabelWrapper struct { } // NewSQL initializes a new sql driver. -func NewSQL(connectionString string, namespace string) (*SQL, error) { +func NewSQL(connectionString, namespace string) (*SQL, error) { db, err := sqlx.Connect(postgreSQLDialect, connectionString) if err != nil { return nil, err @@ -690,7 +690,7 @@ func (s *SQL) Delete(key string) (release.Releaser, error) { } // Get release custom labels from database -func (s *SQL) getReleaseCustomLabels(key string, _ string) (map[string]string, error) { +func (s *SQL) getReleaseCustomLabels(key, _ string) (map[string]string, error) { query, args, err := s.statementBuilder. Select(sqlCustomLabelsTableKeyColumn, sqlCustomLabelsTableValueColumn). From(sqlCustomLabelsTableName). diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index 044e9df7b..e5fde405b 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -493,7 +493,7 @@ func TestSqlDelete(t *testing.T) { assert.Equalf(t, rel, deletedRelease, "Expected release {%v}, got {%v}", rel, deletedRelease) } -func mockGetReleaseCustomLabels(mock sqlmock.Sqlmock, key string, namespace string, labels map[string]string) { +func mockGetReleaseCustomLabels(mock sqlmock.Sqlmock, key, namespace string, labels map[string]string) { query := fmt.Sprintf( regexp.QuoteMeta("SELECT %s, %s FROM %s WHERE %s = $1 AND %s = $2"), sqlCustomLabelsTableKeyColumn,