coremodels: Automatically generate coremodel registries (#50057)

* coremodel: Generate static registry

* Actually make codegen work

Also, remove the per-coremodel assignability test from generator set.

* Make wire gen depend on cue gen

This is necessary now that we're generating a wire set as part of
coremodel registry generation.

* Add wire inject bits to http server

* s/staticregistry/registry/

* move to static and dynamic wording

* Move registry type into registry package

* Use static registry in http handler

* Oi comments
This commit is contained in:
sam boyer
2022-06-15 15:47:04 +02:00
committed by GitHub
parent 8a6ed3d81b
commit 4c4aa95d38
16 changed files with 390 additions and 158 deletions
@@ -0,0 +1,25 @@
package registry_test
import (
"testing"
"github.com/grafana/grafana/pkg/framework/coremodel/registry"
"github.com/grafana/thema"
)
func TestSchemaAssignability(t *testing.T) {
reg, err := registry.ProvideGeneric()
if err != nil {
t.Fatal(err)
}
for _, cm := range reg.List() {
tcm := cm
t.Run(tcm.Lineage().Name(), func(t *testing.T) {
err := thema.AssignableTo(tcm.CurrentSchema(), tcm.GoType())
if err != nil {
t.Fatal(err)
}
})
}
}
@@ -0,0 +1,40 @@
package registry
import (
"github.com/grafana/thema"
)
// ProvideStatic provides access to individual coremodels via explicit method calls.
//
// Prefer this to the ProvideGeneric type when your code works with known,
// specific coremodels(s), rather than generically across all of them. This
// allows standard Go static analysis tools to determine which code is depending
// on particular coremodels.
//
// This will use the default Grafana thema.Library, defined in pkg/cuectx, which
// will avoid duplicate parsing of Thema CUE schemas. If you need control over the
// thema.Library in use, use ProvideStaticWithLib instead.
func ProvideStatic() (*Static, error) {
return provideStatic(nil)
}
// ProvideStaticWithLib is the same as ProvideStatic, but
// allows control over the thema.Library used to initialize the underlying
// coremodels.
//
// Prefer ProvideStatic unless you absolutely need this control.
func ProvideStaticWithLib(lib thema.Library) (*Static, error) {
return provideStatic(&lib)
}
// ProvideGeneric provides a simple Generic registry of all coremodels.
//
// Prefer this to the static ProvideStatic when your code needs to
// work with all coremodels generically, rather than specific coremodels.
func ProvideGeneric() (*Generic, error) {
return provideGeneric()
}
// NOTE - no ProvideRegistryWithLib is defined because there are no anticipated
// cases where a caller would need to operate generically across all coremodels,
// and control the library they're initialized with. If that changes, add one.
@@ -0,0 +1,80 @@
package registry
import (
"errors"
"fmt"
"sync"
"github.com/grafana/grafana/pkg/framework/coremodel"
"github.com/grafana/thema"
)
var (
// ErrModelAlreadyRegistered is returned when trying to register duplicate model to Generic.
ErrModelAlreadyRegistered = errors.New("error registering duplicate model")
)
// Generic is a registry of coremodel instances. It is intended for use in cases where
// generic operations limited to coremodel.Interface are being performed.
type Generic struct {
lock sync.RWMutex
models []coremodel.Interface
modelIdx map[string]coremodel.Interface
}
// NewRegistry returns a new Generic with the provided coremodel instances.
func NewRegistry(models ...coremodel.Interface) (*Generic, error) {
r := &Generic{
models: make([]coremodel.Interface, 0, len(models)),
modelIdx: make(map[string]coremodel.Interface, len(models)),
}
if err := r.addModels(models); err != nil {
return nil, err
}
return r, nil
}
// Register adds coremodels to the Generic.
func (r *Generic) Register(models ...coremodel.Interface) error {
return r.addModels(models)
}
// List returns all coremodels registered in this Generic.
func (r *Generic) List() []coremodel.Interface {
r.lock.RLock()
defer r.lock.RUnlock()
return r.models
}
func (r *Generic) addModels(models []coremodel.Interface) error {
r.lock.Lock()
defer r.lock.Unlock()
// Update model index and return an error if trying to register a duplicate.
for _, m := range models {
k := m.Lineage().Name()
// Ensure assignability first. TODO will this blow up for dashboards?
if err := thema.AssignableTo(m.CurrentSchema(), m.GoType()); err != nil {
return fmt.Errorf("%s schema version %v not assignable to provided Go type: %w", k, m.CurrentSchema().Version(), err)
}
if _, ok := r.modelIdx[k]; ok {
return ErrModelAlreadyRegistered
}
r.modelIdx[k] = m
}
// Remake model list.
// TODO: this can be more performant (proper resizing, maybe single loop with index building, etc.).
r.models = r.models[:0]
for _, m := range r.modelIdx {
r.models = append(r.models, m)
}
return nil
}
@@ -0,0 +1,91 @@
// This file is autogenerated. DO NOT EDIT.
//
// Generated by pkg/framework/coremodel/gen.go
// Run "make gen-cue" from repository root to regenerate.
package registry
import (
"sync"
"github.com/google/wire"
"github.com/grafana/grafana/pkg/coremodel/dashboard"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/grafana/pkg/framework/coremodel"
"github.com/grafana/thema"
)
// CoremodelSet contains all of the wire-style providers related to coremodels.
var CoremodelSet = wire.NewSet(
ProvideStatic,
ProvideGeneric,
)
var (
staticOnce sync.Once
defaultStatic *Static
defaultStaticErr error
genericOnce sync.Once
defaultGeneric *Generic
defaultGenericErr error
)
// Static is a registry that provides access to individual coremodels via
// explicit method calls, to aid with static analysis.
type Static struct {
dashboard *dashboard.Coremodel
}
// type guards
var (
_ coremodel.Interface = &dashboard.Coremodel{}
)
// Dashboard returns the dashboard coremodel. The return value is guaranteed to
// implement coremodel.Interface.
func (s *Static) Dashboard() *dashboard.Coremodel {
return s.dashboard
}
func provideStatic(lib *thema.Library) (*Static, error) {
if lib == nil {
staticOnce.Do(func() {
defaultStatic, defaultStaticErr = doProvideStatic(cuectx.ProvideThemaLibrary())
})
return defaultStatic, defaultStaticErr
}
return doProvideStatic(*lib)
}
func doProvideStatic(lib thema.Library) (*Static, error) {
var err error
reg := &Static{}
reg.dashboard, err = dashboard.New(lib)
if err != nil {
return nil, err
}
return reg, nil
}
func provideGeneric() (*Generic, error) {
ereg, err := provideStatic(nil)
if err != nil {
return nil, err
}
genericOnce.Do(func() {
defaultGeneric, defaultGenericErr = doProvideGeneric(ereg)
})
return defaultGeneric, defaultGenericErr
}
func doProvideGeneric(ereg *Static) (*Generic, error) {
return NewRegistry(
ereg.Dashboard(),
)
}