Replace xeipuuv/gojsonschema with santhosh-tekuri/jsonschema

Signed-off-by: Ferran Vidal <ferran.vidal.p@gmail.com>
pull/11340/head
Ferran Vidal 3 years ago
parent 55a4fc121b
commit 7e89d0dcd3
No known key found for this signature in database

@ -33,7 +33,6 @@ require (
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6 github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.37.0 golang.org/x/crypto v0.37.0
golang.org/x/term v0.31.0 golang.org/x/term v0.31.0
golang.org/x/text v0.24.0 golang.org/x/text v0.24.0
@ -136,8 +135,6 @@ require (
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cast v1.7.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xlab/treeprint v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect

@ -326,13 +326,6 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

@ -24,8 +24,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/santhosh-tekuri/jsonschema/v6" "github.com/santhosh-tekuri/jsonschema/v6"
"github.com/xeipuuv/gojsonschema"
"sigs.k8s.io/yaml"
chart "helm.sh/helm/v4/pkg/chart/v2" chart "helm.sh/helm/v4/pkg/chart/v2"
) )
@ -34,10 +32,9 @@ import (
func ValidateAgainstSchema(chrt *chart.Chart, values map[string]interface{}) error { func ValidateAgainstSchema(chrt *chart.Chart, values map[string]interface{}) error {
var sb strings.Builder var sb strings.Builder
if chrt.Schema != nil { if chrt.Schema != nil {
err := ValidateAgainstSingleSchema(values, chrt.Schema) if err := ValidateAgainstSingleSchema(values, chrt.Schema); 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(fmt.Sprintf("%s\n", err.Error()))
} }
} }
@ -64,69 +61,57 @@ func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) (reterr error
} }
}() }()
valuesData, err := yaml.Marshal(values) schema, err := jsonschema.UnmarshalJSON(bytes.NewReader(schemaJSON))
if err != nil {
return err
}
valuesJSON, err := yaml.YAMLToJSON(valuesData)
if err != nil { if err != nil {
return err return err
} }
if bytes.Equal(valuesJSON, []byte("null")) {
valuesJSON = []byte("{}")
}
if schemaIs2020(schemaJSON) { compiler := jsonschema.NewCompiler()
return validateUsingNewValidator(valuesJSON, schemaJSON) if err := compiler.AddResource("file:///values.schema.json", schema); err != nil {
}
schemaLoader := gojsonschema.NewBytesLoader(schemaJSON)
valuesLoader := gojsonschema.NewBytesLoader(valuesJSON)
result, err := gojsonschema.Validate(schemaLoader, valuesLoader)
if err != nil {
return err return err
} }
if !result.Valid() { validator, err := compiler.Compile("file:///values.schema.json")
var sb strings.Builder
for _, desc := range result.Errors() {
sb.WriteString(fmt.Sprintf("- %s\n", desc))
}
return errors.New(sb.String())
}
return nil
}
func validateUsingNewValidator(valuesJSON, schemaJSON []byte) error {
schema, err := jsonschema.UnmarshalJSON(bytes.NewReader(schemaJSON))
if err != nil { if err != nil {
return err return err
} }
values, err := jsonschema.UnmarshalJSON(bytes.NewReader(valuesJSON))
valuesJSON, err := json.Marshal(values)
if err != nil { if err != nil {
return err return err
} }
compiler := jsonschema.NewCompiler() if bytes.Equal(valuesJSON, []byte("null")) {
err = compiler.AddResource("file:///values.schema.json", schema) valuesJSON = []byte("{}")
if err != nil {
return err
} }
validator, err := compiler.Compile("file:///values.schema.json") valuesObj, err := jsonschema.UnmarshalJSON(bytes.NewReader(valuesJSON))
if err != nil { if err != nil {
return err return err
} }
return validator.Validate(values) if err = validator.Validate(valuesObj); err != nil {
} var jsonschemaError *jsonschema.ValidationError
if errors.As(err, &jsonschemaError) {
// We remove the initial error line as it points to a fake schema file location, and might lead to confusion.
// We replace the empty location string with `/` to make it more readable.
cleanErrMessage := strings.Replace(
strings.Replace(
jsonschemaError.Error(),
"jsonschema validation failed with 'file:///values.schema.json#'\n",
"",
-1,
),
"- at '':",
"- at '/':",
-1,
)
return errors.New(cleanErrMessage)
}
func schemaIs2020(schemaJSON []byte) bool { return err
var partialSchema struct {
Schema string `json:"$schema"`
} }
_ = json.Unmarshal(schemaJSON, &partialSchema)
return partialSchema.Schema == "https://json-schema.org/draft/2020-12/schema" return nil
} }

@ -20,6 +20,8 @@ import (
"os" "os"
"testing" "testing"
"github.com/stretchr/testify/assert"
chart "helm.sh/helm/v4/pkg/chart/v2" chart "helm.sh/helm/v4/pkg/chart/v2"
) )
@ -40,51 +42,31 @@ func TestValidateAgainstSingleSchema(t *testing.T) {
func TestValidateAgainstInvalidSingleSchema(t *testing.T) { func TestValidateAgainstInvalidSingleSchema(t *testing.T) {
values, err := ReadValuesFile("./testdata/test-values.yaml") values, err := ReadValuesFile("./testdata/test-values.yaml")
if err != nil { assert.NoError(t, err)
t.Fatalf("Error reading YAML file: %s", err)
}
schema, err := os.ReadFile("./testdata/test-values-invalid.schema.json")
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
var errString string schema, err := os.ReadFile("./testdata/test-values-invalid.schema.json")
if err := ValidateAgainstSingleSchema(values, schema); err == nil { assert.NoError(t, err)
t.Fatalf("Expected an error, but got nil")
} else {
errString = err.Error()
}
expectedErrString := "unable to validate schema: runtime error: invalid " + assert.EqualError(
"memory address or nil pointer dereference" t,
if errString != expectedErrString { ValidateAgainstSingleSchema(values, schema),
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) "unable to validate schema: runtime error: invalid memory address or nil pointer dereference",
} )
} }
func TestValidateAgainstSingleSchemaNegative(t *testing.T) { func TestValidateAgainstSingleSchemaNegative(t *testing.T) {
values, err := ReadValuesFile("./testdata/test-values-negative.yaml") values, err := ReadValuesFile("./testdata/test-values-negative.yaml")
if err != nil { assert.NoError(t, err)
t.Fatalf("Error reading YAML file: %s", err)
}
schema, err := os.ReadFile("./testdata/test-values.schema.json")
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
var errString string
if err := ValidateAgainstSingleSchema(values, schema); err == nil {
t.Fatalf("Expected an error, but got nil")
} else {
errString = err.Error()
}
expectedErrString := `- (root): employmentInfo is required schema, err := os.ReadFile("./testdata/test-values.schema.json")
- age: Must be greater than or equal to 0 assert.NoError(t, err)
`
if errString != expectedErrString { assert.EqualError(
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) t,
} ValidateAgainstSingleSchema(values, schema),
`- at '/': missing property 'employmentInfo'
- at '/age': minimum: got -5, want 0`,
)
} }
const subchartSchema = `{ const subchartSchema = `{
@ -166,19 +148,13 @@ func TestValidateAgainstSchemaNegative(t *testing.T) {
"subchart": map[string]interface{}{}, "subchart": map[string]interface{}{},
} }
var errString string assert.EqualError(
if err := ValidateAgainstSchema(chrt, vals); err == nil { t,
t.Fatalf("Expected an error, but got nil") ValidateAgainstSchema(chrt, vals),
} else { `subchart:
errString = err.Error() - at '/': missing property 'age'
} `,
)
expectedErrString := `subchart:
- (root): age is required
`
if errString != expectedErrString {
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
}
} }
func TestValidateAgainstSchema2020(t *testing.T) { func TestValidateAgainstSchema2020(t *testing.T) {
@ -230,18 +206,12 @@ func TestValidateAgainstSchema2020Negative(t *testing.T) {
}, },
} }
var errString string assert.EqualError(
if err := ValidateAgainstSchema(chrt, vals); err == nil { t,
t.Fatalf("Expected an error, but got nil") ValidateAgainstSchema(chrt, vals),
} else { `subchart:
errString = err.Error()
}
expectedErrString := `subchart:
jsonschema validation failed with 'file:///values.schema.json#'
- at '/data': no items match contains schema - at '/data': no items match contains schema
- at '/data/0': got number, want string` - at '/data/0': got number, want string
if errString != expectedErrString { `,
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) )
}
} }

@ -1,4 +1,4 @@
Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s): Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s):
empty: empty:
- age: Must be greater than or equal to 0 - at '/age': minimum: got -5, want 0

@ -1,5 +1,5 @@
Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s): Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s):
empty: empty:
- (root): employmentInfo is required - at '/': missing property 'employmentInfo'
- age: Must be greater than or equal to 0 - at '/age': minimum: got -5, want 0

@ -1,4 +1,4 @@
Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s): Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s):
subchart-with-schema: subchart-with-schema:
- age: Must be greater than or equal to 0 - at '/age': minimum: got -25, want 0

@ -1,6 +1,6 @@
Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s): Error: INSTALLATION FAILED: values don't meet the specifications of the schema(s) in the following chart(s):
chart-without-schema: chart-without-schema:
- (root): lastname is required - at '/': missing property 'lastname'
subchart-with-schema: subchart-with-schema:
- (root): age is required - at '/': missing property 'age'

@ -96,7 +96,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) {
t.Fatal("expected values file to fail parsing") t.Fatal("expected values file to fail parsing")
} }
assert.Contains(t, err.Error(), "Expected: string, given: integer", "integer should be caught by schema") assert.Equal(t, err.Error(), "- at '/username': got number, want string", "integer should be caught by schema")
} }
func TestValidateValuesFileSchemaOverrides(t *testing.T) { func TestValidateValuesFileSchemaOverrides(t *testing.T) {
@ -129,7 +129,7 @@ func TestValidateValuesFile(t *testing.T) {
name: "value not overridden", name: "value not overridden",
yaml: "username: admin\npassword:", yaml: "username: admin\npassword:",
overrides: map[string]interface{}{"username": "anotherUser"}, overrides: map[string]interface{}{"username": "anotherUser"},
errorMessage: "Expected: string, given: null", errorMessage: "- at '/password': got null, want string",
}, },
{ {
name: "value overridden", name: "value overridden",
@ -153,7 +153,7 @@ func TestValidateValuesFile(t *testing.T) {
case err == nil && tt.errorMessage != "": case err == nil && tt.errorMessage != "":
t.Error("expected values file to fail parsing") t.Error("expected values file to fail parsing")
case err != nil && tt.errorMessage != "": case err != nil && tt.errorMessage != "":
assert.Contains(t, err.Error(), tt.errorMessage, "Failed with unexpected error") assert.Equal(t, err.Error(), tt.errorMessage, "Failed with unexpected error")
} }
}) })
} }

Loading…
Cancel
Save