plugindef: Move pluginmeta out of coremodels as standalone thema lineage (#56765)

* Get pluginmeta mostly moved over to pkg/plugins/plugindef

* Remove dead func

* Fix up pfs, use sync.Once in plugindef

* Update to latest thema

* Chase Endec->Codec conversion in Thema

* Comments on slash header gen; use ToSlash

* Also generate JSON schema for plugindef

* Generate JSON Schema as well

* Fix slot loading from kindsys cue decls

* Remove unused vars

* skip generating plugin.schema.json for now

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
This commit is contained in:
sam boyer
2022-11-15 14:48:31 +01:00
committed by GitHub
co-authored by Marcus Efraimsson
parent ff1afbb699
commit 78f0340031
29 changed files with 458 additions and 451 deletions
-107
View File
@@ -1,107 +0,0 @@
package coremodel
import (
"embed"
"fmt"
"io/fs"
"path/filepath"
"testing/fstest"
"cuelang.org/go/cue"
"cuelang.org/go/cue/load"
tload "github.com/grafana/thema/load"
"github.com/grafana/grafana/pkg/cuectx"
)
// Embed for all framework-related CUE files in this directory
//
//go:embed *.cue
var cueFS embed.FS
var defaultFramework cue.Value
func init() {
var err error
defaultFramework, err = doLoadFrameworkCUE(cuectx.GrafanaCUEContext())
if err != nil {
panic(err)
}
}
var prefix = filepath.Join("/pkg", "framework", "coremodel")
//nolint:nakedret
func doLoadFrameworkCUE(ctx *cue.Context) (v cue.Value, err error) {
m := make(fstest.MapFS)
err = fs.WalkDir(cueFS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
b, err := fs.ReadFile(cueFS, path)
if err != nil {
return err
}
m[path] = &fstest.MapFile{Data: b}
return nil
})
if err != nil {
return
}
over := make(map[string]load.Source)
absolutePath := prefix
if !filepath.IsAbs(absolutePath) {
absolutePath, err = filepath.Abs(absolutePath)
if err != nil {
return
}
}
err = tload.ToOverlay(absolutePath, m, over)
if err != nil {
return
}
bi := load.Instances(nil, &load.Config{
Dir: absolutePath,
Package: "coremodel",
Overlay: over,
})
v = ctx.BuildInstance(bi[0])
if v.Err() != nil {
return cue.Value{}, fmt.Errorf("coremodel framework loaded cue.Value has err: %w", v.Err())
}
return
}
// CUEFramework returns a cue.Value representing all the coremodel framework
// raw CUE files.
//
// For low-level use in constructing other types and APIs, while still letting
// us declare all the frameworky CUE bits in a single package. Other types and
// subpackages make the constructs in this value easy to use.
//
// The returned cue.Value is built from Grafana's standard central CUE context,
// ["github.com/grafana/grafana/pkg/cuectx".ProvideCueContext].
func CUEFramework() cue.Value {
return defaultFramework
}
// CUEFrameworkWithContext is the same as CUEFramework, but allows control over
// the cue.Context that's used.
//
// Prefer CUEFramework unless you understand cue.Context, and absolutely need
// this control.
func CUEFrameworkWithContext(ctx *cue.Context) cue.Value {
// Error guaranteed to be nil here because erroring would have caused init() to panic
v, _ := doLoadFrameworkCUE(ctx) // nolint:errcheck
return v
}
-29
View File
@@ -1,29 +0,0 @@
package coremodel
// Generates all code derived from coremodel Thema lineages that's used directly
// by both the frontend and backend.
//go:generate go run gen.go
import (
"github.com/grafana/thema"
)
// Interface is the primary coremodel interface that must be implemented by all
// Grafana coremodels. A coremodel is the foundational, canonical schema for
// some known-at-compile-time Grafana object.
//
// Currently, all Coremodels are expressed as Thema lineages.
type Interface interface {
// Lineage should return the canonical Thema lineage for the coremodel.
Lineage() thema.Lineage
// CurrentSchema should return the schema of the version that the Grafana backend
// is currently written against. (While Grafana can accept data from all
// older versions of the Thema schema, backend Go code is written against a
// single version for simplicity)
CurrentSchema() thema.Schema
// GoType should return a pointer to the Go struct type that corresponds to
// the Current() schema.
GoType() interface{}
}
-103
View File
@@ -1,103 +0,0 @@
package coremodel
import (
"cuelang.org/go/cue"
)
// Slot represents one of Grafana's named Thema composition slot definitions.
type Slot struct {
name string
raw cue.Value
plugins map[string]bool
}
// Name returns the name of the Slot. The name is also used as the path at which
// a Slot lineage is defined in a plugin models.cue file.
func (s Slot) Name() string {
return s.name
}
// MetaSchema returns the meta-schema that is the contract between coremodels
// that compose the Slot, and plugins that implement it.
func (s Slot) MetaSchema() cue.Value {
return s.raw
}
// ForPluginType indicates whether for this Slot, plugins of the given type may
// provide a slot implementation (first return value), and for those types that
// may, whether they must produce one (second return value).
//
// Expected values here are those in the set of
// ["github.com/grafana/grafana/pkg/coremodel/pluginmeta".Type], though passing
// a string not in that set will harmlessly return {false, false}. That type is
// not used here to avoid import cycles.
//
// Note that, at least for now, plugins are not required to provide any slot
// implementations, and do so by simply not containing a models.cue file.
// Consequently, the "must" return value here is best understood as, "IF a
// plugin provides a models.cue file, it MUST contain an implementation of this
// slot."
func (s Slot) ForPluginType(plugintype string) (may, must bool) {
must, may = s.plugins[plugintype]
return
}
// IsGroup indicates whether the slot specifies a group lineage - one in which
// each top-level key represents a distinct schema for objects that are expected
// to exist in the wild, but objects corresponding to the root of the schema are not
// expected to exist.
func (s Slot) IsGroup() bool {
// TODO rely on first-class Thema properties for this, one they exist - https://github.com/grafana/thema/issues/62
switch s.name {
case "Panel", "DSOptions":
return true
case "Query":
return false
default:
panic("unreachable - unknown slot name " + s.name)
}
}
func AllSlots() map[string]*Slot {
fw := CUEFramework()
slots := make(map[string]*Slot)
// Ignore err, can only happen if we change structure of fw files, and all we'd
// do is panic and that's what the next line will do anyway. Same for similar ignored
// errors later in this func
iter, _ := fw.LookupPath(cue.ParsePath("pluginTypeMetaSchema")).Fields(cue.Optional(true))
type nameopt struct {
name string
req bool
}
plugslots := make(map[string][]nameopt)
for iter.Next() {
plugin := iter.Selector().String()
iiter, _ := iter.Value().Fields(cue.Optional(true))
for iiter.Next() {
slotname := iiter.Selector().String()
plugslots[slotname] = append(plugslots[slotname], nameopt{
name: plugin,
req: !iiter.IsOptional(),
})
}
}
iter, _ = fw.LookupPath(cue.ParsePath("slots")).Fields(cue.Optional(true))
for iter.Next() {
n := iter.Selector().String()
sl := Slot{
name: n,
raw: iter.Value(),
plugins: make(map[string]bool),
}
for _, no := range plugslots[n] {
sl.plugins[no.name] = no.req
}
slots[n] = &sl
}
return slots
}
-71
View File
@@ -1,71 +0,0 @@
package coremodel
// The slots named and specified in this file are meta-schemas that act as a
// shared contract between Grafana plugins (producers) and coremodel types
// (consumers).
//
// On the consumer side, any coremodel Thema lineage can choose to define a
// standard Thema composition slot that specifies one of these named slots as
// its meta-schema. Such a specification entails that all schemas in any lineage
// placed into that composition slot must adhere to the meta-schema.
//
// On the producer side, Grafana's plugin system enforces that certain plugin
// types are expected to provide Thema lineages for these named slots which
// adhere to the slot meta-schema.
//
// For example, the Panel slot is consumed by the dashboard coremodel, and is
// expected to be produced by panel plugins.
//
// The name given to each slot in this file must be used as the name of the
// slot in the coremodel, and the name of the field under which the lineage
// is provided in a plugin's models.cue file.
//
// Conformance to meta-schema is achieved by Thema's native lineage joinSchema,
// which Thema internals automatically enforce across all schemas in a lineage.
// Meta-schema for the Panel slot, as implemented in Grafana panel plugins.
//
// This is a grouped meta-schema, intended solely for use in composition. Object
// literals conforming to it are not expected to exist.
slots: Panel: {
// Defines plugin-specific options for a panel that should be persisted. Required,
// though a panel without any options may specify an empty struct.
//
// Currently mapped to #Panel.options within the dashboard schema.
PanelOptions: {...}
// Plugin-specific custom field properties. Optional.
//
// Currently mapped to #Panel.fieldConfig.defaults.custom within the dashboard schema.
PanelFieldConfig?: {...}
}
// Meta-schema for the Query slot, as implemented in Grafana datasource plugins.
slots: Query: {...}
// Meta-schema for the DSOptions slot, as implemented in Grafana datasource plugins.
//
// This is a grouped meta-schema, intended solely for use in composition. Object
// literals conforming to it are not expected to exist.
slots: DSOptions: {
// Normal datasource configuration options.
Options: {...}
// Sensitive datasource configuration options that require encryption.
SecureOptions: {...}
}
// pluginTypeMetaSchema defines which plugin types should use which metaschemas
// as joinSchema for the lineages declared at which paths.
pluginTypeMetaSchema: [string]: {...}
pluginTypeMetaSchema: {
// Panel plugins are expected to provide a lineage at path Panel conforming to
// the Panel joinSchema.
panel: {
Panel: slots.Panel
}
// Datasource plugins are expected to provide lineages at paths Query and
// DSOptions, conforming to those joinSchemas respectively.
datasource: {
Query: slots.Query
DSOptions: slots.DSOptions
}
}