diff --git a/kinds/constraint.cue b/kinds/constraint.cue index 35ff089dda7..4d1bb954cf5 100644 --- a/kinds/constraint.cue +++ b/kinds/constraint.cue @@ -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 diff --git a/pkg/codegen/generators.go b/pkg/codegen/generators.go index eaff6621abd..037766d13d7 100644 --- a/pkg/codegen/generators.go +++ b/pkg/codegen/generators.go @@ -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 } diff --git a/pkg/codegen/jenny_basecorereg.go b/pkg/codegen/jenny_basecorereg.go index 1811fce7aa9..d97dfd3e6cc 100644 --- a/pkg/codegen/jenny_basecorereg.go +++ b/pkg/codegen/jenny_basecorereg.go @@ -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, diff --git a/pkg/codegen/jenny_corekind.go b/pkg/codegen/jenny_corekind.go index 81d83993e32..806ab2531ad 100644 --- a/pkg/codegen/jenny_corekind.go +++ b/pkg/codegen/jenny_corekind.go @@ -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{} diff --git a/pkg/codegen/tmpl.go b/pkg/codegen/tmpl.go index a7c2e405b6b..3c1668bb216 100644 --- a/pkg/codegen/tmpl.go +++ b/pkg/codegen/tmpl.go @@ -38,7 +38,6 @@ type ( } tvars_kind_registry struct { // Header tvars_autogen_header - NumStructured int PackageName string KindPackagePrefix string Kinds []*DeclForGen diff --git a/pkg/codegen/tmpl/kind_core.tmpl b/pkg/codegen/tmpl/kind_core.tmpl index cf006b155c6..eac0d9bc7e2 100644 --- a/pkg/codegen/tmpl/kind_core.tmpl +++ b/pkg/codegen/tmpl/kind_core.tmpl @@ -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], diff --git a/pkg/kinds/dashboard/dashboard_kind_gen.go b/pkg/kinds/dashboard/dashboard_kind_gen.go index 42aa0b38d92..2f228a3e55a 100644 --- a/pkg/kinds/dashboard/dashboard_kind_gen.go +++ b/pkg/kinds/dashboard/dashboard_kind_gen.go @@ -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], diff --git a/pkg/kinds/playlist/playlist_kind_gen.go b/pkg/kinds/playlist/playlist_kind_gen.go index 2b0287d7687..202a8883e42 100644 --- a/pkg/kinds/playlist/playlist_kind_gen.go +++ b/pkg/kinds/playlist/playlist_kind_gen.go @@ -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], diff --git a/pkg/kinds/team/team_kind_gen.go b/pkg/kinds/team/team_kind_gen.go index ab4de5cfad8..4c08d5de435 100644 --- a/pkg/kinds/team/team_kind_gen.go +++ b/pkg/kinds/team/team_kind_gen.go @@ -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], diff --git a/pkg/kindsys/kind.go b/pkg/kindsys/kind.go index 7458ac779f2..9e360889b70 100644 --- a/pkg/kindsys/kind.go +++ b/pkg/kindsys/kind.go @@ -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] +} diff --git a/pkg/kindsys/kindcat_composable.cue b/pkg/kindsys/kindcat_composable.cue new file mode 100644 index 00000000000..1169b74bca0 --- /dev/null +++ b/pkg/kindsys/kindcat_composable.cue @@ -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 +} diff --git a/pkg/kindsys/kindcat_custom.cue b/pkg/kindsys/kindcat_custom.cue index 92564bf8688..d8f30275749 100644 --- a/pkg/kindsys/kindcat_custom.cue +++ b/pkg/kindsys/kindcat_custom.cue @@ -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 - ... } diff --git a/pkg/kindsys/kindcats.cue b/pkg/kindsys/kindcats.cue index cc07d2884a6..f0cd4005d82 100644 --- a/pkg/kindsys/kindcats.cue +++ b/pkg/kindsys/kindcats.cue @@ -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 } -} diff --git a/pkg/kindsys/kindmetas.go b/pkg/kindsys/kindmetas.go index ce05b49ebac..c0d498e0e49 100644 --- a/pkg/kindsys/kindmetas.go +++ b/pkg/kindsys/kindmetas.go @@ -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 diff --git a/pkg/kindsys/load.go b/pkg/kindsys/load.go index 804a48a1eae..efb3ef9298b 100644 --- a/pkg/kindsys/load.go +++ b/pkg/kindsys/load.go @@ -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 } diff --git a/pkg/kindsys/report.go b/pkg/kindsys/report.go index 7ede815620e..0f7e65e8db7 100644 --- a/pkg/kindsys/report.go +++ b/pkg/kindsys/report.go @@ -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() } diff --git a/pkg/kindsys/report.json b/pkg/kindsys/report.json index 34e41bce973..6773c6c1445 100644 --- a/pkg/kindsys/report.json +++ b/pkg/kindsys/report.json @@ -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, diff --git a/pkg/kindsys/schema_interface.cue b/pkg/kindsys/schema_interface.cue new file mode 100644 index 00000000000..9c855884685 --- /dev/null +++ b/pkg/kindsys/schema_interface.cue @@ -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 + } +} diff --git a/pkg/kindsys/schema_interface.go b/pkg/kindsys/schema_interface.go new file mode 100644 index 00000000000..83aa25bab2f --- /dev/null +++ b/pkg/kindsys/schema_interface.go @@ -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 +} diff --git a/pkg/kindsys/slot.go b/pkg/kindsys/slot.go deleted file mode 100644 index c16491269c2..00000000000 --- a/pkg/kindsys/slot.go +++ /dev/null @@ -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 -} diff --git a/pkg/kindsys/slot_test.go b/pkg/kindsys/slot_test.go deleted file mode 100644 index a4a17c8ae45..00000000000 --- a/pkg/kindsys/slot_test.go +++ /dev/null @@ -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") -} diff --git a/pkg/kindsys/slots.cue b/pkg/kindsys/slots.cue deleted file mode 100644 index 2d4976ff274..00000000000 --- a/pkg/kindsys/slots.cue +++ /dev/null @@ -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 - } -} diff --git a/pkg/plugins/codegen/jenny_plugingotypes.go b/pkg/plugins/codegen/jenny_plugingotypes.go index 34d94e23cdd..f3e2257d84f 100644 --- a/pkg/plugins/codegen/jenny_plugingotypes.go +++ b/pkg/plugins/codegen/jenny_plugingotypes.go @@ -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) diff --git a/pkg/plugins/codegen/jenny_plugintstypes.go b/pkg/plugins/codegen/jenny_plugintstypes.go index 016f1fbe1a1..e76db2bea62 100644 --- a/pkg/plugins/codegen/jenny_plugintstypes.go +++ b/pkg/plugins/codegen/jenny_plugintstypes.go @@ -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{ diff --git a/pkg/plugins/manager/testdata/missing-slot-impl/models.cue b/pkg/plugins/manager/testdata/missing-kind-datasource/models.cue similarity index 82% rename from pkg/plugins/manager/testdata/missing-slot-impl/models.cue rename to pkg/plugins/manager/testdata/missing-kind-datasource/models.cue index 2d0dc790c98..7adee7855f3 100644 --- a/pkg/plugins/manager/testdata/missing-slot-impl/models.cue +++ b/pkg/plugins/manager/testdata/missing-kind-datasource/models.cue @@ -3,7 +3,7 @@ package grafanaplugin import "github.com/grafana/thema" Query: thema.#Lineage & { - name: "missing_slot_impl" + name: "missing_kind_datasource" seqs: [ { schemas: [ diff --git a/pkg/plugins/manager/testdata/missing-slot-impl/plugin.json b/pkg/plugins/manager/testdata/missing-kind-datasource/plugin.json similarity index 74% rename from pkg/plugins/manager/testdata/missing-slot-impl/plugin.json rename to pkg/plugins/manager/testdata/missing-kind-datasource/plugin.json index 0915b37a530..48836019d86 100644 --- a/pkg/plugins/manager/testdata/missing-slot-impl/plugin.json +++ b/pkg/plugins/manager/testdata/missing-kind-datasource/plugin.json @@ -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": { diff --git a/pkg/plugins/pfs/decl.go b/pkg/plugins/pfs/decl.go index 0639d3eb031..ce6eac514ff 100644 --- a/pkg/plugins/pfs/decl.go +++ b/pkg/plugins/pfs/decl.go @@ -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 } diff --git a/pkg/plugins/pfs/decl_parser.go b/pkg/plugins/pfs/decl_parser.go index e772de921fc..0e5a94591f1 100644 --- a/pkg/plugins/pfs/decl_parser.go +++ b/pkg/plugins/pfs/decl_parser.go @@ -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, }) } } diff --git a/pkg/plugins/pfs/pfs.go b/pkg/plugins/pfs/pfs.go index 3de4651ef8c..a39f265c8ad 100644 --- a/pkg/plugins/pfs/pfs.go +++ b/pkg/plugins/pfs/pfs.go @@ -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 { diff --git a/pkg/plugins/pfs/pfs_test.go b/pkg/plugins/pfs/pfs_test.go index ede09039767..de08cb5c082 100644 --- a/pkg/plugins/pfs/pfs_test.go +++ b/pkg/plugins/pfs/pfs_test.go @@ -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", diff --git a/public/app/plugins/gen.go b/public/app/plugins/gen.go index 7a0be5c1435..543e0e43c06 100644 --- a/public/app/plugins/gen.go +++ b/public/app/plugins/gen.go @@ -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(), } }) }