From 072e2a689ad8ed72258f851e23c5d987f7979ffe Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Wed, 10 Sep 2025 15:19:17 +0200 Subject: [PATCH] Extend --skip-schema-validation for lint command When --skip-schema-validation is enabled, the lint command will now skip JSON schema validation for values.yaml files, allowing charts with schema validation errors to pass linting when the flag is used. This addresses the gap where --skip-schema-validation only applied to templates but not to values files, providing complete schema validation bypass when needed. Fixes: #13413 Signed-off-by: Suleiman Dibirov Signed-off-by: Benoit Tigeot --- internal/chart/v3/lint/lint.go | 2 +- internal/chart/v3/lint/rules/values.go | 13 +++++++---- internal/chart/v3/lint/rules/values_test.go | 24 ++++++++++++++++----- pkg/chart/v2/lint/lint.go | 2 +- pkg/chart/v2/lint/rules/values.go | 13 +++++++---- pkg/chart/v2/lint/rules/values_test.go | 24 ++++++++++++++++----- 6 files changed, 58 insertions(+), 20 deletions(-) diff --git a/internal/chart/v3/lint/lint.go b/internal/chart/v3/lint/lint.go index 231bb6803..0cd949065 100644 --- a/internal/chart/v3/lint/lint.go +++ b/internal/chart/v3/lint/lint.go @@ -57,7 +57,7 @@ func RunAll(baseDir string, values map[string]interface{}, namespace string, opt } rules.Chartfile(&result) - rules.ValuesWithOverrides(&result, values) + rules.ValuesWithOverrides(&result, values, lo.SkipSchemaValidation) rules.TemplatesWithSkipSchemaValidation(&result, values, namespace, lo.KubeVersion, lo.SkipSchemaValidation) rules.Dependencies(&result) rules.Crds(&result) diff --git a/internal/chart/v3/lint/rules/values.go b/internal/chart/v3/lint/rules/values.go index adf2e2c52..0af9765dd 100644 --- a/internal/chart/v3/lint/rules/values.go +++ b/internal/chart/v3/lint/rules/values.go @@ -32,7 +32,7 @@ import ( // they are only tested for well-formedness. // // If additional values are supplied, they are coalesced into the values in values.yaml. -func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]interface{}) { +func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]interface{}, skipSchemaValidation bool) { file := "values.yaml" vf := filepath.Join(linter.ChartDir, file) fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf)) @@ -41,7 +41,7 @@ func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]inter return } - linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, valueOverrides)) + linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, valueOverrides, skipSchemaValidation)) } func validateValuesFileExistence(valuesPath string) error { @@ -52,7 +52,7 @@ func validateValuesFileExistence(valuesPath string) error { return nil } -func validateValuesFile(valuesPath string, overrides map[string]interface{}) error { +func validateValuesFile(valuesPath string, overrides map[string]interface{}, skipSchemaValidation bool) error { values, err := common.ReadValuesFile(valuesPath) if err != nil { return fmt.Errorf("unable to parse YAML: %w", err) @@ -75,5 +75,10 @@ func validateValuesFile(valuesPath string, overrides map[string]interface{}) err if err != nil { return err } - return util.ValidateAgainstSingleSchema(coalescedValues, schema) + + if !skipSchemaValidation { + return util.ValidateAgainstSingleSchema(coalescedValues, schema) + } + + return nil } diff --git a/internal/chart/v3/lint/rules/values_test.go b/internal/chart/v3/lint/rules/values_test.go index 348695785..288b77436 100644 --- a/internal/chart/v3/lint/rules/values_test.go +++ b/internal/chart/v3/lint/rules/values_test.go @@ -67,7 +67,7 @@ func TestValidateValuesFileWellFormed(t *testing.T) { ` tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml)) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}); err == nil { + if err := validateValuesFile(valfile, map[string]interface{}{}, false); err == nil { t.Fatal("expected values file to fail parsing") } } @@ -78,7 +78,7 @@ func TestValidateValuesFileSchema(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}); err != nil { + if err := validateValuesFile(valfile, map[string]interface{}{}, false); err != nil { t.Fatalf("Failed validation with %s", err) } } @@ -91,7 +91,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, map[string]interface{}{}) + err := validateValuesFile(valfile, map[string]interface{}{}, false) if err == nil { t.Fatal("expected values file to fail parsing") } @@ -99,6 +99,20 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { assert.Contains(t, err.Error(), "- at '/username': got number, want string") } +func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T) { + // 1234 is an int, not a string. This should fail normally but pass with skipSchemaValidation. + yaml := "username: 1234\npassword: swordfish" + tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml)) + createTestingSchema(t, tmpdir) + + valfile := filepath.Join(tmpdir, "values.yaml") + + err := validateValuesFile(valfile, map[string]interface{}{}, true) + if err != nil { + t.Fatal("expected values file to pass parsing because of skipSchemaValidation") + } +} + func TestValidateValuesFileSchemaOverrides(t *testing.T) { yaml := "username: admin" overrides := map[string]interface{}{ @@ -108,7 +122,7 @@ func TestValidateValuesFileSchemaOverrides(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, overrides); err != nil { + if err := validateValuesFile(valfile, overrides, false); err != nil { t.Fatalf("Failed validation with %s", err) } } @@ -145,7 +159,7 @@ func TestValidateValuesFile(t *testing.T) { valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, tt.overrides) + err := validateValuesFile(valfile, tt.overrides, false) switch { case err != nil && tt.errorMessage == "": diff --git a/pkg/chart/v2/lint/lint.go b/pkg/chart/v2/lint/lint.go index 773c9bc5e..b26d65a34 100644 --- a/pkg/chart/v2/lint/lint.go +++ b/pkg/chart/v2/lint/lint.go @@ -57,7 +57,7 @@ func RunAll(baseDir string, values map[string]interface{}, namespace string, opt } rules.Chartfile(&result) - rules.ValuesWithOverrides(&result, values) + rules.ValuesWithOverrides(&result, values, lo.SkipSchemaValidation) rules.TemplatesWithSkipSchemaValidation(&result, values, namespace, lo.KubeVersion, lo.SkipSchemaValidation) rules.Dependencies(&result) rules.Crds(&result) diff --git a/pkg/chart/v2/lint/rules/values.go b/pkg/chart/v2/lint/rules/values.go index 5260bf8b3..994a6a463 100644 --- a/pkg/chart/v2/lint/rules/values.go +++ b/pkg/chart/v2/lint/rules/values.go @@ -32,7 +32,7 @@ import ( // they are only tested for well-formedness. // // If additional values are supplied, they are coalesced into the values in values.yaml. -func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]interface{}) { +func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]interface{}, skipSchemaValidation bool) { file := "values.yaml" vf := filepath.Join(linter.ChartDir, file) fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf)) @@ -41,7 +41,7 @@ func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]inter return } - linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, valueOverrides)) + linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, valueOverrides, skipSchemaValidation)) } func validateValuesFileExistence(valuesPath string) error { @@ -52,7 +52,7 @@ func validateValuesFileExistence(valuesPath string) error { return nil } -func validateValuesFile(valuesPath string, overrides map[string]interface{}) error { +func validateValuesFile(valuesPath string, overrides map[string]interface{}, skipSchemaValidation bool) error { values, err := common.ReadValuesFile(valuesPath) if err != nil { return fmt.Errorf("unable to parse YAML: %w", err) @@ -75,5 +75,10 @@ func validateValuesFile(valuesPath string, overrides map[string]interface{}) err if err != nil { return err } - return util.ValidateAgainstSingleSchema(coalescedValues, schema) + + if !skipSchemaValidation { + return util.ValidateAgainstSingleSchema(coalescedValues, schema) + } + + return nil } diff --git a/pkg/chart/v2/lint/rules/values_test.go b/pkg/chart/v2/lint/rules/values_test.go index 348695785..288b77436 100644 --- a/pkg/chart/v2/lint/rules/values_test.go +++ b/pkg/chart/v2/lint/rules/values_test.go @@ -67,7 +67,7 @@ func TestValidateValuesFileWellFormed(t *testing.T) { ` tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml)) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}); err == nil { + if err := validateValuesFile(valfile, map[string]interface{}{}, false); err == nil { t.Fatal("expected values file to fail parsing") } } @@ -78,7 +78,7 @@ func TestValidateValuesFileSchema(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}); err != nil { + if err := validateValuesFile(valfile, map[string]interface{}{}, false); err != nil { t.Fatalf("Failed validation with %s", err) } } @@ -91,7 +91,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, map[string]interface{}{}) + err := validateValuesFile(valfile, map[string]interface{}{}, false) if err == nil { t.Fatal("expected values file to fail parsing") } @@ -99,6 +99,20 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { assert.Contains(t, err.Error(), "- at '/username': got number, want string") } +func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T) { + // 1234 is an int, not a string. This should fail normally but pass with skipSchemaValidation. + yaml := "username: 1234\npassword: swordfish" + tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml)) + createTestingSchema(t, tmpdir) + + valfile := filepath.Join(tmpdir, "values.yaml") + + err := validateValuesFile(valfile, map[string]interface{}{}, true) + if err != nil { + t.Fatal("expected values file to pass parsing because of skipSchemaValidation") + } +} + func TestValidateValuesFileSchemaOverrides(t *testing.T) { yaml := "username: admin" overrides := map[string]interface{}{ @@ -108,7 +122,7 @@ func TestValidateValuesFileSchemaOverrides(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, overrides); err != nil { + if err := validateValuesFile(valfile, overrides, false); err != nil { t.Fatalf("Failed validation with %s", err) } } @@ -145,7 +159,7 @@ func TestValidateValuesFile(t *testing.T) { valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, tt.overrides) + err := validateValuesFile(valfile, tt.overrides, false) switch { case err != nil && tt.errorMessage == "":