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
@@ -163,4 +163,3 @@ _sharedKind: {
|
||||
// It is required that lineage.name is the same as the [machineName].
|
||||
lineage: thema.#Lineage & { name: S.machineName }
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -227,7 +227,7 @@ func (decl *Decl[T]) Some() *SomeDecl {
|
||||
// For representations of core kinds that are useful in Go programs at runtime,
|
||||
// see ["github.com/grafana/grafana/pkg/registry/corekind"].
|
||||
func LoadCoreKind[T RawMeta | CoreStructuredMeta](declpath string, ctx *cue.Context, overlay fs.FS) (*Decl[T], error) {
|
||||
vk, err := cuectx.BuildGrafanaInstance(declpath, "kind", ctx, overlay)
|
||||
vk, err := cuectx.BuildGrafanaInstance(ctx, declpath, "kind", overlay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package kindsys
|
||||
|
||||
import (
|
||||
"cuelang.org/go/cue"
|
||||
)
|
||||
|
||||
// Slot represents one of Grafana's named slot definitions.
|
||||
// TODO link to framework docs
|
||||
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 core or
|
||||
// custom kinds that compose the meta-schema, and the plugin-declared composable
|
||||
// kinds that implement the meta-schema.
|
||||
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/plugins/plugindef".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 any .cue files in the
|
||||
// "grafanaplugin" package. Consequently, the "must" return value is best
|
||||
// understood as, "IF a plugin provides a *.cue files, 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)
|
||||
}
|
||||
}
|
||||
|
||||
// AllSlots returns a map of all [Slot]s defined in the Grafana kindsys
|
||||
// framework.
|
||||
//
|
||||
// TODO cache this for core context
|
||||
func AllSlots(ctx *cue.Context) map[string]*Slot {
|
||||
fw := CUEFramework(ctx)
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package kindsys
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// This is a brick-dumb test that just ensures slots are being loaded correctly
|
||||
// from their declarations in .cue files.
|
||||
//
|
||||
// If this test fails, it's either because:
|
||||
// - They're not being loaded correctly - there's a bug in kindsys somewhere, fix it
|
||||
// - The set of slots names has been modified - update the static list here
|
||||
func TestSlotsAreLoaded(t *testing.T) {
|
||||
slots := []string{"Panel", "Query", "DSOptions"}
|
||||
all := AllSlots(cuecontext.New())
|
||||
var loadedSlots []string
|
||||
for k := range all {
|
||||
loadedSlots = append(loadedSlots, k)
|
||||
}
|
||||
|
||||
sort.Strings(slots)
|
||||
sort.Strings(loadedSlots)
|
||||
|
||||
require.Equal(t, slots, loadedSlots, "slots loaded from cue differs from fixture set - either a bug or fixture needs updating")
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package kindsys
|
||||
|
||||
// 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