Reconcile coremodels, entities, objects under new kind framework (#56492)

* Update thema to latest

* Deal with s/Library/*Runtime/

* Commit new, working results of codegen

* We like pointers now

* Always take runtime arg for NewBase()

* Sketchy handwavy pass at entity meta framework

* Little nibbles

* Update pkg/framework/coremodel/entityframework.cue

Co-authored-by: Artur Wierzbicki <wierzbicki.artur.94@gmail.com>

* Move file into new framework location

* Introduce loaders, Go code

* Complete rename to kind

* Flesh out framework, add svg/dashboard examples

* Cruft removal

* Remove generated kind go files from gitignore

* Refine maturity concept, add SlotKind

* Update embed and go deps

* Export PrefixWithGrafanaCUE

* Make the loader actually work, holy crap

* Many small tweaks to type.cue

* Add Apache 2 licensing exceptions for kinds

* Add new kinds dir, start of generator

* Roll back to earlier oapi-codegen

* Introduce new grafana-specific CUE loaders

* Introduce new tidy code generators framework

* Catch up kind framework with tinkering

* Add slices for the generators

* Add write/verify step to main generator

* Many renames

* Split up kind framework cue files

* Use kind.Decl within generated kinds

* Create kind.SomeDecl wrapper type to cache lineages

* Better names again

* Get one generated implemented, hopefully

* Copy dashboard schema into new kind.cue

* Small fixes to make the initial gen work

* Put svg kind in its new home

* Add generated Go dashboard type

* More renames and cleanups

* Add base kind registry and generator

* Stop blacklisting *_gen.go files

This is not the Go best practice, anyway. All we actually want to ignore
for enterprise is generated wire files.

* Change codegen output directories

pkg/kind -> pkg/kinds
pkg/registry/kindreg -> pkg/registry/corekind

* Rename pkg/framework/kind to pkg/kindsys

* Add core structured kind generator

* Add plural and machine names to kind spec

* Copy playlist over to kind system

* Consolidate kindsys files

* Add raw kind generator

* Update CODEOWNERS for kind framework

* Touch up comments a bit

* More docs tweaks

* Remove generated types to reduce noise for review

* Split each generator into its own file

* Rename Slot kind to Composable kind

* Add handwavy types for customkind loading

* Guard against init calls to framework loader

* First pass at doc on extending the kind system

* Improve attribute example in docs

* Fix wire imports

* Add basic TS types generator

* Fix composable kind category def

* No need for a separate file with generate directive

* Catch dashboard schema up

* Rename generator types to something saner and generic

* Make version configurable in ts/go generators

* Add CommonMeta to ease property access

* Add kindsys prop indicating whether lineage is group

* Put all kind categories back in a single file

* Finish with kindsys group props

* Refactor maturity progression per discussion

- Replace "committed" with "merged"
- All kindcats can use all maturity levels, at least for now

* Convert ts veneer index generator to modular system

* Move over to new jennywrites framework

* Strip down old coremodel generator

* Use public version of jennywrites

* Pull latest thema

* Commit generated Go types

* Add header injection postprocessor

* Move sdboyer/jennywrites to grafana/codejen

* Tweak header output

* Remove dashboard and playlist coremodels

* Fix up backend dashboards devenv test

* Fix TS import patterns to new gen filename

* Update internal imports, remove coremodel registry

* Fix compilation errors, wire generation

* Export and replace the prefix dropper

* More Go struct and field name changes

* Last name fixes, hopefully

* Fix lint errors

* Last lint error

Co-authored-by: Artur Wierzbicki <wierzbicki.artur.94@gmail.com>
This commit is contained in:
sam boyer
2022-11-10 12:36:40 -08:00
committed by GitHub
co-authored by Artur Wierzbicki
parent f92d978386
commit 07e5f8117f
68 changed files with 2702 additions and 1208 deletions
+60
View File
@@ -0,0 +1,60 @@
# Kind System
This package contains Grafana's kind system, which defines the rules that govern all Grafana kind declarations, including both core and plugin kinds. It contains many contracts on which public promises of backwards compatibility are made. All changes must be considered with care.
While this package is maintained by @grafana/grafana-as-code, contributions from others are a main goal! Any time you have the thought, "I wish this part of Grafana's codebase was consistent," rather than writing docs (that people will inevitably miss), it's worth seeing if you can express that consistency as a kindsys extension instead.
This document is the guide to extending kindsys. But first, we have to identify kindsys's key components.
## Elements of kindsys
* **CUE framework** - the collection of .cue files in this directory, `pkg/kindsys`. These are schemas that define how Kinds are declared.
* **Go framework** - the Go package in this directory containing utilities for loading individual kind declarations, validating them against the CUE framework, and representing them consistently in Go.
* **Code generators** - `pkg/codegen` contains the codegen framework. Individual generators (which take one or many `pkg/kindsys.Decl`, and produce a single file) each have a `pkg/codegen/generator_*.go` file.
* **Registries** - generated lists of all or a well-defined subset of kinds that can be used in code. `pkg/registries/corekind` is a registry of all core `pkg/kindsys.Interface` implementations; `packages/grafana-schema/src/index.gen.ts` is a registry of all the TypeScript types generated from the current versions of each kind's schema.
* **Kind declarations** - the declarations of individual kinds. By kind category:
* **Core Structured** - each child directory of `kinds/structured`.
* **Raw** - each child directory of `kinds/raw`.
* **Composable** - In Grafana core, `public/app/plugins/*/*/models.cue` files.
* **Custom** - No examples in Grafana core. See [operator-app-sdk](https://github.com/grafana/operator-app-sdk) (TODO that repo is private; make it public, or point to public examples).
The above are treated as similarly to stateless libraries - a layer beneath the main Grafana frontend and backend without dependencies on it (no storage, no API, no wire, etc.). This lack of dependencies, and their Apache v2 licensing, allow their use as libraries for external tools.
## Extending kindsys
Extending the kind system generally involves:
* Introducing one or more new fields into the CUE framework
* Updating the Go framework to accommodate the new fields
* Updating the kind authoring and maturity planning docs to reflect the new extension
* (possibly) Writing one or more new code generators
* (possibly) Writing/refactoring some frontend code that depends on new codegen output
* (possibly) Writing/refactoring some backend code that depends on codegen output and/or the Go kind framework
* (possibly) Tweaking all existing kinds as-needed to accommodate the new extension
_TODO detailed guide to the above steps_
The above steps certainly aren't trivial. But they all come only after figuring out a way to solve the problem you want to solve in terms of the kind system and code generation in the first place.
_TODO brief guide on how to think in codegen_
## Extensions not involving kind metadata
While the main path for extending kindsys is through adding metadata, there are some other ways of extending kindsys.
### CUE attributes
[CUE attributes](https://cuelang.org/docs/references/spec/#attributes) provide additional information to kind tooling. They are suitable when it is necessary for a schema author to express additional information about a particular field or definition within a schema, without actually modifying the meaning of the schema. Two such known patterns are:
* Controlling nuanced behavior of code generators for some field or type. Example: [@cuetsy](https://github.com/grafana/cuetsy#usage) attributes, which govern TS output
* Expressing some kind of structured TODO or WIP information on a field or type that can be easily analyzed and fed into other systems. Example: a kind marked at least `stable` maturity may not have any `@grafanamaturity` attributes
In both of these cases, attributes are a tool _for the individual kind author_ to convey something to downstream consumers of kind declarations. It is essential. While attributes allow consistency in _how_ a particular task is accomplished, they leave _when_ to apply the rule up to the judgment of the kind author.
Attributes occupy an awkward middle ground. They are more challenging to implement than standard kind framework properties, and less consistent than general codegen transformers while still imposing a cognitive burden on kind authors. They should be the last tool you reach for - but may be the only tool available when field-level schema metadata is required.
TODO create a general pattern for self-contained attribute parser/validators to follow
### Codegen transformers
TODO actually write this - use `Uid`->`UID` as example
+35
View File
@@ -0,0 +1,35 @@
package kindsys
import "errors"
// TODO consider rewriting with https://github.com/cockroachdb/errors
var (
// ErrValueNotExist indicates that a necessary CUE value did not exist.
ErrValueNotExist = errors.New("cue value does not exist")
// ErrValueNotAKind indicates that a provided CUE value is not any variety of
// Interface. This is almost always an end-user error - they oops'd and provided the
// wrong path, file, etc.
ErrValueNotAKind = errors.New("not a kind")
)
func ewrap(actual, is error) error {
return &errPassthrough{
actual: actual,
is: is,
}
}
type errPassthrough struct {
actual error
is error
}
func (e *errPassthrough) Is(err error) bool {
return errors.Is(err, e.actual) || errors.Is(err, e.is)
}
func (e *errPassthrough) Error() string {
return e.actual.Error()
}
+82
View File
@@ -0,0 +1,82 @@
package kindsys
import (
"fmt"
"github.com/grafana/thema"
)
// TODO docs
type Maturity string
const (
MaturityMerged Maturity = "merged"
MaturityExperimental Maturity = "experimental"
MaturityStable Maturity = "stable"
MaturityMature Maturity = "mature"
)
func maturityIdx(m Maturity) int {
// icky to do this globally, this is effectively setting a default
if string(m) == "" {
m = MaturityMerged
}
for i, ms := range maturityOrder {
if m == ms {
return i
}
}
panic(fmt.Sprintf("unknown maturity milestone %s", m))
}
var maturityOrder = []Maturity{
MaturityMerged,
MaturityExperimental,
MaturityStable,
MaturityMature,
}
func (m Maturity) Less(om Maturity) bool {
return maturityIdx(m) < maturityIdx(om)
}
// TODO docs
type Interface interface {
// TODO docs
Name() string
// TODO docs
MachineName() string
// TODO docs
Maturity() Maturity // TODO unclear if we want maturity for raw kinds
}
// TODO docs
type Raw interface {
Interface
// TODO docs
Meta() RawMeta
}
type Structured interface {
Interface
// TODO docs
Lineage() thema.Lineage
// TODO docs
Meta() CoreStructuredMeta // TODO figure out how to reconcile this interface with CustomStructuredMeta
}
// type Composable interface {
// Interface
//
// // TODO docs
// Lineage() thema.Lineage
//
// // TODO docs
// Meta() CoreStructuredMeta // TODO figure out how to reconcile this interface with CustomStructuredMeta
// }
+166
View File
@@ -0,0 +1,166 @@
package kindsys
import (
"strings"
"github.com/grafana/thema"
)
// A Kind specifies a type of Grafana resource.
//
// An instance of a Kind is called an entity. An entity is a sequence of bytes -
// for example, a JSON file or HTTP request body - that conforms to the
// constraints defined in a Kind, and enforced by Grafana's entity system.
//
// Once Grafana has determined a given byte sequence to be an
// instance of a known Kind, kind-specific behaviors can be applied,
// requests can be routed, events can be triggered, etc.
//
// Classes and objects in most programming languages are analogous:
// - #Kind is like a `class` keyword
// - Each declaration of #Kind is like a class declaration
// - Byte sequences are like arguments to the class constructor
// - Entities are like objects - what's returned from the constructor
//
// There are four categories of kinds: Raw, Composable, CoreStructured,
// and CustomStructured.
#Kind: #Raw | #Composable | #CoreStructured | #CustomStructured
// properties shared between all kind categories.
_sharedKind: {
// name is the canonical name of a Kind, as expressed in PascalCase.
//
// To ensure names are generally portable and amenable for consumption
// in various mechanical tasks, name largely follows the relatively
// strict DNS label naming standard as defined in RFC 1123:
// - Contain at most 63 characters
// - Contain only lowercase alphanumeric characters or '-'
// - Start with an uppercase alphabetic character
// - End with an alphanumeric character
name: =~"^([A-Z][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$"
// machineName is the case-normalized (lowercase) version of [name]. This
// version of the name is preferred for use in most mechanical contexts,
// as case normalization ensures that case-insensitive and case-sensitive
// checks will never disagree on uniqueness.
//
// In addition to lowercase normalization, dashes are transformed to underscores.
machineName: strings.ToLower(strings.Replace(name, "-", "_", -1))
// pluralName is the pluralized form of name. Defaults to name + "s".
pluralName: =~"^([A-Z][a-zA-Z0-9-]{0,61}[a-zA-Z])$" | *(name + "s")
// pluralMachineName is the pluralized form of [machineName]. The same case
// normalization and dash transformation is applied to [pluralName] as [machineName]
// applies to [name].
pluralMachineName: strings.ToLower(strings.Replace(pluralName, "-", "_", -1))
// lineageIsGroup indicates whether the lineage in this kind is "grouped". In a
// grouped lineage, each top-level field in the schema specifies a discrete
// object that is expected to exist in the wild
//
// This field is set at the framework level, and cannot be in the declaration of
// any individual kind.
//
// This is likely to eventually become a first-class property in Thema:
// https://github.com/grafana/thema/issues/62
lineageIsGroup: bool
maturity: #Maturity
// The kind system itself is not mature enough yet for any single
// kind to advance beyond "experimental"
// TODO allow more maturity stages once system is ready https://github.com/orgs/grafana/projects/133/views/8
maturity: *"merged" | "experimental"
// form indicates whether the kind has a schema ("structured") or not ("raw")
form: "structured" | "raw"
}
// Maturity indicates the how far a given kind declaration is in its initial
// journey. Mature kinds still evolve, but with guarantees about compatibility.
#Maturity: "merged" | "experimental" | "stable" | "mature"
// Structured encompasses all three of the structured kind categories, in which
// a schema specifies validity rules for the byte sequence. These represent all
// the conventional types and functional resources in Grafana, such as
// dashboards and datasources.
//
// Structured kinds may be defined either by Grafana itself (#CoreStructured),
// or by plugins (#CustomStructured). Plugin-defined kinds have a slightly
// reduced set of capabilities, due to the constraints imposed by them being run
// in separate processes, and the risks arising from executing code from
// potentially untrusted third parties.
#Structured: S={
_sharedKind
form: "structured"
// 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 }
currentVersion: thema.#SyntacticVersion & (thema.#LatestVersion & {lin: lineage}).out
}
// Raw is a category of Kind that specifies handling for a raw file,
// like an image, or an svg or parquet file. Grafana mostly acts as asset storage for raw
// kinds: the byte sequence is a black box to Grafana, and type is determined
// through metadata such as file extension.
#Raw: {
_sharedKind
form: "raw"
// TODO docs
extensions?: [...string]
lineageIsGroup: false
// known TODOs
// - sanitize function
// - get summary
}
// TODO
#CustomStructured: {
#Structured
lineageIsGroup: false
...
}
// TODO
#CoreStructured: {
#Structured
lineageIsGroup: false
}
// Composable is a category of structured kind that provides schema elements for
// composition into CoreStructured and CustomStructured 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
form: "structured"
// 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 }
}
+74
View File
@@ -0,0 +1,74 @@
package kindsys
import "github.com/grafana/thema"
// CommonMeta contains the kind metadata common to all categories of kinds.
type CommonMeta struct {
Name string `json:"name"`
PluralName string `json:"pluralName"`
MachineName string `json:"machineName"`
PluralMachineName string `json:"pluralMachineName"`
LineageIsGroup bool `json:"lineageIsGroup"`
Maturity Maturity `json:"maturity"`
}
// TODO generate from type.cue
type RawMeta struct {
CommonMeta
Extensions []string `json:"extensions"`
}
func (m RawMeta) _private() {}
func (m RawMeta) Common() CommonMeta {
return m.CommonMeta
}
// TODO
type CoreStructuredMeta struct {
CommonMeta
CurrentVersion thema.SyntacticVersion `json:"currentVersion"`
}
func (m CoreStructuredMeta) _private() {}
func (m CoreStructuredMeta) Common() CommonMeta {
return m.CommonMeta
}
// TODO
type CustomStructuredMeta struct {
CommonMeta
CurrentVersion thema.SyntacticVersion `json:"currentVersion"`
}
func (m CustomStructuredMeta) _private() {}
func (m CustomStructuredMeta) Common() CommonMeta {
return m.CommonMeta
}
// TODO
type ComposableMeta struct {
CommonMeta
CurrentVersion thema.SyntacticVersion `json:"currentVersion"`
}
func (m ComposableMeta) _private() {}
func (m ComposableMeta) Common() CommonMeta {
return m.CommonMeta
}
// SomeKindMeta is an interface type to abstract over the different kind
// metadata struct types: [RawMeta], [CoreStructuredMeta],
// [CustomStructuredMeta].
//
// It is the traditional interface counterpart to the generic type constraint
// KindMetas.
type SomeKindMeta interface {
_private()
Common() CommonMeta
}
// KindMetas is a type parameter that comprises the base possible set of
// kind metadata configurations.
type KindMetas interface {
RawMeta | CoreStructuredMeta | CustomStructuredMeta | ComposableMeta
}
+242
View File
@@ -0,0 +1,242 @@
package kindsys
import (
"fmt"
"io/fs"
"path/filepath"
"sync"
"cuelang.org/go/cue"
"cuelang.org/go/cue/errors"
"github.com/grafana/grafana"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/thema"
tload "github.com/grafana/thema/load"
)
// CoreStructuredDeclParentPath is the path, relative to the repository root, where
// each child directory is expected to contain .cue files declaring one
// CoreStructured kind.
var CoreStructuredDeclParentPath = filepath.Join("kinds", "structured")
// RawDeclParentPath is the path, relative to the repository root, where each child
// directory is expected to contain .cue files declaring one Raw kind.
var RawDeclParentPath = filepath.Join("kinds", "raw")
// GoCoreKindParentPath is the path, relative to the repository root, to the directory
// containing one directory per kind, full of generated Go kind output: types and bindings.
var GoCoreKindParentPath = filepath.Join("pkg", "kinds")
// TSCoreKindParentPath is the path, relative to the repository root, to the directory that
// contains one directory per kind, full of generated TS kind output: types and default consts.
var TSCoreKindParentPath = filepath.Join("packages", "grafana-schema", "src", "raw")
var defaultFramework cue.Value
var fwOnce sync.Once
func init() {
loadpFrameworkOnce()
}
func loadpFrameworkOnce() {
fwOnce.Do(func() {
var err error
defaultFramework, err = doLoadFrameworkCUE(cuectx.GrafanaCUEContext())
if err != nil {
panic(err)
}
})
}
var prefix = filepath.Join("/pkg", "kindsys")
func doLoadFrameworkCUE(ctx *cue.Context) (cue.Value, error) {
var v cue.Value
var err error
absolutePath := prefix
if !filepath.IsAbs(absolutePath) {
absolutePath, err = filepath.Abs(absolutePath)
if err != nil {
return v, err
}
}
bi, err := tload.InstancesWithThema(grafana.CueSchemaFS, absolutePath)
if err != nil {
return v, err
}
v = ctx.BuildInstance(bi)
if err = v.Validate(cue.Concrete(false), cue.All()); err != nil {
return cue.Value{}, fmt.Errorf("coremodel framework loaded cue.Value has err: %w", err)
}
return v, nil
}
// CUEFramework returns a cue.Value representing all the kind framework
// raw CUE files.
//
// For low-level use in constructing other types and APIs, while still letting
// us declare all the frameworky CUE bits in a single package. Other Go types
// make the constructs in this 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.
func CUEFramework(ctx *cue.Context) cue.Value {
if ctx == nil || ctx == cuectx.GrafanaCUEContext() {
// Ensure framework is loaded, even if this func is called
// from an init() somewhere.
loadpFrameworkOnce()
return defaultFramework
}
// Error guaranteed to be nil here because erroring would have caused init() to panic
v, _ := doLoadFrameworkCUE(ctx) // nolint:errcheck
return v
}
// ToKindMeta 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 KindMetas](v cue.Value) (T, error) {
meta := new(T)
if !v.Exists() {
return *meta, ErrValueNotExist
}
fw := CUEFramework(v.Context())
var kdef cue.Value
anymeta := any(*meta).(SomeKindMeta)
switch anymeta.(type) {
case RawMeta:
kdef = fw.LookupPath(cue.MakePath(cue.Def("Raw")))
case CoreStructuredMeta:
kdef = fw.LookupPath(cue.MakePath(cue.Def("CoreStructured")))
case CustomStructuredMeta:
kdef = fw.LookupPath(cue.MakePath(cue.Def("CustomStructured")))
case ComposableMeta:
kdef = fw.LookupPath(cue.MakePath(cue.Def("Composable")))
default:
// unreachable so long as all the possibilities in KindMetas have switch branches
panic("unreachable")
}
item := v.Unify(kdef)
if err := item.Validate(cue.Concrete(false), cue.All()); err != nil {
return *meta, ewrap(item.Err(), ErrValueNotAKind)
}
if err := item.Decode(meta); err != nil {
// Should only be reachable if CUE and Go framework types have diverged
panic(errors.Details(err, nil))
}
return *meta, nil
}
// SomeDecl represents a single kind declaration, having been loaded
// and validated by a func such as [LoadCoreKind].
//
// The underlying type of the Meta field indicates the category of
// kind.
type SomeDecl struct {
// V is the cue.Value containing the entire Kind declaration.
V cue.Value
// Meta contains the kind's metadata settings.
Meta SomeKindMeta
}
// BindKindLineage binds the lineage for the kind declaration. nil, nil is returned
// for raw kinds.
//
// 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) {
if rt == nil {
rt = cuectx.GrafanaThemaRuntime()
}
switch decl.Meta.(type) {
case RawMeta:
return nil, nil
case CoreStructuredMeta, CustomStructuredMeta, ComposableMeta:
return thema.BindLineage(decl.V.LookupPath(cue.MakePath(cue.Str("lineage"))), rt, opts...)
default:
panic("unreachable")
}
}
// IsRaw indicates whether the represented kind is a raw kind.
func (decl *SomeDecl) IsRaw() bool {
_, is := decl.Meta.(RawMeta)
return is
}
// IsCoreStructured indicates whether the represented kind is a core structured kind.
func (decl *SomeDecl) IsCoreStructured() bool {
_, is := decl.Meta.(CoreStructuredMeta)
return is
}
// IsCustomStructured indicates whether the represented kind is a custom structured kind.
func (decl *SomeDecl) IsCustomStructured() bool {
_, is := decl.Meta.(CustomStructuredMeta)
return is
}
// IsComposable indicates whether the represented kind is a composable kind.
func (decl *SomeDecl) IsComposable() bool {
_, is := decl.Meta.(ComposableMeta)
return is
}
// Decl represents a single kind declaration, having been loaded
// and validated by a func such as [LoadCoreKind].
//
// Its type parameter indicates the category of kind.
type Decl[T KindMetas] struct {
// V is the cue.Value containing the entire Kind declaration.
V cue.Value
// Meta contains the kind's metadata settings.
Meta T
}
// Some converts the typed Decl to the equivalent typeless SomeDecl.
func (decl *Decl[T]) Some() *SomeDecl {
return &SomeDecl{
V: decl.V,
Meta: any(decl.Meta).(SomeKindMeta),
}
}
// LoadCoreKind loads and validates a core kind declaration of the kind category
// indicated by the type parameter. On success, it returns a [Decl] which
// contains the entire contents of the kind declaration.
//
// declpath is the path to the directory containing the core kind declaration,
// relative to the grafana/grafana root. For example, dashboards are in
// "kinds/structured/dashboard".
//
// The .cue file bytes containing the core kind declaration will be retrieved
// from the central embedded FS, [grafana.CueSchemaFS]. If desired (e.g. for
// testing), an optional fs.FS may be provided via the overlay parameter, which
// will be merged over [grafana.CueSchemaFS]. But in typical circumstances,
// overlay can and should be nil.
//
// 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[T RawMeta | CoreStructuredMeta](declpath string, ctx *cue.Context, overlay fs.FS) (*Decl[T], error) {
vk, err := cuectx.BuildGrafanaInstance(declpath, "kind", ctx, overlay)
if err != nil {
return nil, err
}
decl := &Decl[T]{
V: vk,
}
decl.Meta, err = ToKindMeta[T](vk)
if err != nil {
return nil, err
}
return decl, nil
}