mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
Resolve JSON schema reference paths for relative imports
When validating values against JSON schemas, relative file references in the schema (like "$ref": "./other-schema.json") were failing because in some cases. The JSON schema validator couldn't resolve the correct base directory. This change passes the absolute base directory to the schema validator, allowing it to properly resolve relative JSON schema references by constructing the correct file:// URLs with the proper path context. Tested with: - `helm lint .` - `helm lint subfolder` Fixes #31260 Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr> Add test
This commit is contained in:
parent
1455d12abb
commit
7aba8bbfe6
10 changed files with 132 additions and 23 deletions
|
|
@ -75,9 +75,9 @@ func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaV
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseDir := filepath.Dir(schemaPath)
|
||||
if !skipSchemaValidation {
|
||||
return util.ValidateAgainstSingleSchema(coalescedValues, schema)
|
||||
return util.ValidateAgainstSingleSchema(coalescedValues, schema, baseDir)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -80,8 +81,7 @@ func ValidateAgainstSchema(ch chart.Charter, values map[string]any) error {
|
|||
}
|
||||
var sb strings.Builder
|
||||
if chrt.Schema() != nil {
|
||||
slog.Debug("chart name", "chart-name", chrt.Name())
|
||||
err := ValidateAgainstSingleSchema(values, chrt.Schema())
|
||||
err := ValidateAgainstSingleSchema(values, chrt.Schema(), chrt.ChartFullPath())
|
||||
if err != nil {
|
||||
fmt.Fprintf(&sb, "%s:\n", chrt.Name())
|
||||
sb.WriteString(err.Error())
|
||||
|
|
@ -121,7 +121,7 @@ func ValidateAgainstSchema(ch chart.Charter, values map[string]any) error {
|
|||
}
|
||||
|
||||
// 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() {
|
||||
if r := recover(); r != nil {
|
||||
reterr = fmt.Errorf("unable to validate schema: %s", r)
|
||||
|
|
@ -146,12 +146,13 @@ func ValidateAgainstSingleSchema(values common.Values, schemaJSON []byte) (reter
|
|||
|
||||
compiler := jsonschema.NewCompiler()
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
validator, err := compiler.Compile("file:///values.schema.json")
|
||||
validator, err := compiler.Compile(base)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ func TestValidateAgainstSingleSchema(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -53,7 +54,7 @@ func TestValidateAgainstInvalidSingleSchema(t *testing.T) {
|
|||
}
|
||||
|
||||
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")
|
||||
} else {
|
||||
errString = err.Error()
|
||||
|
|
@ -77,7 +78,7 @@ func TestValidateAgainstSingleSchemaNegative(t *testing.T) {
|
|||
}
|
||||
|
||||
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")
|
||||
} else {
|
||||
errString = err.Error()
|
||||
|
|
@ -177,11 +178,12 @@ func TestValidateAgainstSchemaNegative(t *testing.T) {
|
|||
errString = err.Error()
|
||||
}
|
||||
|
||||
expectedErrString := `subchart:
|
||||
- at '': missing property 'age'
|
||||
`
|
||||
if errString != expectedErrString {
|
||||
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
|
||||
expectedValidationError := "missing property 'age'"
|
||||
if !strings.Contains(errString, "subchart:") {
|
||||
t.Errorf("Error string should contain 'subchart:', got: %s", errString)
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
expectedErrString := `subchart:
|
||||
- at '/data': no items match contains schema
|
||||
- at '/data/0': got number, want string
|
||||
`
|
||||
if errString != expectedErrString {
|
||||
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
|
||||
expectedValidationErrors := []string{
|
||||
"no items match contains schema",
|
||||
"got number, want string",
|
||||
}
|
||||
if !strings.Contains(errString, "subchart:") {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
10
pkg/chart/common/util/testdata/current-dir-test/base.schema.json
vendored
Normal file
10
pkg/chart/common/util/testdata/current-dir-test/base.schema.json
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
3
pkg/chart/common/util/testdata/current-dir-test/test-values.yaml
vendored
Normal file
3
pkg/chart/common/util/testdata/current-dir-test/test-values.yaml
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
user:
|
||||
name: "John Doe"
|
||||
age: 30
|
||||
14
pkg/chart/common/util/testdata/current-dir-test/values.schema.json
vendored
Normal file
14
pkg/chart/common/util/testdata/current-dir-test/values.schema.json
vendored
Normal file
|
|
@ -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"]
|
||||
}
|
||||
10
pkg/chart/common/util/testdata/subdir-test/shared.schema.json
vendored
Normal file
10
pkg/chart/common/util/testdata/subdir-test/shared.schema.json
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["config"]
|
||||
}
|
||||
3
pkg/chart/common/util/testdata/subdir-test/subfolder/test-values.yaml
vendored
Normal file
3
pkg/chart/common/util/testdata/subdir-test/subfolder/test-values.yaml
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
appConfig:
|
||||
config: "production"
|
||||
replicas: 3
|
||||
14
pkg/chart/common/util/testdata/subdir-test/subfolder/values.schema.json
vendored
Normal file
14
pkg/chart/common/util/testdata/subdir-test/subfolder/values.schema.json
vendored
Normal file
|
|
@ -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]any, skipSchemaV
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseDir := filepath.Dir(schemaPath)
|
||||
if !skipSchemaValidation {
|
||||
return util.ValidateAgainstSingleSchema(coalescedValues, schema)
|
||||
return util.ValidateAgainstSingleSchema(coalescedValues, schema, baseDir)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
Loading…
Reference in a new issue