Scuemata: Add test to validate devenv resources (#35810)
* Add test for devenv resources * Refactor validation tests for grokkability * Devenv dashboards error-tracking script * Refactor to use cueerrors.Details() * Further test refinement * Close major elements of dashboard schema * Centralize dashboard validation tests General dashboard validation testing belongs in the load package. * Better names for error context on glue CUE code * Fixup validate-resource Do only one of base or dist, and fix copied docs. * Skip the devenv test * Remove test for validateResources * Fix shellcheck * Backend linter Co-authored-by: sam boyer <sdboyer@grafana.com>
This commit is contained in:
co-authored by
sam boyer
parent
8de218d5f1
commit
2e0dc835cf
@@ -142,13 +142,18 @@ var cueCommands = []*cli.Command{
|
||||
},
|
||||
{
|
||||
Name: "validate-resource",
|
||||
Usage: "validate *.cue files in the project",
|
||||
Usage: "validate resource files (e.g. dashboard JSON) against schema",
|
||||
Action: runPluginCommand(cmd.validateResources),
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "dashboard",
|
||||
Usage: "dashboard JSON file to validate",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "base-only",
|
||||
Usage: "validate using only base schema, not dist (includes plugin schema)",
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
gerrors "errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"cuelang.org/go/cue/errors"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||
"github.com/grafana/grafana/pkg/schema"
|
||||
"github.com/grafana/grafana/pkg/schema/load"
|
||||
@@ -25,37 +27,31 @@ func (cmd Command) validateScuemataBasics(c utils.CommandLine) error {
|
||||
}
|
||||
|
||||
func (cmd Command) validateResources(c utils.CommandLine) error {
|
||||
resource := c.String("dashboard")
|
||||
b, err := os.Open(filepath.Clean(resource))
|
||||
filename := c.String("dashboard")
|
||||
baseonly := c.Bool("base-only")
|
||||
if filename == "" {
|
||||
return gerrors.New("must specify dashboard to validate with --dashboard")
|
||||
}
|
||||
b, err := os.Open(filepath.Clean(filename))
|
||||
res := schema.Resource{Value: b, Name: filename}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateResources(b, paths, load.BaseDashboardFamily); err != nil {
|
||||
return err
|
||||
var sch schema.VersionedCueSchema
|
||||
if baseonly {
|
||||
sch, err = load.BaseDashboardFamily(paths)
|
||||
} else {
|
||||
sch, err = load.DistDashboardFamily(paths)
|
||||
}
|
||||
|
||||
if err := validateResources(b, paths, load.DistDashboardFamily); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateResources(resource interface{}, p load.BaseLoadPaths, loader func(p load.BaseLoadPaths) (schema.VersionedCueSchema, error)) error {
|
||||
dash, err := loader(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while loading dashboard scuemata, err: %w", err)
|
||||
}
|
||||
|
||||
// Validate checks that the resource is correct with respect to the schema.
|
||||
if resource != nil {
|
||||
err = dash.Validate(schema.Resource{Value: resource})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed validation: %w", err)
|
||||
}
|
||||
err = sch.Validate(res)
|
||||
if err != nil {
|
||||
return gerrors.New(errors.Details(err, nil))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
@@ -67,55 +65,4 @@ func TestValidateScuemataBasics(t *testing.T) {
|
||||
err = validateScuemata(baseLoadPaths, load.DistDashboardFamily)
|
||||
assert.EqualError(t, err, "all schema should be valid with respect to basic CUE rules, Family.lineages.0.0: field #Panel not allowed")
|
||||
})
|
||||
|
||||
t.Run("Testing validateResources against scuemata and resource inputs", func(t *testing.T) {
|
||||
validPanel, err := os.ReadFile("testdata/panels/valid_resource_panel.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
invalidPanel, err := os.ReadFile("testdata/panels/invalid_resource_panel.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
filesystem := fstest.MapFS{
|
||||
"valid.json": &fstest.MapFile{Data: validPanel},
|
||||
"invalid.json": &fstest.MapFile{Data: invalidPanel},
|
||||
}
|
||||
mergedFS := mergefs.Merge(filesystem, defaultBaseLoadPaths.BaseCueFS)
|
||||
|
||||
var baseLoadPaths = load.BaseLoadPaths{
|
||||
BaseCueFS: mergedFS,
|
||||
DistPluginCueFS: defaultBaseLoadPaths.DistPluginCueFS,
|
||||
}
|
||||
|
||||
require.NoError(t, fs.WalkDir(mergedFS, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
require.NoError(t, err)
|
||||
|
||||
if d.IsDir() || filepath.Ext(d.Name()) != ".json" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if d.Name() == "valid.json" {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
b, err := mergedFS.Open(path)
|
||||
require.NoError(t, err, "failed to open dashboard file")
|
||||
|
||||
err = validateResources(b, baseLoadPaths, load.BaseDashboardFamily)
|
||||
require.NoError(t, err, "error while loading base dashboard scuemata")
|
||||
|
||||
err = validateResources(b, baseLoadPaths, load.DistDashboardFamily)
|
||||
require.NoError(t, err, "error while loading base dashboard scuemata")
|
||||
})
|
||||
}
|
||||
if d.Name() == "invalid.json" {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
b, err := mergedFS.Open(path)
|
||||
require.NoError(t, err, "failed to open dashboard file")
|
||||
|
||||
err = validateResources(b, baseLoadPaths, load.BaseDashboardFamily)
|
||||
assert.EqualError(t, err, "failed validation: Family.lineages.0.0.panels.0.type: incomplete value !=\"\"")
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user