diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 794fef52c..73bceb0f5 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -169,6 +169,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") + f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") addValueOptionsFlags(f, valueOpts) addChartPathOptionsFlags(f, &client.ChartPathOptions) diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go index 73a37b6fe..454bc432f 100644 --- a/cmd/helm/lint.go +++ b/cmd/helm/lint.go @@ -137,6 +137,7 @@ func newLintCmd(out io.Writer) *cobra.Command { f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings") f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts") f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors") + f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") addValueOptionsFlags(f, valueOpts) return cmd diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 33db703c4..5e397f3f5 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -231,6 +231,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.StringVar(&client.Description, "description", "", "add a custom description") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") + f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") addChartPathOptionsFlags(f, &client.ChartPathOptions) addValueOptionsFlags(f, valueOpts) bindOutputFlag(cmd, &outfmt) diff --git a/pkg/action/__debug_bin b/pkg/action/__debug_bin new file mode 100755 index 000000000..cc7701187 Binary files /dev/null and b/pkg/action/__debug_bin differ diff --git a/pkg/action/install.go b/pkg/action/install.go index fa5508234..321c7f72d 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -86,6 +86,7 @@ type Install struct { OutputDir string Atomic bool SkipCRDs bool + SkipSchemaValidation bool SubNotes bool DisableOpenAPIValidation bool IncludeCRDs bool @@ -248,9 +249,18 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma IsInstall: !isUpgrade, IsUpgrade: isUpgrade, } - valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps) - if err != nil { - return nil, err + + var valuesToRender chartutil.Values + if i.SkipSchemaValidation { + valuesToRender, err = chartutil.ToRenderValuesSkipSchemaValidation(chrt, vals, options, caps) + if err != nil { + return nil, err + } + } else { + valuesToRender, err = chartutil.ToRenderValues(chrt, vals, options, caps) + if err != nil { + return nil, err + } } rel := i.createRelease(chrt, vals) @@ -456,10 +466,10 @@ func (i *Install) failRelease(rel *release.Release, err error) (*release.Release // // 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 diff --git a/pkg/action/lint.go b/pkg/action/lint.go index 5b566e9d3..f91cf2007 100644 --- a/pkg/action/lint.go +++ b/pkg/action/lint.go @@ -33,10 +33,11 @@ import ( // // It provides the implementation of 'helm lint'. type Lint struct { - Strict bool - Namespace string - WithSubcharts bool - Quiet bool + SkipSchemaValidation bool + Strict bool + Namespace string + WithSubcharts bool + Quiet bool } // LintResult is the result of Lint @@ -59,7 +60,7 @@ func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult { } result := &LintResult{} for _, path := range paths { - linter, err := lintChart(path, vals, l.Namespace, l.Strict) + linter, err := lintChart(path, vals, l.Namespace, l.Strict, l.SkipSchemaValidation) if err != nil { result.Errors = append(result.Errors, err) continue @@ -86,7 +87,7 @@ func HasWarningsOrErrors(result *LintResult) bool { return false } -func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) { +func lintChart(path string, vals map[string]interface{}, namespace string, strict bool, skipSchemaValidation bool) (support.Linter, error) { var chartPath string linter := support.Linter{} @@ -125,5 +126,5 @@ func lintChart(path string, vals map[string]interface{}, namespace string, stric return linter, errors.Wrap(err, "unable to check Chart.yaml file in chart") } - return lint.All(chartPath, vals, namespace, strict), nil + return lint.All(chartPath, vals, namespace, strict, skipSchemaValidation), nil } diff --git a/pkg/action/lint_test.go b/pkg/action/lint_test.go index 1828461f3..46da4e103 100644 --- a/pkg/action/lint_test.go +++ b/pkg/action/lint_test.go @@ -23,6 +23,7 @@ import ( var ( values = make(map[string]interface{}) namespace = "testNamespace" + skipSchemaValidation = false strict = false chart1MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-1" chart2MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-2" @@ -78,7 +79,7 @@ func TestLintChart(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, strict) + _, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, strict, skipSchemaValidation) switch { case err != nil && !tt.err: t.Errorf("%s", err) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 690397d4a..742aa2a4f 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -101,6 +101,8 @@ type Upgrade struct { DisableOpenAPIValidation bool // Get missing dependencies DependencyUpdate bool + // Skip JSON schema validation + SkipSchemaValidation bool // Lock to control raceconditions when the process receives a SIGTERM Lock sync.Mutex } @@ -226,9 +228,18 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin if err != nil { return nil, nil, err } - valuesToRender, err := chartutil.ToRenderValues(chart, vals, options, caps) - if err != nil { - return nil, nil, err + + var valuesToRender chartutil.Values + if u.SkipSchemaValidation { + valuesToRender, err = chartutil.ToRenderValuesSkipSchemaValidation(chart, vals, options, caps) + if err != nil { + return nil, nil, err + } + } else { + valuesToRender, err = chartutil.ToRenderValues(chart, vals, options, caps) + if err != nil { + return nil, nil, err + } } hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun) diff --git a/pkg/chartutil/values.go b/pkg/chartutil/values.go index 97bf44217..38b36b160 100644 --- a/pkg/chartutil/values.go +++ b/pkg/chartutil/values.go @@ -135,6 +135,17 @@ type ReleaseOptions struct { // // This takes both ReleaseOptions and Capabilities to merge into the render values. func ToRenderValues(chrt *chart.Chart, chrtVals map[string]interface{}, options ReleaseOptions, caps *Capabilities) (Values, error) { + return toRenderValues(chrt, chrtVals, options, caps, true) +} + +// ToRenderValuesSkipSchemaValidation composes the struct from the data coming from the Releases, Charts and Values files but skips schema validation +// +// This takes both ReleaseOptions and Capabilities to merge into the render values. +func ToRenderValuesSkipSchemaValidation(chrt *chart.Chart, chrtVals map[string]interface{}, options ReleaseOptions, caps *Capabilities) (Values, error) { + return toRenderValues(chrt, chrtVals, options, caps, false) +} + +func toRenderValues(chrt *chart.Chart, chrtVals map[string]interface{}, options ReleaseOptions, caps *Capabilities, schemaValidation bool) (Values, error) { if caps == nil { caps = DefaultCapabilities } @@ -156,9 +167,11 @@ func ToRenderValues(chrt *chart.Chart, chrtVals map[string]interface{}, options return top, err } - if err := ValidateAgainstSchema(chrt, vals); err != nil { - errFmt := "values don't meet the specifications of the schema(s) in the following chart(s):\n%s" - return top, fmt.Errorf(errFmt, err.Error()) + if schemaValidation { + if err := ValidateAgainstSchema(chrt, vals); err != nil { + errFmt := "values don't meet the specifications of the schema(s) in the following chart(s):\n%s" + return top, fmt.Errorf(errFmt, err.Error()) + } } top["Values"] = vals diff --git a/pkg/lint/lint.go b/pkg/lint/lint.go index 67e76bd3d..bdfc185d5 100644 --- a/pkg/lint/lint.go +++ b/pkg/lint/lint.go @@ -24,14 +24,14 @@ import ( ) // All runs all of the available linters on the given base directory. -func All(basedir string, values map[string]interface{}, namespace string, strict bool) support.Linter { +func All(basedir string, values map[string]interface{}, namespace string, strict bool, skipSchemaValidation bool) support.Linter { // Using abs path to get directory context chartDir, _ := filepath.Abs(basedir) linter := support.Linter{ChartDir: chartDir} rules.Chartfile(&linter) rules.ValuesWithOverrides(&linter, values) - rules.Templates(&linter, values, namespace, strict) + rules.Templates(&linter, values, namespace, strict, skipSchemaValidation) rules.Dependencies(&linter) return linter } diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index 0e5d42391..5b08f85a5 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -27,6 +27,7 @@ import ( var values map[string]interface{} const namespace = "testNamespace" +const skipSchemaValidation = false const strict = false const badChartDir = "rules/testdata/badchartfile" @@ -36,7 +37,7 @@ const goodChartDir = "rules/testdata/goodone" const subChartValuesDir = "rules/testdata/withsubchart" func TestBadChart(t *testing.T) { - m := All(badChartDir, values, namespace, strict).Messages + m := All(badChartDir, values, namespace, strict, skipSchemaValidation).Messages if len(m) != 8 { t.Errorf("Number of errors %v", len(m)) t.Errorf("All didn't fail with expected errors, got %#v", m) @@ -85,7 +86,7 @@ func TestBadChart(t *testing.T) { } func TestInvalidYaml(t *testing.T) { - m := All(badYamlFileDir, values, namespace, strict).Messages + m := All(badYamlFileDir, values, namespace, strict, skipSchemaValidation).Messages if len(m) != 1 { t.Fatalf("All didn't fail with expected errors, got %#v", m) } @@ -95,7 +96,7 @@ func TestInvalidYaml(t *testing.T) { } func TestBadValues(t *testing.T) { - m := All(badValuesFileDir, values, namespace, strict).Messages + m := All(badValuesFileDir, values, namespace, strict, skipSchemaValidation).Messages if len(m) < 1 { t.Fatalf("All didn't fail with expected errors, got %#v", m) } @@ -105,7 +106,7 @@ func TestBadValues(t *testing.T) { } func TestGoodChart(t *testing.T) { - m := All(goodChartDir, values, namespace, strict).Messages + m := All(goodChartDir, values, namespace, strict, skipSchemaValidation).Messages if len(m) != 0 { t.Error("All returned linter messages when it shouldn't have") for i, msg := range m { @@ -129,7 +130,7 @@ func TestHelmCreateChart(t *testing.T) { // Note: we test with strict=true here, even though others have // strict = false. - m := All(createdChart, values, namespace, true).Messages + m := All(createdChart, values, namespace, true, skipSchemaValidation).Messages if ll := len(m); ll != 1 { t.Errorf("All should have had exactly 1 error. Got %d", ll) for i, msg := range m { @@ -143,7 +144,7 @@ func TestHelmCreateChart(t *testing.T) { // lint ignores import-values // See https://github.com/helm/helm/issues/9658 func TestSubChartValuesChart(t *testing.T) { - m := All(subChartValuesDir, values, namespace, strict).Messages + m := All(subChartValuesDir, values, namespace, strict, skipSchemaValidation).Messages if len(m) != 0 { t.Error("All returned linter messages when it shouldn't have") for i, msg := range m { diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 61425f92e..b33cbc31c 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -45,7 +45,7 @@ var ( ) // Templates lints the templates in the Linter. -func Templates(linter *support.Linter, values map[string]interface{}, namespace string, strict bool) { +func Templates(linter *support.Linter, values map[string]interface{}, namespace string, strict bool, skipSchemaValidation bool) { fpath := "templates/" templatesPath := filepath.Join(linter.ChartDir, fpath) @@ -80,11 +80,22 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace if err != nil { return } - valuesToRender, err := chartutil.ToRenderValues(chart, cvals, options, nil) - if err != nil { - linter.RunLinterRule(support.ErrorSev, fpath, err) - return + + var valuesToRender chartutil.Values + if skipSchemaValidation { + valuesToRender, err = chartutil.ToRenderValuesSkipSchemaValidation(chart, cvals, options, nil) + if err != nil { + linter.RunLinterRule(support.ErrorSev, fpath, err) + return + } + } else { + valuesToRender, err = chartutil.ToRenderValues(chart, cvals, options, nil) + if err != nil { + linter.RunLinterRule(support.ErrorSev, fpath, err) + return + } } + var e engine.Engine e.LintMode = true renderedContentMap, err := e.Render(chart, valuesToRender) diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go index f3aa641f2..bb7d156ca 100644 --- a/pkg/lint/rules/template_test.go +++ b/pkg/lint/rules/template_test.go @@ -51,11 +51,12 @@ func TestValidateAllowedExtension(t *testing.T) { var values = map[string]interface{}{"nameOverride": "", "httpPort": 80} const namespace = "testNamespace" +const skipSchemaValidation = false const strict = false func TestTemplateParsing(t *testing.T) { linter := support.Linter{ChartDir: templateTestBasedir} - Templates(&linter, values, namespace, strict) + Templates(&linter, values, namespace, strict, skipSchemaValidation) res := linter.Messages if len(res) != 1 { @@ -78,7 +79,7 @@ func TestTemplateIntegrationHappyPath(t *testing.T) { defer os.Rename(ignoredTemplatePath, wrongTemplatePath) linter := support.Linter{ChartDir: templateTestBasedir} - Templates(&linter, values, namespace, strict) + Templates(&linter, values, namespace, strict, skipSchemaValidation) res := linter.Messages if len(res) != 0 { @@ -88,7 +89,7 @@ func TestTemplateIntegrationHappyPath(t *testing.T) { func TestV3Fail(t *testing.T) { linter := support.Linter{ChartDir: "./testdata/v3-fail"} - Templates(&linter, values, namespace, strict) + Templates(&linter, values, namespace, strict, skipSchemaValidation) res := linter.Messages if len(res) != 3 { @@ -108,7 +109,7 @@ func TestV3Fail(t *testing.T) { func TestMultiTemplateFail(t *testing.T) { linter := support.Linter{ChartDir: "./testdata/multi-template-fail"} - Templates(&linter, values, namespace, strict) + Templates(&linter, values, namespace, strict, skipSchemaValidation) res := linter.Messages if len(res) != 1 { @@ -229,7 +230,7 @@ func TestDeprecatedAPIFails(t *testing.T) { } linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} - Templates(&linter, values, namespace, strict) + Templates(&linter, values, namespace, strict, skipSchemaValidation) if l := len(linter.Messages); l != 1 { for i, msg := range linter.Messages { t.Logf("Message %d: %s", i, msg) @@ -286,7 +287,7 @@ func TestStrictTemplateParsingMapError(t *testing.T) { linter := &support.Linter{ ChartDir: filepath.Join(dir, ch.Metadata.Name), } - Templates(linter, ch.Values, namespace, strict) + Templates(linter, ch.Values, namespace, strict, skipSchemaValidation) if len(linter.Messages) != 0 { t.Errorf("expected zero messages, got %d", len(linter.Messages)) for i, msg := range linter.Messages { @@ -416,7 +417,7 @@ func TestEmptyWithCommentsManifests(t *testing.T) { } linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} - Templates(&linter, values, namespace, strict) + Templates(&linter, values, namespace, strict, skipSchemaValidation) if l := len(linter.Messages); l > 0 { for i, msg := range linter.Messages { t.Logf("Message %d: %s", i, msg)