summaryrefslogtreecommitdiffhomepage
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
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
-rw-r--r--.github/workflows/build.yml2
-rw-r--r--.github/workflows/go.yml2
-rw-r--r--.github/workflows/issue_reviver.yml2
-rw-r--r--.github/workflows/labeler.yml2
-rw-r--r--.github/workflows/stale.yml2
-rw-r--r--BUILD19
-rw-r--r--WORKSPACE36
-rw-r--r--tools/nogo/BUILD2
-rw-r--r--tools/nogo/config-schema.json97
-rw-r--r--tools/yamltest/BUILD13
-rw-r--r--tools/yamltest/defs.bzl41
-rw-r--r--tools/yamltest/main.go133
12 files changed, 345 insertions, 6 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3be10b9bb..ab0bf9cb5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -6,7 +6,7 @@
# This workflow also generates the build badge that is referred to by
# the main README.
name: "Build"
-on:
+"on":
push:
branches:
- master
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index ff3059e2a..e62991691 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -3,7 +3,7 @@
# workflow simply generates and pushes the branch, as long as appropriate
# permissions are available.
name: "Go"
-on:
+"on":
push:
branches:
- master
diff --git a/.github/workflows/issue_reviver.yml b/.github/workflows/issue_reviver.yml
index f03b814c9..3bd883035 100644
--- a/.github/workflows/issue_reviver.yml
+++ b/.github/workflows/issue_reviver.yml
@@ -1,7 +1,7 @@
# This workflow revives issues that are still referenced in the code, and may
# have been accidentally closed or marked stale.
name: "Issue reviver"
-on:
+"on":
schedule:
- cron: '0 0 * * *'
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
index a53fdb3e9..3a19065e1 100644
--- a/.github/workflows/labeler.yml
+++ b/.github/workflows/labeler.yml
@@ -1,6 +1,6 @@
# Labeler labels incoming pull requests.
name: "Labeler"
-on:
+"on":
- pull_request
jobs:
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index be10c5bc4..3a4aa22e2 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -1,7 +1,7 @@
# The stale workflow closes stale issues and pull requests, unless specific
# tags have been applied in order to keep them open.
name: "Close stale issues"
-on:
+"on":
schedule:
- cron: "0 0 * * *"
diff --git a/BUILD b/BUILD
index 6f802fe6a..d19d19866 100644
--- a/BUILD
+++ b/BUILD
@@ -1,5 +1,6 @@
load("//tools:defs.bzl", "build_test", "gazelle", "go_path")
load("//tools/nogo:defs.bzl", "nogo_config")
+load("//tools/yamltest:defs.bzl", "yaml_test")
load("//website:defs.bzl", "doc")
package(licenses = ["notice"])
@@ -50,6 +51,24 @@ doc(
weight = "99",
)
+yaml_test(
+ name = "nogo_config_test",
+ srcs = glob(["nogo*.yaml"]),
+ schema = "//tools/nogo:config-schema.json",
+)
+
+yaml_test(
+ name = "github_workflows_test",
+ srcs = glob([".github/workflows/*.yml"]),
+ schema = "@github_workflow_schema//file",
+)
+
+yaml_test(
+ name = "buildkite_pipelines_test",
+ srcs = glob([".buildkite/*.yaml"]),
+ schema = "@buildkite_pipeline_schema//file",
+)
+
# The sandbox filegroup is used for sandbox-internal dependencies.
package_group(
name = "sandbox",
diff --git a/WORKSPACE b/WORKSPACE
index 933c1ff19..031c21163 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,4 +1,4 @@
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
# Bazel/starlark utilities.
@@ -176,6 +176,19 @@ http_archive(
],
)
+# Schemas for testing.
+http_file(
+ name = "buildkite_pipeline_schema",
+ sha256 = "3369c58038b4d55c08928affafb653716eb1e7b3cabb4a391aef979dd921f4e1",
+ urls = ["https://raw.githubusercontent.com/buildkite/pipeline-schema/f7a0894074d194bcf19eec5411fec0528f7f4180/schema.json"],
+)
+
+http_file(
+ name = "github_workflow_schema",
+ sha256 = "2c375bb43dbc8b32b1bed46c290d0b70a8fa2aca7a5484dfca1b6e9c38cf9e7a",
+ urls = ["https://raw.githubusercontent.com/SchemaStore/schemastore/27612065234778feaac216ce14dd47846fe0a2dd/src/schemas/json/github-workflow.json"],
+)
+
# External Go repositories.
#
# Unfortunately, gazelle will automatically parse go modules in the
@@ -1391,3 +1404,24 @@ go_repository(
sum = "h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE=",
version = "v0.0.0-20190801114015-581e00157fb1",
)
+
+go_repository(
+ name = "com_github_xeipuuv_gojsonpointer",
+ importpath = "github.com/xeipuuv/gojsonpointer",
+ sum = "h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=",
+ version = "v0.0.0-20190905194746-02993c407bfb",
+)
+
+go_repository(
+ name = "com_github_xeipuuv_gojsonreference",
+ importpath = "github.com/xeipuuv/gojsonreference",
+ sum = "h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=",
+ version = "v0.0.0-20180127040603-bd5ef7bd5415",
+)
+
+go_repository(
+ name = "com_github_xeipuuv_gojsonschema",
+ importpath = "github.com/xeipuuv/gojsonschema",
+ sum = "h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=",
+ version = "v1.2.0",
+)
diff --git a/tools/nogo/BUILD b/tools/nogo/BUILD
index 12b8b597c..566e0889e 100644
--- a/tools/nogo/BUILD
+++ b/tools/nogo/BUILD
@@ -3,6 +3,8 @@ load("//tools/nogo:defs.bzl", "nogo_objdump_tool", "nogo_stdlib", "nogo_target")
package(licenses = ["notice"])
+exports_files(["config-schema.json"])
+
nogo_target(
name = "target",
goarch = select_goarch(),
diff --git a/tools/nogo/config-schema.json b/tools/nogo/config-schema.json
new file mode 100644
index 000000000..3c25fe221
--- /dev/null
+++ b/tools/nogo/config-schema.json
@@ -0,0 +1,97 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "definitions": {
+ "group": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "The name of the group.",
+ "type": "string"
+ },
+ "regex": {
+ "description": "A regular expression for matching paths.",
+ "type": "string"
+ },
+ "default": {
+ "description": "Whether the group is enabled by default.",
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "name",
+ "regex",
+ "default"
+ ],
+ "additionalProperties": false
+ },
+ "regexlist": {
+ "description": "A list of regular expressions.",
+ "oneOf": [
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "rule": {
+ "type": "object",
+ "properties": {
+ "exclude": {
+ "description": "A regular expression for paths to exclude.",
+ "$ref": "#/definitions/regexlist"
+ },
+ "suppress": {
+ "description": "A regular expression for messages to suppress.",
+ "$ref": "#/definitions/regexlist"
+ }
+ },
+ "additionalProperties": false
+ },
+ "ruleList": {
+ "type": "object",
+ "additionalProperties": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/rule"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ },
+ "properties": {
+ "groups": {
+ "description": "A definition of all groups.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/group"
+ },
+ "minItems": 1
+ },
+ "global": {
+ "description": "A global set of rules.",
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/rule"
+ }
+ },
+ "analyzers": {
+ "description": "A definition of all groups.",
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/ruleList"
+ }
+ }
+ },
+ "required": [
+ "groups"
+ ],
+ "additionalProperties": false
+}
diff --git a/tools/yamltest/BUILD b/tools/yamltest/BUILD
new file mode 100644
index 000000000..475b3badd
--- /dev/null
+++ b/tools/yamltest/BUILD
@@ -0,0 +1,13 @@
+load("//tools:defs.bzl", "go_binary")
+
+package(licenses = ["notice"])
+
+go_binary(
+ name = "yamltest",
+ srcs = ["main.go"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "@com_github_xeipuuv_gojsonschema//:go_default_library",
+ "@in_gopkg_yaml_v2//:go_default_library",
+ ],
+)
diff --git a/tools/yamltest/defs.bzl b/tools/yamltest/defs.bzl
new file mode 100644
index 000000000..fd04f947d
--- /dev/null
+++ b/tools/yamltest/defs.bzl
@@ -0,0 +1,41 @@
+"""Tools for testing yaml files against schemas."""
+
+def _yaml_test_impl(ctx):
+ """Implementation for yaml_test."""
+ runner = ctx.actions.declare_file(ctx.label.name)
+ ctx.actions.write(runner, "\n".join([
+ "#!/bin/bash",
+ "set -euo pipefail",
+ "%s -schema=%s -- %s" % (
+ ctx.files._tool[0].short_path,
+ ctx.files.schema[0].short_path,
+ " ".join([f.short_path for f in ctx.files.srcs]),
+ ),
+ ]), is_executable = True)
+ return [DefaultInfo(
+ runfiles = ctx.runfiles(files = ctx.files._tool + ctx.files.schema + ctx.files.srcs),
+ executable = runner,
+ )]
+
+yaml_test = rule(
+ implementation = _yaml_test_impl,
+ doc = "Tests a yaml file against a schema.",
+ attrs = {
+ "srcs": attr.label_list(
+ doc = "The input yaml files.",
+ mandatory = True,
+ allow_files = True,
+ ),
+ "schema": attr.label(
+ doc = "The schema file in JSON schema format.",
+ allow_single_file = True,
+ mandatory = True,
+ ),
+ "_tool": attr.label(
+ executable = True,
+ cfg = "host",
+ default = Label("//tools/yamltest:yamltest"),
+ ),
+ },
+ test = True,
+)
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)
+ }
+}