coremodels: Update to latest Thema with generics (#56602)

* Update thema to latest

* Deal with s/Library/*Runtime/

* Commit new, working results of codegen
This commit is contained in:
sam boyer 2022-10-11 04:45:07 -04:00 committed by GitHub
parent 668cb25b82
commit e5a6547a94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 171 additions and 247 deletions

2
go.mod
View File

@ -58,7 +58,7 @@ require (
github.com/grafana/grafana-aws-sdk v0.11.0
github.com/grafana/grafana-azure-sdk-go v1.3.1
github.com/grafana/grafana-plugin-sdk-go v0.139.0
github.com/grafana/thema v0.0.0-20220817114012-ebeee841c104
github.com/grafana/thema v0.0.0-20220929145912-2c7c4a7bb20b
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/hashicorp/go-hclog v1.0.0
github.com/hashicorp/go-plugin v1.4.3

2
go.sum
View File

@ -1380,6 +1380,8 @@ github.com/grafana/saml v0.4.9-0.20220727151557-61cd9c9353fc h1:1PY8n+rXuBNr3r1J
github.com/grafana/saml v0.4.9-0.20220727151557-61cd9c9353fc/go.mod h1:9Zh6dWPtB3MSzTRt8fIFH60Z351QQ+s7hCU3J/tTlA4=
github.com/grafana/thema v0.0.0-20220817114012-ebeee841c104 h1:dYpwFYIChrMfpq3wDa/ZBxAbUGSW5NYmYBeSezhaoao=
github.com/grafana/thema v0.0.0-20220817114012-ebeee841c104/go.mod h1:fCV1rqv6XRQg2GfIQ7pU9zdxd5fLRcEBCnrDVwlK+ZY=
github.com/grafana/thema v0.0.0-20220929145912-2c7c4a7bb20b h1:OEGzlaj04LE6Eq7aGMOh0bCplGW5rXNeSSSwgamPBEY=
github.com/grafana/thema v0.0.0-20220929145912-2c7c4a7bb20b/go.mod h1:i3/NX50sNrwsPSAQAj56ckjQTb4biaYG/6y+zyKgpb0=
github.com/grafana/xorm v0.8.3-0.20220614223926-2fcda7565af6 h1:I9dh1MXGX0wGyxdV/Sl7+ugnki4Dfsy8lv2s5Yf887o=
github.com/grafana/xorm v0.8.3-0.20220614223926-2fcda7565af6/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=

View File

@ -301,8 +301,8 @@ var wireSet = wire.NewSet(
authproxy.ProvideAuthProxy,
statscollector.ProvideService,
cmreg.CoremodelSet,
cuectx.ProvideCUEContext,
cuectx.ProvideThemaLibrary,
cuectx.GrafanaCUEContext,
cuectx.GrafanaThemaRuntime,
csrf.ProvideCSRFFilter,
ossaccesscontrol.ProvideTeamPermissions,
wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)),

View File

@ -50,7 +50,7 @@ type CoremodelDeclaration struct {
// This loading approach is intended primarily for use with code generators, or
// other use cases external to grafana-server backend. For code within
// grafana-server, prefer lineage loaders provided in e.g. pkg/coremodel/*.
func ExtractLineage(path string, lib thema.Library) (*CoremodelDeclaration, error) {
func ExtractLineage(path string, rt *thema.Runtime) (*CoremodelDeclaration, error) {
if !filepath.IsAbs(path) {
return nil, fmt.Errorf("must provide an absolute path, got %q", path)
}
@ -99,7 +99,7 @@ func ExtractLineage(path string, lib thema.Library) (*CoremodelDeclaration, erro
panic(err)
}
ec.RelativePath = filepath.ToSlash(ec.RelativePath)
ec.Lineage, err = cuectx.LoadGrafanaInstancesWithThema(filepath.Dir(ec.RelativePath), fs, lib)
ec.Lineage, err = cuectx.LoadGrafanaInstancesWithThema(filepath.Dir(ec.RelativePath), fs, rt)
if err != nil {
return ec, err
}
@ -156,7 +156,7 @@ func (cd *CoremodelDeclaration) PathVersion() string {
// The provided path must be a directory. Generated code files will be written
// to that path. The final element of the path must match the Lineage.Name().
func (cd *CoremodelDeclaration) GenerateGoCoremodel(path string) (WriteDiffer, error) {
lin, lib := cd.Lineage, cd.Lineage.Library()
lin, rt := cd.Lineage, cd.Lineage.Runtime()
_, name := filepath.Split(path)
if name != lin.Name() {
return nil, fmt.Errorf("lineage name %q must match final element of path, got %q", lin.Name(), path)
@ -168,7 +168,7 @@ func (cd *CoremodelDeclaration) GenerateGoCoremodel(path string) (WriteDiffer, e
return nil, fmt.Errorf("thema openapi generation failed: %w", err)
}
str, err := yaml.Marshal(lib.Context().BuildFile(f))
str, err := yaml.Marshal(rt.Context().BuildFile(f))
if err != nil {
return nil, fmt.Errorf("cue-yaml marshaling failed: %w", err)
}

View File

@ -63,7 +63,7 @@ func MapCUEImportToTS(path string) (string, error) {
// Errors returned from [pfs.ParsePluginFS] are placed in the option map. Only
// filesystem traversal and read errors will result in a non-nil second return
// value.
func ExtractPluginTrees(parent fs.FS, lib thema.Library) (map[string]PluginTreeOrErr, error) {
func ExtractPluginTrees(parent fs.FS, rt *thema.Runtime) (map[string]PluginTreeOrErr, error) {
ents, err := fs.ReadDir(parent, ".")
if err != nil {
return nil, fmt.Errorf("error reading fs root directory: %w", err)
@ -78,7 +78,7 @@ func ExtractPluginTrees(parent fs.FS, lib thema.Library) (map[string]PluginTreeO
}
var either PluginTreeOrErr
if ptree, err := pfs.ParsePluginFS(sub, lib); err == nil {
if ptree, err := pfs.ParsePluginFS(sub, rt); err == nil {
either.Tree = (*PluginTree)(ptree)
} else {
either.Err = err
@ -235,7 +235,7 @@ func genGoTypes(plug pfs.PluginInfo, path, subpath, prefix string) (WriteDiffer,
wd := NewWriteDiffer()
for slotname, lin := range plug.SlotImplementations() {
lowslot := strings.ToLower(slotname)
lib := lin.Library()
rt := lin.Runtime()
sch := thema.SchemaP(lin, thema.LatestVersion(lin))
// FIXME gotta hack this out of thema in order to deal with our custom imports :scream:
@ -244,7 +244,7 @@ func genGoTypes(plug pfs.PluginInfo, path, subpath, prefix string) (WriteDiffer,
return nil, fmt.Errorf("thema openapi generation failed: %w", err)
}
str, err := yaml.Marshal(lib.Context().BuildFile(f))
str, err := yaml.Marshal(rt.Context().BuildFile(f))
if err != nil {
return nil, fmt.Errorf("cue-yaml marshaling failed: %w", err)
}

View File

@ -18,8 +18,8 @@ var currentVersion = thema.SV({{ .LatestSeqv }}, {{ .LatestSchv }})
{{- if .IsComposed }}//
// This is the base variant of the schema. It does not include any composed
// plugin schemas.{{ end }}
func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "{{ .Name }}"), cueFS, lib, opts...)
func Lineage(rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "{{ .Name }}"), cueFS, rt, opts...)
}
var _ thema.LineageFactory = Lineage
@ -52,8 +52,8 @@ func (c *Coremodel) GoType() interface{} {
// Note that this function does not cache, and initially loading a Thema lineage
// can be expensive. As such, the Grafana backend should prefer to access this
// coremodel through a registry (pkg/framework/coremodel/registry), which does cache.
func New(lib thema.Library) (*Coremodel, error) {
lin, err := Lineage(lib)
func New(rt *thema.Runtime) (*Coremodel, error) {
lin, err := Lineage(rt)
if err != nil {
return nil, err
}

View File

@ -42,12 +42,12 @@ func (b *Base) {{ .TitleName }}() *{{ .Name }}.Coremodel {
}
{{end}}
func doProvideBase(lib thema.Library) *Base {
func doProvideBase(rt *thema.Runtime) *Base {
var err error
reg := &Base{}
{{range .Coremodels }}
reg.{{ .Name }}, err = {{ .Name }}.New(lib)
reg.{{ .Name }}, err = {{ .Name }}.New(rt)
if err != nil {
panic(fmt.Sprintf("error while initializing {{ .Name }} coremodel: %s", err))
}

View File

@ -7,6 +7,6 @@ var currentVersion{{ .SlotName }} = thema.SV({{ .LatestSeqv }}, {{ .LatestSchv }
// {{ .SlotName }}Lineage returns the Thema lineage for the {{ .PluginID }} {{ .PluginType }} plugin's
// {{ .SlotName }} ["github.com/grafana/grafana/pkg/framework/coremodel".Slot] implementation.
func {{ .SlotName }}Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("public", "app", "{{ .Name }}"), cueFS, lib, opts...)
func {{ .SlotName }}Lineage(rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("public", "app", "{{ .Name }}"), cueFS, rt, opts...)
}

View File

@ -19,14 +19,14 @@ var ptree *pfs.Tree
var plugFS embed.FS
// PluginTree returns the plugin tree representing the statically analyzable contents of the {{ .PluginID }} plugin.
func PluginTree(lib *thema.Library) *pfs.Tree {
func PluginTree(rt *thema.Runtime) *pfs.Tree {
var err error
if lib == nil {
if rt == nil || rt == cuectx.GrafanaThemaRuntime() {
parseOnce.Do(func() {
ptree, err = pfs.ParsePluginFS(plugFS, cuectx.ProvideThemaLibrary())
ptree, err = pfs.ParsePluginFS(plugFS, cuectx.GrafanaThemaRuntime())
})
} else {
ptree, err = pfs.ParsePluginFS(plugFS, cuectx.ProvideThemaLibrary())
ptree, err = pfs.ParsePluginFS(plugFS, rt)
}
if err != nil {
@ -40,8 +40,8 @@ func PluginTree(lib *thema.Library) *pfs.Tree {
{{ $pluginfo := . }}{{ range $slot := .SlotImpls }}
// {{ .SlotName }}Lineage returns the Thema lineage for the {{ $pluginfo.PluginID }} {{ $pluginfo.PluginType }} plugin's
// {{ .SlotName }} ["github.com/grafana/grafana/pkg/framework/coremodel".Slot] implementation.
func {{ .SlotName }}Lineage(lib *thema.Library, opts ...thema.BindOption) (thema.Lineage, error) {
t := PluginTree(lib)
func {{ .SlotName }}Lineage(rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
t := PluginTree(rt)
lin, has := t.RootPlugin().SlotImplementations()["{{ .SlotName }}"]
if !has {
panic("unreachable: lineage for {{ .SlotName }} does not exist, but code is only generated for existing lineages")

View File

@ -10,22 +10,22 @@ import (
"github.com/grafana/thema"
)
func makeTreeOrPanic(path string, pkgname string, lib thema.Library) *pfs.Tree {
func makeTreeOrPanic(path string, pkgname string, rt *thema.Runtime) *pfs.Tree {
sub, err := fs.Sub(grafana.CueSchemaFS, path)
if err != nil {
panic("could not create fs sub to " + path)
}
tree, err := pfs.ParsePluginFS(sub, lib)
tree, err := pfs.ParsePluginFS(sub, rt)
if err != nil {
panic(fmt.Sprintf("error parsing plugin metadata for %s: %s", pkgname, err))
}
return tree
}
func coreTreeList(lib thema.Library) pfs.TreeList{
func coreTreeList(rt *thema.Runtime) pfs.TreeList{
return pfs.TreeList{
{{- range .Plugins }}
makeTreeOrPanic("{{ .Path }}", "{{ .PkgName }}", lib),
makeTreeOrPanic("{{ .Path }}", "{{ .PkgName }}", rt),
{{- end }}
}
}

View File

@ -8,8 +8,8 @@ import (
{{ if .NoAlias }}{{ .PkgName }} {{end}}"{{ .Path }}"{{ end }}
)
func coreTreeLoaders() []func(*thema.Library) *pfs.Tree{
return []func(*thema.Library) *pfs.Tree{
func coreTreeLoaders() []func(*thema.Runtime) *pfs.Tree{
return []func(*thema.Runtime) *pfs.Tree{
{{- range .Plugins }}
{{ .PkgName }}.PluginTree,{{ end }}
}

View File

@ -1018,8 +1018,8 @@ var currentVersion = thema.SV(0, 0)
// The lineage is the canonical specification of the current dashboard schema,
// all prior schema versions, and the mappings that allow migration between
// schema versions.
func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "dashboard"), cueFS, lib, opts...)
func Lineage(rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "dashboard"), cueFS, rt, opts...)
}
var _ thema.LineageFactory = Lineage
@ -1052,8 +1052,8 @@ func (c *Coremodel) GoType() interface{} {
// Note that this function does not cache, and initially loading a Thema lineage
// can be expensive. As such, the Grafana backend should prefer to access this
// coremodel through a registry (pkg/framework/coremodel/registry), which does cache.
func New(lib thema.Library) (*Coremodel, error) {
lin, err := Lineage(lib)
func New(rt *thema.Runtime) (*Coremodel, error) {
lin, err := Lineage(rt)
if err != nil {
return nil, err
}

View File

@ -22,7 +22,7 @@ func TestDevenvDashboardValidity(t *testing.T) {
m, err := themaTestableDashboards(os.DirFS(path))
require.NoError(t, err)
cm, err := dashboard.New(cuectx.ProvideThemaLibrary())
cm, err := dashboard.New(cuectx.GrafanaThemaRuntime())
require.NoError(t, err)
for path, b := range m {

View File

@ -90,8 +90,8 @@ var currentVersion = thema.SV(0, 0)
// The lineage is the canonical specification of the current playlist schema,
// all prior schema versions, and the mappings that allow migration between
// schema versions.
func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "playlist"), cueFS, lib, opts...)
func Lineage(rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "playlist"), cueFS, rt, opts...)
}
var _ thema.LineageFactory = Lineage
@ -124,8 +124,8 @@ func (c *Coremodel) GoType() interface{} {
// Note that this function does not cache, and initially loading a Thema lineage
// can be expensive. As such, the Grafana backend should prefer to access this
// coremodel through a registry (pkg/framework/coremodel/registry), which does cache.
func New(lib thema.Library) (*Coremodel, error) {
lin, err := Lineage(lib)
func New(rt *thema.Runtime) (*Coremodel, error) {
lin, err := Lineage(rt)
if err != nil {
return nil, err
}

View File

@ -560,8 +560,8 @@ var currentVersion = thema.SV(0, 0)
// The lineage is the canonical specification of the current pluginmeta schema,
// all prior schema versions, and the mappings that allow migration between
// schema versions.
func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "pluginmeta"), cueFS, lib, opts...)
func Lineage(rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "pluginmeta"), cueFS, rt, opts...)
}
var _ thema.LineageFactory = Lineage
@ -594,8 +594,8 @@ func (c *Coremodel) GoType() interface{} {
// Note that this function does not cache, and initially loading a Thema lineage
// can be expensive. As such, the Grafana backend should prefer to access this
// coremodel through a registry (pkg/framework/coremodel/registry), which does cache.
func New(lib thema.Library) (*Coremodel, error) {
lin, err := Lineage(lib)
func New(rt *thema.Runtime) (*Coremodel, error) {
lin, err := Lineage(rt)
if err != nil {
return nil, err
}

View File

@ -1,6 +1,6 @@
// Package cuectx provides a single, central CUE context (runtime) and Thema
// library that can be used uniformly across Grafana, and related helper
// functions for loading Thema lineages.
// Package cuectx provides a single, central ["cuelang.org/go/cue".Context] and
// ["github.com/grafana/thema".Runtime] that can be used uniformly across
// Grafana, and related helper functions for loading Thema lineages.
package cuectx
@ -12,21 +12,27 @@ import (
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"github.com/grafana/thema"
"github.com/grafana/thema/kernel"
"github.com/grafana/thema/load"
"github.com/grafana/thema/vmux"
)
var ctx = cuecontext.New()
var lib = thema.NewLibrary(ctx)
var rt = thema.NewRuntime(ctx)
// ProvideCUEContext is a wire service provider of a central cue.Context.
func ProvideCUEContext() *cue.Context {
// GrafanaCUEContext returns Grafana's singleton instance of [cue.Context].
//
// All code within grafana/grafana that needs a *cue.Context should get it
// from this function, when one was not otherwise provided.
func GrafanaCUEContext() *cue.Context {
return ctx
}
// ProvideThemaLibrary is a wire service provider of a central thema.Library.
func ProvideThemaLibrary() thema.Library {
return lib
// GrafanaThemaRuntime returns Grafana's singleton instance of [thema.Runtime].
//
// All code within grafana/grafana that needs a *thema.Runtime should get it
// from this function, when one was not otherwise provided.
func GrafanaThemaRuntime() *thema.Runtime {
return rt
}
// JSONtoCUE attempts to decode the given []byte into a cue.Value, relying on
@ -37,10 +43,10 @@ func ProvideThemaLibrary() thema.Library {
// returned cue.Value.
//
// This is a convenience function for one-off JSON decoding. It's wasteful to
// call it repeatedly. Most use cases use cases should probably prefer making
// call it repeatedly. Most use cases should probably prefer making
// their own Thema/CUE decoders.
func JSONtoCUE(path string, b []byte) (cue.Value, error) {
return kernel.NewJSONDecoder(path)(ctx, b)
return vmux.NewJSONEndec(path).Decode(ctx, b)
}
// LoadGrafanaInstancesWithThema loads CUE files containing a lineage
@ -54,7 +60,7 @@ func JSONtoCUE(path string, b []byte) (cue.Value, error) {
// More details on underlying behavior can be found in the docs for github.com/grafana/thema/load.InstancesWithThema.
//
// TODO this approach is complicated and confusing, refactor to something understandable
func LoadGrafanaInstancesWithThema(path string, cueFS fs.FS, lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) {
func LoadGrafanaInstancesWithThema(path string, cueFS fs.FS, rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
prefix := filepath.FromSlash(path)
fs, err := prefixWithGrafanaCUE(prefix, cueFS)
if err != nil {
@ -68,9 +74,9 @@ func LoadGrafanaInstancesWithThema(path string, cueFS fs.FS, lib thema.Library,
return nil, err
}
val := lib.Context().BuildInstance(inst)
val := rt.Context().BuildInstance(inst)
lin, err := thema.BindLineage(val, lib, opts...)
lin, err := thema.BindLineage(val, rt, opts...)
if err != nil {
return nil, err
}

View File

@ -20,11 +20,10 @@ import (
"github.com/grafana/cuetsy/ts"
"github.com/grafana/cuetsy/ts/ast"
gcgen "github.com/grafana/grafana/pkg/codegen"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/thema"
)
var lib = thema.NewLibrary(cuecontext.New())
const sep = string(filepath.Separator)
var tsroot, cmroot, groot string
@ -46,6 +45,7 @@ func init() {
// Generate Go and Typescript implementations for all coremodels, and populate the
// coremodel static registry.
func main() {
rt := cuectx.GrafanaThemaRuntime()
if len(os.Args) > 1 {
fmt.Fprintf(os.Stderr, "coremodel code generator does not currently accept any arguments\n, got %q", os.Args)
os.Exit(1)
@ -60,7 +60,7 @@ func main() {
var lins []*gcgen.CoremodelDeclaration
for _, item := range items {
if item.IsDir() {
lin, err := gcgen.ExtractLineage(filepath.Join(cmroot, item.Name(), "coremodel.cue"), lib)
lin, err := gcgen.ExtractLineage(filepath.Join(cmroot, item.Name(), "coremodel.cue"), rt)
if err != nil {
fmt.Fprintf(os.Stderr, "could not process coremodel dir %s: %s\n", filepath.Join(cmroot, item.Name()), err)
os.Exit(1)

View File

@ -9,7 +9,6 @@ import (
"cuelang.org/go/cue"
"cuelang.org/go/cue/load"
"github.com/grafana/thema/kernel"
tload "github.com/grafana/thema/load"
"github.com/grafana/grafana/pkg/cuectx"
@ -24,7 +23,7 @@ var defaultFramework cue.Value
func init() {
var err error
defaultFramework, err = doLoadFrameworkCUE(cuectx.ProvideCUEContext())
defaultFramework, err = doLoadFrameworkCUE(cuectx.GrafanaCUEContext())
if err != nil {
panic(err)
}
@ -106,81 +105,3 @@ func CUEFrameworkWithContext(ctx *cue.Context) cue.Value {
v, _ := doLoadFrameworkCUE(ctx) // nolint:errcheck
return v
}
// Mux takes a coremodel and returns a Thema version muxer that, given a byte
// slice containing any version of schema for that coremodel, will translate it
// to the Interface.CurrentSchema() version, and optionally decode it onto the
// Interface.GoType().
//
// By default, JSON decoding will be used, and the filename given to any input
// bytes (shown in errors, which may be user-facing) will be
// "<name>.<encoding>", e.g. dashboard.json.
func Mux(cm Interface, opts ...MuxOption) kernel.InputKernel {
c := &muxConfig{}
for _, opt := range opts {
opt(c)
}
cfg := kernel.InputKernelConfig{
Typ: cm.GoType(),
Lineage: cm.Lineage(),
To: cm.CurrentSchema().Version(),
}
switch c.decodetyp {
case "", "json": // json by default
if c.filename == "" {
c.filename = fmt.Sprintf("%s.json", cm.Lineage().Name())
}
cfg.Loader = kernel.NewJSONDecoder(c.filename)
case "yaml":
if c.filename == "" {
c.filename = fmt.Sprintf("%s.yaml", cm.Lineage().Name())
}
cfg.Loader = kernel.NewYAMLDecoder(c.filename)
default:
panic("")
}
mux, err := kernel.NewInputKernel(cfg)
if err != nil {
// Barring a fundamental bug in Thema's schema->Go type assignability checker or
// a direct attempt by a Grafana dev to get around the invariants of coremodel codegen,
// this should be unreachable. (And even the latter case should be caught elsewhere
// by tests).
panic(err)
}
return mux
}
// A MuxOption defines options that may be specified only at initial
// construction of a Lineage via BindLineage.
type MuxOption muxOption
// Internal representation of MuxOption.
type muxOption func(c *muxConfig)
type muxConfig struct {
filename string
decodetyp string
}
// YAML indicates that the resulting Mux should look for YAML in input bytes,
// rather than the default JSON.
func YAML() MuxOption {
return func(c *muxConfig) {
c.decodetyp = "yaml"
}
}
// Filename specifies the filename that is given to input bytes passing through
// the mux.
//
// The filename has no impact on mux behavior, but is used in user-facing error
// output, such as schema validation failures. Thus, it is recommended to pick a
// name that will make sense in the context a user is expected to see the error.
func Filename(name string) MuxOption {
return func(c *muxConfig) {
c.filename = name
}
}

View File

@ -17,19 +17,20 @@ var CoremodelSet = wire.NewSet(
// NewBase provides a registry of all coremodels, without any composition of
// plugin-defined schemas.
//
// The returned registry will use the default Grafana thema.Library, defined in
// pkg/cuectx. If you need control over the thema.Library used by the coremodel
// lineages, use NewBaseWithLib instead.
// The returned registry will use Grafana's singleton [thema.Runtime],
// returned from [cuectx.GrafanaThemaRuntime].
func NewBase() *Base {
return provideBase(nil)
}
// NewBaseWithLib is the same as NewBase, but allows control over the
// thema.Library used to initialize the underlying coremodels.
// NewBaseWithRuntime is the same as NewBase, but allows control over the
// [thema.Runtime] used to initialize the underlying coremodels.
//
// Prefer NewBase unless you absolutely need this control.
func NewBaseWithLib(lib thema.Library) *Base {
return provideBase(&lib)
//
// TODO it's OK to export this if it's ever actually needed
func NewBaseWithRuntime(rt *thema.Runtime) *Base {
return provideBase(rt)
}
var (
@ -37,15 +38,15 @@ var (
defaultBase *Base
)
func provideBase(lib *thema.Library) *Base {
if lib == nil {
func provideBase(rt *thema.Runtime) *Base {
if rt == nil {
baseOnce.Do(func() {
defaultBase = doProvideBase(cuectx.ProvideThemaLibrary())
defaultBase = doProvideBase(cuectx.GrafanaThemaRuntime())
})
return defaultBase
}
return doProvideBase(*lib)
return doProvideBase(rt)
}
// All returns a slice of all registered coremodels.

View File

@ -57,23 +57,23 @@ func (b *Base) Pluginmeta() *pluginmeta.Coremodel {
return b.pluginmeta
}
func doProvideBase(lib thema.Library) *Base {
func doProvideBase(rt *thema.Runtime) *Base {
var err error
reg := &Base{}
reg.dashboard, err = dashboard.New(lib)
reg.dashboard, err = dashboard.New(rt)
if err != nil {
panic(fmt.Sprintf("error while initializing dashboard coremodel: %s", err))
}
reg.all = append(reg.all, reg.dashboard)
reg.playlist, err = playlist.New(lib)
reg.playlist, err = playlist.New(rt)
if err != nil {
panic(fmt.Sprintf("error while initializing playlist coremodel: %s", err))
}
reg.all = append(reg.all, reg.playlist)
reg.pluginmeta, err = pluginmeta.New(lib)
reg.pluginmeta, err = pluginmeta.New(rt)
if err != nil {
panic(fmt.Sprintf("error while initializing pluginmeta coremodel: %s", err))
}

View File

@ -27,61 +27,61 @@ import (
"github.com/grafana/thema"
)
func makeTreeOrPanic(path string, pkgname string, lib thema.Library) *pfs.Tree {
func makeTreeOrPanic(path string, pkgname string, rt *thema.Runtime) *pfs.Tree {
sub, err := fs.Sub(grafana.CueSchemaFS, path)
if err != nil {
panic("could not create fs sub to " + path)
}
tree, err := pfs.ParsePluginFS(sub, lib)
tree, err := pfs.ParsePluginFS(sub, rt)
if err != nil {
panic(fmt.Sprintf("error parsing plugin metadata for %s: %s", pkgname, err))
}
return tree
}
func coreTreeList(lib thema.Library) pfs.TreeList {
func coreTreeList(rt *thema.Runtime) pfs.TreeList {
return pfs.TreeList{
makeTreeOrPanic("public/app/plugins/datasource/alertmanager", "alertmanager", lib),
makeTreeOrPanic("public/app/plugins/datasource/cloud-monitoring", "stackdriver", lib),
makeTreeOrPanic("public/app/plugins/datasource/cloudwatch", "cloudwatch", lib),
makeTreeOrPanic("public/app/plugins/datasource/dashboard", "dashboard", lib),
makeTreeOrPanic("public/app/plugins/datasource/elasticsearch", "elasticsearch", lib),
makeTreeOrPanic("public/app/plugins/datasource/grafana", "grafana", lib),
makeTreeOrPanic("public/app/plugins/datasource/grafana-azure-monitor-datasource", "grafana_azure_monitor_datasource", lib),
makeTreeOrPanic("public/app/plugins/datasource/graphite", "graphite", lib),
makeTreeOrPanic("public/app/plugins/datasource/jaeger", "jaeger", lib),
makeTreeOrPanic("public/app/plugins/datasource/loki", "loki", lib),
makeTreeOrPanic("public/app/plugins/datasource/mssql", "mssql", lib),
makeTreeOrPanic("public/app/plugins/datasource/mysql", "mysql", lib),
makeTreeOrPanic("public/app/plugins/datasource/postgres", "postgres", lib),
makeTreeOrPanic("public/app/plugins/datasource/prometheus", "prometheus", lib),
makeTreeOrPanic("public/app/plugins/datasource/tempo", "tempo", lib),
makeTreeOrPanic("public/app/plugins/datasource/testdata", "testdata", lib),
makeTreeOrPanic("public/app/plugins/datasource/zipkin", "zipkin", lib),
makeTreeOrPanic("public/app/plugins/panel/alertGroups", "alertGroups", lib),
makeTreeOrPanic("public/app/plugins/panel/alertlist", "alertlist", lib),
makeTreeOrPanic("public/app/plugins/panel/annolist", "annolist", lib),
makeTreeOrPanic("public/app/plugins/panel/barchart", "barchart", lib),
makeTreeOrPanic("public/app/plugins/panel/bargauge", "bargauge", lib),
makeTreeOrPanic("public/app/plugins/panel/dashlist", "dashlist", lib),
makeTreeOrPanic("public/app/plugins/panel/debug", "debug", lib),
makeTreeOrPanic("public/app/plugins/panel/flamegraph", "flamegraph", lib),
makeTreeOrPanic("public/app/plugins/panel/gauge", "gauge", lib),
makeTreeOrPanic("public/app/plugins/panel/geomap", "geomap", lib),
makeTreeOrPanic("public/app/plugins/panel/gettingstarted", "gettingstarted", lib),
makeTreeOrPanic("public/app/plugins/panel/graph", "graph", lib),
makeTreeOrPanic("public/app/plugins/panel/histogram", "histogram", lib),
makeTreeOrPanic("public/app/plugins/panel/icon", "icon", lib),
makeTreeOrPanic("public/app/plugins/panel/live", "live", lib),
makeTreeOrPanic("public/app/plugins/panel/logs", "logs", lib),
makeTreeOrPanic("public/app/plugins/panel/news", "news", lib),
makeTreeOrPanic("public/app/plugins/panel/nodeGraph", "nodeGraph", lib),
makeTreeOrPanic("public/app/plugins/panel/piechart", "piechart", lib),
makeTreeOrPanic("public/app/plugins/panel/stat", "stat", lib),
makeTreeOrPanic("public/app/plugins/panel/table-old", "table_old", lib),
makeTreeOrPanic("public/app/plugins/panel/text", "text", lib),
makeTreeOrPanic("public/app/plugins/panel/traces", "traces", lib),
makeTreeOrPanic("public/app/plugins/panel/welcome", "welcome", lib),
makeTreeOrPanic("public/app/plugins/panel/xychart", "xychart", lib),
makeTreeOrPanic("public/app/plugins/datasource/alertmanager", "alertmanager", rt),
makeTreeOrPanic("public/app/plugins/datasource/cloud-monitoring", "stackdriver", rt),
makeTreeOrPanic("public/app/plugins/datasource/cloudwatch", "cloudwatch", rt),
makeTreeOrPanic("public/app/plugins/datasource/dashboard", "dashboard", rt),
makeTreeOrPanic("public/app/plugins/datasource/elasticsearch", "elasticsearch", rt),
makeTreeOrPanic("public/app/plugins/datasource/grafana", "grafana", rt),
makeTreeOrPanic("public/app/plugins/datasource/grafana-azure-monitor-datasource", "grafana_azure_monitor_datasource", rt),
makeTreeOrPanic("public/app/plugins/datasource/graphite", "graphite", rt),
makeTreeOrPanic("public/app/plugins/datasource/jaeger", "jaeger", rt),
makeTreeOrPanic("public/app/plugins/datasource/loki", "loki", rt),
makeTreeOrPanic("public/app/plugins/datasource/mssql", "mssql", rt),
makeTreeOrPanic("public/app/plugins/datasource/mysql", "mysql", rt),
makeTreeOrPanic("public/app/plugins/datasource/postgres", "postgres", rt),
makeTreeOrPanic("public/app/plugins/datasource/prometheus", "prometheus", rt),
makeTreeOrPanic("public/app/plugins/datasource/tempo", "tempo", rt),
makeTreeOrPanic("public/app/plugins/datasource/testdata", "testdata", rt),
makeTreeOrPanic("public/app/plugins/datasource/zipkin", "zipkin", rt),
makeTreeOrPanic("public/app/plugins/panel/alertGroups", "alertGroups", rt),
makeTreeOrPanic("public/app/plugins/panel/alertlist", "alertlist", rt),
makeTreeOrPanic("public/app/plugins/panel/annolist", "annolist", rt),
makeTreeOrPanic("public/app/plugins/panel/barchart", "barchart", rt),
makeTreeOrPanic("public/app/plugins/panel/bargauge", "bargauge", rt),
makeTreeOrPanic("public/app/plugins/panel/dashlist", "dashlist", rt),
makeTreeOrPanic("public/app/plugins/panel/debug", "debug", rt),
makeTreeOrPanic("public/app/plugins/panel/flamegraph", "flamegraph", rt),
makeTreeOrPanic("public/app/plugins/panel/gauge", "gauge", rt),
makeTreeOrPanic("public/app/plugins/panel/geomap", "geomap", rt),
makeTreeOrPanic("public/app/plugins/panel/gettingstarted", "gettingstarted", rt),
makeTreeOrPanic("public/app/plugins/panel/graph", "graph", rt),
makeTreeOrPanic("public/app/plugins/panel/histogram", "histogram", rt),
makeTreeOrPanic("public/app/plugins/panel/icon", "icon", rt),
makeTreeOrPanic("public/app/plugins/panel/live", "live", rt),
makeTreeOrPanic("public/app/plugins/panel/logs", "logs", rt),
makeTreeOrPanic("public/app/plugins/panel/news", "news", rt),
makeTreeOrPanic("public/app/plugins/panel/nodeGraph", "nodeGraph", rt),
makeTreeOrPanic("public/app/plugins/panel/piechart", "piechart", rt),
makeTreeOrPanic("public/app/plugins/panel/stat", "stat", rt),
makeTreeOrPanic("public/app/plugins/panel/table-old", "table_old", rt),
makeTreeOrPanic("public/app/plugins/panel/text", "text", rt),
makeTreeOrPanic("public/app/plugins/panel/traces", "traces", rt),
makeTreeOrPanic("public/app/plugins/panel/welcome", "welcome", rt),
makeTreeOrPanic("public/app/plugins/panel/xychart", "xychart", rt),
}
}

View File

@ -15,16 +15,16 @@ var coreOnce sync.Once
// in the current version of Grafana.
//
// Go code within the grafana codebase should only ever call this with nil.
func New(lib *thema.Library) pfs.TreeList {
func New(rt *thema.Runtime) pfs.TreeList {
var tl pfs.TreeList
if lib == nil {
if rt == nil {
coreOnce.Do(func() {
coreTrees = coreTreeList(cuectx.ProvideThemaLibrary())
coreTrees = coreTreeList(cuectx.GrafanaThemaRuntime())
})
tl = make(pfs.TreeList, len(coreTrees))
copy(tl, coreTrees)
} else {
return coreTreeList(*lib)
return coreTreeList(rt)
}
return tl
}

View File

@ -13,11 +13,11 @@ import (
"cuelang.org/go/cue/parser"
"github.com/grafana/grafana"
"github.com/grafana/grafana/pkg/coremodel/pluginmeta"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/grafana/pkg/framework/coremodel"
"github.com/grafana/grafana/pkg/framework/coremodel/registry"
"github.com/grafana/thema"
"github.com/grafana/thema/kernel"
"github.com/grafana/thema/load"
"github.com/grafana/thema/vmux"
"github.com/yalue/merged_fs"
)
@ -51,11 +51,9 @@ type slotandname struct {
var allslots []slotandname
var plugmux kernel.InputKernel
// TODO re-enable after go1.18
// var tsch thema.TypedSchema[pluginmeta.Model]
// var plugmux vmux.ValueMux[pluginmeta.Model]
var tsch thema.TypedSchema[*pluginmeta.Model]
var plugmux vmux.ValueMux[*pluginmeta.Model]
func init() {
var all []string
@ -78,13 +76,6 @@ func init() {
var muxonce sync.Once
func loadMux() kernel.InputKernel {
muxonce.Do(func() {
plugmux = coremodel.Mux(registry.NewBase().Pluginmeta(), coremodel.Filename("plugin.json"))
})
return plugmux
}
// This used to be in init(), but that creates a risk for codegen.
//
// thema.BindType ensures that Go type and Thema schema are aligned. If we were
@ -102,19 +93,22 @@ func loadMux() kernel.InputKernel {
// called as needed to get our muxer, and internally relies on a sync.Once to avoid
// repeated processing of thema.BindType.
// TODO mux loading is easily generalizable in pkg/f/coremodel, shouldn't need one-off
// TODO switch to this generic signature after go1.18
// func loadMux() (thema.TypedSchema[pluginmeta.Model], vmux.ValueMux[pluginmeta.Model]) {
// muxonce.Do(func() {
// var err error
// var t pluginmeta.Model
// tsch, err = thema.BindType[pluginmeta.Model](pm.CurrentSchema(), t)
// if err != nil {
// panic(err)
// }
// plugmux = vmux.NewValueMux(tsch, vmux.NewJSONEndec("plugin.json"))
// })
// return tsch, plugmux
// }
func loadMux() (thema.TypedSchema[*pluginmeta.Model], vmux.ValueMux[*pluginmeta.Model]) {
muxonce.Do(func() {
var err error
t := new(pluginmeta.Model)
pm, err := pluginmeta.New(cuectx.GrafanaThemaRuntime())
if err != nil {
panic(err)
}
tsch, err = thema.BindType[*pluginmeta.Model](pm.CurrentSchema(), t)
if err != nil {
panic(err)
}
plugmux = vmux.NewValueMux(tsch, vmux.NewJSONEndec("plugin.json"))
})
return tsch, plugmux
}
// Tree represents the contents of a plugin filesystem tree.
type Tree struct {
@ -190,13 +184,12 @@ func (pi PluginInfo) Meta() pluginmeta.Model {
// It does not descend into subdirectories to search for additional plugin.json
// files.
// TODO no descent is ok for core plugins, but won't cut it in general
func ParsePluginFS(f fs.FS, lib thema.Library) (*Tree, error) {
func ParsePluginFS(f fs.FS, rt *thema.Runtime) (*Tree, error) {
if f == nil {
return nil, ErrEmptyFS
}
// _, mux := loadMux()
mux := loadMux()
ctx := lib.Context()
_, mux := loadMux()
ctx := rt.Context()
b, err := fs.ReadFile(f, "plugin.json")
if err != nil {
@ -216,13 +209,14 @@ func ParsePluginFS(f fs.FS, lib thema.Library) (*Tree, error) {
// Pass the raw bytes into the muxer, get the populated Model type out that we want.
// TODO stop ignoring second return. (for now, lacunas are a WIP and can't occur until there's >1 schema in the pluginmeta lineage)
metaany, _, err := mux.Converge(b)
// metaany, _, err := mux(b)
pmeta, _, err := mux(b)
if err != nil {
// TODO more nuanced error handling by class of Thema failure
// return nil, fmt.Errorf("plugin.json was invalid: %w", err)
return nil, ewrap(err, ErrInvalidRootFile)
}
r.meta = *metaany.(*pluginmeta.Model)
r.meta = *pmeta
if modbyt, err := fs.ReadFile(f, "models.cue"); err == nil {
// TODO introduce layered CUE dependency-injecting loader
@ -260,7 +254,7 @@ func ParsePluginFS(f fs.FS, lib thema.Library) (*Tree, error) {
}
for _, s := range allslots {
iv := val.LookupPath(cue.ParsePath(s.slot.Name()))
lin, err := bindSlotLineage(iv, s.slot, r.meta, lib)
lin, err := bindSlotLineage(iv, s.slot, r.meta, rt)
if lin != nil {
r.slotimpls[s.slot.Name()] = lin
}
@ -273,7 +267,7 @@ func ParsePluginFS(f fs.FS, lib thema.Library) (*Tree, error) {
return tree, nil
}
func bindSlotLineage(v cue.Value, s *coremodel.Slot, meta pluginmeta.Model, lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) {
func bindSlotLineage(v cue.Value, s *coremodel.Slot, meta pluginmeta.Model, rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
accept, required := s.ForPluginType(string(meta.Type))
exists := v.Exists()
@ -292,8 +286,8 @@ func bindSlotLineage(v cue.Value, s *coremodel.Slot, meta pluginmeta.Model, lib
}
// TODO make this opt real in thema, then uncomment to enforce joinSchema
// lin, err := thema.BindLineage(iv, lib, thema.SatisfiesJoinSchema(s.MetaSchema()))
lin, err := thema.BindLineage(v, lib, opts...)
// lin, err := thema.BindLineage(iv, rt, thema.SatisfiesJoinSchema(s.MetaSchema()))
lin, err := thema.BindLineage(v, rt, opts...)
if err != nil {
return nil, ewrap(fmt.Errorf("%s: invalid thema lineage for slot %s: %w", meta.Id, s.Name(), err), ErrInvalidLineage)
}

View File

@ -157,7 +157,7 @@ func TestParseTreeTestdata(t *testing.T) {
tab[ent.Name()] = tst
}
lib := cuectx.ProvideThemaLibrary()
lib := cuectx.GrafanaThemaRuntime()
for name, otst := range tab {
tst := otst // otherwise var is shadowed within func by looping
t.Run(name, func(t *testing.T) {
@ -256,7 +256,7 @@ func TestParseTreeZips(t *testing.T) {
tab[ent.Name()] = tst
}
lib := cuectx.ProvideThemaLibrary()
lib := cuectx.GrafanaThemaRuntime()
for name, otst := range tab {
tst := otst // otherwise var is shadowed within func by looping
t.Run(name, func(t *testing.T) {

View File

@ -330,8 +330,8 @@ var wireBasicSet = wire.NewSet(
authproxy.ProvideAuthProxy,
statscollector.ProvideService,
cmreg.CoremodelSet,
cuectx.ProvideCUEContext,
cuectx.ProvideThemaLibrary,
cuectx.GrafanaCUEContext,
cuectx.GrafanaThemaRuntime,
csrf.ProvideCSRFFilter,
ossaccesscontrol.ProvideTeamPermissions,
wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)),

View File

@ -48,7 +48,7 @@ func main() {
groot := filepath.Join(sep, filepath.Join(grootp[:len(grootp)-3]...))
wd := codegen.NewWriteDiffer()
lib := cuectx.ProvideThemaLibrary()
lib := cuectx.GrafanaThemaRuntime()
type ptreepath struct {
Path string