diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 1c6d88ee8..8a807bca0 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -44,6 +44,7 @@ faked locally. Additionally, none of the server-side testing of chart validity func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { var validate bool + var clientOnly bool client := action.NewInstall(cfg) valueOpts := &values.Options{} var extraAPIs []string @@ -58,7 +59,8 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.DryRun = true client.ReleaseName = "RELEASE-NAME" client.Replace = true // Skip the name check - client.ClientOnly = !validate + client.NoValidate = !validate + client.ClientOnly = clientOnly client.APIVersions = chartutil.VersionSet(extraAPIs) rel, err := runInstall(args, client, valueOpts, out) if err != nil { @@ -119,7 +121,8 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { addInstallFlags(f, client, 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, "establish a connection to Kubernetes for schema validation") + f.BoolVar(&validate, "validate", true, "Runs schema validation") + f.BoolVar(&clientOnly, "client-only", true, "Prevents establishing a connection to Kubernetes for schema validation") f.StringArrayVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions") return cmd diff --git a/pkg/action/install.go b/pkg/action/install.go index 606226500..3cf0dcefd 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -68,6 +68,7 @@ type Install struct { ChartPathOptions ClientOnly bool + NoValidate bool DryRun bool DisableHooks bool Replace bool @@ -101,6 +102,10 @@ type ChartPathOptions struct { Version string // --version } +type RenderOptions struct { + NoValidate bool +} + // NewInstall creates a new Install object with the given configuration. func NewInstall(cfg *Configuration) *Install { return &Install{ @@ -151,8 +156,10 @@ func (i *Install) installCRDs(crds []*chart.File) error { // // If DryRun is set to true, this will prepare the release, but not install it func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { + validate := !i.NoValidate + // Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`) - if !i.ClientOnly { + if validate && !i.ClientOnly { if err := i.cfg.KubeClient.IsReachable(); err != nil { return nil, err } @@ -164,7 +171,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // Pre-install anything in the crd/ directory. We do this before Helm // contacts the upstream server and builds the capabilities object. - if crds := chrt.CRDs(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 { + if crds := chrt.CRDs(); validate && !i.SkipCRDs && len(crds) > 0 { // On dry run, bail here if i.DryRun { i.cfg.Log("WARNING: This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") @@ -211,7 +218,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. rel := i.createRelease(chrt, vals) var manifestDoc *bytes.Buffer - rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.OutputDir, i.SubNotes) + rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.OutputDir, i.SubNotes, RenderOptions{NoValidate: i.NoValidate}) // Even for errors, attach this if available if manifestDoc != nil { rel.Manifest = manifestDoc.String() @@ -237,7 +244,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // 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 // resource - if !i.ClientOnly { + if validate && !i.ClientOnly { if err := existingResourceConflict(resources); err != nil { return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install") } @@ -409,7 +416,7 @@ func (i *Install) replaceRelease(rel *release.Release) error { } // renderResources renders the templates in a chart -func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, outputDir string, subNotes bool) ([]*release.Hook, *bytes.Buffer, string, error) { +func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, outputDir string, subNotes bool, opts RenderOptions) ([]*release.Hook, *bytes.Buffer, string, error) { hs := []*release.Hook{} b := bytes.NewBuffer(nil) @@ -452,7 +459,7 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values // Sort hooks, manifests, and partials. Only hooks and manifests are returned, // as partials are not used after renderer.Render. Empty manifests are also // removed here. - hs, manifests, err := releaseutil.SortManifests(files, caps.APIVersions, releaseutil.InstallOrder) + hs, manifests, err := releaseutil.SortManifests(files, caps.APIVersions, releaseutil.InstallOrder, releaseutil.SortOptions{NoValidate: opts.NoValidate}) if err != nil { // By catching parse errors here, we can prevent bogus releases from going // to Kubernetes. diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 31fcc1471..bef1d417a 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -160,7 +160,7 @@ 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) + hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", u.SubNotes, RenderOptions{}) if err != nil { return nil, nil, err } diff --git a/pkg/releaseutil/manifest_sorter.go b/pkg/releaseutil/manifest_sorter.go index 68ba1bf5e..f30cb5015 100644 --- a/pkg/releaseutil/manifest_sorter.go +++ b/pkg/releaseutil/manifest_sorter.go @@ -38,9 +38,10 @@ type Manifest struct { // manifestFile represents a file that contains a manifest. type manifestFile struct { - entries map[string]string - path string - apis chartutil.VersionSet + entries map[string]string + path string + apis chartutil.VersionSet + validate bool } // result is an intermediate structure used during sorting. @@ -65,6 +66,10 @@ var events = map[string]release.HookEvent{ "test-success": release.HookTest, } +type SortOptions struct { + NoValidate bool +} + // SortManifests takes a map of filename/YAML contents, splits the file // by manifest entries, and sorts the entries into hook types. // @@ -74,7 +79,12 @@ var events = map[string]release.HookEvent{ // // Files that do not parse into the expected format are simply placed into a map and // returned. -func SortManifests(files map[string]string, apis chartutil.VersionSet, sort KindSortOrder) ([]*release.Hook, []Manifest, error) { +func SortManifests(files map[string]string, apis chartutil.VersionSet, sort KindSortOrder, opts ...SortOptions) ([]*release.Hook, []Manifest, error) { + var opt SortOptions + if len(opts) > 0 { + opt = opts[0] + } + result := &result{} for filePath, c := range files { @@ -90,9 +100,10 @@ func SortManifests(files map[string]string, apis chartutil.VersionSet, sort Kind } manifestFile := &manifestFile{ - entries: SplitManifests(c), - path: filePath, - apis: apis, + entries: SplitManifests(c), + path: filePath, + apis: apis, + validate: !opt.NoValidate, } if err := manifestFile.sort(result); err != nil { @@ -129,7 +140,7 @@ func (file *manifestFile) sort(result *result) error { return errors.Wrapf(err, "YAML parse error on %s", file.path) } - if entry.Version != "" && !file.apis.Has(entry.Version) { + if file.validate && entry.Version != "" && !file.apis.Has(entry.Version) { return errors.Errorf("apiVersion %q in %s is not available", entry.Version, file.path) }