Skip to content

Commit

Permalink
Basic schema cache
Browse files Browse the repository at this point in the history
  • Loading branch information
johanneswuerbach committed May 27, 2019
1 parent a048e1a commit 364103a
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 8 deletions.
35 changes: 29 additions & 6 deletions kubeval/kubeval.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func determineAPIVersion(body interface{}) (string, error) {

// validateResource validates a single Kubernetes resource against
// the relevant schema, detecting the type of resource automatically
func validateResource(data []byte, fileName string) (ValidationResult, error) {
func validateResource(data []byte, fileName string, schemaCache map[string]*gojsonschema.Schema) (ValidationResult, error) {
var spec interface{}
result := ValidationResult{}
result.FileName = fileName
Expand Down Expand Up @@ -180,9 +180,17 @@ func validateResource(data []byte, fileName string) (ValidationResult, error) {
}
result.APIVersion = apiVersion

schema := determineSchema(kind, apiVersion)
schemaRef := determineSchema(kind, apiVersion)

schemaLoader := gojsonschema.NewReferenceLoader(schema)
schema, ok := schemaCache[schemaRef]
if !ok {
schemaLoader := gojsonschema.NewReferenceLoader(schemaRef)
schema, err = gojsonschema.NewSchema(schemaLoader)
if err != nil {
return result, fmt.Errorf("Failed initalizing schema %s: %s", schemaRef, err)
}
schemaCache[schemaRef] = schema
}

// Without forcing these types the schema fails to load
// Need to Work out proper handling for these types
Expand All @@ -191,9 +199,9 @@ func validateResource(data []byte, fileName string) (ValidationResult, error) {
gojsonschema.FormatCheckers.Add("int32", ValidFormat{})
gojsonschema.FormatCheckers.Add("int-or-string", ValidFormat{})

results, err := gojsonschema.Validate(schemaLoader, documentLoader)
results, err := schema.Validate(documentLoader)
if err != nil {
return result, fmt.Errorf("Problem loading schema from the network at %s: %s", schema, err)
return result, fmt.Errorf("Problem loading schema from the network at %s: %s", schemaRef, err)
}

if results.Valid() {
Expand All @@ -204,10 +212,25 @@ func validateResource(data []byte, fileName string) (ValidationResult, error) {
return result, nil
}

// NewSchemaCache returns a new schema cache to be used with
// ValidateWithCache
func NewSchemaCache() map[string]*gojsonschema.Schema {
return make(map[string]*gojsonschema.Schema, 0)
}

// Validate a Kubernetes YAML file, parsing out individual resources
// and validating them all according to the relevant schemas
// TODO This function requires a judicious amount of refactoring.
func Validate(config []byte, fileName string) ([]ValidationResult, error) {
schemaCache := NewSchemaCache()
return ValidateWithCache(config, fileName, schemaCache)
}

// ValidateWithCache validates a Kubernetes YAML file, parsing out individual resources
// and validating them all according to the relevant schemas
// Allows passing a kubeval.NewSchemaCache() to cache schemas in-memory
// between validations
func ValidateWithCache(config []byte, fileName string, schemaCache map[string]*gojsonschema.Schema) ([]ValidationResult, error) {
results := make([]ValidationResult, 0)

if len(config) == 0 {
Expand All @@ -222,7 +245,7 @@ func Validate(config []byte, fileName string) ([]ValidationResult, error) {
var errors *multierror.Error
for _, element := range bits {
if len(element) > 0 {
result, err := validateResource(element, fileName)
result, err := validateResource(element, fileName, schemaCache)
results = append(results, result)
if err != nil {
errors = multierror.Append(errors, err)
Expand Down
27 changes: 27 additions & 0 deletions kubeval/kubeval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"io/ioutil"
"path/filepath"
"testing"

"github.com/xeipuuv/gojsonschema"
)

func TestValidateBlankInput(t *testing.T) {
Expand Down Expand Up @@ -37,6 +39,31 @@ func TestValidateValidInputs(t *testing.T) {
}
}

func TestValidateValidInputsWithCache(t *testing.T) {
var tests = []string{
"blank.yaml",
"comment.yaml",
"valid.yaml",
"valid.json",
"multi_valid.yaml",
"int_or_string.yaml",
"null_array.yaml",
"quantity.yaml",
"extra_property.yaml",
"full_domain_group.yaml",
}
schemaCache := make(map[string]*gojsonschema.Schema, 0)

for _, test := range tests {
filePath, _ := filepath.Abs("../fixtures/" + test)
fileContents, _ := ioutil.ReadFile(filePath)
_, err := ValidateWithCache(fileContents, test, schemaCache)
if err != nil {
t.Errorf("Validate should pass when testing valid configuration in " + test)
}
}
}

func TestValidateInvalidInputs(t *testing.T) {
var tests = []string{
"missing_kind.yaml",
Expand Down
6 changes: 4 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ var RootCmd = &cobra.Command{
for scanner.Scan() {
buffer.WriteString(scanner.Text() + "\n")
}
results, err := kubeval.Validate(buffer.Bytes(), viper.GetString("filename"))
schemaCache := kubeval.NewSchemaCache()
results, err := kubeval.ValidateWithCache(buffer.Bytes(), viper.GetString("filename"), schemaCache)
if err != nil {
log.Error(err)
os.Exit(1)
Expand All @@ -62,14 +63,15 @@ var RootCmd = &cobra.Command{
log.Error("You must pass at least one file as an argument")
os.Exit(1)
}
schemaCache := kubeval.NewSchemaCache()
for _, fileName := range args {
filePath, _ := filepath.Abs(fileName)
fileContents, err := ioutil.ReadFile(filePath)
if err != nil {
log.Error("Could not open file", fileName)
os.Exit(1)
}
results, err := kubeval.Validate(fileContents, fileName)
results, err := kubeval.ValidateWithCache(fileContents, fileName, schemaCache)
if err != nil {
log.Error(err)
os.Exit(1)
Expand Down

0 comments on commit 364103a

Please sign in to comment.