mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Re-enable devenv dashboard validation * Open up dashboard schema composition points * Introduce composition space at front of scuemata * Refactor go code to use new composition structure * Bunch of small cleanups in dashboard.cue * Enable both base and dist tests of devenv * Get rid of obsolete CUE loading hacks * Skip weird failures on these tests Really don't seem to be testing for what we intend them to be testing for.
215 lines
6.1 KiB
Go
215 lines
6.1 KiB
Go
package load
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"cuelang.org/go/cue"
|
|
"cuelang.org/go/cue/load"
|
|
"github.com/grafana/grafana/pkg/schema"
|
|
)
|
|
|
|
var panelSubpath = cue.MakePath(cue.Def("#Panel"))
|
|
|
|
var dashboardDir = filepath.Join("packages", "grafana-schema", "src", "scuemata", "dashboard")
|
|
|
|
func defaultOverlay(p BaseLoadPaths) (map[string]load.Source, error) {
|
|
overlay := make(map[string]load.Source)
|
|
|
|
if err := toOverlay(prefix, p.BaseCueFS, overlay); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := toOverlay(prefix, p.DistPluginCueFS, overlay); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return overlay, nil
|
|
}
|
|
|
|
// BaseDashboardFamily loads the family of schema representing the "Base" variant of
|
|
// a Grafana dashboard: the core-defined dashboard schema that applies universally to
|
|
// all dashboards, independent of any plugins.
|
|
//
|
|
// The returned VersionedCueSchema will always be the oldest schema in the
|
|
// family: the 0.0 schema. schema.Find() provides easy traversal to newer schema
|
|
// versions.
|
|
func BaseDashboardFamily(p BaseLoadPaths) (schema.VersionedCueSchema, error) {
|
|
v, err := baseDashboardFamily(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buildGenericScuemata(v)
|
|
}
|
|
|
|
// Helper that gets the entire scuemata family, for reuse by Dist/Instance callers.
|
|
func baseDashboardFamily(p BaseLoadPaths) (cue.Value, error) {
|
|
overlay, err := defaultOverlay(p)
|
|
if err != nil {
|
|
return cue.Value{}, err
|
|
}
|
|
|
|
cfg := &load.Config{
|
|
Overlay: overlay,
|
|
ModuleRoot: prefix,
|
|
Module: "github.com/grafana/grafana",
|
|
Dir: filepath.Join(prefix, dashboardDir),
|
|
}
|
|
inst, err := rt.Build(load.Instances(nil, cfg)[0])
|
|
if err != nil {
|
|
cueError := schema.WrapCUEError(err)
|
|
if err != nil {
|
|
return cue.Value{}, cueError
|
|
}
|
|
}
|
|
|
|
famval := inst.Value().LookupPath(cue.MakePath(cue.Str("Family")))
|
|
if !famval.Exists() {
|
|
return cue.Value{}, errors.New("dashboard schema family did not exist at expected path in expected file")
|
|
}
|
|
|
|
return famval, nil
|
|
}
|
|
|
|
// DistDashboardFamily loads the family of schema representing the "Dist"
|
|
// variant of a Grafana dashboard: the "Base" variant (see
|
|
// BaseDashboardFamily()), but constrained such that all substructures (e.g.
|
|
// panels) must be valid with respect to the schemas provided by the core
|
|
// plugins that ship with Grafana.
|
|
//
|
|
// The returned VersionedCueSchema will always be the oldest schema in the
|
|
// family: the 0.0 schema. schema.Find() provides easy traversal to newer schema
|
|
// versions.
|
|
func DistDashboardFamily(p BaseLoadPaths) (schema.VersionedCueSchema, error) {
|
|
famval, err := baseDashboardFamily(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
scuemap, err := loadPanelScuemata(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO see if unifying into the expected form in a loop, then unifying that
|
|
// consolidated form improves performance
|
|
for typ, fam := range scuemap {
|
|
famval = famval.FillPath(cue.MakePath(cue.Str("compose"), cue.Str("Panel"), cue.Str(typ)), fam)
|
|
}
|
|
head, err := buildGenericScuemata(famval)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO sloppy duplicate logic of what's in readPanelModels(), for now
|
|
all := make(map[string]schema.VersionedCueSchema)
|
|
for id, val := range scuemap {
|
|
fam, err := buildGenericScuemata(val)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
all[id] = fam
|
|
}
|
|
|
|
var first, prev *compositeDashboardSchema
|
|
for head != nil {
|
|
cds := &compositeDashboardSchema{
|
|
base: head,
|
|
actual: head.CUE(),
|
|
panelFams: all,
|
|
// TODO migrations
|
|
migration: terminalMigrationFunc,
|
|
}
|
|
|
|
if prev == nil {
|
|
first = cds
|
|
} else {
|
|
prev.next = cds
|
|
}
|
|
|
|
prev = cds
|
|
head = head.Successor()
|
|
}
|
|
return first, nil
|
|
}
|
|
|
|
type compositeDashboardSchema struct {
|
|
// The base/root dashboard schema
|
|
base schema.VersionedCueSchema
|
|
actual cue.Value
|
|
next *compositeDashboardSchema
|
|
migration migrationFunc
|
|
panelFams map[string]schema.VersionedCueSchema
|
|
}
|
|
|
|
// Validate checks that the resource is correct with respect to the schema.
|
|
func (cds *compositeDashboardSchema) Validate(r schema.Resource) error {
|
|
name := r.Name
|
|
if name == "" {
|
|
name = "resource"
|
|
}
|
|
rv, err := rt.Compile(name, r.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return cds.actual.Unify(rv.Value()).Validate(cue.Concrete(true))
|
|
}
|
|
|
|
// CUE returns the cue.Value representing the actual schema.
|
|
func (cds *compositeDashboardSchema) CUE() cue.Value {
|
|
return cds.actual
|
|
}
|
|
|
|
// Version reports the major and minor versions of the schema.
|
|
func (cds *compositeDashboardSchema) Version() (major int, minor int) {
|
|
return cds.base.Version()
|
|
}
|
|
|
|
// Returns the next VersionedCueSchema
|
|
func (cds *compositeDashboardSchema) Successor() schema.VersionedCueSchema {
|
|
if cds.next == nil {
|
|
// Untyped nil, allows `<sch> == nil` checks to work as people expect
|
|
return nil
|
|
}
|
|
return cds.next
|
|
}
|
|
|
|
func (cds *compositeDashboardSchema) Migrate(x schema.Resource) (schema.Resource, schema.VersionedCueSchema, error) { // TODO restrict input/return type to concrete
|
|
r, sch, err := cds.migration(x.Value)
|
|
if err != nil || sch == nil {
|
|
// TODO fix sloppy types
|
|
r = x.Value.(cue.Value)
|
|
}
|
|
|
|
return schema.Resource{Value: r}, sch, nil
|
|
}
|
|
|
|
func (cds *compositeDashboardSchema) LatestPanelSchemaFor(id string) (schema.VersionedCueSchema, error) {
|
|
// So much slop rn, but it's OK because i FINALLY know where this is going!
|
|
psch, has := cds.panelFams[id]
|
|
if !has {
|
|
// TODO typed errors
|
|
return nil, fmt.Errorf("unknown panel plugin type %q", id)
|
|
}
|
|
|
|
latest := schema.Find(psch, schema.Latest())
|
|
// FIXME this relies on old sloppiness
|
|
sch := &genericVersionedSchema{
|
|
actual: cds.base.CUE().LookupPath(panelSubpath).Unify(mapPanelModel(id, latest)),
|
|
}
|
|
sch.major, sch.minor = latest.Version()
|
|
|
|
return sch, nil
|
|
}
|
|
|
|
// One-off special interface for dashboard composite schema, until the composite
|
|
// dashboard schema pattern is fully generalized.
|
|
//
|
|
// NOTE: THIS IS A TEMPORARY TYPE. IT WILL BE REPLACED WITH A GENERIC INTERFACE
|
|
// TO REPRESENT COMPOSITIONAL SCHEMA FAMILY PRIOR TO GRAFANA 8. UPDATING WILL
|
|
// SHOULD BE TRIVIAL, BUT IT WILL CAUSE BREAKAGES.
|
|
type CompositeDashboardSchema interface {
|
|
schema.VersionedCueSchema
|
|
LatestPanelSchemaFor(id string) (schema.VersionedCueSchema, error)
|
|
}
|