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>
pull/9426/head
Tapas Kapadia 3 years ago
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…
Cancel
Save