refactor: Replace action 'DryRun' string with DryRunStrategy type + deprecations

Signed-off-by: George Jenkins <gvjenkins@gmail.com>
pull/30833/head
George Jenkins 5 months ago
parent 5b43b744b8
commit f21b143bef

@ -70,6 +70,21 @@ var (
errPending = errors.New("another operation (install/upgrade/rollback) is in progress") errPending = errors.New("another operation (install/upgrade/rollback) is in progress")
) )
type DryRunStrategy string
const (
// DryRunNone indicates the client will make all mutating calls
DryRunNone DryRunStrategy = "none"
// DryRunClient, or client-side dry-run, indicates the client will avoid
// making calls to the server
DryRunClient DryRunStrategy = "client"
// DryRunServer, or server-side dry-run, indicates the client will send
// calls to the APIServer with the dry-run parameter to prevent persisting changes
DryRunServer DryRunStrategy = "server"
)
// Configuration injects the dependencies that all actions share. // Configuration injects the dependencies that all actions share.
type Configuration struct { type Configuration struct {
// RESTClientGetter is an interface that loads Kubernetes clients. // RESTClientGetter is an interface that loads Kubernetes clients.
@ -528,3 +543,13 @@ func determineReleaseSSApplyMethod(serverSideApply bool) release.ApplyMethod {
} }
return release.ApplyMethodClientSideApply return release.ApplyMethodClientSideApply
} }
// isDryRun returns true if the strategy is set to run as a DryRun
func isDryRun(strategy DryRunStrategy) bool {
return strategy == DryRunClient || strategy == DryRunServer
}
// interactWithServer determine whether or not to interact with a remote Kubernetes server
func interactWithServer(strategy DryRunStrategy) bool {
return strategy == DryRunNone || strategy == DryRunServer
}

@ -951,3 +951,15 @@ func TestDetermineReleaseSSAApplyMethod(t *testing.T) {
assert.Equal(t, release.ApplyMethodClientSideApply, determineReleaseSSApplyMethod(false)) assert.Equal(t, release.ApplyMethodClientSideApply, determineReleaseSSApplyMethod(false))
assert.Equal(t, release.ApplyMethodServerSideApply, determineReleaseSSApplyMethod(true)) assert.Equal(t, release.ApplyMethodServerSideApply, determineReleaseSSApplyMethod(true))
} }
func TestIsDryRun(t *testing.T) {
assert.False(t, isDryRun(DryRunNone))
assert.True(t, isDryRun(DryRunClient))
assert.True(t, isDryRun(DryRunServer))
}
func TestInteractWithServer(t *testing.T) {
assert.True(t, interactWithServer(DryRunNone))
assert.False(t, interactWithServer(DryRunClient))
assert.True(t, interactWithServer(DryRunServer))
}

@ -75,7 +75,6 @@ type Install struct {
ChartPathOptions ChartPathOptions
ClientOnly bool
// ForceReplace will, if set to `true`, ignore certain warnings and perform the install anyway. // ForceReplace will, if set to `true`, ignore certain warnings and perform the install anyway.
// //
// This should be used with caution. // This should be used with caution.
@ -87,8 +86,8 @@ type Install struct {
// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/ // see: https://kubernetes.io/docs/reference/using-api/server-side-apply/
ServerSideApply bool ServerSideApply bool
CreateNamespace bool CreateNamespace bool
DryRun bool // DryRunStrategy can be set to prepare, but not execute the operation and whether or not to interact with the remote cluster
DryRunOption string DryRunStrategy DryRunStrategy
// HideSecret can be set to true when DryRun is enabled in order to hide // HideSecret can be set to true when DryRun is enabled in order to hide
// Kubernetes Secrets in the output. It cannot be used outside of DryRun. // Kubernetes Secrets in the output. It cannot be used outside of DryRun.
HideSecret bool HideSecret bool
@ -159,6 +158,7 @@ func NewInstall(cfg *Configuration) *Install {
in := &Install{ in := &Install{
cfg: cfg, cfg: cfg,
ServerSideApply: true, ServerSideApply: true,
DryRunStrategy: DryRunNone,
} }
in.registryClient = cfg.RegistryClient in.registryClient = cfg.RegistryClient
@ -264,8 +264,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
return nil, errors.New("invalid chart apiVersion") return nil, errors.New("invalid chart apiVersion")
} }
// Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`) if interactWithServer(i.DryRunStrategy) {
if !i.ClientOnly {
if err := i.cfg.KubeClient.IsReachable(); err != nil { if err := i.cfg.KubeClient.IsReachable(); err != nil {
slog.Error(fmt.Sprintf("cluster reachability check failed: %v", err)) slog.Error(fmt.Sprintf("cluster reachability check failed: %v", err))
return nil, fmt.Errorf("cluster reachability check failed: %w", err) return nil, fmt.Errorf("cluster reachability check failed: %w", err)
@ -273,7 +272,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
} }
// HideSecret must be used with dry run. Otherwise, return an error. // HideSecret must be used with dry run. Otherwise, return an error.
if !i.isDryRun() && i.HideSecret { if !isDryRun(i.DryRunStrategy) && i.HideSecret {
slog.Error("hiding Kubernetes secrets requires a dry-run mode") slog.Error("hiding Kubernetes secrets requires a dry-run mode")
return nil, errors.New("hiding Kubernetes secrets requires a dry-run mode") return nil, errors.New("hiding Kubernetes secrets requires a dry-run mode")
} }
@ -288,23 +287,18 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
return nil, fmt.Errorf("chart dependencies processing failed: %w", err) return nil, fmt.Errorf("chart dependencies processing failed: %w", err)
} }
var interactWithRemote bool
if !i.isDryRun() || i.DryRunOption == "server" || i.DryRunOption == "none" || i.DryRunOption == "false" {
interactWithRemote = true
}
// Pre-install anything in the crd/ directory. We do this before Helm // Pre-install anything in the crd/ directory. We do this before Helm
// contacts the upstream server and builds the capabilities object. // contacts the upstream server and builds the capabilities object.
if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 { if crds := chrt.CRDObjects(); interactWithServer(i.DryRunStrategy) && !i.SkipCRDs && len(crds) > 0 {
// On dry run, bail here // On dry run, bail here
if i.isDryRun() { if isDryRun(i.DryRunStrategy) {
slog.Warn("This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") slog.Warn("This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.")
} else if err := i.installCRDs(crds); err != nil { } else if err := i.installCRDs(crds); err != nil {
return nil, err return nil, err
} }
} }
if i.ClientOnly { if !interactWithServer(i.DryRunStrategy) {
// Add mock objects in here so it doesn't use Kube API server // Add mock objects in here so it doesn't use Kube API server
// NOTE(bacongobbler): used for `helm template` // NOTE(bacongobbler): used for `helm template`
i.cfg.Capabilities = common.DefaultCapabilities.Copy() i.cfg.Capabilities = common.DefaultCapabilities.Copy()
@ -317,7 +311,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
mem := driver.NewMemory() mem := driver.NewMemory()
mem.SetNamespace(i.Namespace) mem.SetNamespace(i.Namespace)
i.cfg.Releases = storage.Init(mem) i.cfg.Releases = storage.Init(mem)
} else if !i.ClientOnly && len(i.APIVersions) > 0 { } else if interactWithServer(i.DryRunStrategy) && len(i.APIVersions) > 0 {
slog.Debug("API Version list given outside of client only mode, this list will be ignored") slog.Debug("API Version list given outside of client only mode, this list will be ignored")
} }
@ -333,7 +327,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
} }
// special case for helm template --is-upgrade // special case for helm template --is-upgrade
isUpgrade := i.IsUpgrade && i.isDryRun() isUpgrade := i.IsUpgrade && isDryRun(i.DryRunStrategy)
options := common.ReleaseOptions{ options := common.ReleaseOptions{
Name: i.ReleaseName, Name: i.ReleaseName,
Namespace: i.Namespace, Namespace: i.Namespace,
@ -353,7 +347,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
rel := i.createRelease(chrt, vals, i.Labels) rel := i.createRelease(chrt, vals, i.Labels)
var manifestDoc *bytes.Buffer var manifestDoc *bytes.Buffer
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithRemote, i.EnableDNS, i.HideSecret) rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithServer(i.DryRunStrategy), i.EnableDNS, i.HideSecret)
// Even for errors, attach this if available // Even for errors, attach this if available
if manifestDoc != nil { if manifestDoc != nil {
rel.Manifest = manifestDoc.String() rel.Manifest = manifestDoc.String()
@ -386,7 +380,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
// we'll end up in a state where we will delete those resources upon // we'll end up in a state where we will delete those resources upon
// deleting the release because the manifest will be pointing at that // deleting the release because the manifest will be pointing at that
// resource // resource
if !i.ClientOnly && !isUpgrade && len(resources) > 0 { if interactWithServer(i.DryRunStrategy) && !isUpgrade && len(resources) > 0 {
if i.TakeOwnership { if i.TakeOwnership {
toBeAdopted, err = requireAdoption(resources) toBeAdopted, err = requireAdoption(resources)
} else { } else {
@ -398,7 +392,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
} }
// Bail out here if it is a dry run // Bail out here if it is a dry run
if i.isDryRun() { if isDryRun(i.DryRunStrategy) {
rel.Info.Description = "Dry run complete" rel.Info.Description = "Dry run complete"
return rel, nil return rel, nil
} }
@ -480,14 +474,6 @@ func (i *Install) getGoroutineCount() int32 {
return i.goroutineCount.Load() return i.goroutineCount.Load()
} }
// isDryRun returns true if Upgrade is set to run as a DryRun
func (i *Install) isDryRun() bool {
if i.DryRun || i.DryRunOption == "client" || i.DryRunOption == "server" || i.DryRunOption == "true" {
return true
}
return false
}
func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) (*release.Release, error) { func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) (*release.Release, error) {
var err error var err error
// pre-install hooks // pre-install hooks
@ -590,7 +576,7 @@ func (i *Install) availableName() error {
return fmt.Errorf("release name %q: %w", start, err) return fmt.Errorf("release name %q: %w", start, err)
} }
// On dry run, bail here // On dry run, bail here
if i.isDryRun() { if isDryRun(i.DryRunStrategy) {
return nil return nil
} }

@ -249,16 +249,6 @@ func TestInstallReleaseWithValues(t *testing.T) {
is.Equal(expectedUserValues, rel.Config) is.Equal(expectedUserValues, rel.Config)
} }
func TestInstallReleaseClientOnly(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ClientOnly = true
instAction.Run(buildChart(), nil) // disregard output
is.Equal(instAction.cfg.Capabilities, common.DefaultCapabilities)
is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: io.Discard})
}
func TestInstallRelease_NoName(t *testing.T) { func TestInstallRelease_NoName(t *testing.T) {
instAction := installAction(t) instAction := installAction(t)
instAction.ReleaseName = "" instAction.ReleaseName = ""
@ -355,10 +345,12 @@ func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) {
is.Equal(rel.Info.Description, "Install complete") is.Equal(rel.Info.Description, "Install complete")
} }
func TestInstallRelease_DryRun(t *testing.T) { func TestInstallRelease_DryRunClient(t *testing.T) {
for _, dryRunStrategy := range []DryRunStrategy{DryRunClient, DryRunServer} {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.DryRun = true instAction.DryRunStrategy = dryRunStrategy
vals := map[string]interface{}{} vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withSampleTemplates()), vals) res, err := instAction.Run(buildChart(withSampleTemplates()), vals)
if err != nil { if err != nil {
@ -376,6 +368,7 @@ func TestInstallRelease_DryRun(t *testing.T) {
is.Len(res.Hooks, 1) is.Len(res.Hooks, 1)
is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "expect hook to not be marked as run") is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "expect hook to not be marked as run")
is.Equal(res.Info.Description, "Dry run complete") is.Equal(res.Info.Description, "Dry run complete")
}
} }
func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { func TestInstallRelease_DryRunHiddenSecret(t *testing.T) {
@ -383,7 +376,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) {
instAction := installAction(t) instAction := installAction(t)
// First perform a normal dry-run with the secret and confirm its presence. // First perform a normal dry-run with the secret and confirm its presence.
instAction.DryRun = true instAction.DryRunStrategy = DryRunClient
vals := map[string]interface{}{} vals := map[string]interface{}{}
res, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) res, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals)
if err != nil { if err != nil {
@ -410,7 +403,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) {
is.Equal(res2.Info.Description, "Dry run complete") is.Equal(res2.Info.Description, "Dry run complete")
// Ensure there is an error when HideSecret True but not in a dry-run mode // Ensure there is an error when HideSecret True but not in a dry-run mode
instAction.DryRun = false instAction.DryRunStrategy = DryRunNone
vals = map[string]interface{}{} vals = map[string]interface{}{}
_, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) _, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals)
if err == nil { if err == nil {
@ -422,7 +415,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) {
func TestInstallRelease_DryRun_Lookup(t *testing.T) { func TestInstallRelease_DryRun_Lookup(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.DryRun = true instAction.DryRunStrategy = DryRunNone
vals := map[string]interface{}{} vals := map[string]interface{}{}
mockChart := buildChart(withSampleTemplates()) mockChart := buildChart(withSampleTemplates())
@ -442,7 +435,7 @@ func TestInstallRelease_DryRun_Lookup(t *testing.T) {
func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.DryRun = true instAction.DryRunStrategy = DryRunNone
vals := map[string]interface{}{} vals := map[string]interface{}{}
_, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals) _, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals)
expectedErr := `hello/templates/incorrect:1:10 expectedErr := `hello/templates/incorrect:1:10
@ -1001,7 +994,6 @@ func TestInstallRun_UnreachableKubeClient(t *testing.T) {
config.KubeClient = &failingKubeClient config.KubeClient = &failingKubeClient
instAction := NewInstall(config) instAction := NewInstall(config)
instAction.ClientOnly = false
ctx, done := context.WithCancel(t.Context()) ctx, done := context.WithCancel(t.Context())
chrt := buildChart() chrt := buildChart()
res, err := instAction.RunWithContext(ctx, chrt, nil) res, err := instAction.RunWithContext(ctx, chrt, nil)

@ -39,7 +39,8 @@ type Rollback struct {
WaitStrategy kube.WaitStrategy WaitStrategy kube.WaitStrategy
WaitForJobs bool WaitForJobs bool
DisableHooks bool DisableHooks bool
DryRun bool // DryRunStrategy can be set to prepare, but not execute the operation and whether or not to interact with the remote cluster
DryRunStrategy DryRunStrategy
// ForceReplace will, if set to `true`, ignore certain warnings and perform the rollback anyway. // ForceReplace will, if set to `true`, ignore certain warnings and perform the rollback anyway.
// //
// This should be used with caution. // This should be used with caution.
@ -60,6 +61,7 @@ type Rollback struct {
func NewRollback(cfg *Configuration) *Rollback { func NewRollback(cfg *Configuration) *Rollback {
return &Rollback{ return &Rollback{
cfg: cfg, cfg: cfg,
DryRunStrategy: DryRunNone,
} }
} }
@ -77,7 +79,7 @@ func (r *Rollback) Run(name string) error {
return err return err
} }
if !r.DryRun { if !isDryRun(r.DryRunStrategy) {
slog.Debug("creating rolled back release", "name", name) slog.Debug("creating rolled back release", "name", name)
if err := r.cfg.Releases.Create(targetRelease); err != nil { if err := r.cfg.Releases.Create(targetRelease); err != nil {
return err return err
@ -89,7 +91,7 @@ func (r *Rollback) Run(name string) error {
return err return err
} }
if !r.DryRun { if !isDryRun(r.DryRunStrategy) {
slog.Debug("updating status for rolled back release", "name", name) slog.Debug("updating status for rolled back release", "name", name)
if err := r.cfg.Releases.Update(targetRelease); err != nil { if err := r.cfg.Releases.Update(targetRelease); err != nil {
return err return err
@ -175,7 +177,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
} }
func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release, serverSideApply bool) (*release.Release, error) { func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release, serverSideApply bool) (*release.Release, error) {
if r.DryRun { if isDryRun(r.DryRunStrategy) {
slog.Debug("dry run", "name", targetRelease.Name) slog.Debug("dry run", "name", targetRelease.Name)
return targetRelease, nil return targetRelease, nil
} }

@ -73,10 +73,8 @@ type Upgrade struct {
WaitForJobs bool WaitForJobs bool
// DisableHooks disables hook processing if set to true. // DisableHooks disables hook processing if set to true.
DisableHooks bool DisableHooks bool
// DryRun controls whether the operation is prepared, but not executed. // DryRunStrategy can be set to prepare, but not execute the operation and whether or not to interact with the remote cluster
DryRun bool DryRunStrategy DryRunStrategy
// DryRunOption controls whether the operation is prepared, but not executed with options on whether or not to interact with the remote cluster.
DryRunOption string
// HideSecret can be set to true when DryRun is enabled in order to hide // HideSecret can be set to true when DryRun is enabled in order to hide
// Kubernetes Secrets in the output. It cannot be used outside of DryRun. // Kubernetes Secrets in the output. It cannot be used outside of DryRun.
HideSecret bool HideSecret bool
@ -140,6 +138,7 @@ func NewUpgrade(cfg *Configuration) *Upgrade {
up := &Upgrade{ up := &Upgrade{
cfg: cfg, cfg: cfg,
ServerSideApply: "auto", ServerSideApply: "auto",
DryRunStrategy: DryRunNone,
} }
up.registryClient = cfg.RegistryClient up.registryClient = cfg.RegistryClient
@ -198,7 +197,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Char
} }
// Do not update for dry runs // Do not update for dry runs
if !u.isDryRun() { if !isDryRun(u.DryRunStrategy) {
slog.Debug("updating status for upgraded release", "name", name) slog.Debug("updating status for upgraded release", "name", name)
if err := u.cfg.Releases.Update(upgradedRelease); err != nil { if err := u.cfg.Releases.Update(upgradedRelease); err != nil {
return res, err return res, err
@ -208,14 +207,6 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Char
return res, nil return res, nil
} }
// isDryRun returns true if Upgrade is set to run as a DryRun
func (u *Upgrade) isDryRun() bool {
if u.DryRun || u.DryRunOption == "client" || u.DryRunOption == "server" || u.DryRunOption == "true" {
return true
}
return false
}
// prepareUpgrade builds an upgraded release for an upgrade operation. // prepareUpgrade builds an upgraded release for an upgrade operation.
func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[string]interface{}) (*release.Release, *release.Release, bool, error) { func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[string]interface{}) (*release.Release, *release.Release, bool, error) {
if chart == nil { if chart == nil {
@ -223,7 +214,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
} }
// HideSecret must be used with dry run. Otherwise, return an error. // HideSecret must be used with dry run. Otherwise, return an error.
if !u.isDryRun() && u.HideSecret { if !isDryRun(u.DryRunStrategy) && u.HideSecret {
return nil, nil, false, errors.New("hiding Kubernetes secrets requires a dry-run mode") return nil, nil, false, errors.New("hiding Kubernetes secrets requires a dry-run mode")
} }
@ -289,13 +280,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
return nil, nil, false, err return nil, nil, false, err
} }
// Determine whether or not to interact with remote hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithServer(u.DryRunStrategy), u.EnableDNS, u.HideSecret)
var interactWithRemote bool
if !u.isDryRun() || u.DryRunOption == "server" || u.DryRunOption == "none" || u.DryRunOption == "false" {
interactWithRemote = true
}
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithRemote, u.EnableDNS, u.HideSecret)
if err != nil { if err != nil {
return nil, nil, false, err return nil, nil, false, err
} }
@ -391,8 +376,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
return nil return nil
}) })
// Run if it is a dry run if isDryRun(u.DryRunStrategy) {
if u.isDryRun() {
slog.Debug("dry run for release", "name", upgradedRelease.Name) slog.Debug("dry run for release", "name", upgradedRelease.Name)
if len(u.Description) > 0 { if len(u.Description) > 0 {
upgradedRelease.Info.Description = u.Description upgradedRelease.Info.Description = u.Description

@ -545,7 +545,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
rel.Info.Status = release.StatusDeployed rel.Info.Status = release.StatusDeployed
req.NoError(upAction.cfg.Releases.Create(rel)) req.NoError(upAction.cfg.Releases.Create(rel))
upAction.DryRun = true upAction.DryRunStrategy = DryRunClient
vals := map[string]interface{}{} vals := map[string]interface{}{}
ctx, done := context.WithCancel(t.Context()) ctx, done := context.WithCancel(t.Context())
@ -577,7 +577,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
is.Equal(1, lastRelease.Version) is.Equal(1, lastRelease.Version)
// Ensure in a dry run mode when using HideSecret // Ensure in a dry run mode when using HideSecret
upAction.DryRun = false upAction.DryRunStrategy = DryRunNone
vals = map[string]interface{}{} vals = map[string]interface{}{}
ctx, done = context.WithCancel(t.Context()) ctx, done = context.WithCancel(t.Context())

@ -0,0 +1,83 @@
/*
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 (
"fmt"
"log/slog"
"strconv"
"github.com/spf13/cobra"
"helm.sh/helm/v4/pkg/action"
)
func addDryRunFlag(cmd *cobra.Command) {
// --dry-run options with expected outcome:
// - Not set means no dry run and server is contacted.
// - Set with no value, a value of client, or a value of true and the server is not contacted
// - Set with a value of false, none, or false and the server is contacted
// The true/false part is meant to reflect some legacy behavior while none is equal to "".
f := cmd.Flags()
f.String(
"dry-run",
"none",
`simulates the operation without persisting changes. Must be one of: "none" (default), "client", or "server". '--dry-run=none' executes the operation normally and persists changes (no simulation). '--dry-run=client' simulates the operation client-side only and avoids cluster connections. '--dry-run=server' simulates the operation on the server, requiring cluster connectivity.`)
f.Lookup("dry-run").NoOptDefVal = "unset"
}
// Determine the `action.DryRunStrategy` given -dry-run=<value>` flag (or absence of)
// Legacy usage of the flag: boolean values, and `--dry-run` (without value) are supported, and log warnings emitted
func cmdGetDryRunFlagStrategy(cmd *cobra.Command, isTemplate bool) (action.DryRunStrategy, error) {
f := cmd.Flag("dry-run")
v := f.Value.String()
switch v {
case f.NoOptDefVal:
slog.Warn(`--dry-run is deprecated and should be replaced with '--dry-run=client'`)
return action.DryRunClient, nil
case string(action.DryRunClient):
return action.DryRunClient, nil
case string(action.DryRunServer):
return action.DryRunServer, nil
case string(action.DryRunNone):
if isTemplate {
// Special case hack for `helm template`, which is always a dry run
return action.DryRunNone, fmt.Errorf(`invalid dry-run value (%q). Must be "server" or "client"`, v)
}
return action.DryRunNone, nil
}
b, err := strconv.ParseBool(v)
if err != nil {
return action.DryRunNone, fmt.Errorf(`invalid dry-run value (%q). Must be "none", "server", or "client"`, v)
}
if isTemplate && !b {
// Special case for `helm template`, which is always a dry run
return action.DryRunNone, fmt.Errorf(`invalid dry-run value (%q). Must be "server" or "client"`, v)
}
result := action.DryRunNone
if b {
result = action.DryRunClient
}
slog.Warn(fmt.Sprintf(`boolean '--dry-run=%v' flag is deprecated and must be replaced with '--dry-run=%s'`, v, result))
return result, nil
}

@ -18,7 +18,9 @@ package cmd
import ( import (
"bytes" "bytes"
"encoding/json"
"io" "io"
"log/slog"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -26,6 +28,8 @@ import (
shellwords "github.com/mattn/go-shellwords" shellwords "github.com/mattn/go-shellwords"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/internal/test"
"helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/action"
@ -151,3 +155,155 @@ func resetEnv() func() {
settings = cli.New() settings = cli.New()
} }
} }
func TestCmdGetDryRunFlagStrategy(t *testing.T) {
type testCaseExpectedLog struct {
Level string
Msg string
}
testCases := map[string]struct {
DryRunFlagArg string
IsTemplate bool
ExpectedStrategy action.DryRunStrategy
ExpectedError bool
ExpectedLog *testCaseExpectedLog
}{
"unset_value": {
DryRunFlagArg: "--dry-run",
ExpectedStrategy: action.DryRunClient,
ExpectedLog: &testCaseExpectedLog{
Level: "WARN",
Msg: `--dry-run is deprecated and should be replaced with '--dry-run=client'`,
},
},
"unset_special": {
DryRunFlagArg: "--dry-run=unset", // Special value that matches cmd.Flags("dry-run").NoOptDefVal
ExpectedStrategy: action.DryRunClient,
ExpectedLog: &testCaseExpectedLog{
Level: "WARN",
Msg: `--dry-run is deprecated and should be replaced with '--dry-run=client'`,
},
},
"none": {
DryRunFlagArg: "--dry-run=none",
ExpectedStrategy: action.DryRunNone,
},
"client": {
DryRunFlagArg: "--dry-run=client",
ExpectedStrategy: action.DryRunClient,
},
"server": {
DryRunFlagArg: "--dry-run=server",
ExpectedStrategy: action.DryRunServer,
},
"bool_false": {
DryRunFlagArg: "--dry-run=false",
ExpectedStrategy: action.DryRunNone,
ExpectedLog: &testCaseExpectedLog{
Level: "WARN",
Msg: `boolean '--dry-run=false' flag is deprecated and must be replaced with '--dry-run=none'`,
},
},
"bool_true": {
DryRunFlagArg: "--dry-run=true",
ExpectedStrategy: action.DryRunClient,
ExpectedLog: &testCaseExpectedLog{
Level: "WARN",
Msg: `boolean '--dry-run=true' flag is deprecated and must be replaced with '--dry-run=client'`,
},
},
"bool_0": {
DryRunFlagArg: "--dry-run=0",
ExpectedStrategy: action.DryRunNone,
ExpectedLog: &testCaseExpectedLog{
Level: "WARN",
Msg: `boolean '--dry-run=0' flag is deprecated and must be replaced with '--dry-run=none'`,
},
},
"bool_1": {
DryRunFlagArg: "--dry-run=1",
ExpectedStrategy: action.DryRunClient,
ExpectedLog: &testCaseExpectedLog{
Level: "WARN",
Msg: `boolean '--dry-run=1' flag is deprecated and must be replaced with '--dry-run=client'`,
},
},
"invalid": {
DryRunFlagArg: "--dry-run=invalid",
ExpectedError: true,
},
"template_unset_value": {
DryRunFlagArg: "--dry-run",
IsTemplate: true,
ExpectedStrategy: action.DryRunClient,
ExpectedLog: &testCaseExpectedLog{
Level: "WARN",
Msg: `--dry-run is deprecated and should be replaced with '--dry-run=client'`,
},
},
"template_bool_false": {
DryRunFlagArg: "--dry-run=false",
IsTemplate: true,
ExpectedError: true,
},
"template_bool_template_true": {
DryRunFlagArg: "--dry-run=true",
IsTemplate: true,
ExpectedStrategy: action.DryRunClient,
ExpectedLog: &testCaseExpectedLog{
Level: "WARN",
Msg: `boolean '--dry-run=true' flag is deprecated and must be replaced with '--dry-run=client'`,
},
},
"template_none": {
DryRunFlagArg: "--dry-run=none",
IsTemplate: true,
ExpectedError: true,
},
"template_client": {
DryRunFlagArg: "--dry-run=client",
IsTemplate: true,
ExpectedStrategy: action.DryRunClient,
},
"template_server": {
DryRunFlagArg: "--dry-run=server",
IsTemplate: true,
ExpectedStrategy: action.DryRunServer,
},
}
for name, tc := range testCases {
logBuf := new(bytes.Buffer)
logger := slog.New(slog.NewJSONHandler(logBuf, nil))
slog.SetDefault(logger)
cmd := &cobra.Command{
Use: "helm",
}
addDryRunFlag(cmd)
cmd.Flags().Parse([]string{"helm", tc.DryRunFlagArg})
t.Run(name, func(t *testing.T) {
dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, tc.IsTemplate)
if tc.ExpectedError {
assert.Error(t, err)
} else {
assert.Nil(t, err)
assert.Equal(t, tc.ExpectedStrategy, dryRunStrategy)
}
if tc.ExpectedLog != nil {
logResult := map[string]string{}
err = json.Unmarshal(logBuf.Bytes(), &logResult)
require.Nil(t, err)
assert.Equal(t, tc.ExpectedLog.Level, logResult["level"])
assert.Equal(t, tc.ExpectedLog.Msg, logResult["msg"])
} else {
assert.Equal(t, 0, logBuf.Len())
}
})
}
}

@ -18,14 +18,12 @@ package cmd
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
"log/slog" "log/slog"
"os" "os"
"os/signal" "os/signal"
"slices"
"syscall" "syscall"
"time" "time"
@ -144,7 +142,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compInstall(args, toComplete, client) return compInstall(args, toComplete, client)
}, },
RunE: func(_ *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password)
if err != nil { if err != nil {
@ -152,12 +150,12 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
client.SetRegistryClient(registryClient) client.SetRegistryClient(registryClient)
// This is for the case where "" is specifically passed in as a dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, false)
// value. When there is no value passed in NoOptDefVal will be used if err != nil {
// and it is set to client. See addInstallFlags. return err
if client.DryRunOption == "" {
client.DryRunOption = "none"
} }
client.DryRunStrategy = dryRunStrategy
rel, err := runInstall(args, client, valueOpts, out) rel, err := runInstall(args, client, valueOpts, out)
if err != nil { if err != nil {
return fmt.Errorf("INSTALLATION FAILED: %w", err) return fmt.Errorf("INSTALLATION FAILED: %w", err)
@ -173,11 +171,12 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}, },
} }
addInstallFlags(cmd, cmd.Flags(), client, valueOpts) f := cmd.Flags()
addInstallFlags(cmd, f, client, valueOpts)
// hide-secret is not available in all places the install flags are used so // hide-secret is not available in all places the install flags are used so
// it is added separately // it is added separately
f := cmd.Flags()
f.BoolVar(&client.HideSecret, "hide-secret", false, "hide Kubernetes Secrets when also using the --dry-run flag") f.BoolVar(&client.HideSecret, "hide-secret", false, "hide Kubernetes Secrets when also using the --dry-run flag")
addDryRunFlag(cmd)
bindOutputFlag(cmd, &outfmt) bindOutputFlag(cmd, &outfmt)
bindPostRenderFlag(cmd, &client.PostRenderer, settings) bindPostRenderFlag(cmd, &client.PostRenderer, settings)
@ -186,13 +185,6 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) { func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present") f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present")
// --dry-run options with expected outcome:
// - Not set means no dry run and server is contacted.
// - Set with no value, a value of client, or a value of true and the server is not contacted
// - Set with a value of false, none, or false and the server is contacted
// The true/false part is meant to reflect some legacy behavior while none is equal to "".
f.StringVar(&client.DryRunOption, "dry-run", "", "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections.")
f.Lookup("dry-run").NoOptDefVal = "client"
f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement") f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement")
f.BoolVar(&client.ForceReplace, "force", false, "deprecated") f.BoolVar(&client.ForceReplace, "force", false, "deprecated")
f.MarkDeprecated("force", "use --force-replace instead") f.MarkDeprecated("force", "use --force-replace instead")
@ -316,11 +308,6 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
client.Namespace = settings.Namespace() client.Namespace = settings.Namespace()
// Validate DryRunOption member is one of the allowed values
if err := validateDryRunOptionFlag(client.DryRunOption); err != nil {
return nil, err
}
// Create context and prepare the handle of SIGTERM // Create context and prepare the handle of SIGTERM
ctx := context.Background() ctx := context.Background()
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
@ -363,13 +350,3 @@ func compInstall(args []string, toComplete string, client *action.Install) ([]st
} }
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }
func validateDryRunOptionFlag(dryRunOptionFlagValue string) error {
// Validate dry-run flag value with a set of allowed value
allowedDryRunValues := []string{"false", "true", "none", "client", "server"}
isAllowed := slices.Contains(allowedDryRunValues, dryRunOptionFlagValue)
if !isAllowed {
return errors.New("invalid dry-run flag. Flag must one of the following: false, true, none, client, server")
}
return nil
}

@ -57,7 +57,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return noMoreArgsComp() return noMoreArgsComp()
}, },
RunE: func(_ *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 1 { if len(args) > 1 {
ver, err := strconv.Atoi(args[1]) ver, err := strconv.Atoi(args[1])
if err != nil { if err != nil {
@ -66,6 +66,12 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.Version = ver client.Version = ver
} }
dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, false)
if err != nil {
return err
}
client.DryRunStrategy = dryRunStrategy
if err := client.Run(args[0]); err != nil { if err := client.Run(args[0]); err != nil {
return err return err
} }
@ -76,7 +82,6 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback")
f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement") f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement")
f.BoolVar(&client.ForceReplace, "force", false, "deprecated") f.BoolVar(&client.ForceReplace, "force", false, "deprecated")
f.MarkDeprecated("force", "use --force-replace instead") f.MarkDeprecated("force", "use --force-replace instead")
@ -87,6 +92,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this rollback when rollback fails") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this rollback when rollback fails")
f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit")
addDryRunFlag(cmd)
AddWaitFlag(cmd, &client.WaitStrategy) AddWaitFlag(cmd, &client.WaitStrategy)
cmd.MarkFlagsMutuallyExclusive("force-replace", "force-conflicts") cmd.MarkFlagsMutuallyExclusive("force-replace", "force-conflicts")
cmd.MarkFlagsMutuallyExclusive("force", "force-conflicts") cmd.MarkFlagsMutuallyExclusive("force", "force-conflicts")

@ -67,7 +67,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compInstall(args, toComplete, client) return compInstall(args, toComplete, client)
}, },
RunE: func(_ *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if kubeVersion != "" { if kubeVersion != "" {
parsedKubeVersion, err := common.ParseKubeVersion(kubeVersion) parsedKubeVersion, err := common.ParseKubeVersion(kubeVersion)
if err != nil { if err != nil {
@ -83,16 +83,17 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
client.SetRegistryClient(registryClient) client.SetRegistryClient(registryClient)
// This is for the case where "" is specifically passed in as a dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, true)
// value. When there is no value passed in NoOptDefVal will be used if err != nil {
// and it is set to client. See addInstallFlags. return err
if client.DryRunOption == "" { }
client.DryRunOption = "true" if validate {
// Mimic deprecated --validate flag behavior by enabling server dry run
dryRunStrategy = action.DryRunServer
} }
client.DryRun = true client.DryRunStrategy = dryRunStrategy
client.ReleaseName = "release-name" client.ReleaseName = "release-name"
client.Replace = true // Skip the name check client.Replace = true // Skip the name check
client.ClientOnly = !validate
client.APIVersions = common.VersionSet(extraAPIs) client.APIVersions = common.VersionSet(extraAPIs)
client.IncludeCRDs = includeCrds client.IncludeCRDs = includeCrds
rel, err := runInstall(args, client, valueOpts, out) rel, err := runInstall(args, client, valueOpts, out)
@ -196,14 +197,21 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
addInstallFlags(cmd, f, client, valueOpts) addInstallFlags(cmd, f, client, valueOpts)
f.StringArrayVarP(&showFiles, "show-only", "s", []string{}, "only show manifests rendered from the given templates") f.StringArrayVarP(&showFiles, "show-only", "s", []string{}, "only show manifests rendered from the given templates")
f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
f.BoolVar(&validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. This is the same validation performed on an install") f.BoolVar(&validate, "validate", false, "deprecated")
f.MarkDeprecated("validate", "use '--dry-run=server' instead")
f.BoolVar(&includeCrds, "include-crds", false, "include CRDs in the templated output") f.BoolVar(&includeCrds, "include-crds", false, "include CRDs in the templated output")
f.BoolVar(&skipTests, "skip-tests", false, "skip tests from templated output") f.BoolVar(&skipTests, "skip-tests", false, "skip tests from templated output")
f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall") f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall")
f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for Capabilities.KubeVersion") f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for Capabilities.KubeVersion")
f.StringSliceVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions (multiple can be specified)") f.StringSliceVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions (multiple can be specified)")
f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.") f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.")
f.String(
"dry-run",
"client",
`simulates the operation either client-side or server-side. Must be either: "client", or "server". '--dry-run=client simulates the operation client-side only and avoids cluster connections. '--dry-run=server' simulates/validates the operation on the server, requiring cluster connectivity.`)
f.Lookup("dry-run").NoOptDefVal = "unset"
bindPostRenderFlag(cmd, &client.PostRenderer, settings) bindPostRenderFlag(cmd, &client.PostRenderer, settings)
cmd.MarkFlagsMutuallyExclusive("validate", "dry-run")
return cmd return cmd
} }

@ -100,7 +100,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
return noMoreArgsComp() return noMoreArgsComp()
}, },
RunE: func(_ *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.Namespace = settings.Namespace() client.Namespace = settings.Namespace()
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
@ -110,12 +110,12 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
client.SetRegistryClient(registryClient) client.SetRegistryClient(registryClient)
// This is for the case where "" is specifically passed in as a dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, false)
// value. When there is no value passed in NoOptDefVal will be used if err != nil {
// and it is set to client. See addInstallFlags. return err
if client.DryRunOption == "" {
client.DryRunOption = "none"
} }
client.DryRunStrategy = dryRunStrategy
// Fixes #7002 - Support reading values from STDIN for `upgrade` command // Fixes #7002 - Support reading values from STDIN for `upgrade` command
// Must load values AFTER determining if we have to call install so that values loaded from stdin are not read twice // Must load values AFTER determining if we have to call install so that values loaded from stdin are not read twice
if client.Install { if client.Install {
@ -132,8 +132,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.CreateNamespace = createNamespace instClient.CreateNamespace = createNamespace
instClient.ChartPathOptions = client.ChartPathOptions instClient.ChartPathOptions = client.ChartPathOptions
instClient.ForceReplace = client.ForceReplace instClient.ForceReplace = client.ForceReplace
instClient.DryRun = client.DryRun instClient.DryRunStrategy = client.DryRunStrategy
instClient.DryRunOption = client.DryRunOption
instClient.DisableHooks = client.DisableHooks instClient.DisableHooks = client.DisableHooks
instClient.SkipCRDs = client.SkipCRDs instClient.SkipCRDs = client.SkipCRDs
instClient.Timeout = client.Timeout instClient.Timeout = client.Timeout
@ -183,10 +182,6 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if err != nil { if err != nil {
return err return err
} }
// Validate dry-run flag value is one of the allowed values
if err := validateDryRunOptionFlag(client.DryRunOption); err != nil {
return err
}
p := getter.All(settings) p := getter.All(settings)
vals, err := valueOpts.MergeValues(p) vals, err := valueOpts.MergeValues(p)
@ -274,9 +269,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present") f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present")
f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install") f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
f.StringVar(&client.DryRunOption, "dry-run", "", "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections.")
f.BoolVar(&client.HideSecret, "hide-secret", false, "hide Kubernetes Secrets when also using the --dry-run flag") f.BoolVar(&client.HideSecret, "hide-secret", false, "hide Kubernetes Secrets when also using the --dry-run flag")
f.Lookup("dry-run").NoOptDefVal = "client"
f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement") f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement")
f.BoolVar(&client.ForceReplace, "force", false, "deprecated") f.BoolVar(&client.ForceReplace, "force", false, "deprecated")
f.MarkDeprecated("force", "use --force-replace instead") f.MarkDeprecated("force", "use --force-replace instead")
@ -303,6 +296,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates")
f.BoolVar(&client.TakeOwnership, "take-ownership", false, "if set, upgrade will ignore the check for helm annotations and take ownership of the existing resources") f.BoolVar(&client.TakeOwnership, "take-ownership", false, "if set, upgrade will ignore the check for helm annotations and take ownership of the existing resources")
addDryRunFlag(cmd)
addChartPathOptionsFlags(f, &client.ChartPathOptions) addChartPathOptionsFlags(f, &client.ChartPathOptions)
addValueOptionsFlags(f, valueOpts) addValueOptionsFlags(f, valueOpts)
bindOutputFlag(cmd, &outfmt) bindOutputFlag(cmd, &outfmt)

Loading…
Cancel
Save