mirror of https://github.com/helm/helm.git
				
				
				
			feat(helm): add ability for a dry-run to evaluate lookup functions
When a helm command is run with the --dry-run-option=server flag, it will try to connect to the cluster to be able to render lookup functions. Closes #8137 Signed-off-by: Tapas Kapadia <tapaskapadia10@gmail.com>
This commit is contained in:
		
							parent
							
								
									be99ebe8af
								
							
						
					
					
						commit
						ddb33580db
					
				|  | @ -154,8 +154,9 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { | |||
| 
 | ||||
| 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.StringVar(&client.DryRun, "dry-run", "none", "simulate an install. If --dry-run is set with no option being specified or as 'client', it will not attempt cluster connections. Setting option as 'server' allows attempting cluster connections.") | ||||
| 	f.Lookup("dry-run").NoOptDefVal = "client" | ||||
| 	f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install") | ||||
| 	f.StringVar(&client.DryRunOption, "dry-run-option", "none", "simulate an install. If --dry-run is set with no option being specified or as 'client', it will not attempt cluster connections. Setting option as 'server' allows attempting cluster connections.") | ||||
| 	f.Lookup("dry-run-option").NoOptDefVal = "client" | ||||
| 	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") | ||||
|  | @ -174,6 +175,8 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal | |||
| 	addValueOptionsFlags(f, valueOpts) | ||||
| 	addChartPathOptionsFlags(f, &client.ChartPathOptions) | ||||
| 
 | ||||
| 	cmd.MarkFlagsMutuallyExclusive("dry-run", "dry-run-option") | ||||
| 
 | ||||
| 	err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||||
| 		requiredArgs := 2 | ||||
| 		if client.GenerateName { | ||||
|  | @ -263,7 +266,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options | |||
| 	client.Namespace = settings.Namespace() | ||||
| 
 | ||||
| 	// validate dry-run flag value is one of the allowed values
 | ||||
| 	if err := validateDryRunFlag(client.DryRun); err != nil { | ||||
| 	if err := validateDryRunOptionFlag(client.DryRunOption); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
|  | @ -308,12 +311,12 @@ func compInstall(args []string, toComplete string, client *action.Install) ([]st | |||
| 	return nil, cobra.ShellCompDirectiveNoFileComp | ||||
| } | ||||
| 
 | ||||
| func validateDryRunFlag(dryRunFlagValue string) error { | ||||
| func validateDryRunOptionFlag(dryRunOptionFlagValue string) error { | ||||
| 	// validate dry-run flag value with set of allowed value
 | ||||
| 	allowedDryRunValues := []string{"false", "true", "none", "client", "server"} | ||||
| 	isAllowed := false | ||||
| 	for _, v := range allowedDryRunValues { | ||||
| 		if dryRunFlagValue == v { | ||||
| 		if dryRunOptionFlagValue == v { | ||||
| 			isAllowed = true | ||||
| 			break | ||||
| 		} | ||||
|  |  | |||
|  | @ -73,7 +73,6 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { | |||
| 				client.KubeVersion = parsedKubeVersion | ||||
| 			} | ||||
| 
 | ||||
| 			client.DryRun = "client" | ||||
| 			client.ReleaseName = "release-name" | ||||
| 			client.Replace = true // Skip the name check
 | ||||
| 			client.ClientOnly = !validate | ||||
|  |  | |||
|  | @ -106,6 +106,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { | |||
| 					instClient.ChartPathOptions = client.ChartPathOptions | ||||
| 					instClient.Force = client.Force | ||||
| 					instClient.DryRun = client.DryRun | ||||
| 					instClient.DryRunOption = client.DryRunOption | ||||
| 					instClient.DisableHooks = client.DisableHooks | ||||
| 					instClient.SkipCRDs = client.SkipCRDs | ||||
| 					instClient.Timeout = client.Timeout | ||||
|  | @ -119,7 +120,6 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { | |||
| 					instClient.SubNotes = client.SubNotes | ||||
| 					instClient.Description = client.Description | ||||
| 					instClient.DependencyUpdate = client.DependencyUpdate | ||||
| 
 | ||||
| 					rel, err := runInstall(args, instClient, valueOpts, out) | ||||
| 					if err != nil { | ||||
| 						return err | ||||
|  | @ -140,7 +140,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { | |||
| 				return err | ||||
| 			} | ||||
| 			// validate dry-run flag value is one of the allowed values
 | ||||
| 			if err := validateDryRunFlag(client.DryRun); err != nil { | ||||
| 			if err := validateDryRunOptionFlag(client.DryRunOption); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 
 | ||||
|  | @ -218,8 +218,9 @@ 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.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.StringVar(&client.DryRun, "dry-run", "none", "simulate an install. If --dry-run is set with no option being specified or as 'client', it will not attempt cluster connections. Setting option as 'server' allows attempting cluster connections.") | ||||
| 	f.Lookup("dry-run").NoOptDefVal = "client" | ||||
| 	f.BoolVar(&client.DryRun, "dry-run", false, "simulate an upgrade") | ||||
| 	f.StringVar(&client.DryRunOption, "dry-run-option", "none", "simulate an install. If --dry-run is set with no option being specified or as 'client', it will not attempt cluster connections. Setting option as 'server' allows attempting cluster connections.") | ||||
| 	f.Lookup("dry-run-option").NoOptDefVal = "client" | ||||
| 	f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") | ||||
| 	f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods") | ||||
| 	f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") | ||||
|  | @ -242,6 +243,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { | |||
| 	bindOutputFlag(cmd, &outfmt) | ||||
| 	bindPostRenderFlag(cmd, &client.PostRenderer) | ||||
| 
 | ||||
| 	cmd.MarkFlagsMutuallyExclusive("dry-run", "dry-run-option") | ||||
| 
 | ||||
| 	err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||||
| 		if len(args) != 2 { | ||||
| 			return nil, cobra.ShellCompDirectiveNoFileComp | ||||
|  |  | |||
|  | @ -102,7 +102,7 @@ type Configuration struct { | |||
| // TODO: This function is badly in need of a refactor.
 | ||||
| // TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed
 | ||||
| //       This code has to do with writing files to disk.
 | ||||
| func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun string) ([]*release.Hook, *bytes.Buffer, string, error) { | ||||
| func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, interactWithRemote bool) ([]*release.Hook, *bytes.Buffer, string, error) { | ||||
| 	hs := []*release.Hook{} | ||||
| 	b := bytes.NewBuffer(nil) | ||||
| 
 | ||||
|  | @ -121,11 +121,11 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu | |||
| 	var err2 error | ||||
| 
 | ||||
| 	// A `helm template`  should not talk to the remote cluster. However, commands
 | ||||
| 	// with `--dry-run` with the value of false, none, or sever should try to connect to the cluster.
 | ||||
| 	// This enables the ability to render 'lookup' functions.
 | ||||
| 	// with the flag `--dry-run-option` with the value of false, none, or sever  
 | ||||
| 	// or with the flag `--dry-run` with the value of false should try to interact with the cluster.
 | ||||
| 	// It may break in interesting and exotic ways because other data (e.g. discovery)
 | ||||
| 	// is mocked.
 | ||||
| 	if (dryRun == "server" || dryRun == "none" || dryRun == "false") && cfg.RESTClientGetter != nil { | ||||
| 	if interactWithRemote && cfg.RESTClientGetter != nil { | ||||
| 		restConfig, err := cfg.RESTClientGetter.ToRESTConfig() | ||||
| 		if err != nil { | ||||
| 			return hs, b, "", err | ||||
|  |  | |||
|  | @ -71,7 +71,8 @@ type Install struct { | |||
| 	ClientOnly               bool | ||||
| 	Force                    bool | ||||
| 	CreateNamespace          bool | ||||
| 	DryRun                   string | ||||
| 	DryRun                   bool | ||||
| 	DryRunOption             string | ||||
| 	DisableHooks             bool | ||||
| 	Replace                  bool | ||||
| 	Wait                     bool | ||||
|  | @ -128,8 +129,6 @@ type ChartPathOptions struct { | |||
| func NewInstall(cfg *Configuration) *Install { | ||||
| 	in := &Install{ | ||||
| 		cfg: cfg, | ||||
| 		// Set default value of DryRun for before flags are binded (tests)
 | ||||
| 		DryRun: "none", | ||||
| 	} | ||||
| 	in.ChartPathOptions.registryClient = cfg.RegistryClient | ||||
| 
 | ||||
|  | @ -205,11 +204,21 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma | |||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// determine dry run behavior
 | ||||
| 	if i.DryRun || i.DryRunOption == "client" || i.DryRunOption == "server" || i.DryRunOption == "true" { | ||||
| 		i.DryRun = true | ||||
| 	} | ||||
| 
 | ||||
| 	var interactWithRemote bool | ||||
| 	if !i.DryRun || i.DryRunOption == "server" { | ||||
| 		interactWithRemote = true | ||||
| 	} | ||||
| 
 | ||||
| 	// Pre-install anything in the crd/ directory. We do this before Helm
 | ||||
| 	// 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 != "none" && i.DryRun != "false" { | ||||
| 		if i.DryRun { | ||||
| 			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 | ||||
|  | @ -243,7 +252,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 != "none" && i.DryRun != "false") | ||||
| 	isUpgrade := i.IsUpgrade && i.DryRun | ||||
| 	options := chartutil.ReleaseOptions{ | ||||
| 		Name:      i.ReleaseName, | ||||
| 		Namespace: i.Namespace, | ||||
|  | @ -259,8 +268,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma | |||
| 	rel := i.createRelease(chrt, vals) | ||||
| 
 | ||||
| 	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, i.DryRun) | ||||
| 	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) | ||||
| 	// Even for errors, attach this if available
 | ||||
| 	if manifestDoc != nil { | ||||
| 		rel.Manifest = manifestDoc.String() | ||||
|  | @ -301,7 +309,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 != "none" && i.DryRun != "false" { | ||||
| 	if i.DryRun { | ||||
| 		rel.Info.Description = "Dry run complete" | ||||
| 		return rel, nil | ||||
| 	} | ||||
|  | @ -471,7 +479,7 @@ func (i *Install) availableName() error { | |||
| 		return errors.Wrapf(err, "release name %q", start) | ||||
| 	} | ||||
| 	// On dry run, bail here
 | ||||
| 	if i.DryRun != "none" && i.DryRun != "false" { | ||||
| 	if i.DryRun { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -234,7 +234,7 @@ func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) { | |||
| func TestInstallRelease_DryRun(t *testing.T) { | ||||
| 	is := assert.New(t) | ||||
| 	instAction := installAction(t) | ||||
| 	instAction.DryRun = "true" | ||||
| 	instAction.DryRun = true | ||||
| 	vals := map[string]interface{}{} | ||||
| 	res, err := instAction.Run(buildChart(withSampleTemplates()), vals) | ||||
| 	if err != nil { | ||||
|  | @ -258,7 +258,7 @@ func TestInstallRelease_DryRun(t *testing.T) { | |||
| func TestInstallRelease_DryRun_Lookup(t *testing.T) { | ||||
| 	is := assert.New(t) | ||||
| 	instAction := installAction(t) | ||||
| 	instAction.DryRun = "true" | ||||
| 	instAction.DryRun = true | ||||
| 	vals := map[string]interface{}{} | ||||
| 
 | ||||
| 	mockChart := buildChart(withSampleTemplates()) | ||||
|  | @ -278,7 +278,7 @@ func TestInstallRelease_DryRun_Lookup(t *testing.T) { | |||
| func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { | ||||
| 	is := assert.New(t) | ||||
| 	instAction := installAction(t) | ||||
| 	instAction.DryRun = "true" | ||||
| 	instAction.DryRun = true | ||||
| 	vals := map[string]interface{}{} | ||||
| 	_, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals) | ||||
| 	expectedErr := "\"hello/templates/incorrect\" at <.Values.bad.doh>: nil pointer evaluating interface {}.doh" | ||||
|  |  | |||
|  | @ -70,7 +70,9 @@ type Upgrade struct { | |||
| 	// DisableHooks disables hook processing if set to true.
 | ||||
| 	DisableHooks bool | ||||
| 	// DryRun controls whether the operation is prepared, but not executed.
 | ||||
| 	DryRun string | ||||
| 	DryRun bool | ||||
| 	// DryRunOption controls whether the operation is prepared, but not executed with options on whether or not to interact with the remote cluster.
 | ||||
| 	DryRunOption string | ||||
| 	// Force will, if set to `true`, ignore certain warnings and perform the upgrade anyway.
 | ||||
| 	//
 | ||||
| 	// This should be used with caution.
 | ||||
|  | @ -113,8 +115,6 @@ type resultMessage struct { | |||
| func NewUpgrade(cfg *Configuration) *Upgrade { | ||||
| 	up := &Upgrade{ | ||||
| 		cfg: cfg, | ||||
| 		// Set default value of DryRun for before flags are binded (tests)
 | ||||
| 		DryRun: "none", | ||||
| 	} | ||||
| 	up.ChartPathOptions.registryClient = cfg.RegistryClient | ||||
| 
 | ||||
|  | @ -140,6 +140,12 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart. | |||
| 	if err := chartutil.ValidateReleaseName(name); err != nil { | ||||
| 		return nil, errors.Errorf("release name is invalid: %s", name) | ||||
| 	} | ||||
| 
 | ||||
| 	// determine dry run behavior
 | ||||
| 	if u.DryRun || u.DryRunOption == "client" || u.DryRunOption == "server" || u.DryRunOption == "true" { | ||||
| 		u.DryRun = true | ||||
| 	} | ||||
| 
 | ||||
| 	u.cfg.Log("preparing upgrade for %s", name) | ||||
| 	currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals) | ||||
| 	if err != nil { | ||||
|  | @ -153,8 +159,9 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart. | |||
| 	if err != nil { | ||||
| 		return res, err | ||||
| 	} | ||||
| 
 | ||||
| 	// Do not update for dry runs
 | ||||
| 	if u.DryRun == "none" || u.DryRun == "false" { | ||||
| 	if !u.DryRun { | ||||
| 		u.cfg.Log("updating status for upgraded release for %s", name) | ||||
| 		if err := u.cfg.Releases.Update(upgradedRelease); err != nil { | ||||
| 			return res, err | ||||
|  | @ -232,7 +239,13 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin | |||
| 		return nil, nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun) | ||||
| 	// determine whether or not to interact with remote
 | ||||
| 	var interactWithRemote bool | ||||
| 	if !u.DryRun || u.DryRunOption == "server" { | ||||
| 		interactWithRemote = true | ||||
| 	} | ||||
| 
 | ||||
| 	hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithRemote) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | @ -311,7 +324,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR | |||
| 	}) | ||||
| 
 | ||||
| 	// Run if it is a dry run
 | ||||
| 	if u.DryRun != "none" && u.DryRun != "false" { | ||||
| 	if u.DryRun { | ||||
| 		u.cfg.Log("dry run for %s", upgradedRelease.Name) | ||||
| 		if len(u.Description) > 0 { | ||||
| 			upgradedRelease.Info.Description = u.Description | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue