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
pull/50889/head
sam boyer 3 years ago committed by GitHub
parent 8a6ed3d81b
commit 4c4aa95d38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 2
      Makefile
  3. 2
      packages/grafana-schema/src/schema/dashboard/dashboard_experimental.gen.ts
  4. 28
      pkg/api/dashboard.go
  5. 28
      pkg/api/dashboard_test.go
  6. 9
      pkg/api/http_server.go
  7. 187
      pkg/codegen/coremodel.go
  8. 12
      pkg/coremodel/dashboard/coremodel_gen.go
  9. 28
      pkg/coremodel/dashboard/coremodel_gen_test.go
  10. 15
      pkg/framework/coremodel/gen.go
  11. 25
      pkg/framework/coremodel/registry/assignability_test.go
  12. 40
      pkg/framework/coremodel/registry/provide.go
  13. 43
      pkg/framework/coremodel/registry/registry.go
  14. 91
      pkg/framework/coremodel/registry/registry_gen.go
  15. 19
      pkg/framework/coremodel/staticregistry/provide.go
  16. 6
      pkg/server/wire.go

1
.gitignore vendored

@ -160,6 +160,7 @@ compilation-stats.json
*_gen.go
!pkg/services/featuremgmt/toggles_gen.go
!pkg/coremodel/**/*_gen.go
!pkg/framework/**/*_gen.go
# Auto-generated localisation files
public/locales/_build/

@ -92,7 +92,7 @@ gen-cue: ## Do all CUE/Thema code generation
go generate ./pkg/framework/coremodel
go generate ./public/app/plugins
gen-go: $(WIRE)
gen-go: $(WIRE) gen-cue
@echo "generate go files"
$(WIRE) gen -tags $(WIRE_TAGS) ./pkg/server ./pkg/cmd/grafana-cli/runner

@ -1,6 +1,6 @@
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from repository root.
// Run "make gen-cue" from repository root to regenerate.
//
// Derived from the Thema lineage at pkg/coremodel/dashboard

@ -303,22 +303,22 @@ func (hs *HTTPServer) PostDashboard(c *models.ReqContext) response.Response {
}
if hs.Features.IsEnabled(featuremgmt.FlagValidateDashboardsOnSave) {
cm := hs.CoremodelStaticRegistry.Dashboard()
// Ideally, coremodel validation calls would be integrated into the web
// framework. But this does the job for now.
if cm, has := hs.CoremodelRegistry.Get("dashboard"); has {
schv, err := cmd.Dashboard.Get("schemaVersion").Int()
// Only try to validate if the schemaVersion is at least the handoff version
// (the minimum schemaVersion against which the dashboard schema is known to
// work), or if schemaVersion is absent (which will happen once the Thema
// schema becomes canonical).
if err != nil || schv >= dashboard.HandoffSchemaVersion {
// Can't fail, web.Bind() already ensured it's valid JSON
b, _ := cmd.Dashboard.Bytes()
v, _ := cuectx.JSONtoCUE("dashboard.json", b)
if _, err := cm.CurrentSchema().Validate(v); err != nil {
return response.Error(http.StatusBadRequest, "invalid dashboard json", err)
}
schv, err := cmd.Dashboard.Get("schemaVersion").Int()
// Only try to validate if the schemaVersion is at least the handoff version
// (the minimum schemaVersion against which the dashboard schema is known to
// work), or if schemaVersion is absent (which will happen once the Thema
// schema becomes canonical).
if err != nil || schv >= dashboard.HandoffSchemaVersion {
// Can't fail, web.Bind() already ensured it's valid JSON
b, _ := cmd.Dashboard.Bytes()
v, _ := cuectx.JSONtoCUE("dashboard.json", b)
if _, err := cm.CurrentSchema().Validate(v); err != nil {
return response.Error(http.StatusBadRequest, "invalid dashboard json", err)
}
}
}

@ -9,6 +9,7 @@ import (
"net/http"
"testing"
"github.com/grafana/grafana/pkg/framework/coremodel/registry"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
@ -17,9 +18,6 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/coremodel/dashboard"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/grafana/pkg/framework/coremodel"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
@ -59,8 +57,8 @@ func TestGetHomeDashboard(t *testing.T) {
SQLStore: mockstore.NewSQLStoreMock(),
preferenceService: prefService,
dashboardVersionService: dashboardVersionService,
CoremodelRegistry: setupDashboardCoremodel(t),
}
hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t)
tests := []struct {
name string
@ -143,8 +141,8 @@ func TestDashboardAPIEndpoint(t *testing.T) {
Features: featuremgmt.WithFeatures(),
dashboardService: dashboardService,
dashboardVersionService: fakeDashboardVersionService,
CoremodelRegistry: setupDashboardCoremodel(t),
}
hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t)
setUp := func() {
viewerRole := models.ROLE_VIEWER
@ -263,8 +261,8 @@ func TestDashboardAPIEndpoint(t *testing.T) {
AccessControl: accesscontrolmock.New(),
dashboardService: dashboardService,
dashboardVersionService: fakeDashboardVersionService,
CoremodelRegistry: setupDashboardCoremodel(t),
}
hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t)
setUp := func() {
origCanEdit := setting.ViewersCanEdit
@ -900,11 +898,11 @@ func TestDashboardAPIEndpoint(t *testing.T) {
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
dashboardProvisioningService: mockDashboardProvisioningService{},
CoremodelRegistry: setupDashboardCoremodel(t),
SQLStore: mockSQLStore,
AccessControl: accesscontrolmock.New(),
dashboardService: dashboardService,
}
hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t)
hs.callGetDashboard(sc)
assert.Equal(t, 200, sc.resp.Code)
@ -951,7 +949,6 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
LibraryElementService: &libraryElementsService,
SQLStore: sc.sqlStore,
ProvisioningService: provisioningService,
CoremodelRegistry: setupDashboardCoremodel(t),
AccessControl: accesscontrolmock.New(),
dashboardProvisioningService: service.ProvideDashboardService(
cfg, dashboardStore, nil, features,
@ -959,6 +956,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
),
dashboardService: dashboardService,
}
hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t)
hs.callGetDashboard(sc)
@ -1020,11 +1018,11 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
pluginStore: &fakePluginStore{},
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
CoremodelRegistry: setupDashboardCoremodel(t),
dashboardService: dashboardService,
folderService: folderService,
Features: featuremgmt.WithFeatures(),
}
hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t)
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
@ -1055,8 +1053,8 @@ func postDiffScenario(t *testing.T, desc string, url string, routePattern string
LibraryElementService: &mockLibraryElementService{},
SQLStore: sqlmock,
dashboardVersionService: fakeDashboardVersionService,
CoremodelRegistry: setupDashboardCoremodel(t),
}
hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t)
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
@ -1094,8 +1092,8 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
SQLStore: sqlStore,
Features: featuremgmt.WithFeatures(),
dashboardVersionService: fakeDashboardVersionService,
CoremodelRegistry: setupDashboardCoremodel(t),
}
hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t)
sc := setupScenarioContext(t, url)
sc.sqlStore = sqlStore
@ -1119,14 +1117,14 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
})
}
func setupDashboardCoremodel(t *testing.T) *coremodel.Registry {
func setupDashboardCoremodel(t *testing.T) (*registry.Static, *registry.Generic) {
// TODO abstract and generalize this further for wider reuse
t.Helper()
dcm, err := dashboard.ProvideCoremodel(cuectx.ProvideThemaLibrary())
sreg, err := registry.ProvideStatic()
require.NoError(t, err)
reg, err := coremodel.NewRegistry(dcm)
greg, err := registry.ProvideGeneric()
require.NoError(t, err)
return reg
return sreg, greg
}
func (sc *scenarioContext) ToJSON() *simplejson.Json {

@ -23,7 +23,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
httpstatic "github.com/grafana/grafana/pkg/api/static"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/framework/coremodel"
"github.com/grafana/grafana/pkg/framework/coremodel/registry"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
@ -162,7 +162,8 @@ type HTTPServer struct {
dashboardPermissionsService accesscontrol.DashboardPermissionsService
dashboardVersionService dashver.Service
starService star.Service
CoremodelRegistry *coremodel.Registry
CoremodelRegistry *registry.Generic
CoremodelStaticRegistry *registry.Static
kvStore kvstore.KVStore
}
@ -197,7 +198,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
avatarCacheServer *avatar.AvatarCacheServer, preferenceService pref.Service, entityEventsService store.EntityEventsService,
teamsPermissionsService accesscontrol.TeamPermissionsService, folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardVersionService dashver.Service,
starService star.Service, coremodelRegistry *coremodel.Registry, csrfService csrf.Service, kvStore kvstore.KVStore,
starService star.Service, csrfService csrf.Service, coremodelRegistry *registry.Generic, coremodelStaticRegistry *registry.Static,
kvStore kvstore.KVStore,
) (*HTTPServer, error) {
web.Env = cfg.Env
m := web.New()
@ -279,6 +281,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
dashboardVersionService: dashboardVersionService,
starService: starService,
CoremodelRegistry: coremodelRegistry,
CoremodelStaticRegistry: coremodelStaticRegistry,
kvStore: kvStore,
}
if hs.Listener != nil {

@ -103,11 +103,26 @@ func ExtractLineage(path string, lib thema.Library) (*ExtractedLineage, error) {
return ec, nil
}
// toTemplateObj extracts creates a struct with all the useful strings for template generation.
func (ls *ExtractedLineage) toTemplateObj() tplVars {
lin := ls.Lineage
sch := thema.SchemaP(lin, thema.LatestVersion(lin))
return tplVars{
Name: lin.Name(),
LineagePath: ls.RelativePath,
PkgPath: filepath.ToSlash(filepath.Join("github.com/grafana/grafana", ls.RelativePath)),
TitleName: strings.Title(lin.Name()), // nolint
LatestSeqv: sch.Version()[0],
LatestSchv: sch.Version()[1],
}
}
func isCanonical(name string) bool {
return canonicalCoremodels[name]
}
// FIXME specificying coremodel canonicality DOES NOT belong here - it should be part of the coremodel declaration.
// FIXME specifying coremodel canonicality DOES NOT belong here - it should be part of the coremodel declaration.
var canonicalCoremodels = map[string]bool{
"dashboard": false,
}
@ -153,12 +168,7 @@ func (ls *ExtractedLineage) GenerateGoCoremodel(path string) (WriteDiffer, error
return nil, fmt.Errorf("openapi generation failed: %w", err)
}
vars := goPkg{
Name: lin.Name(),
LineagePath: ls.RelativePath,
LatestSeqv: sch.Version()[0],
LatestSchv: sch.Version()[1],
}
vars := ls.toTemplateObj()
var buuf bytes.Buffer
err = tmplAddenda.Execute(&buuf, vars)
if err != nil {
@ -184,23 +194,16 @@ func (ls *ExtractedLineage) GenerateGoCoremodel(path string) (WriteDiffer, error
return nil, fmt.Errorf("goimports processing failed: %w", err)
}
// Generate the assignability test. TODO do this in a framework test instead
var buf3 bytes.Buffer
err = tmplAssignableTest.Execute(&buf3, vars)
if err != nil {
return nil, fmt.Errorf("failed generating assignability test file: %w", err)
}
wd := NewWriteDiffer()
wd[filepath.Join(path, "coremodel_gen.go")] = byt
wd[filepath.Join(path, "coremodel_gen_test.go")] = buf3.Bytes()
return wd, nil
}
type goPkg struct {
type tplVars struct {
Name string
LineagePath string
LineagePath, PkgPath string
TitleName string
LatestSeqv, LatestSchv uint
IsComposed bool
}
@ -274,9 +277,38 @@ func (m modelReplacer) replacePrefix(str string) string {
return str
}
// GenerateCoremodelRegistry produces Go files that define a static registry
// with references to all the Go code that is expected to be generated from the
// provided lineages.
func GenerateCoremodelRegistry(path string, ecl []*ExtractedLineage) (WriteDiffer, error) {
var cml []tplVars
for _, ec := range ecl {
cml = append(cml, ec.toTemplateObj())
}
var buf bytes.Buffer
err := tmplRegistry.Execute(&buf, struct {
Coremodels []tplVars
}{
Coremodels: cml,
})
if err != nil {
return nil, fmt.Errorf("failed generating template: %w", err)
}
byt, err := imports.Process(path, buf.Bytes(), nil)
if err != nil {
return nil, fmt.Errorf("goimports processing failed: %w", err)
}
wd := NewWriteDiffer()
wd[path] = byt
return wd, nil
}
var genHeader = `// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from repository root.
// Run "make gen-cue" from repository root to regenerate.
//
// Derived from the Thema lineage at %s
@ -331,8 +363,10 @@ func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error)
}
var _ thema.LineageFactory = Lineage
var _ coremodel.Interface = &Coremodel{}
// Coremodel contains the foundational schema declaration for {{ .Name }}s.
// It implements coremodel.Interface.
type Coremodel struct {
lin thema.Lineage
}
@ -353,7 +387,12 @@ func (c *Coremodel) GoType() interface{} {
return &Model{}
}
func ProvideCoremodel(lib thema.Library) (*Coremodel, error) {
// New returns a new instance of the {{ .Name }} coremodel.
//
// 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)
if err != nil {
return nil, err
@ -365,35 +404,111 @@ func ProvideCoremodel(lib thema.Library) (*Coremodel, error) {
}
`))
var tmplAssignableTest = template.Must(template.New("addenda").Parse(fmt.Sprintf(genHeader, "{{ .LineagePath }}") + `package {{ .Name }}
var tmplTypedef = `{{range .Types}}
{{ with .Schema.Description }}{{ . }}{{ else }}// {{.TypeName}} defines model for {{.JsonName}}.{{ end }}
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.TypeDecl}}
{{end}}
`
var tmplRegistry = template.Must(template.New("registry").Parse(`
// 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 (
"testing"
"sync"
"github.com/google/wire"
{{range .Coremodels }}
"{{ .PkgPath }}"{{end}}
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/grafana/pkg/framework/coremodel"
"github.com/grafana/thema"
)
func TestSchemaAssignability(t *testing.T) {
lin, err := Lineage(cuectx.ProvideThemaLibrary())
// 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 {
{{- range .Coremodels }}
{{ .Name }} *{{ .Name }}.Coremodel{{end}}
}
// type guards
var (
{{- range .Coremodels }}
_ coremodel.Interface = &{{ .Name }}.Coremodel{}{{end}}
)
{{range .Coremodels }}
// {{ .TitleName }} returns the {{ .Name }} coremodel. The return value is guaranteed to
// implement coremodel.Interface.
func (s *Static) {{ .TitleName }}() *{{ .Name }}.Coremodel {
return s.{{ .Name }}
}
{{end}}
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{}
{{range .Coremodels }}
reg.{{ .Name }}, err = {{ .Name }}.New(lib)
if err != nil {
t.Fatal(err)
return nil, err
}
{{end}}
sch := thema.SchemaP(lin, currentVersion)
return reg, nil
}
err = thema.AssignableTo(sch, &Model{})
func provideGeneric() (*Generic, error) {
ereg, err := provideStatic(nil)
if err != nil {
t.Fatal(err)
return nil, err
}
genericOnce.Do(func() {
defaultGeneric, defaultGenericErr = doProvideGeneric(ereg)
})
return defaultGeneric, defaultGenericErr
}
`))
var tmplTypedef = `{{range .Types}}
{{ with .Schema.Description }}{{ . }}{{ else }}// {{.TypeName}} defines model for {{.JsonName}}.{{ end }}
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.TypeDecl}}
{{end}}
`
func doProvideGeneric(ereg *Static) (*Generic, error) {
return NewRegistry({{ range .Coremodels }}
ereg.{{ .TitleName }}(),{{ end }}
)
}
`))

@ -1,6 +1,6 @@
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from repository root.
// Run "make gen-cue" from repository root to regenerate.
//
// Derived from the Thema lineage at pkg/coremodel/dashboard
@ -11,6 +11,7 @@ import (
"path/filepath"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/grafana/pkg/framework/coremodel"
"github.com/grafana/thema"
)
@ -706,8 +707,10 @@ func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error)
}
var _ thema.LineageFactory = Lineage
var _ coremodel.Interface = &Coremodel{}
// Coremodel contains the foundational schema declaration for dashboards.
// It implements coremodel.Interface.
type Coremodel struct {
lin thema.Lineage
}
@ -728,7 +731,12 @@ func (c *Coremodel) GoType() interface{} {
return &Model{}
}
func ProvideCoremodel(lib thema.Library) (*Coremodel, error) {
// New returns a new instance of the dashboard coremodel.
//
// 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)
if err != nil {
return nil, err

@ -1,28 +0,0 @@
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from repository root.
//
// Derived from the Thema lineage at pkg/coremodel/dashboard
package dashboard
import (
"testing"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/thema"
)
func TestSchemaAssignability(t *testing.T) {
lin, err := Lineage(cuectx.ProvideThemaLibrary())
if err != nil {
t.Fatal(err)
}
sch := thema.SchemaP(lin, currentVersion)
err = thema.AssignableTo(sch, &Model{})
if err != nil {
t.Fatal(err)
}
}

@ -62,21 +62,28 @@ func main() {
wd := gcgen.NewWriteDiffer()
for _, ls := range lins {
wdg, err := ls.GenerateGoCoremodel(filepath.Join(cmroot, ls.Lineage.Name()))
gofiles, err := ls.GenerateGoCoremodel(filepath.Join(cmroot, ls.Lineage.Name()))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to generate Go for %s: %s\n", ls.Lineage.Name(), err)
os.Exit(1)
}
wd.Merge(wdg)
wd.Merge(gofiles)
wdt, err := ls.GenerateTypescriptCoremodel(filepath.Join(tsroot, ls.Lineage.Name()))
tsfiles, err := ls.GenerateTypescriptCoremodel(filepath.Join(tsroot, ls.Lineage.Name()))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to generate TypeScript for %s: %s\n", ls.Lineage.Name(), err)
os.Exit(1)
}
wd.Merge(wdt)
wd.Merge(tsfiles)
}
regfiles, err := gcgen.GenerateCoremodelRegistry(filepath.Join(groot, "pkg", "framework", "coremodel", "registry", "registry_gen.go"), lins)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to generate coremodel registry: %s\n", err)
os.Exit(1)
}
wd.Merge(regfiles)
if _, set := os.LookupEnv("CODEGEN_VERIFY"); set {
err = wd.Verify()
if err != nil {

@ -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.

@ -1,30 +1,32 @@
package coremodel
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 Registry.
// ErrModelAlreadyRegistered is returned when trying to register duplicate model to Generic.
ErrModelAlreadyRegistered = errors.New("error registering duplicate model")
)
// Registry is a registry of coremodel instances.
type Registry struct {
// 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 []Interface
modelIdx map[string]Interface
models []coremodel.Interface
modelIdx map[string]coremodel.Interface
}
// NewRegistry returns a new Registry with the provided coremodel instances.
func NewRegistry(models ...Interface) (*Registry, error) {
r := &Registry{
models: make([]Interface, 0, len(models)),
modelIdx: make(map[string]Interface, len(models)),
// 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 {
@ -34,20 +36,20 @@ func NewRegistry(models ...Interface) (*Registry, error) {
return r, nil
}
// Register adds coremodels to the Registry.
func (r *Registry) Register(models ...Interface) error {
// 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 Registry.
func (r *Registry) List() []Interface {
// 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 *Registry) addModels(models []Interface) error {
func (r *Generic) addModels(models []coremodel.Interface) error {
r.lock.Lock()
defer r.lock.Unlock()
@ -76,12 +78,3 @@ func (r *Registry) addModels(models []Interface) error {
return nil
}
// Get retrieves a coremodel with the given string identifier. nil, false
// is returned if no such coremodel exists.
func (r *Registry) Get(name string) (cm Interface, has bool) {
r.lock.RLock()
cm, has = r.modelIdx[name]
r.lock.RUnlock()
return
}

@ -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(),
)
}

@ -1,19 +0,0 @@
package staticregistry
import (
"github.com/grafana/grafana/pkg/coremodel/dashboard"
"github.com/grafana/grafana/pkg/framework/coremodel"
)
// ProvideRegistry provides a simple static Registry for coremodels.
// Coremodels have to be manually added.
// TODO dynamism
func ProvideRegistry(
dashboard *dashboard.Coremodel,
) (*coremodel.Registry, error) {
cmlist := []coremodel.Interface{
dashboard,
}
return coremodel.NewRegistry(cmlist...)
}

@ -11,10 +11,9 @@ import (
"github.com/grafana/grafana/pkg/api/avatar"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/coremodel/dashboard"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/grafana/pkg/expr"
cmreg "github.com/grafana/grafana/pkg/framework/coremodel/staticregistry"
cmreg "github.com/grafana/grafana/pkg/framework/coremodel/registry"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider"
"github.com/grafana/grafana/pkg/infra/kvstore"
@ -259,8 +258,7 @@ var wireBasicSet = wire.NewSet(
avatar.ProvideAvatarCacheServer,
authproxy.ProvideAuthProxy,
statscollector.ProvideService,
dashboard.ProvideCoremodel,
cmreg.ProvideRegistry,
cmreg.CoremodelSet,
cuectx.ProvideCUEContext,
cuectx.ProvideThemaLibrary,
csrf.ProvideCSRFFilter,

Loading…
Cancel
Save