Merge branch 'main' of github.com:ratulbasak/helm into list-all-releases

pull/32388/head
ratulbasak 3 months ago
commit 016c17a518

@ -74,9 +74,9 @@ linters:
errorlint: errorlint:
# Check for plain type assertions and type switches. # Check for plain type assertions and type switches.
asserts: false asserts: true
# Check for plain error comparisons. # Check for plain error comparisons.
comparison: false comparison: true
exhaustive: exhaustive:
default-signifies-exhaustive: true default-signifies-exhaustive: true

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package main // import "helm.sh/helm/v4/cmd/helm" package main
import ( import (
"errors" "errors"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package lint // import "helm.sh/helm/v4/internal/chart/v3/lint" package lint
import ( import (
"path/filepath" "path/filepath"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" package rules
import ( import (
"errors" "errors"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" package rules
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" package rules
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" package rules
import ( import (
"testing" "testing"
@ -29,14 +29,12 @@ func TestValidateNoDeprecations(t *testing.T) {
Kind: "Deployment", Kind: "Deployment",
} }
err := validateNoDeprecations(deprecated, nil) err := validateNoDeprecations(deprecated, nil)
var depErr deprecatedAPIError
require.Error(t, err, "Expected deprecated extension to be flagged") require.Error(t, err, "Expected deprecated extension to be flagged")
var depErr deprecatedAPIError
require.ErrorAs(t, err, &depErr, "Expected error to be of type deprecatedAPIError") require.ErrorAs(t, err, &depErr, "Expected error to be of type deprecatedAPIError")
require.NotEmpty(t, depErr.Message, "Expected error message to be non-blank: %v", err) require.NotEmpty(t, depErr.Message, "Expected error message to be non-blank: %v", err)
assert.NoError(t, validateNoDeprecations(&k8sYamlStruct{
err = validateNoDeprecations(&k8sYamlStruct{
APIVersion: "v1", APIVersion: "v1",
Kind: "Pod", Kind: "Pod",
}, nil) }, nil), "Expected a v1 Pod to not be deprecated")
assert.NoError(t, err, "Expected a v1 Pod to not be deprecated")
} }

@ -20,4 +20,4 @@ Package support contains tools for linting charts.
Linting is the process of testing charts for errors or warnings regarding Linting is the process of testing charts for errors or warnings regarding
formatting, compilation, or standards compliance. formatting, compilation, or standards compliance.
*/ */
package support // import "helm.sh/helm/v4/internal/chart/v3/lint/support" package support

@ -14,7 +14,7 @@ limitations under the License.
*/ */
// Package cache provides a key generator for vcs urls. // Package cache provides a key generator for vcs urls.
package cache // import "helm.sh/helm/v4/internal/plugin/cache" package cache
import ( import (
"net/url" "net/url"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"path/filepath" "path/filepath"

@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"testing" "testing"

@ -14,4 +14,4 @@ limitations under the License.
*/ */
// Package installer provides an interface for installing Helm plugins. // Package installer provides an interface for installing Helm plugins.
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"archive/tar" "archive/tar"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"bytes" "bytes"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"archive/tar" "archive/tar"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"bytes" "bytes"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"archive/tar" "archive/tar"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"archive/tar" "archive/tar"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"errors" "errors"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"fmt" "fmt"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package plugin // import "helm.sh/helm/v4/internal/plugin" package plugin
import ( import (
"context" "context"

@ -18,6 +18,7 @@ package plugin
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"log/slog" "log/slog"
@ -152,7 +153,8 @@ func (r *SubprocessPluginRuntime) InvokeHook(event string) error {
slog.Debug("executing plugin hook command", slog.String("pluginName", r.metadata.Name), slog.String("command", cmd.String())) slog.Debug("executing plugin hook command", slog.String("pluginName", r.metadata.Name), slog.String("command", cmd.String()))
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok { var eerr *exec.ExitError
if errors.As(err, &eerr) {
os.Stderr.Write(eerr.Stderr) os.Stderr.Write(eerr.Stderr)
return fmt.Errorf("plugin %s hook for %q exited with error", event, r.metadata.Name) return fmt.Errorf("plugin %s hook for %q exited with error", event, r.metadata.Name)
} }
@ -166,7 +168,8 @@ func (r *SubprocessPluginRuntime) InvokeHook(event string) error {
// then replace the other three with a call to this func // then replace the other three with a call to this func
func executeCmd(prog *exec.Cmd, pluginName string) error { func executeCmd(prog *exec.Cmd, pluginName string) error {
if err := prog.Run(); err != nil { if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok { var eerr *exec.ExitError
if errors.As(err, &eerr) {
slog.Debug( slog.Debug(
"plugin execution failed", "plugin execution failed",
slog.String("pluginName", pluginName), slog.String("pluginName", pluginName),

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package plugin // import "helm.sh/helm/v4/internal/plugin" package plugin
// Types of hooks // Types of hooks
const ( const (

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
v2 "helm.sh/helm/v4/internal/release/v2" v2 "helm.sh/helm/v4/internal/release/v2"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"sort" "sort"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"bytes" "bytes"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"sort" "sort"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"testing" "testing"

@ -21,6 +21,7 @@ limitations under the License.
package sympath package sympath
import ( import (
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
@ -40,7 +41,7 @@ func Walk(root string, walkFn filepath.WalkFunc) error {
} else { } else {
err = symwalk(root, info, walkFn) err = symwalk(root, info, walkFn)
} }
if err == filepath.SkipDir { if errors.Is(err, filepath.SkipDir) {
return nil return nil
} }
return err return err
@ -75,7 +76,7 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
if info, err = os.Lstat(resolved); err != nil { if info, err = os.Lstat(resolved); err != nil {
return err return err
} }
if err := symwalk(path, info, walkFn); err != nil && err != filepath.SkipDir { if err := symwalk(path, info, walkFn); err != nil && !errors.Is(err, filepath.SkipDir) {
return err return err
} }
return nil return nil
@ -98,13 +99,13 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
filename := filepath.Join(path, name) filename := filepath.Join(path, name)
fileInfo, err := os.Lstat(filename) fileInfo, err := os.Lstat(filename)
if err != nil { if err != nil {
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { if err := walkFn(filename, fileInfo, err); err != nil && !errors.Is(err, filepath.SkipDir) {
return err return err
} }
} else { } else {
err = symwalk(filename, fileInfo, walkFn) err = symwalk(filename, fileInfo, walkFn)
if err != nil { if err != nil {
if (!fileInfo.IsDir() && !IsSymlink(fileInfo)) || err != filepath.SkipDir { if (!fileInfo.IsDir() && !IsSymlink(fileInfo)) || !errors.Is(err, filepath.SkipDir) {
return err return err
} }
} }

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package lint // import "helm.sh/helm/v4/pkg/chart/v2/lint" package lint
import ( import (
"path/filepath" "path/filepath"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package rules // import "helm.sh/helm/v4/pkg/chart/v2/lint/rules" package rules
import ( import (
"errors" "errors"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package rules // import "helm.sh/helm/v4/pkg/chart/v2/lint/rules" package rules
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package rules // import "helm.sh/helm/v4/pkg/chart/v2/lint/rules" package rules
import ( import (
"fmt" "fmt"

@ -14,9 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package rules // import "helm.sh/helm/v4/pkg/chart/v2/lint/rules" package rules
import "testing" import (
"testing"
"github.com/stretchr/testify/require"
)
func TestValidateNoDeprecations(t *testing.T) { func TestValidateNoDeprecations(t *testing.T) {
deprecated := &k8sYamlStruct{ deprecated := &k8sYamlStruct{
@ -27,10 +31,9 @@ func TestValidateNoDeprecations(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("Expected deprecated extension to be flagged") t.Fatal("Expected deprecated extension to be flagged")
} }
depErr := err.(deprecatedAPIError) var depErr deprecatedAPIError
if depErr.Message == "" { require.ErrorAs(t, err, &depErr)
t.Fatalf("Expected error message to be non-blank: %v", err) require.NotEmptyf(t, depErr.Message, "Expected error message to be non-blank")
}
if err := validateNoDeprecations(&k8sYamlStruct{ if err := validateNoDeprecations(&k8sYamlStruct{
APIVersion: "v1", APIVersion: "v1",

@ -24,6 +24,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/pkg/chart/common" "helm.sh/helm/v4/pkg/chart/common"
chart "helm.sh/helm/v4/pkg/chart/v2" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chart/v2/lint/support" "helm.sh/helm/v4/pkg/chart/v2/lint/support"
@ -235,10 +238,9 @@ func TestDeprecatedAPIFails(t *testing.T) {
t.Fatalf("Expected 1 lint error, got %d", l) t.Fatalf("Expected 1 lint error, got %d", l)
} }
err := linter.Messages[0].Err.(deprecatedAPIError) var depErr deprecatedAPIError
if err.Deprecated != "apps/v1beta1 Deployment" { require.ErrorAs(t, linter.Messages[0].Err, &depErr)
t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated) assert.Equalf(t, "apps/v1beta1 Deployment", depErr.Deprecated, "Surprised to learn that %q is deprecated", depErr.Deprecated)
}
} }
const manifest = `apiVersion: v1 const manifest = `apiVersion: v1

@ -20,4 +20,4 @@ Package support contains tools for linting charts.
Linting is the process of testing charts for errors or warnings regarding Linting is the process of testing charts for errors or warnings regarding
formatting, compilation, or standards compliance. formatting, compilation, or standards compliance.
*/ */
package support // import "helm.sh/helm/v4/pkg/chart/v2/lint/support" package support

@ -21,6 +21,8 @@ import (
"strconv" "strconv"
"testing" "testing"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/pkg/chart/common" "helm.sh/helm/v4/pkg/chart/common"
chart "helm.sh/helm/v4/pkg/chart/v2" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chart/v2/loader" "helm.sh/helm/v4/pkg/chart/v2/loader"
@ -251,12 +253,8 @@ func TestProcessDependencyImportValues(t *testing.T) {
if err == nil { if err == nil {
t.Error("expect nil value not found but found it") t.Error("expect nil value not found but found it")
} }
switch xerr := err.(type) { var xerr common.ErrNoValue
case common.ErrNoValue: require.ErrorAs(t, err, &xerr, "expected an ErrNoValue")
// We found what we expected
default:
t.Errorf("expected an ErrNoValue but got %q instead", xerr)
}
c = loadChart(t, "testdata/subpop") c = loadChart(t, "testdata/subpop")
if err := processDependencyImportValues(c, true); err != nil { if err := processDependencyImportValues(c, true); err != nil {

@ -65,6 +65,26 @@ func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) {
cmd.Flags().Lookup("wait").NoOptDefVal = string(kube.StatusWatcherStrategy) cmd.Flags().Lookup("wait").NoOptDefVal = string(kube.StatusWatcherStrategy)
} }
// cliDefaultStatusComputeWorkers is the number of concurrent status-compute
// workers the Helm CLI enables by default. This prevents the informer
// notification pipeline from being blocked by slow API calls (e.g. LIST
// ReplicaSets/Pods for Deployments) when many resources are updated
// simultaneously. See https://github.com/fluxcd/cli-utils/pull/20.
//
// SDK consumers (e.g. helm-controller) inherit the zero value and can opt in
// via kube.WithStatusComputeWorkers when they want the same behavior.
const cliDefaultStatusComputeWorkers = 8
// defaultCLIWaitOptions returns the set of WaitOptions the Helm CLI applies
// by default to every wait-enabled command. Keeping these in one place keeps
// behavior consistent across install/upgrade/rollback/uninstall and makes the
// CLI-vs-SDK default asymmetry explicit.
func defaultCLIWaitOptions() []kube.WaitOption {
return []kube.WaitOption{
kube.WithStatusComputeWorkers(cliDefaultStatusComputeWorkers),
}
}
type waitValue kube.WaitStrategy type waitValue kube.WaitStrategy
func newWaitValue(defaultValue kube.WaitStrategy, ws *kube.WaitStrategy) *waitValue { func newWaitValue(defaultValue kube.WaitStrategy, ws *kube.WaitStrategy) *waitValue {

@ -131,6 +131,7 @@ charts in a repository, use 'helm search'.
func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewInstall(cfg) client := action.NewInstall(cfg)
client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...)
valueOpts := &values.Options{} valueOpts := &values.Options{}
var outfmt output.Format var outfmt output.Format

@ -144,16 +144,10 @@ func TestLoadCLIPlugins(t *testing.T) {
// tests until this is fixed // tests until this is fixed
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
if err := pluginCmd.RunE(pluginCmd, tt.args); err != nil { if err := pluginCmd.RunE(pluginCmd, tt.args); err != nil {
if tt.code > 0 { if assert.Positive(t, tt.code, "Error running %s: %+v", tt.use, err) {
cerr, ok := err.(CommandError) var cerr CommandError
if !ok { require.ErrorAs(t, err, &cerr, "Expected %s to return pluginError: got %v(%T)", tt.use, err, err)
t.Errorf("Expected %s to return pluginError: got %v(%T)", tt.use, err, err) assert.Equalf(t, tt.code, cerr.ExitCode, "Expected %s to return %d: got %d", tt.use, tt.code, cerr.ExitCode)
}
if cerr.ExitCode != tt.code {
t.Errorf("Expected %s to return %d: got %d", tt.use, tt.code, cerr.ExitCode)
}
} else {
t.Errorf("Error running %s: %+v", tt.use, err)
} }
} }
assert.Equal(t, tt.expect, out.String(), "expected output for %q", tt.use) assert.Equal(t, tt.expect, out.String(), "expected output for %q", tt.use)
@ -218,16 +212,10 @@ func TestLoadPluginsWithSpace(t *testing.T) {
// tests until this is fixed // tests until this is fixed
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
if err := pp.RunE(pp, tt.args); err != nil { if err := pp.RunE(pp, tt.args); err != nil {
if tt.code > 0 { if assert.Positive(t, tt.code, "Error running %s: %+v", tt.use, err) {
cerr, ok := err.(CommandError) var cerr CommandError
if !ok { require.ErrorAs(t, err, &cerr, "Expected %s to return pluginError: got %v(%T)", tt.use, err, err)
t.Errorf("Expected %s to return pluginError: got %v(%T)", tt.use, err, err) assert.Equalf(t, tt.code, cerr.ExitCode, "Expected %s to return %d: got %d", tt.use, tt.code, cerr.ExitCode)
}
if cerr.ExitCode != tt.code {
t.Errorf("Expected %s to return %d: got %d", tt.use, tt.code, cerr.ExitCode)
}
} else {
t.Errorf("Error running %s: %+v", tt.use, err)
} }
} }
assert.Equal(t, tt.expect, out.String(), "expected output for %s", tt.use) assert.Equal(t, tt.expect, out.String(), "expected output for %s", tt.use)

@ -40,6 +40,7 @@ The tests to be run are defined in the chart that was installed.
func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewReleaseTesting(cfg) client := action.NewReleaseTesting(cfg)
client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...)
outfmt := output.Table outfmt := output.Table
var outputLogs bool var outputLogs bool
var filter []string var filter []string

@ -40,6 +40,7 @@ To see revision numbers, run 'helm history RELEASE'.
func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewRollback(cfg) client := action.NewRollback(cfg)
client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "rollback <RELEASE> [REVISION]", Use: "rollback <RELEASE> [REVISION]",

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package cmd // import "helm.sh/helm/v4/pkg/cmd" package cmd
import ( import (
"context" "context"

@ -42,6 +42,7 @@ are fully deleted before the command returns.
func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewUninstall(cfg) client := action.NewUninstall(cfg)
client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "uninstall RELEASE_NAME [...]", Use: "uninstall RELEASE_NAME [...]",

@ -84,6 +84,7 @@ which can contain sensitive values. To hide Kubernetes Secrets use the
func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewUpgrade(cfg) client := action.NewUpgrade(cfg)
client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...)
valueOpts := &values.Options{} valueOpts := &values.Options{}
var outfmt output.Format var outfmt output.Format
var createNamespace bool var createNamespace bool
@ -139,6 +140,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.SkipCRDs = client.SkipCRDs instClient.SkipCRDs = client.SkipCRDs
instClient.Timeout = client.Timeout instClient.Timeout = client.Timeout
instClient.WaitStrategy = client.WaitStrategy instClient.WaitStrategy = client.WaitStrategy
instClient.WaitOptions = client.WaitOptions
instClient.WaitForJobs = client.WaitForJobs instClient.WaitForJobs = client.WaitForJobs
instClient.Devel = client.Devel instClient.Devel = client.Devel
instClient.Namespace = client.Namespace instClient.Namespace = client.Namespace

@ -21,4 +21,4 @@ When Helm renders templates it does so with additional functions and different
modes (e.g., strict, lint mode). This package handles the helm specific modes (e.g., strict, lint mode). This package handles the helm specific
implementation. implementation.
*/ */
package engine // import "helm.sh/helm/v4/pkg/engine" package engine

@ -65,4 +65,4 @@ Notable differences from .gitignore:
- The evaluation of escape sequences has not been tested for compatibility - The evaluation of escape sequences has not been tested for compatibility
- There is no support for '\!' as a special leading sequence. - There is no support for '\!' as a special leading sequence.
*/ */
package ignore // import "helm.sh/helm/v4/pkg/ignore" package ignore

@ -167,14 +167,15 @@ func (c *Client) newStatusWatcher(opts ...WaitOption) (*statusWaiter, error) {
waitContext = c.WaitContext waitContext = c.WaitContext
} }
sw := &statusWaiter{ sw := &statusWaiter{
restMapper: restMapper, restMapper: restMapper,
client: dynamicClient, client: dynamicClient,
ctx: waitContext, ctx: waitContext,
watchUntilReadyCtx: o.watchUntilReadyCtx, watchUntilReadyCtx: o.watchUntilReadyCtx,
waitCtx: o.waitCtx, waitCtx: o.waitCtx,
waitWithJobsCtx: o.waitWithJobsCtx, waitWithJobsCtx: o.waitWithJobsCtx,
waitForDeleteCtx: o.waitForDeleteCtx, waitForDeleteCtx: o.waitForDeleteCtx,
readers: o.statusReaders, readers: o.statusReaders,
statusComputeWorkers: o.statusComputeWorkers,
} }
sw.SetLogger(c.Logger().Handler()) sw.SetLogger(c.Logger().Handler())
return sw, nil return sw, nil
@ -249,7 +250,7 @@ func (c *Client) getKubeClient() (kubernetes.Interface, error) {
// IsReachable tests connectivity to the cluster. // IsReachable tests connectivity to the cluster.
func (c *Client) IsReachable() error { func (c *Client) IsReachable() error {
client, err := c.getKubeClient() client, err := c.getKubeClient()
if err == genericclioptions.ErrEmptyConfig { if errors.Is(err, genericclioptions.ErrEmptyConfig) {
// re-replace kubernetes ErrEmptyConfig error with a friendly error // re-replace kubernetes ErrEmptyConfig error with a friendly error
// moar workarounds for Kubernetes API breaking. // moar workarounds for Kubernetes API breaking.
return errors.New("kubernetes cluster unreachable") return errors.New("kubernetes cluster unreachable")
@ -949,11 +950,12 @@ func (c *Client) Delete(resources ResourceList, policy metav1.DeletionPropagatio
func isIncompatibleServerError(err error) bool { func isIncompatibleServerError(err error) bool {
// 415: Unsupported media type means we're talking to a server which doesn't // 415: Unsupported media type means we're talking to a server which doesn't
// support server-side apply. // support server-side apply.
if _, ok := err.(*apierrors.StatusError); !ok { var sErr *apierrors.StatusError
if !errors.As(err, &sErr) {
// Non-StatusError means the error isn't because the server is incompatible. // Non-StatusError means the error isn't because the server is incompatible.
return false return false
} }
return err.(*apierrors.StatusError).Status().Code == http.StatusUnsupportedMediaType return sErr.Status().Code == http.StatusUnsupportedMediaType
} }
// isServerSideRetryable checks if an error encountered during server-side apply // isServerSideRetryable checks if an error encountered during server-side apply

@ -72,11 +72,34 @@ func WithKStatusReaders(readers ...engine.StatusReader) WaitOption {
} }
} }
// WithStatusComputeWorkers sets the number of concurrent goroutines used to
// compute object status per informer. This prevents the informer notification
// pipeline from being blocked by slow API calls (e.g., LIST ReplicaSets/Pods
// for Deployments) when many resources are updated simultaneously.
//
// A value of 0 (the default) keeps the underlying cli-utils behavior, where
// status is computed synchronously on the informer goroutine. Negative values
// are clamped to 0 so callers cannot propagate invalid counts to the
// underlying watcher. SDK consumers (for example helm-controller) inherit
// this conservative default and can opt in explicitly. The Helm CLI passes
// a non-zero value so that `helm install/upgrade/rollback` users get the
// fix for multi-minute waits out of the box.
// See https://github.com/fluxcd/cli-utils/pull/20.
func WithStatusComputeWorkers(n int) WaitOption {
return func(wo *waitOptions) {
if n < 0 {
n = 0
}
wo.statusComputeWorkers = n
}
}
type waitOptions struct { type waitOptions struct {
ctx context.Context ctx context.Context
watchUntilReadyCtx context.Context watchUntilReadyCtx context.Context
waitCtx context.Context waitCtx context.Context
waitWithJobsCtx context.Context waitWithJobsCtx context.Context
waitForDeleteCtx context.Context waitForDeleteCtx context.Context
statusReaders []engine.StatusReader statusReaders []engine.StatusReader
statusComputeWorkers int
} }

@ -43,14 +43,15 @@ import (
) )
type statusWaiter struct { type statusWaiter struct {
client dynamic.Interface client dynamic.Interface
restMapper meta.RESTMapper restMapper meta.RESTMapper
ctx context.Context ctx context.Context
watchUntilReadyCtx context.Context watchUntilReadyCtx context.Context
waitCtx context.Context waitCtx context.Context
waitWithJobsCtx context.Context waitWithJobsCtx context.Context
waitForDeleteCtx context.Context waitForDeleteCtx context.Context
readers []engine.StatusReader readers []engine.StatusReader
statusComputeWorkers int
logging.LogHolder logging.LogHolder
} }
@ -82,6 +83,7 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D
defer cancel() defer cancel()
w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout)
sw := getStatusWatcher(w.client, w.restMapper) sw := getStatusWatcher(w.client, w.restMapper)
sw.StatusComputeWorkers = w.statusComputeWorkers
jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper)
podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper) podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper)
// We don't want to wait on any other resources as watchUntilReady is only for Helm hooks. // We don't want to wait on any other resources as watchUntilReady is only for Helm hooks.
@ -104,6 +106,7 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er
defer cancel() defer cancel()
w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout)
sw := getStatusWatcher(w.client, w.restMapper) sw := getStatusWatcher(w.client, w.restMapper)
sw.StatusComputeWorkers = w.statusComputeWorkers
sw.StatusReader = statusreaders.NewStatusReader(w.restMapper, w.readers...) sw.StatusReader = statusreaders.NewStatusReader(w.restMapper, w.readers...)
return w.wait(ctx, resourceList, sw) return w.wait(ctx, resourceList, sw)
} }
@ -116,6 +119,7 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura
defer cancel() defer cancel()
w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout)
sw := getStatusWatcher(w.client, w.restMapper) sw := getStatusWatcher(w.client, w.restMapper)
sw.StatusComputeWorkers = w.statusComputeWorkers
newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper)
readers := append([]engine.StatusReader(nil), w.readers...) readers := append([]engine.StatusReader(nil), w.readers...)
readers = append(readers, newCustomJobStatusReader) readers = append(readers, newCustomJobStatusReader)

@ -1293,6 +1293,28 @@ func TestWaitOptionFunctions(t *testing.T) {
WithWaitForDeleteMethodContext(ctx)(opts) WithWaitForDeleteMethodContext(ctx)(opts)
assert.Equal(t, ctx, opts.waitForDeleteCtx) assert.Equal(t, ctx, opts.waitForDeleteCtx)
}) })
t.Run("WithStatusComputeWorkers sets statusComputeWorkers", func(t *testing.T) {
t.Parallel()
opts := &waitOptions{}
WithStatusComputeWorkers(8)(opts)
assert.Equal(t, 8, opts.statusComputeWorkers)
})
t.Run("WithStatusComputeWorkers clamps negative values to zero", func(t *testing.T) {
t.Parallel()
opts := &waitOptions{}
WithStatusComputeWorkers(-1)(opts)
assert.Equal(t, 0, opts.statusComputeWorkers,
"negative worker counts must not propagate to the underlying watcher")
})
t.Run("waitOptions.statusComputeWorkers defaults to zero", func(t *testing.T) {
t.Parallel()
opts := &waitOptions{}
assert.Equal(t, 0, opts.statusComputeWorkers,
"SDK consumers must opt in to concurrent status computation")
})
} }
func TestMethodSpecificContextCancellation(t *testing.T) { func TestMethodSpecificContextCancellation(t *testing.T) {

@ -35,4 +35,4 @@ and using `gpg --verify`, `keybase pgp verify`, or similar:
gpg: Signature made Mon Jul 25 17:23:44 2016 MDT using RSA key ID 1FC18762 gpg: Signature made Mon Jul 25 17:23:44 2016 MDT using RSA key ID 1FC18762
gpg: Good signature from "Helm Testing (This key should only be used for testing. DO NOT TRUST.) <helm-testing@helm.sh>" [ultimate] gpg: Good signature from "Helm Testing (This key should only be used for testing. DO NOT TRUST.) <helm-testing@helm.sh>" [ultimate]
*/ */
package provenance // import "helm.sh/helm/v4/pkg/provenance" package provenance

@ -385,11 +385,9 @@ func TestVerify(t *testing.T) {
_, err = signer.Verify(archiveData, tamperedSigData, filepath.Base(testChartfile)) _, err = signer.Verify(archiveData, tamperedSigData, filepath.Base(testChartfile))
require.Errorf(t, err, "Expected %s to fail.", testTamperedSigBlock) require.Errorf(t, err, "Expected %s to fail.", testTamperedSigBlock)
switch err.(type) { var sErr pgperrors.SignatureError
case pgperrors.SignatureError: if assert.ErrorAs(t, err, &sErr, "Expected invalid signature error, got %q (%T)", err, err) {
t.Logf("Tampered sig block error: %s (%T)", err, err) t.Logf("Tampered sig block error: %s (%T)", sErr, sErr)
default:
t.Errorf("Expected invalid signature error, got %q (%T)", err, err)
} }
} }

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package registry // import "helm.sh/helm/v4/pkg/registry" package registry
import ( import (
"bytes" "bytes"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package registry // import "helm.sh/helm/v4/pkg/registry" package registry
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package registry // import "helm.sh/helm/v4/pkg/registry" package registry
import ( import (
"context" "context"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package registry // import "helm.sh/helm/v4/pkg/registry" package registry
const ( const (
// OCIScheme is the URL scheme for OCI-based requests // OCIScheme is the URL scheme for OCI-based requests

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package registry // import "helm.sh/helm/v4/pkg/registry" package registry
import ( import (
"fmt" "fmt"

@ -18,6 +18,7 @@ package registry
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"log/slog" "log/slog"
@ -126,7 +127,7 @@ func logResponseBody(resp *http.Response) string {
Closer: body, Closer: body,
} }
// read the body up to limit+1 to check if the body exceeds the limit // read the body up to limit+1 to check if the body exceeds the limit
if _, err := io.CopyN(buf, body, payloadSizeLimit+1); err != nil && err != io.EOF { if _, err := io.CopyN(buf, body, payloadSizeLimit+1); err != nil && !errors.Is(err, io.EOF) {
return fmt.Sprintf(" Error reading response body: %v", err) return fmt.Sprintf(" Error reading response body: %v", err)
} }

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/pkg/release/v1/util" package util
import ( import (
"helm.sh/helm/v4/pkg/release/common" "helm.sh/helm/v4/pkg/release/common"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/pkg/release/v1/util" package util
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/pkg/release/v1/util" package util
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/pkg/release/v1/util" package util
import ( import (
"sort" "sort"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/pkg/release/v1/util" package util
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package repo // import "helm.sh/helm/v4/pkg/repo/v1" package repo
import ( import (
"bytes" "bytes"

@ -421,8 +421,8 @@ func jsonOrYamlUnmarshal(b []byte, i any) error {
// And repository indexes may be generated by older/non-compliant software, which doesn't // And repository indexes may be generated by older/non-compliant software, which doesn't
// conform to all validations. // conform to all validations.
func ignoreSkippableChartValidationError(err error) error { func ignoreSkippableChartValidationError(err error) error {
verr, ok := err.(chart.ValidationError) var verr chart.ValidationError
if !ok { if !errors.As(err, &verr) {
return err return err
} }

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package repo // import "helm.sh/helm/v4/pkg/repo/v1" package repo
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package driver // import "helm.sh/helm/v4/pkg/storage/driver" package driver
import ( import (
"context" "context"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package driver // import "helm.sh/helm/v4/pkg/storage/driver" package driver
import ( import (
"errors" "errors"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package driver // import "helm.sh/helm/v4/pkg/storage/driver" package driver
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package driver // import "helm.sh/helm/v4/pkg/storage/driver" package driver
import ( import (
"context" "context"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package driver // import "helm.sh/helm/v4/pkg/storage/driver" package driver
import ( import (
"sort" "sort"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package driver // import "helm.sh/helm/v4/pkg/storage/driver" package driver
import ( import (
"reflect" "reflect"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package driver // import "helm.sh/helm/v4/pkg/storage/driver" package driver
import ( import (
"context" "context"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package driver // import "helm.sh/helm/v4/pkg/storage/driver" package driver
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package driver // import "helm.sh/helm/v4/pkg/storage/driver" package driver
import ( import (
"bytes" "bytes"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package storage // import "helm.sh/helm/v4/pkg/storage" package storage
import ( import (
"errors" "errors"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package storage // import "helm.sh/helm/v4/pkg/storage" package storage
import ( import (
"context" "context"

@ -238,16 +238,16 @@ func (t *parser) key(data map[string]any, nestedNameLevel int) (reterr error) {
// End of key. Consume =, Get value. // End of key. Consume =, Get value.
// FIXME: Get value list first // FIXME: Get value list first
vl, e := t.valList() vl, e := t.valList()
switch e { switch {
case nil: case e == nil:
set(data, string(k), vl) set(data, string(k), vl)
return nil return nil
case io.EOF: case errors.Is(e, io.EOF):
set(data, string(k), "") set(data, string(k), "")
return e return e
case ErrNotList: case errors.Is(e, ErrNotList):
rs, e := t.val() rs, e := t.val()
if e != nil && e != io.EOF { if e != nil && !errors.Is(e, io.EOF) {
return e return e
} }
v, e := t.reader(rs) v, e := t.reader(rs)
@ -370,14 +370,14 @@ func (t *parser) listItem(list []any, i, nestedNameLevel int) ([]any, error) {
return list, err return list, err
} }
vl, e := t.valList() vl, e := t.valList()
switch e { switch {
case nil: case e == nil:
return setIndex(list, i, vl) return setIndex(list, i, vl)
case io.EOF: case errors.Is(e, io.EOF):
return setIndex(list, i, "") return setIndex(list, i, "")
case ErrNotList: case errors.Is(e, ErrNotList):
rs, e := t.val() rs, e := t.val()
if e != nil && e != io.EOF { if e != nil && !errors.Is(e, io.EOF) {
return list, e return list, e
} }
v, e := t.reader(rs) v, e := t.reader(rs)
@ -476,7 +476,7 @@ func (t *parser) valList() ([]any, error) {
for { for {
switch rs, last, err := runesUntil(t.sc, stop); { switch rs, last, err := runesUntil(t.sc, stop); {
case err != nil: case err != nil:
if err == io.EOF { if errors.Is(err, io.EOF) {
err = errors.New("list must terminate with '}'") err = errors.New("list must terminate with '}'")
} }
return list, err return list, err

Loading…
Cancel
Save