mirror of https://github.com/grafana/grafana
plugins: Introduce generated, static core plugin registry (#54118)
* Refactor towards template/codegen framework * Add templates for plugin gen * Add Go codegen for plugins; overhaul framework, too * Add new codegen output; assorted framework fixes * Regenerate after merge * Remove accidental commit file, update templates * Export the pfs.Tree loader from plugin types * Print details from cuetsy errors * Generate loaders for all plugins and list in registry * Use pfs_gen.go over lineage_gen.go * Un-un-ignore main file * Introduce simple List static registry for plugins * Last tweaks to codegen * remove unused tvars * Ensure loop-local instances for both vars * Generate pfs parsing in-place in registry * Stop generating pfs_gen.go * Move Tree into pfs, rename subdir * Change package name to match dir * Ignore gocyclo on HTTPServer.getNavTreepull/55174/head
parent
c69a37f8c2
commit
ced53a8dc2
@ -0,0 +1,65 @@ |
||||
package codegen |
||||
|
||||
import ( |
||||
"embed" |
||||
"text/template" |
||||
"time" |
||||
) |
||||
|
||||
// All the parsed templates in the tmpl subdirectory
|
||||
var tmpls *template.Template |
||||
|
||||
func init() { |
||||
base := template.New("codegen").Funcs(template.FuncMap{ |
||||
"now": time.Now, |
||||
}) |
||||
tmpls = template.Must(base.ParseFS(tmplFS, "tmpl/*.tmpl")) |
||||
} |
||||
|
||||
//go:embed tmpl/*.tmpl
|
||||
var tmplFS embed.FS |
||||
|
||||
// The following group of types, beginning with tvars_*, all contain the set
|
||||
// of variables expected by the corresponding named template file under tmpl/
|
||||
type ( |
||||
tvars_autogen_header struct { |
||||
GeneratorPath string |
||||
LineagePath string |
||||
LineageCUEPath string |
||||
GenLicense bool |
||||
} |
||||
tvars_coremodel_registry struct { |
||||
Header tvars_autogen_header |
||||
Coremodels []tplVars |
||||
} |
||||
tvars_coremodel_imports struct { |
||||
PackageName string |
||||
} |
||||
tvars_plugin_lineage_binding struct { |
||||
SlotName string |
||||
LatestMajv, LatestMinv uint |
||||
} |
||||
tvars_plugin_lineage_file struct { |
||||
PackageName string |
||||
PluginID string |
||||
PluginType string |
||||
HasModels bool |
||||
RootCUE bool |
||||
SlotImpls []tvars_plugin_lineage_binding |
||||
Header tvars_autogen_header |
||||
} |
||||
tvars_cuetsy_multi struct { |
||||
Header tvars_autogen_header |
||||
Imports []*tsImport |
||||
Sections []tsSection |
||||
} |
||||
tvars_plugin_registry struct { |
||||
Header tvars_autogen_header |
||||
Plugins []struct { |
||||
PkgName string |
||||
Path string |
||||
ImportPath string |
||||
NoAlias bool |
||||
} |
||||
} |
||||
) |
@ -0,0 +1,64 @@ |
||||
|
||||
|
||||
//go:embed coremodel.cue |
||||
var cueFS embed.FS |
||||
|
||||
// The current version of the coremodel schema, as declared in coremodel.cue. |
||||
// This version determines what schema version is returned from [Coremodel.CurrentSchema], |
||||
// and which schema version is used for code generation within the grafana/grafana repository. |
||||
// |
||||
// The code generator ensures that this is always the latest Thema schema version. |
||||
var currentVersion = thema.SV({{ .LatestSeqv }}, {{ .LatestSchv }}) |
||||
|
||||
// Lineage returns the Thema lineage representing a Grafana {{ .Name }}. |
||||
// |
||||
// The lineage is the canonical specification of the current {{ .Name }} schema, |
||||
// all prior schema versions, and the mappings that allow migration between |
||||
// schema versions. |
||||
{{- 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...) |
||||
} |
||||
|
||||
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 |
||||
} |
||||
|
||||
// Lineage returns the canonical {{ .Name }} Lineage. |
||||
func (c *Coremodel) Lineage() thema.Lineage { |
||||
return c.lin |
||||
} |
||||
|
||||
// CurrentSchema returns the current (latest) {{ .Name }} Thema schema. |
||||
func (c *Coremodel) CurrentSchema() thema.Schema { |
||||
return thema.SchemaP(c.lin, currentVersion) |
||||
} |
||||
|
||||
// GoType returns a pointer to an empty Go struct that corresponds to |
||||
// the current Thema schema. |
||||
func (c *Coremodel) GoType() interface{} { |
||||
return &Model{} |
||||
} |
||||
|
||||
// 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 |
||||
} |
||||
|
||||
return &Coremodel{ |
||||
lin: lin, |
||||
}, nil |
||||
} |
@ -0,0 +1,28 @@ |
||||
{{ if .GenLicense -}} |
||||
// Copyright {{ now.Year }} Grafana Labs |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
|
||||
{{ end -}} |
||||
// This file is autogenerated. DO NOT EDIT. |
||||
{{- if ne .GeneratorPath "" }} |
||||
// |
||||
// Generated by {{ .GeneratorPath }} |
||||
{{- end }} |
||||
{{- if ne .LineagePath "" }} |
||||
// |
||||
// Derived from the Thema lineage declared in {{ .LineagePath }}{{ if ne .LineageCUEPath "" }} at CUE path "{{ .LineageCUEPath }}"{{ end }} |
||||
{{- end }} |
||||
// |
||||
// Run `make gen-cue` from repository root to regenerate. |
||||
|
@ -0,0 +1,10 @@ |
||||
package {{ .PackageName }} |
||||
|
||||
import ( |
||||
"embed" |
||||
"path/filepath" |
||||
|
||||
"github.com/grafana/grafana/pkg/cuectx" |
||||
"github.com/grafana/grafana/pkg/framework/coremodel" |
||||
"github.com/grafana/thema" |
||||
) |
@ -0,0 +1,58 @@ |
||||
{{ template "autogen_header.tmpl" .Header }} |
||||
package registry |
||||
|
||||
import ( |
||||
"fmt" |
||||
"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" |
||||
) |
||||
|
||||
// Base is a registry of coremodel.Interface. It provides two modes for accessing |
||||
// coremodels: individually via literal named methods, or as a slice returned from All(). |
||||
// |
||||
// Prefer the individual named methods for use cases where the particular coremodel(s) that |
||||
// are needed are known to the caller. For example, a dashboard linter can know that it |
||||
// specifically wants the dashboard coremodel. |
||||
// |
||||
// Prefer All() when performing operations generically across all coremodels. For example, |
||||
// a validation HTTP middleware for any coremodel-schematized object type. |
||||
type Base struct { |
||||
all []coremodel.Interface |
||||
{{- 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 (b *Base) {{ .TitleName }}() *{{ .Name }}.Coremodel { |
||||
return b.{{ .Name }} |
||||
} |
||||
{{end}} |
||||
|
||||
func doProvideBase(lib thema.Library) *Base { |
||||
var err error |
||||
reg := &Base{} |
||||
|
||||
{{range .Coremodels }} |
||||
reg.{{ .Name }}, err = {{ .Name }}.New(lib) |
||||
if err != nil { |
||||
panic(fmt.Sprintf("error while initializing {{ .Name }} coremodel: %s", err)) |
||||
} |
||||
reg.all = append(reg.all, reg.{{ .Name }}) |
||||
{{end}} |
||||
|
||||
return reg |
||||
} |
@ -0,0 +1,7 @@ |
||||
{{ template "autogen_header.tmpl" .Header -}} |
||||
{{range .Imports}} |
||||
import * as {{.Ident}} from '{{.Pkg}}';{{end}} |
||||
{{range .Sections}}{{if ne .ModelName "" }} |
||||
export const {{.ModelName}}ModelVersion = Object.freeze([{{index .V 0}}, {{index .V 1}}]); |
||||
{{end}} |
||||
{{.Body}}{{end}} |
@ -0,0 +1,12 @@ |
||||
// The current version of the coremodel schema, as declared in coremodel.cue. |
||||
// This version determines what schema version is returned from [Coremodel.CurrentSchema], |
||||
// and which schema version is used for code generation within the grafana/grafana repository. |
||||
// |
||||
// The code generator ensures that this is always the latest Thema schema version. |
||||
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...) |
||||
} |
@ -0,0 +1,58 @@ |
||||
{{ template "autogen_header.tmpl" .Header -}} |
||||
package {{ .PackageName }} |
||||
|
||||
import ( |
||||
"embed" |
||||
"fmt" |
||||
"path/filepath" |
||||
"sync" |
||||
|
||||
"github.com/grafana/grafana/pkg/cuectx" |
||||
"github.com/grafana/grafana/pkg/plugins/pfs" |
||||
"github.com/grafana/thema" |
||||
) |
||||
|
||||
var parseOnce sync.Once |
||||
var ptree *pfs.Tree |
||||
|
||||
//go:embed plugin.{{ if .RootCUE }}cue{{ else }}json{{ end }}{{ if .HasModels }} models.cue{{ end }} |
||||
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 { |
||||
var err error |
||||
if lib == nil { |
||||
parseOnce.Do(func() { |
||||
ptree, err = pfs.ParsePluginFS(plugFS, cuectx.ProvideThemaLibrary()) |
||||
}) |
||||
} else { |
||||
ptree, err = pfs.ParsePluginFS(plugFS, cuectx.ProvideThemaLibrary()) |
||||
} |
||||
|
||||
if err != nil { |
||||
// Even the most rudimentary testing in CI ensures this is unreachable |
||||
panic(fmt.Errorf("error parsing plugin fs tree: %w", err)) |
||||
} |
||||
|
||||
return ptree |
||||
} |
||||
|
||||
{{ $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) |
||||
lin, has := t.RootPlugin().SlotImplementations()["{{ .SlotName }}"] |
||||
if !has { |
||||
panic("unreachable: lineage for {{ .SlotName }} does not exist, but code is only generated for existing lineages") |
||||
} |
||||
return lin, nil |
||||
} |
||||
|
||||
// The current schema version of the {{ .SlotName }} slot implementation. |
||||
// |
||||
// Code generation ensures that this is always the version number for the latest schema |
||||
// in the {{ .SlotName }} Thema lineage. |
||||
var currentVersion{{ .SlotName }} = thema.SV({{ .LatestMajv }}, {{ .LatestMinv }}) |
||||
|
||||
{{ end }} |
@ -0,0 +1,31 @@ |
||||
{{ template "autogen_header.tmpl" .Header -}} |
||||
package corelist |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io/fs" |
||||
"sync" |
||||
"github.com/grafana/grafana" |
||||
"github.com/grafana/grafana/pkg/plugins/pfs" |
||||
"github.com/grafana/thema" |
||||
) |
||||
|
||||
func makeTreeOrPanic(path string, pkgname string, lib thema.Library) *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) |
||||
if err != nil { |
||||
panic(fmt.Sprintf("error parsing plugin metadata for %s: %s", pkgname, err)) |
||||
} |
||||
return tree |
||||
} |
||||
|
||||
func coreTreeList(lib thema.Library) pfs.TreeList{ |
||||
return pfs.TreeList{ |
||||
{{- range .Plugins }} |
||||
makeTreeOrPanic("{{ .Path }}", "{{ .PkgName }}", lib), |
||||
{{- end }} |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
{{ template "autogen_header.tmpl" .Header -}} |
||||
package registry |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/plugins/pfs" |
||||
"github.com/grafana/thema" |
||||
{{ range .Plugins }} |
||||
{{ if .NoAlias }}{{ .PkgName }} {{end}}"{{ .Path }}"{{ end }} |
||||
) |
||||
|
||||
func coreTreeLoaders() []func(*thema.Library) *pfs.Tree{ |
||||
return []func(*thema.Library) *pfs.Tree{ |
||||
{{- range .Plugins }} |
||||
{{ .PkgName }}.PluginTree,{{ end }} |
||||
} |
||||
} |
@ -0,0 +1,67 @@ |
||||
package codegen |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"go/ast" |
||||
"go/format" |
||||
"go/parser" |
||||
"go/token" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
"golang.org/x/tools/imports" |
||||
) |
||||
|
||||
type genGoFile struct { |
||||
path string |
||||
walker ast.Visitor |
||||
in []byte |
||||
} |
||||
|
||||
func postprocessGoFile(cfg genGoFile) ([]byte, error) { |
||||
fname := filepath.Base(cfg.path) |
||||
buf := new(bytes.Buffer) |
||||
fset := token.NewFileSet() |
||||
gf, err := parser.ParseFile(fset, fname, string(cfg.in), parser.ParseComments) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error parsing generated file: %w", err) |
||||
} |
||||
|
||||
if cfg.walker != nil { |
||||
ast.Walk(cfg.walker, gf) |
||||
|
||||
err = format.Node(buf, fset, gf) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error formatting Go AST: %w", err) |
||||
} |
||||
} else { |
||||
buf = bytes.NewBuffer(cfg.in) |
||||
} |
||||
|
||||
byt, err := imports.Process(fname, buf.Bytes(), nil) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("goimports processing failed: %w", err) |
||||
} |
||||
|
||||
// Compare imports before and after; warn about performance if some were added
|
||||
gfa, _ := parser.ParseFile(fset, fname, string(byt), parser.ParseComments) |
||||
imap := make(map[string]bool) |
||||
for _, im := range gf.Imports { |
||||
imap[im.Path.Value] = true |
||||
} |
||||
var added []string |
||||
for _, im := range gfa.Imports { |
||||
if !imap[im.Path.Value] { |
||||
added = append(added, im.Path.Value) |
||||
} |
||||
} |
||||
|
||||
if len(added) != 0 { |
||||
// TODO improve the guidance in this error if/when we better abstract over imports to generate
|
||||
fmt.Fprintf(os.Stderr, "The following imports were added by goimports while generating %s: \n\t%s\nRelying on goimports to find imports significantly slows down code generation. Consider adding these to the relevant template.\n", cfg.path, strings.Join(added, "\n\t")) |
||||
} |
||||
|
||||
return byt, nil |
||||
} |
@ -0,0 +1,86 @@ |
||||
// Copyright 2022 Grafana Labs
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file is autogenerated. DO NOT EDIT.
|
||||
//
|
||||
// Run `make gen-cue` from repository root to regenerate.
|
||||
|
||||
package corelist |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io/fs" |
||||
|
||||
"github.com/grafana/grafana" |
||||
"github.com/grafana/grafana/pkg/plugins/pfs" |
||||
"github.com/grafana/thema" |
||||
) |
||||
|
||||
func makeTreeOrPanic(path string, pkgname string, lib thema.Library) *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) |
||||
if err != nil { |
||||
panic(fmt.Sprintf("error parsing plugin metadata for %s: %s", pkgname, err)) |
||||
} |
||||
return tree |
||||
} |
||||
|
||||
func coreTreeList(lib thema.Library) 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/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), |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
package corelist |
||||
|
||||
import ( |
||||
"sync" |
||||
|
||||
"github.com/grafana/grafana/pkg/cuectx" |
||||
"github.com/grafana/grafana/pkg/plugins/pfs" |
||||
"github.com/grafana/thema" |
||||
) |
||||
|
||||
var coreTrees pfs.TreeList |
||||
var coreOnce sync.Once |
||||
|
||||
// New returns a pfs.TreeList containing the plugin trees for all core plugins
|
||||
// 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 { |
||||
var tl pfs.TreeList |
||||
if lib == nil { |
||||
coreOnce.Do(func() { |
||||
coreTrees = coreTreeList(cuectx.ProvideThemaLibrary()) |
||||
}) |
||||
tl = make(pfs.TreeList, len(coreTrees)) |
||||
copy(tl, coreTrees) |
||||
} else { |
||||
return coreTreeList(*lib) |
||||
} |
||||
return tl |
||||
} |
Loading…
Reference in new issue