diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 31e3c50f5..627a5b6f0 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -25,12 +25,14 @@ package cli import ( "fmt" + "log/slog" "net/http" "os" "strconv" "strings" "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/rest" @@ -119,8 +121,8 @@ func New() *EnvSettings { BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit), QPS: envFloat32Or("HELM_QPS", defaultQPS), ColorMode: envColorMode(), - MaxChartSize: envInt64Or("HELM_MAX_CHART_SIZE", 100*1024*1024), // 100 MiB - MaxChartFileSize: envInt64Or("HELM_MAX_FILE_SIZE", 5*1024*1024), // 5 MiB + MaxChartSize: envInt64OrQuantityBytes("HELM_MAX_CHART_SIZE", 100*1024*1024), // 100 MiB + MaxChartFileSize: envInt64OrQuantityBytes("HELM_MAX_FILE_SIZE", 5*1024*1024), // 5 MiB } env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) @@ -220,18 +222,36 @@ func envFloat32Or(name string, def float32) float32 { return float32(ret) } -// We want to handle int64 like returned by https://pkg.go.dev/io/fs#FileInfo -func envInt64Or(name string, def int64) int64 { +// Tries to parse as a k8s Quantity first, falls back to plain int64 parsing. +func envInt64OrQuantityBytes(name string, def int64) int64 { if name == "" { return def } - envVal := envOr(name, strconv.FormatInt(def, 10)) - ret, err := strconv.ParseInt(envVal, 10, 64) - if err != nil { - fmt.Fprintf(os.Stderr, "Warning: Environment variable %s has invalid value %q (expected an integer): %v\n", name, envVal, err) + envVal := os.Getenv(name) + if envVal == "" { return def } - return ret + + envVal = strings.TrimSpace(envVal) + + if q, err := resource.ParseQuantity(envVal); err == nil { + if v, ok := q.AsInt64(); ok { + return v + } + f := q.AsApproximateFloat64() + if f > 0 && f < float64(^uint64(0)>>1) { + return int64(f) + } + slog.Warn("Environment variable %s is too large to fit in int64: %q", name, envVal) + return def + } + + if v, err := strconv.ParseInt(envVal, 10, 64); err == nil { + return v + } + + slog.Warn("Environment variable %s has invalid value %q (expected int or k8s Quantity like 512Mi): using default %d", name, envVal, def) + return def } func envCSV(name string) (ls []string) { diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index 76ee15af6..3d8b39b2f 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -242,7 +242,7 @@ func TestEnvOrBool(t *testing.T) { } } -func TestEnvInt64Or(t *testing.T) { +func TestEnvInt64OrQuantityBytes(t *testing.T) { envName := "TEST_ENV_INT64" tests := []struct { @@ -253,9 +253,9 @@ func TestEnvInt64Or(t *testing.T) { expected int64 }{ { - name: "empty env with default", + name: "empty env name uses default", env: "", - val: "", + val: "999", def: 100, expected: 100, }, @@ -280,16 +280,78 @@ func TestEnvInt64Or(t *testing.T) { def: 200, expected: 200, }, + + // Quantity cases (bytes) + { + name: "quantity Mi", + env: envName, + val: "512Mi", + def: 100, + expected: 512 * 1024 * 1024, + }, + { + name: "quantity Gi", + env: envName, + val: "2Gi", + def: 100, + expected: 2 * 1024 * 1024 * 1024, + }, + { + name: "quantity Ki", + env: envName, + val: "4096Ki", + def: 100, + expected: 4096 * 1024, + }, + { + name: "decimal SI 1G (base10)", + env: envName, + val: "1G", + def: 100, + // 1G in decimal SI is 1,000,000,000 bytes + expected: 1_000_000_000, + }, + { + name: "decimal SI 500M (base10)", + env: envName, + val: "500M", + def: 100, + expected: 500_000_000, + }, + { + name: "lowercase suffix returns default with error message", + env: envName, + val: "1gi", + def: 100, + expected: 100, // Returns default but prints error about uppercase requirement + }, + { + name: "whitespace trimmed", + env: envName, + val: " 256Mi ", + def: 100, + expected: 256 * 1024 * 1024, + }, + { + name: "too large to fit in int64 returns default", + env: envName, + // ~9.22e18 is max int64; use larger than that to trigger overflow handling. + val: "10000000000Gi", // 10,000,000,000 * 1024^3 bytes ≈ 1.07e22 + def: 1234, + expected: 1234, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + // Clear previous value to avoid bleed between tests + t.Setenv(envName, "") if tt.env != "" { t.Setenv(tt.env, tt.val) } - actual := envInt64Or(tt.env, tt.def) + actual := envInt64OrQuantityBytes(tt.env, tt.def) if actual != tt.expected { - t.Errorf("expected result %d, got %d", tt.expected, actual) + t.Errorf("expected result %d, got %d (env=%q val=%q def=%d)", tt.expected, actual, tt.env, tt.val, tt.def) } }) }