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:
co-authored by
Marcus Efraimsson
parent
ff1afbb699
commit
78f0340031
@@ -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
|
||||
}
|
||||
@@ -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{}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user