From 7de91248ce45c2f30947aa11dbd084e5c1242974 Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Fri, 19 Jul 2019 12:27:14 -0700 Subject: [PATCH] feat(template): introduce --validate This feature flag allows `helm template` to be used against a live cluster. Some charts need CRDs to be applied to the cluster before calling `helm install`. This allows users to validate their templates will render with those resources set. Signed-off-by: Matthew Fisher --- cmd/helm/root.go | 2 +- cmd/helm/template.go | 32 ++++++++------------------------ pkg/action/install.go | 12 ++++++++++++ pkg/action/install_test.go | 15 +++++++++++++-- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/cmd/helm/root.go b/cmd/helm/root.go index f034069fe..18fc884bf 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -172,6 +172,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string newReleaseTestCmd(actionConfig, out), newRollbackCmd(actionConfig, out), newStatusCmd(actionConfig, out), + newTemplateCmd(actionConfig, out), newUninstallCmd(actionConfig, out), newUpgradeCmd(actionConfig, out), @@ -179,7 +180,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string newHomeCmd(out), newInitCmd(out), newPluginCmd(out), - newTemplateCmd(out), newVersionCmd(out), // Hidden documentation generator command: 'helm docs' diff --git a/cmd/helm/template.go b/cmd/helm/template.go index ff8bc2963..9fcbc2b10 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -19,18 +19,12 @@ package main import ( "fmt" "io" - "io/ioutil" "strings" "github.com/spf13/cobra" - "github.com/spf13/pflag" "helm.sh/helm/cmd/helm/require" "helm.sh/helm/pkg/action" - "helm.sh/helm/pkg/chartutil" - kubefake "helm.sh/helm/pkg/kube/fake" - "helm.sh/helm/pkg/storage" - "helm.sh/helm/pkg/storage/driver" ) const templateDesc = ` @@ -42,18 +36,9 @@ of the server-side testing of chart validity (e.g. whether an API is supported) is done. ` -func newTemplateCmd(out io.Writer) *cobra.Command { - customConfig := &action.Configuration{ - // Add mock objects in here so it doesn't use Kube API server - Releases: storage.Init(driver.NewMemory()), - KubeClient: &kubefake.PrintingKubeClient{Out: ioutil.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) { - fmt.Fprintf(out, format, v...) - }, - } - - client := action.NewInstall(customConfig) +func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { + var validate bool + client := action.NewInstall(cfg) cmd := &cobra.Command{ Use: "template [NAME] [CHART]", @@ -64,6 +49,7 @@ func newTemplateCmd(out io.Writer) *cobra.Command { client.DryRun = true client.ReleaseName = "RELEASE-NAME" client.Replace = true // Skip the name check + client.ClientOnly = !validate rel, err := runInstall(args, client, out) if err != nil { return err @@ -73,12 +59,10 @@ func newTemplateCmd(out io.Writer) *cobra.Command { }, } - addInstallFlags(cmd.Flags(), client) - addTemplateFlags(cmd.Flags(), client) + f := cmd.Flags() + addInstallFlags(f, client) + f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") + f.BoolVar(&validate, "validate", false, "establish a connection to Kubernetes for schema validation") return cmd } - -func addTemplateFlags(f *pflag.FlagSet, client *action.Install) { - f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") -} diff --git a/pkg/action/install.go b/pkg/action/install.go index 797e9ce1f..fa473a2f0 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -41,9 +41,12 @@ import ( "helm.sh/helm/pkg/engine" "helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/hooks" + kubefake "helm.sh/helm/pkg/kube/fake" "helm.sh/helm/pkg/release" "helm.sh/helm/pkg/releaseutil" "helm.sh/helm/pkg/repo" + "helm.sh/helm/pkg/storage" + "helm.sh/helm/pkg/storage/driver" "helm.sh/helm/pkg/strvals" "helm.sh/helm/pkg/version" ) @@ -70,6 +73,7 @@ type Install struct { ChartPathOptions ValueOptions + ClientOnly bool DryRun bool DisableHooks bool Replace bool @@ -119,6 +123,14 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) { return nil, err } + if i.ClientOnly { + // Add mock objects in here so it doesn't use Kube API server + // NOTE(bacongobbler): used for `helm template` + i.cfg.Capabilities = chartutil.DefaultCapabilities + i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard} + i.cfg.Releases = storage.Init(driver.NewMemory()) + } + // Make sure if Atomic is set, that wait is set as well. This makes it so // the user doesn't have to specify both i.Wait = i.Wait || i.Atomic diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 006c55f43..af8b28ee5 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -29,9 +29,10 @@ import ( "github.com/stretchr/testify/assert" "helm.sh/helm/internal/test" + "helm.sh/helm/pkg/chartutil" + kubefake "helm.sh/helm/pkg/kube/fake" "helm.sh/helm/pkg/release" "helm.sh/helm/pkg/storage/driver" - kubefake "helm.sh/helm/pkg/kube/fake" ) type nameTemplateTestCase struct { @@ -74,6 +75,16 @@ func TestInstallRelease(t *testing.T) { is.Equal(rel.Info.Description, "Install complete") } +func TestInstallReleaseClientOnly(t *testing.T) { + is := assert.New(t) + instAction := installAction(t) + instAction.ClientOnly = true + instAction.Run(buildChart()) // disregard output + + is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities) + is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: ioutil.Discard}) +} + func TestInstallRelease_NoName(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "" @@ -278,7 +289,7 @@ func TestInstallRelease_Atomic(t *testing.T) { is.Error(err) is.Equal(err, driver.ErrReleaseNotFound) }) - + t.Run("atomic uninstall fails", func(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "come-fail-away-with-me"