summaryrefslogtreecommitdiffhomepage
path: root/tools/yamltest/main.go
diff options
context:
space:
mode:
authorAdin Scannell <ascannell@google.com>2021-01-05 10:40:58 -0800
committergVisor bot <gvisor-bot@google.com>2021-01-05 10:43:04 -0800
commit2a5d3c248fbccfd1f711d027d70ba855625f22f3 (patch)
tree62c5dc39a60a1cfa90cafe7bcd5072576f2011cf /tools/yamltest/main.go
parent622db84e4bba468205c85c80a93b8b9a9c2c9ee3 (diff)
Add YAML validation for configuration files.
For validation, the "on" key in existing YAML files is changed to a literal string. In the YAML spec, on is a keyword which encodes a boolean value, so without relying on a specific implementation the YAML files are technically not encoding an object that complies with the specification. PiperOrigin-RevId: 350172147
Diffstat (limited to 'tools/yamltest/main.go')
-rw-r--r--tools/yamltest/main.go133
1 files changed, 133 insertions, 0 deletions
diff --git a/tools/yamltest/main.go b/tools/yamltest/main.go
new file mode 100644
index 000000000..88271fb66
--- /dev/null
+++ b/tools/yamltest/main.go
@@ -0,0 +1,133 @@
+// Copyright 2020 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Binary yamltest does strict yaml parsing and validation.
+package main
+
+import (
+ "encoding/json"
+ "errors"
+ "flag"
+ "fmt"
+ "os"
+
+ "github.com/xeipuuv/gojsonschema"
+ yaml "gopkg.in/yaml.v2"
+)
+
+func fixup(v interface{}) (interface{}, error) {
+ switch x := v.(type) {
+ case map[interface{}]interface{}:
+ // Coerse into a string-based map, required for yaml.
+ strMap := make(map[string]interface{})
+ for k, v := range x {
+ strK, ok := k.(string)
+ if !ok {
+ // This cannot be converted to JSON at all.
+ return nil, fmt.Errorf("invalid key %T in (%#v)", k, x)
+ }
+ fv, err := fixup(v)
+ if err != nil {
+ return nil, fmt.Errorf(".%s%w", strK, err)
+ }
+ strMap[strK] = fv
+ }
+ return strMap, nil
+ case []interface{}:
+ for i := range x {
+ fv, err := fixup(x[i])
+ if err != nil {
+ return nil, fmt.Errorf("[%d]%w", i, err)
+ }
+ x[i] = fv
+ }
+ return x, nil
+ default:
+ return v, nil
+ }
+}
+
+func loadFile(filename string) (gojsonschema.JSONLoader, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ dec := yaml.NewDecoder(f)
+ dec.SetStrict(true)
+ var object interface{}
+ if err := dec.Decode(&object); err != nil {
+ return nil, err
+ }
+ fixedObject, err := fixup(object) // For serialization.
+ if err != nil {
+ return nil, err
+ }
+ bytes, err := json.Marshal(fixedObject)
+ if err != nil {
+ return nil, err
+ }
+ return gojsonschema.NewStringLoader(string(bytes)), nil
+}
+
+var schema = flag.String("schema", "", "path to JSON schema file.")
+
+func main() {
+ flag.Parse()
+ if *schema == "" || len(flag.Args()) == 0 {
+ flag.Usage()
+ os.Exit(2)
+ }
+
+ // Construct our schema loader.
+ schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file://%s", *schema))
+
+ // Parse all documents.
+ allErrors := make(map[string][]error)
+ for _, filename := range flag.Args() {
+ // Record the filename with an empty slice for below, where
+ // we will emit all files (even those without any errors).
+ allErrors[filename] = nil
+ documentLoader, err := loadFile(filename)
+ if err != nil {
+ allErrors[filename] = append(allErrors[filename], err)
+ continue
+ }
+ result, err := gojsonschema.Validate(schemaLoader, documentLoader)
+ if err != nil {
+ allErrors[filename] = append(allErrors[filename], err)
+ continue
+ }
+ for _, desc := range result.Errors() {
+ allErrors[filename] = append(allErrors[filename], errors.New(desc.String()))
+ }
+ }
+
+ // Print errors in yaml format.
+ totalErrors := 0
+ for filename, errs := range allErrors {
+ totalErrors += len(errs)
+ if len(errs) == 0 {
+ fmt.Fprintf(os.Stderr, "%s: ✓\n", filename)
+ continue
+ }
+ fmt.Fprintf(os.Stderr, "%s:\n", filename)
+ for _, err := range errs {
+ fmt.Fprintf(os.Stderr, "- %s\n", err)
+ }
+ }
+ if totalErrors != 0 {
+ os.Exit(1)
+ }
+}