mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Kindsys: Remove defs, Slot->SchemaInterface (#61069)
* kindsys: Remove defs, Slot->SchemaInterface * Remove excess file * Fix up tests * Regenerate kinds report * Final bits of cleanup * Stop complaining, linter * Update pkg/kindsys/kindcat_composable.cue Co-authored-by: Tania <yalyna.ts@gmail.com> Co-authored-by: Tania <yalyna.ts@gmail.com>
This commit is contained in:
parent
c2ad447f8c
commit
4db3b2fd5c
@ -3,5 +3,5 @@ package kind
|
||||
import "github.com/grafana/grafana/pkg/kindsys"
|
||||
|
||||
// In each child directory, the set of .cue files with 'package kind'
|
||||
// must be an instance of kindsys.#Core - a declaration of a core kind.
|
||||
kindsys.#Core
|
||||
// must be an instance of kindsys.Core - a declaration of a core kind.
|
||||
kindsys.Core
|
||||
|
@ -17,7 +17,7 @@ type ManyToMany codejen.ManyToMany[*DeclForGen]
|
||||
|
||||
// ForGen is a codejen input transformer that converts a pure kindsys.SomeDecl into
|
||||
// a DeclForGen by binding its contained lineage.
|
||||
func ForGen(rt *thema.Runtime, decl *kindsys.SomeDecl) (*DeclForGen, error) {
|
||||
func ForGen(rt *thema.Runtime, decl kindsys.SomeDecl) (*DeclForGen, error) {
|
||||
lin, err := decl.BindKindLineage(rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -32,7 +32,7 @@ func ForGen(rt *thema.Runtime, decl *kindsys.SomeDecl) (*DeclForGen, error) {
|
||||
// DeclForGen wraps [kindsys.SomeDecl] to provide trivial caching of
|
||||
// the lineage declared by the kind (nil for raw kinds).
|
||||
type DeclForGen struct {
|
||||
*kindsys.SomeDecl
|
||||
kindsys.SomeDecl
|
||||
lin thema.Lineage
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,6 @@ func (gen *genBaseRegistry) JennyName() string {
|
||||
func (gen *genBaseRegistry) Generate(decls ...*DeclForGen) (*codejen.File, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := tmpls.Lookup("kind_registry.tmpl").Execute(buf, tvars_kind_registry{
|
||||
NumStructured: len(decls),
|
||||
PackageName: filepath.Base(gen.path),
|
||||
KindPackagePrefix: filepath.ToSlash(filepath.Join("github.com/grafana/grafana", gen.kindrelroot)),
|
||||
Kinds: decls,
|
||||
|
@ -15,9 +15,9 @@ import (
|
||||
// all generated kinds.
|
||||
//
|
||||
// This generator only has output for core structured kinds.
|
||||
func CoreKindJenny(gokindsdir string, cfg *CoreStructuredKindGeneratorConfig) OneToOne {
|
||||
func CoreKindJenny(gokindsdir string, cfg *CoreKindJennyConfig) OneToOne {
|
||||
if cfg == nil {
|
||||
cfg = new(CoreStructuredKindGeneratorConfig)
|
||||
cfg = new(CoreKindJennyConfig)
|
||||
}
|
||||
if cfg.GenDirName == nil {
|
||||
cfg.GenDirName = func(decl *DeclForGen) string {
|
||||
@ -31,8 +31,8 @@ func CoreKindJenny(gokindsdir string, cfg *CoreStructuredKindGeneratorConfig) On
|
||||
}
|
||||
}
|
||||
|
||||
// CoreStructuredKindGeneratorConfig holds configuration options for [CoreKindJenny].
|
||||
type CoreStructuredKindGeneratorConfig struct {
|
||||
// CoreKindJennyConfig holds configuration options for [CoreKindJenny].
|
||||
type CoreKindJennyConfig struct {
|
||||
// GenDirName returns the name of the directory in which the file should be
|
||||
// generated. Defaults to DeclForGen.Lineage().Name() if nil.
|
||||
GenDirName func(*DeclForGen) string
|
||||
@ -40,7 +40,7 @@ type CoreStructuredKindGeneratorConfig struct {
|
||||
|
||||
type coreKindJenny struct {
|
||||
gokindsdir string
|
||||
cfg *CoreStructuredKindGeneratorConfig
|
||||
cfg *CoreKindJennyConfig
|
||||
}
|
||||
|
||||
var _ OneToOne = &coreKindJenny{}
|
||||
|
@ -38,7 +38,6 @@ type (
|
||||
}
|
||||
tvars_kind_registry struct {
|
||||
// Header tvars_autogen_header
|
||||
NumStructured int
|
||||
PackageName string
|
||||
KindPackagePrefix string
|
||||
Kinds []*DeclForGen
|
||||
|
@ -30,7 +30,7 @@ func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) {
|
||||
return nil, err
|
||||
}
|
||||
k := &Kind{
|
||||
decl: *decl,
|
||||
decl: decl,
|
||||
}
|
||||
|
||||
lin, err := decl.Some().BindKindLineage(rt, opts...)
|
||||
@ -91,9 +91,8 @@ func (k *Kind) Maturity() kindsys.Maturity {
|
||||
|
||||
// Decl returns the [kindsys.Decl] containing both CUE and Go representations of the
|
||||
// {{ .Properties.MachineName }} declaration in .cue files.
|
||||
func (k *Kind) Decl() *kindsys.Decl[kindsys.CoreProperties] {
|
||||
d := k.decl
|
||||
return &d
|
||||
func (k *Kind) Decl() kindsys.Decl[kindsys.CoreProperties] {
|
||||
return k.decl
|
||||
}
|
||||
|
||||
// Props returns a [kindsys.SomeKindProps], with underlying type [kindsys.CoreProperties],
|
||||
|
@ -39,7 +39,7 @@ func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) {
|
||||
return nil, err
|
||||
}
|
||||
k := &Kind{
|
||||
decl: *decl,
|
||||
decl: decl,
|
||||
}
|
||||
|
||||
lin, err := decl.Some().BindKindLineage(rt, opts...)
|
||||
@ -100,9 +100,8 @@ func (k *Kind) Maturity() kindsys.Maturity {
|
||||
|
||||
// Decl returns the [kindsys.Decl] containing both CUE and Go representations of the
|
||||
// dashboard declaration in .cue files.
|
||||
func (k *Kind) Decl() *kindsys.Decl[kindsys.CoreProperties] {
|
||||
d := k.decl
|
||||
return &d
|
||||
func (k *Kind) Decl() kindsys.Decl[kindsys.CoreProperties] {
|
||||
return k.decl
|
||||
}
|
||||
|
||||
// Props returns a [kindsys.SomeKindProps], with underlying type [kindsys.CoreProperties],
|
||||
|
@ -39,7 +39,7 @@ func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) {
|
||||
return nil, err
|
||||
}
|
||||
k := &Kind{
|
||||
decl: *decl,
|
||||
decl: decl,
|
||||
}
|
||||
|
||||
lin, err := decl.Some().BindKindLineage(rt, opts...)
|
||||
@ -100,9 +100,8 @@ func (k *Kind) Maturity() kindsys.Maturity {
|
||||
|
||||
// Decl returns the [kindsys.Decl] containing both CUE and Go representations of the
|
||||
// playlist declaration in .cue files.
|
||||
func (k *Kind) Decl() *kindsys.Decl[kindsys.CoreProperties] {
|
||||
d := k.decl
|
||||
return &d
|
||||
func (k *Kind) Decl() kindsys.Decl[kindsys.CoreProperties] {
|
||||
return k.decl
|
||||
}
|
||||
|
||||
// Props returns a [kindsys.SomeKindProps], with underlying type [kindsys.CoreProperties],
|
||||
|
@ -39,7 +39,7 @@ func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) {
|
||||
return nil, err
|
||||
}
|
||||
k := &Kind{
|
||||
decl: *decl,
|
||||
decl: decl,
|
||||
}
|
||||
|
||||
lin, err := decl.Some().BindKindLineage(rt, opts...)
|
||||
@ -100,9 +100,8 @@ func (k *Kind) Maturity() kindsys.Maturity {
|
||||
|
||||
// Decl returns the [kindsys.Decl] containing both CUE and Go representations of the
|
||||
// team declaration in .cue files.
|
||||
func (k *Kind) Decl() *kindsys.Decl[kindsys.CoreProperties] {
|
||||
d := k.decl
|
||||
return &d
|
||||
func (k *Kind) Decl() kindsys.Decl[kindsys.CoreProperties] {
|
||||
return k.decl
|
||||
}
|
||||
|
||||
// Props returns a [kindsys.SomeKindProps], with underlying type [kindsys.CoreProperties],
|
||||
|
@ -57,6 +57,9 @@ type Interface interface {
|
||||
// a Decl() method through which these same properties are accessible.
|
||||
Props() SomeKindProperties
|
||||
|
||||
// TODO docs
|
||||
Lineage() thema.Lineage
|
||||
|
||||
// TODO remove, unnecessary with Props()
|
||||
Name() string
|
||||
|
||||
@ -71,18 +74,19 @@ type Core interface {
|
||||
Interface
|
||||
|
||||
// TODO docs
|
||||
Lineage() thema.Lineage
|
||||
|
||||
// TODO docs
|
||||
Decl() *Decl[CoreProperties] // TODO figure out how to reconcile this interface with CustomProperties
|
||||
Decl() Decl[CoreProperties]
|
||||
}
|
||||
|
||||
// type Composable interface {
|
||||
// Interface
|
||||
//
|
||||
// // TODO docs
|
||||
// Lineage() thema.Lineage
|
||||
//
|
||||
// // TODO docs
|
||||
// Properties() CoreProperties // TODO figure out how to reconcile this interface with CustomProperties
|
||||
// }
|
||||
type Custom interface {
|
||||
Interface
|
||||
|
||||
// TODO docs
|
||||
Decl() Decl[CustomProperties]
|
||||
}
|
||||
|
||||
type Composable interface {
|
||||
Interface
|
||||
|
||||
// TODO docs
|
||||
Decl() Decl[ComposableProperties]
|
||||
}
|
||||
|
29
pkg/kindsys/kindcat_composable.cue
Normal file
29
pkg/kindsys/kindcat_composable.cue
Normal file
@ -0,0 +1,29 @@
|
||||
package kindsys
|
||||
|
||||
// Composable is a category of kind that provides schema elements for
|
||||
// composition into Core and Custom kinds. Grafana plugins
|
||||
// provide composable kinds; for example, a datasource plugin provides one to
|
||||
// describe the structure of its queries, which is then composed into dashboards
|
||||
// and alerting rules.
|
||||
//
|
||||
// Each Composable is an implementation of exactly one Slot, a shared meta-schema
|
||||
// defined by Grafana itself that constrains the shape of schemas declared in
|
||||
// that ComposableKind.
|
||||
Composable: S={
|
||||
_sharedKind
|
||||
|
||||
// schemaInterface is the name of the Grafana schema interface implemented by
|
||||
// this Composable kind. The set is open to ensure forward compatibility of
|
||||
// Grafana and tooling with any additional schema interfaces that may be added.
|
||||
// schemaInterface: or([ for k, _ in schemaInterfaces {k}, string])
|
||||
schemaInterface: string
|
||||
|
||||
let schif = schemaInterfaces[S.schemaInterface]
|
||||
|
||||
// lineage is the Thema lineage containing all the schemas that have existed for this kind.
|
||||
// The name of the lineage is constrained to the name of the schema interface being implemented.
|
||||
// lineage: thema.#Lineage & {name: S.schemaInterface, joinSchema: schif.interface}
|
||||
lineage: { joinSchema: (schif.interface | *{}) }
|
||||
|
||||
lineageIsGroup: schif.group | *false
|
||||
}
|
@ -7,10 +7,9 @@ package kindsys
|
||||
//
|
||||
// Grafana provides Kubernetes apiserver-shaped HTTP APIs for interacting with custom
|
||||
// kinds - the same API patterns (and clients) used to interact with k8s CustomResources.
|
||||
#Custom: S={
|
||||
Custom: S={
|
||||
_sharedKind
|
||||
|
||||
lineage: { name: S.machineName }
|
||||
lineageIsGroup: false
|
||||
...
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
// Grafana provides a standard mechanism for representing its kinds as CRDs.
|
||||
//
|
||||
// There are three categories of kinds: Core, Custom, and Composable.
|
||||
#Kind: #Composable | #Core | #Custom
|
||||
Kind: Composable | Core | Custom
|
||||
|
||||
// properties shared between all kind categories.
|
||||
_sharedKind: {
|
||||
@ -86,37 +86,9 @@ _sharedKind: {
|
||||
// Core specifies the kind category for core-defined arbitrary types.
|
||||
// Familiar types and functional resources in Grafana, such as dashboards and
|
||||
// and datasources, are represented as core kinds.
|
||||
#Core: S=close({
|
||||
Core: S=close({
|
||||
_sharedKind
|
||||
|
||||
lineage: { name: S.machineName }
|
||||
lineageIsGroup: false
|
||||
})
|
||||
|
||||
// Composable is a category of kind that provides schema elements for
|
||||
// composition into Core and Custom kinds. Grafana plugins
|
||||
// provide composable kinds; for example, a datasource plugin provides one to
|
||||
// describe the structure of its queries, which is then composed into dashboards
|
||||
// and alerting rules.
|
||||
//
|
||||
// Each Composable is an implementation of exactly one Slot, a shared meta-schema
|
||||
// defined by Grafana itself that constrains the shape of schemas declared in
|
||||
// that ComposableKind.
|
||||
#Composable: S={
|
||||
_sharedKind
|
||||
|
||||
// TODO docs
|
||||
// TODO unify this with the existing slots decls in pkg/framework/coremodel
|
||||
slot: "Panel" | "Query" | "DSConfig"
|
||||
|
||||
// TODO unify this with the existing slots decls in pkg/framework/coremodel
|
||||
lineageIsGroup: bool & [
|
||||
if slot == "Panel" { true },
|
||||
if slot == "DSConfig" { true },
|
||||
if slot == "Query" { false },
|
||||
][0]
|
||||
|
||||
// lineage is the Thema lineage containing all the schemas that have existed for this kind.
|
||||
// It is required that lineage.name is the same as the [machineName].
|
||||
lineage: thema.#Lineage & { name: S.machineName }
|
||||
}
|
||||
|
@ -13,12 +13,12 @@ type CommonProperties struct {
|
||||
}
|
||||
|
||||
// CoreProperties represents the static properties in the declaration of a
|
||||
// #Core kind that are representable with basic Go types. This
|
||||
// Core kind that are representable with basic Go types. This
|
||||
// excludes Thema schemas.
|
||||
//
|
||||
// When a .cue #Core declaration is loaded through the standard [LoadCoreKind],
|
||||
// When a .cue Core declaration is loaded through the standard [LoadCoreKind],
|
||||
// func, it is fully validated and populated according to all rules specified
|
||||
// in CUE for #Core kinds.
|
||||
// in CUE for Core kinds.
|
||||
type CoreProperties struct {
|
||||
CommonProperties
|
||||
CurrentVersion thema.SyntacticVersion `json:"currentVersion"`
|
||||
@ -30,7 +30,7 @@ func (m CoreProperties) Common() CommonProperties {
|
||||
}
|
||||
|
||||
// CustomProperties represents the static properties in the declaration of a
|
||||
// #Custom kind that are representable with basic Go types. This
|
||||
// Custom kind that are representable with basic Go types. This
|
||||
// excludes Thema schemas.
|
||||
type CustomProperties struct {
|
||||
CommonProperties
|
||||
@ -43,7 +43,7 @@ func (m CustomProperties) Common() CommonProperties {
|
||||
}
|
||||
|
||||
// ComposableProperties represents the static properties in the declaration of a
|
||||
// #Composable kind that are representable with basic Go types. This
|
||||
// Composable kind that are representable with basic Go types. This
|
||||
// excludes Thema schemas.
|
||||
type ComposableProperties struct {
|
||||
CommonProperties
|
||||
|
@ -60,11 +60,11 @@ func doLoadFrameworkCUE(ctx *cue.Context) (cue.Value, error) {
|
||||
//
|
||||
// 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 Go types
|
||||
// make the constructs in this value easy to use.
|
||||
// make the constructs in the returned cue.Value easy to use.
|
||||
//
|
||||
// All calling code within grafana/grafana is expected to use Grafana's
|
||||
// singleton [cue.Context], returned from [cuectx.GrafanaCUEContext]. If nil
|
||||
// is passed, the singleton will be used.
|
||||
// Calling this with a nil [cue.Context] (the singleton returned from
|
||||
// [cuectx.GrafanaCUEContext] is used) will memoize certain CUE operations.
|
||||
// Prefer passing nil unless a different cue.Context is specifically required.
|
||||
func CUEFramework(ctx *cue.Context) cue.Value {
|
||||
if ctx == nil || ctx == cuectx.GrafanaCUEContext() {
|
||||
// Ensure framework is loaded, even if this func is called
|
||||
@ -77,9 +77,9 @@ func CUEFramework(ctx *cue.Context) cue.Value {
|
||||
return v
|
||||
}
|
||||
|
||||
// ToKindMeta takes a cue.Value expected to represent a kind of the category
|
||||
// ToKindProps takes a cue.Value expected to represent a kind of the category
|
||||
// specified by the type parameter and populates the Go type from the cue.Value.
|
||||
func ToKindMeta[T KindProperties](v cue.Value) (T, error) {
|
||||
func ToKindProps[T KindProperties](v cue.Value) (T, error) {
|
||||
props := new(T)
|
||||
if !v.Exists() {
|
||||
return *props, ErrValueNotExist
|
||||
@ -91,11 +91,11 @@ func ToKindMeta[T KindProperties](v cue.Value) (T, error) {
|
||||
anyprops := any(*props).(SomeKindProperties)
|
||||
switch anyprops.(type) {
|
||||
case CoreProperties:
|
||||
kdef = fw.LookupPath(cue.MakePath(cue.Def("Core")))
|
||||
kdef = fw.LookupPath(cue.MakePath(cue.Str("Core")))
|
||||
case CustomProperties:
|
||||
kdef = fw.LookupPath(cue.MakePath(cue.Def("Custom")))
|
||||
kdef = fw.LookupPath(cue.MakePath(cue.Str("Custom")))
|
||||
case ComposableProperties:
|
||||
kdef = fw.LookupPath(cue.MakePath(cue.Def("Composable")))
|
||||
kdef = fw.LookupPath(cue.MakePath(cue.Str("Composable")))
|
||||
default:
|
||||
// unreachable so long as all the possibilities in KindProperties have switch branches
|
||||
panic("unreachable")
|
||||
@ -128,8 +128,9 @@ type SomeDecl struct {
|
||||
// BindKindLineage binds the lineage for the kind declaration.
|
||||
//
|
||||
// For kinds with a corresponding Go type, it is left to the caller to associate
|
||||
// that Go type with the lineage returned from this function by a call to [thema.BindType].
|
||||
func (decl *SomeDecl) BindKindLineage(rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
|
||||
// that Go type with the lineage returned from this function by a call to
|
||||
// [thema.BindType].
|
||||
func (decl SomeDecl) BindKindLineage(rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
|
||||
if rt == nil {
|
||||
rt = cuectx.GrafanaThemaRuntime()
|
||||
}
|
||||
@ -142,19 +143,19 @@ func (decl *SomeDecl) BindKindLineage(rt *thema.Runtime, opts ...thema.BindOptio
|
||||
}
|
||||
|
||||
// IsCore indicates whether the represented kind is a core kind.
|
||||
func (decl *SomeDecl) IsCore() bool {
|
||||
func (decl SomeDecl) IsCore() bool {
|
||||
_, is := decl.Properties.(CoreProperties)
|
||||
return is
|
||||
}
|
||||
|
||||
// IsCustom indicates whether the represented kind is a custom kind.
|
||||
func (decl *SomeDecl) IsCustom() bool {
|
||||
func (decl SomeDecl) IsCustom() bool {
|
||||
_, is := decl.Properties.(CustomProperties)
|
||||
return is
|
||||
}
|
||||
|
||||
// IsComposable indicates whether the represented kind is a composable kind.
|
||||
func (decl *SomeDecl) IsComposable() bool {
|
||||
func (decl SomeDecl) IsComposable() bool {
|
||||
_, is := decl.Properties.(ComposableProperties)
|
||||
return is
|
||||
}
|
||||
@ -171,8 +172,8 @@ type Decl[T KindProperties] struct {
|
||||
}
|
||||
|
||||
// Some converts the typed Decl to the equivalent typeless SomeDecl.
|
||||
func (decl *Decl[T]) Some() *SomeDecl {
|
||||
return &SomeDecl{
|
||||
func (decl Decl[T]) Some() SomeDecl {
|
||||
return SomeDecl{
|
||||
V: decl.V,
|
||||
Properties: any(decl.Properties).(SomeKindProperties),
|
||||
}
|
||||
@ -195,17 +196,20 @@ func (decl *Decl[T]) Some() *SomeDecl {
|
||||
// This is a low-level function, primarily intended for use in code generation.
|
||||
// For representations of core kinds that are useful in Go programs at runtime,
|
||||
// see ["github.com/grafana/grafana/pkg/registry/corekind"].
|
||||
func LoadCoreKind(declpath string, ctx *cue.Context, overlay fs.FS) (*Decl[CoreProperties], error) {
|
||||
func LoadCoreKind(declpath string, ctx *cue.Context, overlay fs.FS) (Decl[CoreProperties], error) {
|
||||
none := Decl[CoreProperties]{}
|
||||
vk, err := cuectx.BuildGrafanaInstance(ctx, declpath, "kind", overlay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return none, err
|
||||
}
|
||||
decl := &Decl[CoreProperties]{
|
||||
V: vk,
|
||||
}
|
||||
decl.Properties, err = ToKindMeta[CoreProperties](vk)
|
||||
|
||||
props, err := ToKindProps[CoreProperties](vk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return none, err
|
||||
}
|
||||
return decl, nil
|
||||
|
||||
return Decl[CoreProperties]{
|
||||
V: vk,
|
||||
Properties: props,
|
||||
}, nil
|
||||
}
|
||||
|
@ -139,24 +139,24 @@ func buildKindStateReport() *KindStateReport {
|
||||
}, "core")
|
||||
}
|
||||
|
||||
all := kindsys.AllSlots(nil)
|
||||
all := kindsys.SchemaInterfaces(nil)
|
||||
// TODO this is all hacks until #59001, which will unite plugins with kindsys
|
||||
for _, tree := range corelist.New(nil) {
|
||||
rp := tree.RootPlugin()
|
||||
for _, slot := range all {
|
||||
if may, _ := slot.ForPluginType(string(rp.Meta().Type)); may {
|
||||
n := fmt.Sprintf("%s-%s", strings.Title(rp.Meta().Id), slot.Name())
|
||||
for _, si := range all {
|
||||
if si.Should(string(rp.Meta().Type)) {
|
||||
n := fmt.Sprintf("%s-%s", strings.Title(rp.Meta().Id), si.Name())
|
||||
props := kindsys.ComposableProperties{
|
||||
CommonProperties: kindsys.CommonProperties{
|
||||
Name: n,
|
||||
PluralName: n + "s",
|
||||
MachineName: machinize(n),
|
||||
PluralMachineName: machinize(n) + "s",
|
||||
LineageIsGroup: slot.IsGroup(),
|
||||
LineageIsGroup: si.IsGroup(),
|
||||
Maturity: "planned",
|
||||
},
|
||||
}
|
||||
if ck, has := rp.SlotImplementations()[slot.Name()]; has {
|
||||
if ck, has := rp.SlotImplementations()[si.Name()]; has {
|
||||
props.CommonProperties.Maturity = "merged"
|
||||
props.CurrentVersion = ck.Latest().Version()
|
||||
}
|
||||
|
@ -41,7 +41,7 @@
|
||||
"pluralName": "Alertmanager-Querys",
|
||||
"machineName": "alertmanager_query",
|
||||
"pluralMachineName": "alertmanager_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -113,7 +113,7 @@
|
||||
"pluralName": "Cloudwatch-Querys",
|
||||
"machineName": "cloudwatch_query",
|
||||
"pluralMachineName": "cloudwatch_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -149,7 +149,7 @@
|
||||
"pluralName": "Dashboard-Querys",
|
||||
"machineName": "dashboard_query",
|
||||
"pluralMachineName": "dashboard_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -209,7 +209,7 @@
|
||||
"pluralName": "Elasticsearch-Querys",
|
||||
"machineName": "elasticsearch_query",
|
||||
"pluralMachineName": "elasticsearch_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -293,7 +293,7 @@
|
||||
"pluralName": "Grafana-Azure-Monitor-Datasource-Querys",
|
||||
"machineName": "grafana_azure_monitor_datasource_query",
|
||||
"pluralMachineName": "grafana_azure_monitor_datasource_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -317,7 +317,7 @@
|
||||
"pluralName": "Grafana-Querys",
|
||||
"machineName": "grafana_query",
|
||||
"pluralMachineName": "grafana_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -353,7 +353,7 @@
|
||||
"pluralName": "Graphite-Querys",
|
||||
"machineName": "graphite_query",
|
||||
"pluralMachineName": "graphite_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -401,7 +401,7 @@
|
||||
"pluralName": "Jaeger-Querys",
|
||||
"machineName": "jaeger_query",
|
||||
"pluralMachineName": "jaeger_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -449,7 +449,7 @@
|
||||
"pluralName": "Loki-Querys",
|
||||
"machineName": "loki_query",
|
||||
"pluralMachineName": "loki_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -473,7 +473,7 @@
|
||||
"pluralName": "Mssql-Querys",
|
||||
"machineName": "mssql_query",
|
||||
"pluralMachineName": "mssql_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -497,7 +497,7 @@
|
||||
"pluralName": "Mysql-Querys",
|
||||
"machineName": "mysql_query",
|
||||
"pluralMachineName": "mysql_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -545,7 +545,7 @@
|
||||
"pluralName": "Parca-Querys",
|
||||
"machineName": "parca_query",
|
||||
"pluralMachineName": "parca_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -569,7 +569,7 @@
|
||||
"pluralName": "Phlare-Querys",
|
||||
"machineName": "phlare_query",
|
||||
"pluralMachineName": "phlare_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -617,7 +617,7 @@
|
||||
"pluralName": "Postgres-Querys",
|
||||
"machineName": "postgres_query",
|
||||
"pluralMachineName": "postgres_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -641,7 +641,7 @@
|
||||
"pluralName": "Prometheus-Querys",
|
||||
"machineName": "prometheus_query",
|
||||
"pluralMachineName": "prometheus_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -701,7 +701,7 @@
|
||||
"pluralName": "Stackdriver-Querys",
|
||||
"machineName": "stackdriver_query",
|
||||
"pluralMachineName": "stackdriver_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -761,7 +761,7 @@
|
||||
"pluralName": "Tempo-Querys",
|
||||
"machineName": "tempo_query",
|
||||
"pluralMachineName": "tempo_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -785,7 +785,7 @@
|
||||
"pluralName": "Testdata-Querys",
|
||||
"machineName": "testdata_query",
|
||||
"pluralMachineName": "testdata_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
@ -881,7 +881,7 @@
|
||||
"pluralName": "Zipkin-Querys",
|
||||
"machineName": "zipkin_query",
|
||||
"pluralMachineName": "zipkin_querys",
|
||||
"lineageIsGroup": false,
|
||||
"lineageIsGroup": true,
|
||||
"maturity": "planned",
|
||||
"currentVersion": [
|
||||
0,
|
||||
|
140
pkg/kindsys/schema_interface.cue
Normal file
140
pkg/kindsys/schema_interface.cue
Normal file
@ -0,0 +1,140 @@
|
||||
package kindsys
|
||||
|
||||
// The schema interfaces defined in this file are meta-schemas. They are shared
|
||||
// contracts between the producers (composable kinds, defined in Grafana
|
||||
// plugins) and consumers (core and custom Grafana kinds) of composable schemas.
|
||||
//
|
||||
// This contract is similar to an interface in most programming languages:
|
||||
// producer and consumer implementations depend only on the schema interface
|
||||
// definition, rather than the details of any particular implementation. This
|
||||
// allows producers and consumers to be loosely coupled, while keeping an
|
||||
// explicit contract for composition of sub-schemas from producers into the
|
||||
// consumer schemas that want to use them.
|
||||
//
|
||||
// Schema interfaces allow schema composition to be broken down into a series of
|
||||
// simple "what," "which," and "how" questions:
|
||||
//
|
||||
// - "What" is the subschema to be composed?
|
||||
// - "How" should subschema(s) be composed into another schema to produce a unified result schema?
|
||||
// - "Which" subset of known composable subschemas ("whats") should be provided in composition ("how")?
|
||||
//
|
||||
// On the producer side, Grafana plugin authors may provide Thema lineages
|
||||
// within Composable kinds declared in .cue files adjacent to their
|
||||
// plugin.json, following a pattern (see
|
||||
// github.com/grafana/grafana/pkg/plugins/pfs.#GrafanaPlugin.composableKinds)
|
||||
// corresponding to the name of the schema interface. Each such definition is
|
||||
// an answer to "what."
|
||||
//
|
||||
// On the consumer side, any core or custom kind author can choose to define a
|
||||
// standard Thema composition slot in its contained lineage that uses one of
|
||||
// these schema interfaces as its meta-schema. The slot specification in Thema
|
||||
// answers "how", for that kind.
|
||||
//
|
||||
// Composable kinds declared by a plugin are parsed and validated by Grafana's
|
||||
// plugin system when a plugin is installed. This gives each Grafana instance a
|
||||
// set of all known Composable kinds ("whats"), which can be narrowed into the
|
||||
// subsets ("which") that each known Core or Custom can consume. These subsets
|
||||
// are injected dynamically into the consumers, resulting in the final schema.
|
||||
//
|
||||
// For example, in the Thema lineage for the dashboard core kind:
|
||||
// - There is a slot named `panelcfg`
|
||||
// - It is constrained to accept only Thema lineages following the `panelcfg` schema interface
|
||||
// - The composition logic specifies that the `panelcfg.PanelOptions` from each lineage provided
|
||||
// to the dashboard lineage be one possibility for `panels[].options`
|
||||
//
|
||||
// (TODO actual implementation is pending https://github.com/grafana/thema/issue/8)
|
||||
//
|
||||
// Thus, the dashboard schema used for validation by any particular Grafana instance
|
||||
// can tell the user if a particular dashboard with a `timeseries` panel has invalid
|
||||
// values for `panels[].options`, even though neither the dashboard core kind, nor the
|
||||
// the timeseries composable kind, are directly aware of (import) each other.
|
||||
|
||||
// A SchemaInterface defines a single Grafana schema interface.
|
||||
SchemaInterface: {
|
||||
// name is the unique identifier of the schema interface.
|
||||
//
|
||||
// Often used to provide namespacing of schema interface implementations
|
||||
// in places where implementations must be enumerated, such as:
|
||||
// - In-memory indexes in the Grafana backend
|
||||
// - Documentation URLs
|
||||
// - Parent directory paths or names in generated code
|
||||
name: string & =~"^[A-Z][A-Za-z]{1,19}$"
|
||||
|
||||
// interface is the body of the SchemaInterface - the actual meta-schema that
|
||||
// forms the shared contract between consumers (core & custom kind lineages)
|
||||
// and producers (composable kind lineages).
|
||||
interface: {}
|
||||
|
||||
// pluginTypes is a list of plugin types that are expected to produce composable
|
||||
// kinds following this interface.
|
||||
//
|
||||
// Note that Grafana's plugin architecture intentionally does not enforce this.
|
||||
// The worst that a violation (impl expected and absent, or impl present and not expected)
|
||||
// will currently produce is a warning.
|
||||
//
|
||||
// TODO this relies on information in pkg/plugins/plugindef, awkward having it here
|
||||
pluginTypes: [...string]
|
||||
|
||||
// Whether lineages implementing this are considered "grouped" or not. Generally
|
||||
// this refers to whether an e.g. JSON object is ever expected to exist that
|
||||
// corresponds to the whole schema, or to top-level fields within the schema.
|
||||
//
|
||||
// TODO see https://github.com/grafana/thema/issues/62
|
||||
//
|
||||
// The main effect is whether code generation should produce one type that represents
|
||||
// the root schema for lineages, or only produce types for each of the top-level fields
|
||||
// within the schema.
|
||||
group: bool | *true
|
||||
}
|
||||
|
||||
// The canonical list of all Grafana schema interfaces.
|
||||
schemaInterfaces: [N=string]: SchemaInterface & { name: N }
|
||||
schemaInterfaces: {
|
||||
Panel: {
|
||||
interface: {
|
||||
// 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?: {}
|
||||
}
|
||||
|
||||
pluginTypes: ["panel"]
|
||||
|
||||
// grouped b/c separate non-cross-referring elements always occur together in larger structure (panel)
|
||||
group: true
|
||||
}
|
||||
Query: {
|
||||
// The contract for the queries schema interface is itself a pattern:
|
||||
// Each of its top-level fields must be represent a distinct query type for
|
||||
// the datasource plugin. The queryType field acts as a discriminator, and
|
||||
// is constrained to be the same as the name of the top-level field declaring it.
|
||||
interface: [QT=string]: {
|
||||
queryType?: QT
|
||||
}
|
||||
|
||||
pluginTypes: ["datasource"]
|
||||
|
||||
// grouped b/c separate, non-cross-referring elements are actually themselves each impls of the concept
|
||||
// and it avoids us having to put more levels in the slot system (uggghhh)
|
||||
group: true
|
||||
}
|
||||
DSOptions: {
|
||||
interface: {
|
||||
// Normal datasource configuration options.
|
||||
Options: {}
|
||||
// Sensitive datasource configuration options that require encryption.
|
||||
SecureOptions: {}
|
||||
}
|
||||
|
||||
pluginTypes: ["datasource"]
|
||||
|
||||
// group b/c separate, non-cross-referring elements have diff runtime representation due to encryption
|
||||
group: true
|
||||
}
|
||||
}
|
121
pkg/kindsys/schema_interface.go
Normal file
121
pkg/kindsys/schema_interface.go
Normal file
@ -0,0 +1,121 @@
|
||||
package kindsys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/grafana/grafana/pkg/cuectx"
|
||||
)
|
||||
|
||||
// SchemaInterface represents one of Grafana's named schema interfaces.
|
||||
//
|
||||
// Canonical definition of schema interfaces is done in CUE. Instances of
|
||||
// this type simply represent that information in Go.
|
||||
// TODO link to framework docs
|
||||
type SchemaInterface struct {
|
||||
name string
|
||||
group bool
|
||||
raw cue.Value
|
||||
plugins []string
|
||||
}
|
||||
|
||||
// Name returns the name of the SchemaInterface.
|
||||
//
|
||||
// The name is also used as the path at which a SchemaInterface lineage is defined in a
|
||||
// plugin models.cue file.
|
||||
func (s SchemaInterface) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// Interface returns the cue.Value representing 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 SchemaInterface) Interface() cue.Value {
|
||||
return s.raw.LookupPath(ip)
|
||||
}
|
||||
|
||||
var ip = cue.ParsePath("interface")
|
||||
|
||||
// Should indicates whether the given plugin type is expected (but not required)
|
||||
// to produce a composable kind that implements this SchemaInterface.
|
||||
func (s SchemaInterface) Should(plugintype string) bool {
|
||||
pt := plugintype
|
||||
for _, t := range s.plugins {
|
||||
if pt == t {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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 SchemaInterface) IsGroup() bool {
|
||||
return s.group
|
||||
}
|
||||
|
||||
func FindSchemaInterface(name string) (SchemaInterface, error) {
|
||||
sl, has := SchemaInterfaces(nil)[name]
|
||||
if !has {
|
||||
return SchemaInterface{}, fmt.Errorf("unsupported slot: %s", name)
|
||||
}
|
||||
return sl, nil
|
||||
}
|
||||
|
||||
var defaultIfaces map[string]SchemaInterface
|
||||
var onceIfaces sync.Once
|
||||
|
||||
// SchemaInterfaces returns a map of all [SchemaInterface]s defined by
|
||||
// Grafana's kindsys framework.
|
||||
//
|
||||
// All calling code within grafana/grafana is expected to use Grafana's
|
||||
// singleton [cue.Context], returned from [cuectx.GrafanaCUEContext]. If nil is
|
||||
// passed, the singleton will be used. This is a reasonable default for external
|
||||
// code, as well.
|
||||
//
|
||||
// TODO link to framework docs
|
||||
func SchemaInterfaces(ctx *cue.Context) map[string]SchemaInterface {
|
||||
if ctx == nil || ctx == cuectx.GrafanaCUEContext() {
|
||||
// Ensure framework is loaded, even if this func is called
|
||||
// from an init() somewhere.
|
||||
onceIfaces.Do(func() {
|
||||
defaultIfaces = doSchemaInterfaces(nil)
|
||||
})
|
||||
return defaultIfaces
|
||||
}
|
||||
|
||||
return doSchemaInterfaces(ctx)
|
||||
}
|
||||
|
||||
func doSchemaInterfaces(ctx *cue.Context) map[string]SchemaInterface {
|
||||
fw := CUEFramework(ctx)
|
||||
|
||||
defs := fw.LookupPath(cue.ParsePath("schemaInterfaces"))
|
||||
if !defs.Exists() {
|
||||
panic("schemaInterfaces key does not exist in kindsys framework")
|
||||
}
|
||||
type typ struct {
|
||||
Name string `json:"name"`
|
||||
PluginTypes []string `json:"pluginTypes"`
|
||||
Group bool `json:"group"`
|
||||
}
|
||||
|
||||
ifaces := make(map[string]SchemaInterface)
|
||||
iter, _ := defs.Fields() //nolint:errcheck
|
||||
for iter.Next() {
|
||||
k := iter.Selector().String()
|
||||
v := &typ{}
|
||||
_ = iter.Value().Decode(&v) //nolint:errcheck,gosec
|
||||
ifaces[k] = SchemaInterface{
|
||||
name: v.Name,
|
||||
plugins: v.PluginTypes,
|
||||
group: v.Group,
|
||||
raw: iter.Value(),
|
||||
}
|
||||
}
|
||||
|
||||
return ifaces
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
package kindsys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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)
|
||||
}
|
||||
}
|
||||
|
||||
func FindSlot(name string) (*Slot, error) {
|
||||
sl, has := AllSlots(nil)[name]
|
||||
if !has {
|
||||
return nil, fmt.Errorf("unsupported slot: %s", name)
|
||||
}
|
||||
return sl, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
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")
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ func (j *pgoJenny) Generate(decl *pfs.PluginDecl) (*codejen.File, error) {
|
||||
}
|
||||
|
||||
pluginfolder := filepath.Base(decl.PluginPath)
|
||||
slotname := strings.ToLower(decl.Slot.Name())
|
||||
slotname := strings.ToLower(decl.SchemaInterface.Name())
|
||||
filename := fmt.Sprintf("types_%s_gen.go", slotname)
|
||||
f.RelativePath = filepath.Join(j.root, pluginfolder, filename)
|
||||
f.From = append(f.From, j)
|
||||
|
@ -40,7 +40,7 @@ func (j *ptsJenny) Generate(decl *pfs.PluginDecl) (*codejen.File, error) {
|
||||
}
|
||||
}
|
||||
|
||||
slotname := decl.Slot.Name()
|
||||
slotname := decl.SchemaInterface.Name()
|
||||
v := decl.Lineage.Latest().Version()
|
||||
|
||||
tsf.Nodes = append(tsf.Nodes, tsast.Raw{
|
||||
|
@ -3,7 +3,7 @@ package grafanaplugin
|
||||
import "github.com/grafana/thema"
|
||||
|
||||
Query: thema.#Lineage & {
|
||||
name: "missing_slot_impl"
|
||||
name: "missing_kind_datasource"
|
||||
seqs: [
|
||||
{
|
||||
schemas: [
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "datasource",
|
||||
"name": "Missing slot impl",
|
||||
"id": "missing-slot-datasource",
|
||||
"name": "Missing kind impl",
|
||||
"id": "missing-kind-datasource",
|
||||
"backend": true,
|
||||
"state": "alpha",
|
||||
"info": {
|
@ -8,11 +8,11 @@ import (
|
||||
)
|
||||
|
||||
type PluginDecl struct {
|
||||
Slot *kindsys.Slot
|
||||
Lineage thema.Lineage
|
||||
Imports []*ast.ImportSpec
|
||||
PluginPath string
|
||||
PluginMeta plugindef.PluginDef
|
||||
SchemaInterface *kindsys.SchemaInterface
|
||||
Lineage thema.Lineage
|
||||
Imports []*ast.ImportSpec
|
||||
PluginPath string
|
||||
PluginMeta plugindef.PluginDef
|
||||
}
|
||||
|
||||
func EmptyPluginDecl(path string, meta plugindef.PluginDef) *PluginDecl {
|
||||
@ -24,5 +24,5 @@ func EmptyPluginDecl(path string, meta plugindef.PluginDef) *PluginDecl {
|
||||
}
|
||||
|
||||
func (decl *PluginDecl) HasSchema() bool {
|
||||
return decl.Lineage != nil && decl.Slot != nil
|
||||
return decl.Lineage != nil && decl.SchemaInterface != nil
|
||||
}
|
||||
|
@ -54,17 +54,17 @@ func (psr *declParser) Parse(root fs.FS) ([]*PluginDecl, error) {
|
||||
}
|
||||
|
||||
for slotName, lin := range slots {
|
||||
slot, err := kindsys.FindSlot(slotName)
|
||||
slot, err := kindsys.FindSchemaInterface(slotName)
|
||||
if err != nil {
|
||||
log.Println(fmt.Errorf("parsing plugin failed for %s: %s", dir, err))
|
||||
continue
|
||||
}
|
||||
decls = append(decls, &PluginDecl{
|
||||
Slot: slot,
|
||||
Lineage: lin,
|
||||
Imports: p.CUEImports(),
|
||||
PluginMeta: p.Meta(),
|
||||
PluginPath: path,
|
||||
SchemaInterface: &slot,
|
||||
Lineage: lin,
|
||||
Imports: p.CUEImports(),
|
||||
PluginMeta: p.Meta(),
|
||||
PluginPath: path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ var allowedImportsStr string
|
||||
|
||||
type slotandname struct {
|
||||
name string
|
||||
slot *kindsys.Slot
|
||||
slot kindsys.SchemaInterface
|
||||
}
|
||||
|
||||
var allslots []slotandname
|
||||
@ -55,7 +55,7 @@ func init() {
|
||||
}
|
||||
allowedImportsStr = strings.Join(all, "\n")
|
||||
|
||||
for n, s := range kindsys.AllSlots(nil) {
|
||||
for n, s := range kindsys.SchemaInterfaces(nil) {
|
||||
allslots = append(allslots, slotandname{
|
||||
name: n,
|
||||
slot: s,
|
||||
@ -93,7 +93,7 @@ func (t *Tree) SubPlugins() map[string]PluginInfo {
|
||||
type TreeList []*Tree
|
||||
|
||||
// LineagesForSlot returns the set of plugin-defined lineages that implement a
|
||||
// particular named Grafana slot (See ["github.com/grafana/grafana/pkg/framework/coremodel".Slot]).
|
||||
// particular named Grafana slot (See ["github.com/grafana/grafana/pkg/framework/coremodel".SchemaInterface]).
|
||||
func (tl TreeList) LineagesForSlot(slotname string) map[string]thema.Lineage {
|
||||
m := make(map[string]thema.Lineage)
|
||||
for _, tree := range tl {
|
||||
@ -218,12 +218,14 @@ func ParsePluginFS(f fs.FS, rt *thema.Runtime) (*Tree, error) {
|
||||
}
|
||||
for _, s := range allslots {
|
||||
iv := val.LookupPath(cue.ParsePath(s.slot.Name()))
|
||||
lin, err := bindSlotLineage(iv, s.slot, r.meta, rt)
|
||||
if lin != nil {
|
||||
r.slotimpls[s.slot.Name()] = lin
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if iv.Exists() {
|
||||
lin, err := bindSlotLineage(iv, s.slot, r.meta, rt)
|
||||
if lin != nil {
|
||||
r.slotimpls[s.slot.Name()] = lin
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -231,8 +233,10 @@ func ParsePluginFS(f fs.FS, rt *thema.Runtime) (*Tree, error) {
|
||||
return tree, nil
|
||||
}
|
||||
|
||||
func bindSlotLineage(v cue.Value, s *kindsys.Slot, meta plugindef.PluginDef, rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
|
||||
accept, required := s.ForPluginType(string(meta.Type))
|
||||
func bindSlotLineage(v cue.Value, s kindsys.SchemaInterface, meta plugindef.PluginDef, rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
|
||||
// temporarily keep this around, there are IMMEDIATE plans to refactor
|
||||
var required bool
|
||||
accept := s.Should(string(meta.Type))
|
||||
exists := v.Exists()
|
||||
|
||||
if !accept {
|
||||
|
@ -115,9 +115,7 @@ func TestParseTreeTestdata(t *testing.T) {
|
||||
"wrong-slot-panel": {
|
||||
err: ErrImplementedSlots,
|
||||
},
|
||||
"missing-slot-impl": {
|
||||
err: ErrImplementedSlots,
|
||||
},
|
||||
"missing-kind-datasource": {},
|
||||
"panel-conflicting-joinschema": {
|
||||
err: ErrInvalidLineage,
|
||||
skip: "TODO implement BindOption in thema, SatisfiesJoinSchema, then use it here",
|
||||
|
@ -85,7 +85,7 @@ func adaptToPipeline(j codejen.OneToOne[corecodegen.SchemaForGen]) codejen.OneTo
|
||||
return corecodegen.SchemaForGen{
|
||||
Name: pd.PluginMeta.Name,
|
||||
Schema: pd.Lineage.Latest(),
|
||||
IsGroup: pd.Slot.IsGroup(),
|
||||
IsGroup: pd.SchemaInterface.IsGroup(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user