From 6032df9e39c182f4b4439aa06bd7339609817eee Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Mon, 9 Jan 2023 08:58:16 -0800 Subject: [PATCH] Implment `--dry-run=["none"|"client"|"server"] install/upgrade/template flag Signed-off-by: George Jenkins --- cmd/helm/install.go | 38 ++++++++++++++++++++++++++++++---- cmd/helm/template.go | 25 ++++++++++++++++++++++- pkg/action/install.go | 47 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 95 insertions(+), 15 deletions(-) diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 281679e5c..f03d981ea 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -85,7 +85,7 @@ set for a key called 'foo', the 'newbar' value would take precedence: $ helm install --set foo=bar --set foo=newbar myredis ./redis -Similarly, in the following example 'foo' is set to '["four"]': +Similarly, in the following example 'foo' is set to '["four"]': $ helm install --set-json='foo=["one", "two", "three"]' --set-json='foo=["four"]' myredis ./redis @@ -122,10 +122,26 @@ To see the list of chart repositories, use 'helm repo list'. To search for charts in a repository, use 'helm search'. ` +func determineInstallDryRunMode(dryRunModeFlag string) (*action.DryRunMode, error) { + switch dryRunModeFlag { + case "none": + case "false": // TODO: Remove "false" helm v4 + return &action.DryRunModeNone, nil + case "client": + case "true": // TODO: Remove "true" helm v4 + return &action.DryRunModeClient, nil + case "server": + return &action.DryRunModeServer, nil + } + + return nil, fmt.Errorf("Invalid --dry-run flag value: %s", dryRunModeFlag) +} + func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewInstall(cfg) valueOpts := &values.Options{} var outfmt output.Format + var dryRunModeFlag string cmd := &cobra.Command{ Use: "install [NAME] [CHART]", @@ -136,6 +152,15 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return compInstall(args, toComplete, client) }, RunE: func(_ *cobra.Command, args []string) error { + + dryRunMode, err := determineInstallDryRunMode(dryRunModeFlag) + if err != nil { + return err + } + + client.DryRunMode = *dryRunMode + fmt.Printf("drm: %s\n", client.DryRunMode) + rel, err := runInstall(args, client, valueOpts, out) if err != nil { return errors.Wrap(err, "INSTALLATION FAILED") @@ -145,16 +170,21 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - addInstallFlags(cmd, cmd.Flags(), client, valueOpts) + addInstallFlags(cmd, cmd.Flags(), client, &dryRunModeFlag, valueOpts) bindOutputFlag(cmd, &outfmt) bindPostRenderFlag(cmd, &client.PostRenderer) return cmd } -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, dryRunModeFlag *string, valueOpts *values.Options) { f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present") - f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install") + f.StringVar( + dryRunModeFlag, + "dry-run", + "none", + `simulate an install. Must be "none", "server", or "client". If client strategy, X. If server strategy, Y. For backwards compatibility, boolean values "true" and "false" are also accepted. "true" being a synonym for "client". "false" meaning disable dry-run`, + ) f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install") f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production") diff --git a/cmd/helm/template.go b/cmd/helm/template.go index ce2be55bc..836bc0e2b 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -46,10 +46,28 @@ faked locally. Additionally, none of the server-side testing of chart validity (e.g. whether an API is supported) is done. ` +func determineTemplateDryRunMode(dryRunModeFlag string) (*action.DryRunMode, error) { + switch dryRunModeFlag { + case "none": + return nil, fmt.Errorf("Invalid flag --dry-run=none for template") + case "false": // TODO: Remove "false" helm v4 + // helm template --dry-run=false was previously ignored, and dry-run set anyway + return &action.DryRunModeClient, nil + case "client": + case "true": // TODO: Remove "true" helm v4 + return &action.DryRunModeClient, nil + case "server": + return &action.DryRunModeServer, nil + } + + return nil, fmt.Errorf("Invalid --dry-run flag value: %s", dryRunModeFlag) +} + func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { var validate bool var includeCrds bool var skipTests bool + var dryRunModeFlag string client := action.NewInstall(cfg) valueOpts := &values.Options{} var kubeVersion string @@ -73,7 +91,12 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.KubeVersion = parsedKubeVersion } + dryRunMode, err := determineTemplateDryRunMode(dryRunModeFlag) + if err != nil { + return err + } client.DryRun = true + client.DryRunMode = *dryRunMode client.ReleaseName = "release-name" client.Replace = true // Skip the name check client.ClientOnly = !validate @@ -173,7 +196,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } f := cmd.Flags() - addInstallFlags(cmd, f, client, valueOpts) + addInstallFlags(cmd, f, client, &dryRunModeFlag, valueOpts) 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.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") diff --git a/pkg/action/install.go b/pkg/action/install.go index 425b66f69..ff2874d0f 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -62,6 +62,14 @@ const notesFileSuffix = "NOTES.txt" const defaultDirectoryPermission = 0755 +type DryRunMode string + +var ( + DryRunModeNone DryRunMode = "none" + DryRunModeClient DryRunMode = "client" + DryRunModeServer DryRunMode = "server" +) + // Install performs an installation operation. type Install struct { cfg *Configuration @@ -71,7 +79,8 @@ type Install struct { ClientOnly bool Force bool CreateNamespace bool - DryRun bool + DryRun bool // Deprecated: replaced by DryRunMode, to be removed helm v4 + DryRunMode DryRunMode DisableHooks bool Replace bool Wait bool @@ -179,7 +188,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error { // Run executes the installation // -// If DryRun is set to true, this will prepare the release, but not install it +// If DryRunMode is not 'none', this will prepare the release, but not install it func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { ctx := context.Background() @@ -207,7 +216,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma // contacts the upstream server and builds the capabilities object. if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 { // On dry run, bail here - if i.DryRun { + if i.isDryRun() { i.cfg.Log("WARNING: This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") } else if err := i.installCRDs(crds); err != nil { return nil, err @@ -241,7 +250,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma } // special case for helm template --is-upgrade - isUpgrade := i.IsUpgrade && i.DryRun + isUpgrade := i.IsUpgrade && i.isDryRun() options := chartutil.ReleaseOptions{ Name: i.ReleaseName, Namespace: i.Namespace, @@ -298,7 +307,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma } // Bail out here if it is a dry run - if i.DryRun { + if i.isDryRun() { rel.Info.Description = "Dry run complete" return rel, nil } @@ -453,21 +462,39 @@ func (i *Install) failRelease(rel *release.Release, err error) (*release.Release return rel, err } +func (i *Install) isDryRun() bool { + if i.DryRunMode != "" { + switch i.DryRunMode { + case DryRunModeClient: + case DryRunModeServer: + return true + case DryRunModeNone: + return false + default: + panic("Invalid DryRun mode") + } + } + + // Fallback to legacy + return i.DryRun +} + // availableName tests whether a name is available // // Roughly, this will return an error if name is // -// - empty -// - too long -// - already in use, and not deleted -// - used by a deleted release, and i.Replace is false +// - empty +// - too long +// - already in use, and not deleted +// - used by a deleted release, and i.Replace is false func (i *Install) availableName() error { start := i.ReleaseName if err := chartutil.ValidateReleaseName(start); err != nil { return errors.Wrapf(err, "release name %q", start) } - if i.DryRun { + + if i.isDryRun() { return nil }