pull/31274/merge
Benoit Tigeot 14 hours ago committed by GitHub
commit f6176da0fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -75,9 +75,9 @@ func validateValuesFile(valuesPath string, overrides map[string]interface{}, ski
if err != nil { if err != nil {
return err return err
} }
baseDir := filepath.Dir(schemaPath)
if !skipSchemaValidation { if !skipSchemaValidation {
return util.ValidateAgainstSingleSchema(coalescedValues, schema) return util.ValidateAgainstSingleSchema(coalescedValues, schema, baseDir)
} }
return nil return nil

@ -23,6 +23,7 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"net/http" "net/http"
"path/filepath"
"strings" "strings"
"time" "time"
@ -80,7 +81,7 @@ func ValidateAgainstSchema(ch chart.Charter, values map[string]interface{}) erro
var sb strings.Builder var sb strings.Builder
if chrt.Schema() != nil { if chrt.Schema() != nil {
slog.Debug("chart name", "chart-name", chrt.Name()) slog.Debug("chart name", "chart-name", chrt.Name())
err := ValidateAgainstSingleSchema(values, chrt.Schema()) err := ValidateAgainstSingleSchema(values, chrt.Schema(), chrt.ChartFullPath())
if err != nil { if err != nil {
sb.WriteString(fmt.Sprintf("%s:\n", chrt.Name())) sb.WriteString(fmt.Sprintf("%s:\n", chrt.Name()))
sb.WriteString(err.Error()) sb.WriteString(err.Error())
@ -107,7 +108,7 @@ func ValidateAgainstSchema(ch chart.Charter, values map[string]interface{}) erro
} }
// ValidateAgainstSingleSchema checks that values does not violate the structure laid out in this schema // ValidateAgainstSingleSchema checks that values does not violate the structure laid out in this schema
func ValidateAgainstSingleSchema(values common.Values, schemaJSON []byte) (reterr error) { func ValidateAgainstSingleSchema(values common.Values, schemaJSON []byte, absBaseDir string) (reterr error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
reterr = fmt.Errorf("unable to validate schema: %s", r) reterr = fmt.Errorf("unable to validate schema: %s", r)
@ -131,12 +132,13 @@ func ValidateAgainstSingleSchema(values common.Values, schemaJSON []byte) (reter
compiler := jsonschema.NewCompiler() compiler := jsonschema.NewCompiler()
compiler.UseLoader(loader) compiler.UseLoader(loader)
err = compiler.AddResource("file:///values.schema.json", schema) base := "file://" + filepath.ToSlash(absBaseDir) + "/values.schema.json"
err = compiler.AddResource(base, schema)
if err != nil { if err != nil {
return err return err
} }
validator, err := compiler.Compile("file:///values.schema.json") validator, err := compiler.Compile(base)
if err != nil { if err != nil {
return err return err
} }
@ -165,7 +167,11 @@ func (e JSONSchemaValidationError) Error() string {
// This string prefixes all of our error details. Further up the stack of helm error message // This string prefixes all of our error details. Further up the stack of helm error message
// building more detail is provided to users. This is removed. // building more detail is provided to users. This is removed.
errStr = strings.TrimPrefix(errStr, "jsonschema validation failed with 'file:///values.schema.json#'\n") if strings.HasPrefix(errStr, "jsonschema validation failed with ") {
if idx := strings.Index(errStr, "#'\n"); idx != -1 {
errStr = errStr[idx+3:]
}
}
// The extra new line is needed for when there are sub-charts. // The extra new line is needed for when there are sub-charts.
return errStr + "\n" return errStr + "\n"

@ -20,6 +20,7 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
"path/filepath"
"strings" "strings"
"testing" "testing"
@ -37,7 +38,7 @@ func TestValidateAgainstSingleSchema(t *testing.T) {
t.Fatalf("Error reading YAML file: %s", err) t.Fatalf("Error reading YAML file: %s", err)
} }
if err := ValidateAgainstSingleSchema(values, schema); err != nil { if err := ValidateAgainstSingleSchema(values, schema, ""); err != nil {
t.Errorf("Error validating Values against Schema: %s", err) t.Errorf("Error validating Values against Schema: %s", err)
} }
} }
@ -53,7 +54,7 @@ func TestValidateAgainstInvalidSingleSchema(t *testing.T) {
} }
var errString string var errString string
if err := ValidateAgainstSingleSchema(values, schema); err == nil { if err := ValidateAgainstSingleSchema(values, schema, ""); err == nil {
t.Fatalf("Expected an error, but got nil") t.Fatalf("Expected an error, but got nil")
} else { } else {
errString = err.Error() errString = err.Error()
@ -77,7 +78,7 @@ func TestValidateAgainstSingleSchemaNegative(t *testing.T) {
} }
var errString string var errString string
if err := ValidateAgainstSingleSchema(values, schema); err == nil { if err := ValidateAgainstSingleSchema(values, schema, ""); err == nil {
t.Fatalf("Expected an error, but got nil") t.Fatalf("Expected an error, but got nil")
} else { } else {
errString = err.Error() errString = err.Error()
@ -177,11 +178,12 @@ func TestValidateAgainstSchemaNegative(t *testing.T) {
errString = err.Error() errString = err.Error()
} }
expectedErrString := `subchart: expectedValidationError := "missing property 'age'"
- at '': missing property 'age' if !strings.Contains(errString, "subchart:") {
` t.Errorf("Error string should contain 'subchart:', got: %s", errString)
if errString != expectedErrString { }
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) if !strings.Contains(errString, expectedValidationError) {
t.Errorf("Error string should contain '%s', got: %s", expectedValidationError, errString)
} }
} }
@ -241,12 +243,64 @@ func TestValidateAgainstSchema2020Negative(t *testing.T) {
errString = err.Error() errString = err.Error()
} }
expectedErrString := `subchart: expectedValidationErrors := []string{
- at '/data': no items match contains schema "no items match contains schema",
- at '/data/0': got number, want string "got number, want string",
` }
if errString != expectedErrString { if !strings.Contains(errString, "subchart:") {
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) t.Errorf("Error string should contain 'subchart:', got: %s", errString)
}
for _, expectedErr := range expectedValidationErrors {
if !strings.Contains(errString, expectedErr) {
t.Errorf("Error string should contain '%s', got: %s", expectedErr, errString)
}
}
}
// TestValidateWithRelativeSchemaReferences tests schema validation with relative $ref paths
// This mimics the behavior of "helm lint ." where the schema is in the current directory
func TestValidateWithRelativeSchemaReferencesCurrentDir(t *testing.T) {
values, err := common.ReadValuesFile("./testdata/current-dir-test/test-values.yaml")
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
schema, err := os.ReadFile("./testdata/current-dir-test/values.schema.json")
if err != nil {
t.Fatalf("Error reading JSON schema file: %s", err)
}
// Test with absolute base directory - this should work with your fix
baseDir := "./testdata/current-dir-test"
absBaseDir, err := filepath.Abs(baseDir)
if err != nil {
t.Fatalf("Error getting absolute path: %s", err)
}
if err := ValidateAgainstSingleSchema(values, schema, absBaseDir); err != nil {
t.Errorf("Error validating Values against Schema with relative references: %s", err)
}
}
// TestValidateWithRelativeSchemaReferencesSubfolder tests schema validation with relative $ref paths
// This mimics the behavior of "helm lint subfolder" where the schema is in a subdirectory
func TestValidateWithRelativeSchemaReferencesSubfolder(t *testing.T) {
values, err := common.ReadValuesFile("./testdata/subdir-test/subfolder/test-values.yaml")
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
schema, err := os.ReadFile("./testdata/subdir-test/subfolder/values.schema.json")
if err != nil {
t.Fatalf("Error reading JSON schema file: %s", err)
}
baseDir := "./testdata/subdir-test/subfolder"
absBaseDir, err := filepath.Abs(baseDir)
if err != nil {
t.Fatalf("Error getting absolute path: %s", err)
}
if err := ValidateAgainstSingleSchema(values, schema, absBaseDir); err != nil {
t.Errorf("Error validating Values against Schema with relative references from subfolder: %s", err)
} }
} }

@ -0,0 +1,10 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
}

@ -0,0 +1,3 @@
user:
name: "John Doe"
age: 30

@ -0,0 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"user": {
"$ref": "./base.schema.json"
},
"age": {
"type": "integer",
"minimum": 0
}
},
"required": ["user", "age"]
}

@ -0,0 +1,10 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"config": {
"type": "string"
}
},
"required": ["config"]
}

@ -0,0 +1,3 @@
appConfig:
config: "production"
replicas: 3

@ -0,0 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"appConfig": {
"$ref": "../shared.schema.json"
},
"replicas": {
"type": "integer",
"minimum": 1
}
},
"required": ["appConfig", "replicas"]
}

@ -75,9 +75,9 @@ func validateValuesFile(valuesPath string, overrides map[string]interface{}, ski
if err != nil { if err != nil {
return err return err
} }
baseDir := filepath.Dir(schemaPath)
if !skipSchemaValidation { if !skipSchemaValidation {
return util.ValidateAgainstSingleSchema(coalescedValues, schema) return util.ValidateAgainstSingleSchema(coalescedValues, schema, baseDir)
} }
return nil return nil

Loading…
Cancel
Save