mirror of
https://github.com/grafana/grafana.git
synced 2024-12-23 15:40:19 -06:00
plugindef: Move pluginmeta out of coremodels as standalone thema lineage (#56765)
* Get pluginmeta mostly moved over to pkg/plugins/plugindef * Remove dead func * Fix up pfs, use sync.Once in plugindef * Update to latest thema * Chase Endec->Codec conversion in Thema * Comments on slash header gen; use ToSlash * Also generate JSON schema for plugindef * Generate JSON Schema as well * Fix slot loading from kindsys cue decls * Remove unused vars * skip generating plugin.schema.json for now Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
This commit is contained in:
parent
ff1afbb699
commit
78f0340031
1
Makefile
1
Makefile
@ -66,6 +66,7 @@ openapi3-gen: swagger-api-spec ## Generates OpenApi 3 specs from the Swagger 2 a
|
||||
##@ Building
|
||||
gen-cue: ## Do all CUE/Thema code generation
|
||||
@echo "generate code from .cue files"
|
||||
go generate ./pkg/plugins/plugindef
|
||||
go generate ./kinds/gen.go
|
||||
go generate ./pkg/framework/coremodel
|
||||
go generate ./public/app/plugins
|
||||
|
2
embed.go
2
embed.go
@ -6,5 +6,5 @@ import (
|
||||
|
||||
// CueSchemaFS embeds all schema-related CUE files in the Grafana project.
|
||||
//
|
||||
//go:embed cue.mod/module.cue kinds/*/*.cue kinds/*/*/*.cue packages/grafana-schema/src/schema/*.cue public/app/plugins/*/*/*.cue public/app/plugins/*/*/plugin.json pkg/framework/coremodel/*.cue pkg/kindsys/*.cue
|
||||
//go:embed cue.mod/module.cue kinds/*/*.cue kinds/*/*/*.cue packages/grafana-schema/src/schema/*.cue public/app/plugins/*/*/*.cue public/app/plugins/*/*/plugin.json pkg/kindsys/*.cue pkg/plugins/plugindef/*.cue
|
||||
var CueSchemaFS embed.FS
|
||||
|
4
go.mod
4
go.mod
@ -61,7 +61,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.142.0
|
||||
github.com/grafana/thema v0.0.0-20221107225215-00ad2949c7bc
|
||||
github.com/grafana/thema v0.0.0-20221113112305-b441ed85a1fd
|
||||
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
|
||||
@ -349,7 +349,7 @@ require (
|
||||
github.com/yudai/pp v2.0.1+incompatible // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.16.0 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/mod v0.7.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
|
12
go.sum
12
go.sum
@ -1242,7 +1242,6 @@ github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6A
|
||||
github.com/google/go-replayers/httpreplay v1.1.1/go.mod h1:gN9GeLIs7l6NUoVaSSnv2RiqK1NiwAmD0MrKeC9IIks=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
@ -1370,8 +1369,10 @@ github.com/grafana/prometheus-alertmanager v0.24.1-0.20221012142027-823cd9150293
|
||||
github.com/grafana/prometheus-alertmanager v0.24.1-0.20221012142027-823cd9150293/go.mod h1:HVHqK+BVPa/tmL8EMhLCCrPt2a1GdJpEyxr5hgur2UI=
|
||||
github.com/grafana/saml v0.4.9-0.20220727151557-61cd9c9353fc h1:1PY8n+rXuBNr3r1JQhoytWDCpc+pq+BibxV0SZv+Cr4=
|
||||
github.com/grafana/saml v0.4.9-0.20220727151557-61cd9c9353fc/go.mod h1:9Zh6dWPtB3MSzTRt8fIFH60Z351QQ+s7hCU3J/tTlA4=
|
||||
github.com/grafana/thema v0.0.0-20221107225215-00ad2949c7bc h1:Icv777/PBaqhLmbSBSDaajDl424cbmh5ee77Du2rUFE=
|
||||
github.com/grafana/thema v0.0.0-20221107225215-00ad2949c7bc/go.mod h1:wnIJykzNiNVANl6g/Z4nkXxoMqaaH1LoG0IPNW++BEk=
|
||||
github.com/grafana/thema v0.0.0-20221113034006-50fd3c0da5ce h1:N1K0WWaG0B5i/703ri0WSazQYVsCYj1mgODgElCz0o8=
|
||||
github.com/grafana/thema v0.0.0-20221113034006-50fd3c0da5ce/go.mod h1:ZJHKwNE86ngdQ7edJIFHepCiIg9YP9x+YZPEm3dlkL4=
|
||||
github.com/grafana/thema v0.0.0-20221113112305-b441ed85a1fd h1:y6H9I5fy4sRKf2FJ7W94YWero4mXH50Ft8NAPZ9DapQ=
|
||||
github.com/grafana/thema v0.0.0-20221113112305-b441ed85a1fd/go.mod h1:ZJHKwNE86ngdQ7edJIFHepCiIg9YP9x+YZPEm3dlkL4=
|
||||
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=
|
||||
@ -1823,8 +1824,6 @@ github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vq
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=
|
||||
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
@ -2756,8 +2755,9 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -109,9 +109,6 @@ func main() {
|
||||
if err != nil {
|
||||
die(fmt.Errorf("core kinddirs codegen failed: %w", err))
|
||||
}
|
||||
// for _, f := range jfs.AsFiles() {
|
||||
// fmt.Println(filepath.Join(groot, f.RelativePath))
|
||||
// }
|
||||
|
||||
if _, set := os.LookupEnv("CODEGEN_VERIFY"); set {
|
||||
if err = jfs.Verify(context.Background(), groot); err != nil {
|
||||
|
@ -237,7 +237,7 @@ type tplVars struct {
|
||||
}
|
||||
|
||||
func (cd *CoremodelDeclaration) GenerateTypescriptCoremodel() (*tsast.File, error) {
|
||||
schv := thema.SchemaP(cd.Lineage, thema.LatestVersion(cd.Lineage)).UnwrapCUE()
|
||||
schv := cd.Lineage.Latest().Underlying()
|
||||
|
||||
tf, err := cuetsy.GenerateAST(schv, cuetsy.Config{
|
||||
Export: true,
|
||||
|
@ -3,6 +3,7 @@ package codegen
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/grafana/codejen"
|
||||
"github.com/grafana/grafana/pkg/kindsys"
|
||||
@ -39,12 +40,22 @@ func (decl *DeclForGen) Lineage() thema.Lineage {
|
||||
return decl.lin
|
||||
}
|
||||
|
||||
// SlashHeaderMapper produces a FileMapper that injects a comment header onto
|
||||
// a [codejen.File] indicating the main generator that produced it (via the provided
|
||||
// maingen, which should be a path) and the jenny or jennies that constructed the
|
||||
// file.
|
||||
func SlashHeaderMapper(maingen string) codejen.FileMapper {
|
||||
return func(f codejen.File) (codejen.File, error) {
|
||||
b := new(bytes.Buffer)
|
||||
fmt.Fprintf(b, headerTmpl, maingen, f.FromString())
|
||||
fmt.Fprint(b, string(f.Data))
|
||||
f.Data = b.Bytes()
|
||||
// Never inject on certain filetypes, it's never valid
|
||||
switch filepath.Ext(f.RelativePath) {
|
||||
case ".json", ".yml", ".yaml":
|
||||
return f, nil
|
||||
default:
|
||||
b := new(bytes.Buffer)
|
||||
fmt.Fprintf(b, headerTmpl, filepath.ToSlash(maingen), f.FromString())
|
||||
fmt.Fprint(b, string(f.Data))
|
||||
f.Data = b.Bytes()
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
|
@ -66,11 +66,10 @@ func (gen *genTSVeneerIndex) Generate(decls []*DeclForGen) (*codejen.File, error
|
||||
|
||||
func (gen *genTSVeneerIndex) extractTSIndexVeneerElements(decl *DeclForGen, tf *ast.File) ([]ast.Decl, error) {
|
||||
lin := decl.Lineage()
|
||||
sch := thema.SchemaP(lin, thema.LatestVersion(lin))
|
||||
comm := decl.Meta.Common()
|
||||
|
||||
// Check the root, then walk the tree
|
||||
rootv := sch.UnwrapCUE()
|
||||
rootv := lin.Latest().Underlying()
|
||||
|
||||
var raw, custom, rawD, customD ast.Idents
|
||||
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/grafana/cuetsy"
|
||||
tsast "github.com/grafana/cuetsy/ts/ast"
|
||||
"github.com/grafana/grafana/pkg/framework/coremodel"
|
||||
"github.com/grafana/grafana/pkg/kindsys"
|
||||
"github.com/grafana/grafana/pkg/plugins/pfs"
|
||||
"github.com/grafana/thema"
|
||||
"github.com/grafana/thema/encoding/openapi"
|
||||
@ -146,7 +146,7 @@ func (pt *PluginTree) GenerateTypeScriptAST() (*tsast.File, error) {
|
||||
// whether the slot is a grouped lineage:
|
||||
// https://github.com/grafana/thema/issues/62
|
||||
if isGroupLineage(slotname) {
|
||||
tsf, err := cuetsy.GenerateAST(sch.UnwrapCUE(), cuetsy.Config{
|
||||
tsf, err := cuetsy.GenerateAST(sch.Underlying(), cuetsy.Config{
|
||||
Export: true,
|
||||
})
|
||||
if err != nil {
|
||||
@ -154,7 +154,7 @@ func (pt *PluginTree) GenerateTypeScriptAST() (*tsast.File, error) {
|
||||
}
|
||||
f.Nodes = append(f.Nodes, tsf.Nodes...)
|
||||
} else {
|
||||
pair, err := cuetsy.GenerateSingleAST(strings.Title(lin.Name()), sch.UnwrapCUE(), cuetsy.TypeInterface)
|
||||
pair, err := cuetsy.GenerateSingleAST(strings.Title(lin.Name()), sch.Underlying(), cuetsy.TypeInterface)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error translating %s lineage to TypeScript: %w", slotname, err)
|
||||
}
|
||||
@ -169,7 +169,7 @@ func (pt *PluginTree) GenerateTypeScriptAST() (*tsast.File, error) {
|
||||
}
|
||||
|
||||
func isGroupLineage(slotname string) bool {
|
||||
sl, has := coremodel.AllSlots()[slotname]
|
||||
sl, has := kindsys.AllSlots(nil)[slotname]
|
||||
if !has {
|
||||
panic("unknown slotname name: " + slotname)
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ const rootrel string = "kinds/structured/{{ .Meta.MachineName }}"
|
||||
// TODO standard generated docs
|
||||
type Kind struct {
|
||||
lin thema.ConvergentLineage[*{{ .Meta.Name }}]
|
||||
jendec vmux.Endec
|
||||
jcodec vmux.Codec
|
||||
valmux vmux.ValueMux[*{{ .Meta.Name }}]
|
||||
decl kindsys.Decl[kindsys.CoreStructuredMeta]
|
||||
}
|
||||
@ -47,9 +47,9 @@ func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k.jendec = vmux.NewJSONEndec("{{ .Meta.MachineName }}.json")
|
||||
k.jcodec = vmux.NewJSONCodec("{{ .Meta.MachineName }}.json")
|
||||
k.lin = tsch.ConvergentLineage()
|
||||
k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jendec)
|
||||
k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec)
|
||||
return k, nil
|
||||
}
|
||||
|
||||
|
@ -69,8 +69,8 @@ func postprocessGoFile(cfg genGoFile) ([]byte, error) {
|
||||
}
|
||||
|
||||
type prefixmod struct {
|
||||
str string
|
||||
base string
|
||||
prefix string
|
||||
replace string
|
||||
rxp *regexp.Regexp
|
||||
rxpsuff *regexp.Regexp
|
||||
}
|
||||
@ -80,7 +80,22 @@ type prefixmod struct {
|
||||
// comments in a generated Go file.
|
||||
func PrefixDropper(prefix string) astutil.ApplyFunc {
|
||||
return (&prefixmod{
|
||||
str: prefix,
|
||||
prefix: prefix,
|
||||
rxpsuff: regexp.MustCompile(fmt.Sprintf(`%s([a-zA-Z_]+)`, prefix)),
|
||||
rxp: regexp.MustCompile(fmt.Sprintf(`%s([\s.,;-])`, prefix)),
|
||||
}).applyfunc
|
||||
}
|
||||
|
||||
// PrefixReplacer returns an astutil.ApplyFunc that removes the provided prefix
|
||||
// string when it appears as a leading sequence in type names, var names, and
|
||||
// comments in a generated Go file.
|
||||
//
|
||||
// When an exact match for prefix is found, the provided replace string
|
||||
// is substituted.
|
||||
func PrefixReplacer(prefix, replace string) astutil.ApplyFunc {
|
||||
return (&prefixmod{
|
||||
prefix: prefix,
|
||||
replace: replace,
|
||||
rxpsuff: regexp.MustCompile(fmt.Sprintf(`%s([a-zA-Z_]+)`, prefix)),
|
||||
rxp: regexp.MustCompile(fmt.Sprintf(`%s([\s.,;-])`, prefix)),
|
||||
}).applyfunc
|
||||
@ -113,8 +128,8 @@ func (d prefixmod) applyfunc(c *astutil.Cursor) bool {
|
||||
case *ast.CommentGroup:
|
||||
for _, c := range x.List {
|
||||
c.Text = d.rxpsuff.ReplaceAllString(c.Text, "$1")
|
||||
if d.base != "" {
|
||||
c.Text = d.rxp.ReplaceAllString(c.Text, d.base+"$1")
|
||||
if d.replace != "" {
|
||||
c.Text = d.rxp.ReplaceAllString(c.Text, d.replace+"$1")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -142,9 +157,9 @@ func (d prefixmod) handleExpr(e ast.Expr) {
|
||||
}
|
||||
|
||||
func (d prefixmod) do(n *ast.Ident) {
|
||||
if n.Name != d.str {
|
||||
n.Name = strings.TrimPrefix(n.Name, d.str)
|
||||
} else if d.base != "" {
|
||||
n.Name = d.base
|
||||
if n.Name != d.prefix {
|
||||
n.Name = strings.TrimPrefix(n.Name, d.prefix)
|
||||
} else if d.replace != "" {
|
||||
n.Name = d.replace
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ func GrafanaThemaRuntime() *thema.Runtime {
|
||||
// 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 vmux.NewJSONEndec(path).Decode(ctx, b)
|
||||
return vmux.NewJSONCodec(path).Decode(ctx, b)
|
||||
}
|
||||
|
||||
// LoadGrafanaInstancesWithThema loads CUE files containing a lineage
|
||||
@ -61,7 +61,7 @@ func JSONtoCUE(path string, b []byte) (cue.Value, error) {
|
||||
// path from the grafana root to the directory containing the lineage.cue. The
|
||||
// lineage.cue file must be the sole contents of the provided fs.FS.
|
||||
//
|
||||
// More details on underlying behavior can be found in the docs for github.com/grafana/thema/load.InstancesWithThema.
|
||||
// More details on underlying behavior can be found in the docs for github.com/grafana/thema/load.InstanceWithThema.
|
||||
//
|
||||
// TODO this approach is complicated and confusing, refactor to something understandable
|
||||
func LoadGrafanaInstancesWithThema(path string, cueFS fs.FS, rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
|
||||
@ -70,7 +70,7 @@ func LoadGrafanaInstancesWithThema(path string, cueFS fs.FS, rt *thema.Runtime,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inst, err := load.InstancesWithThema(fs, prefix)
|
||||
inst, err := load.InstanceWithThema(fs, prefix)
|
||||
|
||||
// Need to trick loading by creating the embedded file and
|
||||
// making it look like a module in the root dir.
|
||||
@ -93,7 +93,7 @@ func LoadGrafanaInstancesWithThema(path string, cueFS fs.FS, rt *thema.Runtime,
|
||||
// The provided prefix should be the relative path from the grafana repository
|
||||
// root to the directory root of the provided inputfs.
|
||||
//
|
||||
// The returned fs.FS is suitable for passing to a CUE loader, such as [load.InstancesWithThema].
|
||||
// The returned fs.FS is suitable for passing to a CUE loader, such as [load.InstanceWithThema].
|
||||
func prefixWithGrafanaCUE(prefix string, inputfs fs.FS) (fs.FS, error) {
|
||||
m := fstest.MapFS{
|
||||
// fstest can recognize only forward slashes.
|
||||
@ -124,10 +124,9 @@ func prefixWithGrafanaCUE(prefix string, inputfs fs.FS) (fs.FS, error) {
|
||||
return merged_fs.NewMergedFS(m, grafana.CueSchemaFS), nil
|
||||
}
|
||||
|
||||
// BuildGrafanaInstance wraps [load.InstancesWithThema] to load a
|
||||
// LoadGrafanaInstance wraps [load.InstanceWithThema] to load a
|
||||
// [*build.Instance] corresponding to a particular path within the
|
||||
// github.com/grafana/grafana CUE module, then builds that into a [cue.Value],
|
||||
// checks it for errors and returns.
|
||||
// github.com/grafana/grafana CUE module.
|
||||
//
|
||||
// This allows resolution of imports within the grafana or thema CUE modules to
|
||||
// work correctly and consistently by relying on the embedded FS at
|
||||
@ -143,7 +142,7 @@ func prefixWithGrafanaCUE(prefix string, inputfs fs.FS) (fs.FS, error) {
|
||||
// is the same as the parent directory name, it should be omitted.
|
||||
//
|
||||
// NOTE this function will be removed in favor of a more generic loader
|
||||
func BuildGrafanaInstance(relpath string, pkg string, ctx *cue.Context, overlay fs.FS) (cue.Value, error) {
|
||||
func LoadGrafanaInstance(relpath string, pkg string, overlay fs.FS) (*build.Instance, error) {
|
||||
// notes about how this crap needs to work
|
||||
//
|
||||
// Within grafana/grafana, need:
|
||||
@ -151,41 +150,6 @@ func BuildGrafanaInstance(relpath string, pkg string, ctx *cue.Context, overlay
|
||||
// - has no cue.mod
|
||||
// - gets prefixed with the appropriate path within grafana/grafana
|
||||
// - and merged with all the other .cue files from grafana/grafana
|
||||
if ctx == nil {
|
||||
ctx = GrafanaCUEContext()
|
||||
}
|
||||
relpath = filepath.ToSlash(relpath)
|
||||
|
||||
var v cue.Value
|
||||
var f fs.FS = grafana.CueSchemaFS
|
||||
var err error
|
||||
if overlay != nil {
|
||||
f, err = prefixWithGrafanaCUE(relpath, overlay)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
}
|
||||
|
||||
var bi *build.Instance
|
||||
if pkg != "" {
|
||||
bi, err = load.InstancesWithThema(f, relpath, load.Package(pkg))
|
||||
} else {
|
||||
bi, err = load.InstancesWithThema(f, relpath)
|
||||
}
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
v = ctx.BuildInstance(bi)
|
||||
if v.Err() != nil {
|
||||
return v, fmt.Errorf("%s not a valid CUE instance: %w", relpath, v.Err())
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// TODO docs
|
||||
// NOTE this function will be removed in favor of a more generic loader
|
||||
func LoadInstanceWithGrafana(ifs fs.FS, prefix string) (*build.Instance, error) {
|
||||
// notes about how this crap needs to work
|
||||
//
|
||||
// Need a prefixing instance loader that:
|
||||
@ -193,6 +157,40 @@ func LoadInstanceWithGrafana(ifs fs.FS, prefix string) (*build.Instance, error)
|
||||
// - reconcile at most one of the provided fs with cwd
|
||||
// - behavior must differ depending on whether cwd is in a cue module
|
||||
// - behavior should(?) be controllable depending on
|
||||
relpath = filepath.ToSlash(relpath)
|
||||
|
||||
panic("TODO")
|
||||
var f fs.FS = grafana.CueSchemaFS
|
||||
var err error
|
||||
if overlay != nil {
|
||||
f, err = prefixWithGrafanaCUE(relpath, overlay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if pkg != "" {
|
||||
return load.InstanceWithThema(f, relpath, load.Package(pkg))
|
||||
}
|
||||
return load.InstanceWithThema(f, relpath)
|
||||
}
|
||||
|
||||
// BuildGrafanaInstance wraps [LoadGrafanaInstance], additionally building
|
||||
// the returned [*build.Instance], if valid, into a [cue.Value] that is checked
|
||||
// for errors before returning.
|
||||
//
|
||||
// NOTE this function will be removed in favor of a more generic loader
|
||||
func BuildGrafanaInstance(ctx *cue.Context, relpath string, pkg string, overlay fs.FS) (cue.Value, error) {
|
||||
bi, err := LoadGrafanaInstance(relpath, pkg, overlay)
|
||||
if err != nil {
|
||||
return cue.Value{}, err
|
||||
}
|
||||
|
||||
if ctx == nil {
|
||||
ctx = GrafanaCUEContext()
|
||||
}
|
||||
v := ctx.BuildInstance(bi)
|
||||
if v.Err() != nil {
|
||||
return v, fmt.Errorf("%s not a valid CUE instance: %w", relpath, v.Err())
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
@ -1,107 +0,0 @@
|
||||
package coremodel
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"testing/fstest"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/load"
|
||||
tload "github.com/grafana/thema/load"
|
||||
|
||||
"github.com/grafana/grafana/pkg/cuectx"
|
||||
)
|
||||
|
||||
// Embed for all framework-related CUE files in this directory
|
||||
//
|
||||
//go:embed *.cue
|
||||
var cueFS embed.FS
|
||||
|
||||
var defaultFramework cue.Value
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
defaultFramework, err = doLoadFrameworkCUE(cuectx.GrafanaCUEContext())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var prefix = filepath.Join("/pkg", "framework", "coremodel")
|
||||
|
||||
//nolint:nakedret
|
||||
func doLoadFrameworkCUE(ctx *cue.Context) (v cue.Value, err error) {
|
||||
m := make(fstest.MapFS)
|
||||
|
||||
err = fs.WalkDir(cueFS, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
b, err := fs.ReadFile(cueFS, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m[path] = &fstest.MapFile{Data: b}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
over := make(map[string]load.Source)
|
||||
|
||||
absolutePath := prefix
|
||||
if !filepath.IsAbs(absolutePath) {
|
||||
absolutePath, err = filepath.Abs(absolutePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = tload.ToOverlay(absolutePath, m, over)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bi := load.Instances(nil, &load.Config{
|
||||
Dir: absolutePath,
|
||||
Package: "coremodel",
|
||||
Overlay: over,
|
||||
})
|
||||
v = ctx.BuildInstance(bi[0])
|
||||
|
||||
if v.Err() != nil {
|
||||
return cue.Value{}, fmt.Errorf("coremodel framework loaded cue.Value has err: %w", v.Err())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CUEFramework returns a cue.Value representing all the coremodel framework
|
||||
// raw CUE files.
|
||||
//
|
||||
// For low-level use in constructing other types and APIs, while still letting
|
||||
// us declare all the frameworky CUE bits in a single package. Other types and
|
||||
// subpackages make the constructs in this value easy to use.
|
||||
//
|
||||
// The returned cue.Value is built from Grafana's standard central CUE context,
|
||||
// ["github.com/grafana/grafana/pkg/cuectx".ProvideCueContext].
|
||||
func CUEFramework() cue.Value {
|
||||
return defaultFramework
|
||||
}
|
||||
|
||||
// CUEFrameworkWithContext is the same as CUEFramework, but allows control over
|
||||
// the cue.Context that's used.
|
||||
//
|
||||
// Prefer CUEFramework unless you understand cue.Context, and absolutely need
|
||||
// this control.
|
||||
func CUEFrameworkWithContext(ctx *cue.Context) cue.Value {
|
||||
// Error guaranteed to be nil here because erroring would have caused init() to panic
|
||||
v, _ := doLoadFrameworkCUE(ctx) // nolint:errcheck
|
||||
return v
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package coremodel
|
||||
|
||||
// Generates all code derived from coremodel Thema lineages that's used directly
|
||||
// by both the frontend and backend.
|
||||
//go:generate go run gen.go
|
||||
|
||||
import (
|
||||
"github.com/grafana/thema"
|
||||
)
|
||||
|
||||
// Interface is the primary coremodel interface that must be implemented by all
|
||||
// Grafana coremodels. A coremodel is the foundational, canonical schema for
|
||||
// some known-at-compile-time Grafana object.
|
||||
//
|
||||
// Currently, all Coremodels are expressed as Thema lineages.
|
||||
type Interface interface {
|
||||
// Lineage should return the canonical Thema lineage for the coremodel.
|
||||
Lineage() thema.Lineage
|
||||
|
||||
// CurrentSchema should return the schema of the version that the Grafana backend
|
||||
// is currently written against. (While Grafana can accept data from all
|
||||
// older versions of the Thema schema, backend Go code is written against a
|
||||
// single version for simplicity)
|
||||
CurrentSchema() thema.Schema
|
||||
|
||||
// GoType should return a pointer to the Go struct type that corresponds to
|
||||
// the Current() schema.
|
||||
GoType() interface{}
|
||||
}
|
@ -24,7 +24,7 @@ const rootrel string = "kinds/structured/dashboard"
|
||||
// TODO standard generated docs
|
||||
type Kind struct {
|
||||
lin thema.ConvergentLineage[*Dashboard]
|
||||
jendec vmux.Endec
|
||||
jcodec vmux.Codec
|
||||
valmux vmux.ValueMux[*Dashboard]
|
||||
decl kindsys.Decl[kindsys.CoreStructuredMeta]
|
||||
}
|
||||
@ -56,9 +56,9 @@ func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k.jendec = vmux.NewJSONEndec("dashboard.json")
|
||||
k.jcodec = vmux.NewJSONCodec("dashboard.json")
|
||||
k.lin = tsch.ConvergentLineage()
|
||||
k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jendec)
|
||||
k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec)
|
||||
return k, nil
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ const rootrel string = "kinds/structured/playlist"
|
||||
// TODO standard generated docs
|
||||
type Kind struct {
|
||||
lin thema.ConvergentLineage[*Playlist]
|
||||
jendec vmux.Endec
|
||||
jcodec vmux.Codec
|
||||
valmux vmux.ValueMux[*Playlist]
|
||||
decl kindsys.Decl[kindsys.CoreStructuredMeta]
|
||||
}
|
||||
@ -56,9 +56,9 @@ func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k.jendec = vmux.NewJSONEndec("playlist.json")
|
||||
k.jcodec = vmux.NewJSONCodec("playlist.json")
|
||||
k.lin = tsch.ConvergentLineage()
|
||||
k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jendec)
|
||||
k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec)
|
||||
return k, nil
|
||||
}
|
||||
|
||||
|
@ -163,4 +163,3 @@ _sharedKind: {
|
||||
// It is required that lineage.name is the same as the [machineName].
|
||||
lineage: thema.#Lineage & { name: S.machineName }
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,7 @@ func (decl *Decl[T]) Some() *SomeDecl {
|
||||
// For representations of core kinds that are useful in Go programs at runtime,
|
||||
// see ["github.com/grafana/grafana/pkg/registry/corekind"].
|
||||
func LoadCoreKind[T RawMeta | CoreStructuredMeta](declpath string, ctx *cue.Context, overlay fs.FS) (*Decl[T], error) {
|
||||
vk, err := cuectx.BuildGrafanaInstance(declpath, "kind", ctx, overlay)
|
||||
vk, err := cuectx.BuildGrafanaInstance(ctx, declpath, "kind", overlay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1,24 +1,28 @@
|
||||
package coremodel
|
||||
package kindsys
|
||||
|
||||
import (
|
||||
"cuelang.org/go/cue"
|
||||
)
|
||||
|
||||
// Slot represents one of Grafana's named Thema composition slot definitions.
|
||||
// Slot represents one of Grafana's named slot definitions.
|
||||
// TODO link to framework docs
|
||||
type Slot struct {
|
||||
name string
|
||||
raw cue.Value
|
||||
plugins map[string]bool
|
||||
}
|
||||
|
||||
// Name returns the name of the Slot. The name is also used as the path at which
|
||||
// a Slot lineage is defined in a plugin models.cue file.
|
||||
// Name returns the name of the Slot.
|
||||
//
|
||||
// The name is also used as the path at which a Slot lineage is defined in a
|
||||
// plugin models.cue file.
|
||||
func (s Slot) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// MetaSchema returns the meta-schema that is the contract between coremodels
|
||||
// that compose the Slot, and plugins that implement it.
|
||||
// MetaSchema returns the meta-schema that is the contract between core or
|
||||
// custom kinds that compose the meta-schema, and the plugin-declared composable
|
||||
// kinds that implement the meta-schema.
|
||||
func (s Slot) MetaSchema() cue.Value {
|
||||
return s.raw
|
||||
}
|
||||
@ -28,15 +32,15 @@ func (s Slot) MetaSchema() cue.Value {
|
||||
// may, whether they must produce one (second return value).
|
||||
//
|
||||
// Expected values here are those in the set of
|
||||
// ["github.com/grafana/grafana/pkg/coremodel/pluginmeta".Type], though passing
|
||||
// ["github.com/grafana/grafana/pkg/plugins/plugindef".Type], though passing
|
||||
// a string not in that set will harmlessly return {false, false}. That type is
|
||||
// not used here to avoid import cycles.
|
||||
//
|
||||
// Note that, at least for now, plugins are not required to provide any slot
|
||||
// implementations, and do so by simply not containing a models.cue file.
|
||||
// Consequently, the "must" return value here is best understood as, "IF a
|
||||
// plugin provides a models.cue file, it MUST contain an implementation of this
|
||||
// slot."
|
||||
// implementations, and do so by simply not containing any .cue files in the
|
||||
// "grafanaplugin" package. Consequently, the "must" return value is best
|
||||
// understood as, "IF a plugin provides a *.cue files, it MUST contain an
|
||||
// implementation of this slot."
|
||||
func (s Slot) ForPluginType(plugintype string) (may, must bool) {
|
||||
must, may = s.plugins[plugintype]
|
||||
return
|
||||
@ -58,8 +62,12 @@ func (s Slot) IsGroup() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func AllSlots() map[string]*Slot {
|
||||
fw := CUEFramework()
|
||||
// AllSlots returns a map of all [Slot]s defined in the Grafana kindsys
|
||||
// framework.
|
||||
//
|
||||
// TODO cache this for core context
|
||||
func AllSlots(ctx *cue.Context) map[string]*Slot {
|
||||
fw := CUEFramework(ctx)
|
||||
slots := make(map[string]*Slot)
|
||||
|
||||
// Ignore err, can only happen if we change structure of fw files, and all we'd
|
29
pkg/kindsys/slot_test.go
Normal file
29
pkg/kindsys/slot_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
package kindsys
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// This is a brick-dumb test that just ensures slots are being loaded correctly
|
||||
// from their declarations in .cue files.
|
||||
//
|
||||
// If this test fails, it's either because:
|
||||
// - They're not being loaded correctly - there's a bug in kindsys somewhere, fix it
|
||||
// - The set of slots names has been modified - update the static list here
|
||||
func TestSlotsAreLoaded(t *testing.T) {
|
||||
slots := []string{"Panel", "Query", "DSOptions"}
|
||||
all := AllSlots(cuecontext.New())
|
||||
var loadedSlots []string
|
||||
for k := range all {
|
||||
loadedSlots = append(loadedSlots, k)
|
||||
}
|
||||
|
||||
sort.Strings(slots)
|
||||
sort.Strings(loadedSlots)
|
||||
|
||||
require.Equal(t, slots, loadedSlots, "slots loaded from cue differs from fixture set - either a bug or fixture needs updating")
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package coremodel
|
||||
package kindsys
|
||||
|
||||
// The slots named and specified in this file are meta-schemas that act as a
|
||||
// shared contract between Grafana plugins (producers) and coremodel types
|
@ -2,7 +2,7 @@ package grafanaplugin
|
||||
|
||||
import (
|
||||
"github.com/grafana/thema"
|
||||
"github.com/grafana/grafana/pkg/framework/coremodel"
|
||||
"github.com/grafana/grafana/kinds/structured/dashboard:kind"
|
||||
)
|
||||
|
||||
_dummy: coremodel.slots
|
||||
|
@ -5,16 +5,14 @@ import (
|
||||
"io/fs"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/ast"
|
||||
"cuelang.org/go/cue/errors"
|
||||
"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/kindsys"
|
||||
"github.com/grafana/grafana/pkg/plugins/plugindef"
|
||||
"github.com/grafana/thema"
|
||||
"github.com/grafana/thema/load"
|
||||
"github.com/grafana/thema/vmux"
|
||||
@ -23,6 +21,8 @@ import (
|
||||
|
||||
// PermittedCUEImports returns the list of packages that may be imported in a
|
||||
// plugin models.cue file.
|
||||
//
|
||||
// TODO probably move this into kindsys
|
||||
func PermittedCUEImports() []string {
|
||||
return []string{
|
||||
"github.com/grafana/thema",
|
||||
@ -41,20 +41,13 @@ func importAllowed(path string) bool {
|
||||
|
||||
var allowedImportsStr string
|
||||
|
||||
// Name expected to be used for all models.cue files in Grafana plugins
|
||||
const pkgname = "grafanaplugin"
|
||||
|
||||
type slotandname struct {
|
||||
name string
|
||||
slot *coremodel.Slot
|
||||
slot *kindsys.Slot
|
||||
}
|
||||
|
||||
var allslots []slotandname
|
||||
|
||||
// TODO re-enable after go1.18
|
||||
var tsch thema.TypedSchema[*pluginmeta.Model]
|
||||
var plugmux vmux.ValueMux[*pluginmeta.Model]
|
||||
|
||||
func init() {
|
||||
var all []string
|
||||
for _, im := range PermittedCUEImports() {
|
||||
@ -62,7 +55,7 @@ func init() {
|
||||
}
|
||||
allowedImportsStr = strings.Join(all, "\n")
|
||||
|
||||
for n, s := range coremodel.AllSlots() {
|
||||
for n, s := range kindsys.AllSlots(nil) {
|
||||
allslots = append(allslots, slotandname{
|
||||
name: n,
|
||||
slot: s,
|
||||
@ -74,42 +67,6 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
var muxonce sync.Once
|
||||
|
||||
// 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
|
||||
// to call it during init(), then the code generator that fixes misalignments
|
||||
// between those two could trigger it if it depends on this package. That would
|
||||
// mean that schema changes to pluginmeta get caught in a loop where the codegen
|
||||
// process can't heal itself.
|
||||
//
|
||||
// In theory, that dependency shouldn't exist - this package should only be
|
||||
// imported for plugin codegen, which should all happen after coremodel codegen.
|
||||
// But in practice, it might exist. And it's really brittle and confusing to
|
||||
// fix if that does happen.
|
||||
//
|
||||
// Better to be resilient to the possibility instead. So, this is a standalone function,
|
||||
// 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
|
||||
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 {
|
||||
raw fs.FS
|
||||
@ -154,7 +111,7 @@ func (tl TreeList) LineagesForSlot(slotname string) map[string]thema.Lineage {
|
||||
// PluginInfo represents everything knowable about a single plugin from static
|
||||
// analysis of its filesystem tree contents.
|
||||
type PluginInfo struct {
|
||||
meta pluginmeta.Model
|
||||
meta plugindef.PluginDef
|
||||
slotimpls map[string]thema.Lineage
|
||||
imports []*ast.ImportSpec
|
||||
}
|
||||
@ -174,7 +131,7 @@ func (pi PluginInfo) SlotImplementations() map[string]thema.Lineage {
|
||||
}
|
||||
|
||||
// Meta returns the metadata declared in the plugin's plugin.json file.
|
||||
func (pi PluginInfo) Meta() pluginmeta.Model {
|
||||
func (pi PluginInfo) Meta() plugindef.PluginDef {
|
||||
return pi.meta
|
||||
}
|
||||
|
||||
@ -183,12 +140,21 @@ func (pi PluginInfo) Meta() pluginmeta.Model {
|
||||
//
|
||||
// It does not descend into subdirectories to search for additional plugin.json
|
||||
// files.
|
||||
//
|
||||
// Calling this with a nil thema.Runtime will take advantage of memoization.
|
||||
// Prefer this approach unless a different thema.Runtime is specifically
|
||||
// required.
|
||||
//
|
||||
// TODO no descent is ok for core plugins, but won't cut it in general
|
||||
func ParsePluginFS(f fs.FS, rt *thema.Runtime) (*Tree, error) {
|
||||
if f == nil {
|
||||
return nil, ErrEmptyFS
|
||||
}
|
||||
_, mux := loadMux()
|
||||
lin, err := plugindef.Lineage(rt)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("plugindef lineage is invalid or broken, needs dev attention: %s", err))
|
||||
}
|
||||
mux := vmux.NewValueMux(lin.TypedSchema(), vmux.NewJSONCodec("plugin.json"))
|
||||
ctx := rt.Context()
|
||||
|
||||
b, err := fs.ReadFile(f, "plugin.json")
|
||||
@ -207,13 +173,11 @@ func ParsePluginFS(f fs.FS, rt *thema.Runtime) (*Tree, error) {
|
||||
}
|
||||
r := &tree.rootinfo
|
||||
|
||||
// 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(b)
|
||||
// Pass the raw bytes into the muxer, get the populated PluginDef 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 plugindef lineage)
|
||||
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 = *pmeta
|
||||
@ -231,9 +195,9 @@ func ParsePluginFS(f fs.FS, rt *thema.Runtime) (*Tree, error) {
|
||||
|
||||
mfs := merged_fs.NewMergedFS(f, grafana.CueSchemaFS)
|
||||
|
||||
// Note that this actually will load any .cue files in the fs.FS root dir in the pkgname.
|
||||
// Note that this actually will load any .cue files in the fs.FS root dir in the plugindef.PkgName.
|
||||
// That's...maybe good? But not what it says on the tin
|
||||
bi, err := load.InstancesWithThema(mfs, "", load.Package(pkgname))
|
||||
bi, err := load.InstanceWithThema(mfs, "", load.Package(plugindef.PkgName))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading models.cue failed: %w", err)
|
||||
}
|
||||
@ -267,7 +231,7 @@ func ParsePluginFS(f fs.FS, rt *thema.Runtime) (*Tree, error) {
|
||||
return tree, nil
|
||||
}
|
||||
|
||||
func bindSlotLineage(v cue.Value, s *coremodel.Slot, meta pluginmeta.Model, rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
|
||||
func bindSlotLineage(v cue.Value, s *kindsys.Slot, meta plugindef.PluginDef, rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
|
||||
accept, required := s.ForPluginType(string(meta.Type))
|
||||
exists := v.Exists()
|
||||
|
||||
|
@ -172,6 +172,7 @@ func TestParseTreeTestdata(t *testing.T) {
|
||||
if tst.err == nil {
|
||||
require.NoError(t, err, "unexpected error while parsing plugin tree")
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, tst.err, "unexpected error type while parsing plugin tree")
|
||||
return
|
||||
}
|
||||
|
132
pkg/plugins/plugindef/gen.go
Normal file
132
pkg/plugins/plugindef/gen.go
Normal file
@ -0,0 +1,132 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"github.com/grafana/codejen"
|
||||
"github.com/grafana/grafana/pkg/codegen"
|
||||
"github.com/grafana/grafana/pkg/cuectx"
|
||||
"github.com/grafana/thema"
|
||||
"github.com/grafana/thema/encoding/gocode"
|
||||
"github.com/grafana/thema/encoding/jsonschema"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
var dirPlugindef = filepath.Join("pkg", "plugins", "plugindef")
|
||||
|
||||
// main generator for plugindef. plugindef isn't a kind, so it has its own
|
||||
// one-off main generator.
|
||||
func main() {
|
||||
v := elsedie(cuectx.BuildGrafanaInstance(nil, dirPlugindef, "", nil))("could not load plugindef cue package")
|
||||
|
||||
lin := elsedie(thema.BindLineage(v, cuectx.GrafanaThemaRuntime()))("plugindef lineage is invalid")
|
||||
|
||||
jl := &codejen.JennyList[thema.Lineage]{}
|
||||
jl.AppendOneToOne(&jennytypego{}, &jennybindgo{})
|
||||
jl.AddPostprocessors(codegen.SlashHeaderMapper(filepath.Join(dirPlugindef, "gen.go")))
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not get working directory: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
grootp := strings.Split(cwd, string(os.PathSeparator))
|
||||
groot := filepath.Join(string(os.PathSeparator), filepath.Join(grootp[:len(grootp)-3]...))
|
||||
|
||||
jfs := elsedie(jl.GenerateFS([]thema.Lineage{lin}))("plugindef jenny pipeline failed")
|
||||
if _, set := os.LookupEnv("CODEGEN_VERIFY"); set {
|
||||
if err := jfs.Verify(context.Background(), groot); err != nil {
|
||||
die(fmt.Errorf("generated code is out of sync with inputs:\n%s\nrun `make gen-cue` to regenerate", err))
|
||||
}
|
||||
} else if err := jfs.Write(context.Background(), groot); err != nil {
|
||||
die(fmt.Errorf("error while writing generated code to disk:\n%s", err))
|
||||
}
|
||||
}
|
||||
|
||||
// one-off jenny for plugindef go types
|
||||
type jennytypego struct{}
|
||||
|
||||
func (j *jennytypego) JennyName() string {
|
||||
return "PluginGoTypes"
|
||||
}
|
||||
|
||||
func (j *jennytypego) Generate(lin thema.Lineage) (*codejen.File, error) {
|
||||
b, err := gocode.GenerateTypesOpenAPI(lin.Latest(), &gocode.TypeConfigOpenAPI{
|
||||
ApplyFuncs: []astutil.ApplyFunc{
|
||||
codegen.PrefixReplacer("Plugindef", "PluginDef"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return codejen.NewFile(filepath.Join(dirPlugindef, "plugindef_types_gen.go"), b, j), nil
|
||||
}
|
||||
|
||||
// one-off jenny for plugindef go bindings
|
||||
type jennybindgo struct{}
|
||||
|
||||
func (j *jennybindgo) JennyName() string {
|
||||
return "PluginGoBindings"
|
||||
}
|
||||
|
||||
func (j *jennybindgo) Generate(lin thema.Lineage) (*codejen.File, error) {
|
||||
b, err := gocode.GenerateLineageBinding(lin, &gocode.BindingConfig{
|
||||
TitleName: "PluginDef",
|
||||
Assignee: ast.NewIdent("*PluginDef"),
|
||||
PrivateFactory: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return codejen.NewFile(filepath.Join(dirPlugindef, "plugindef_bindings_gen.go"), b, j), nil
|
||||
}
|
||||
|
||||
// one-off jenny for plugindef json schema generator
|
||||
type jennyjschema struct{}
|
||||
|
||||
func (j *jennyjschema) JennyName() string {
|
||||
return "PluginJSONSchema"
|
||||
}
|
||||
|
||||
func (j *jennyjschema) Generate(lin thema.Lineage) (*codejen.File, error) {
|
||||
f, err := jsonschema.GenerateSchema(lin.Latest())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, _ := cuecontext.New().BuildFile(f).MarshalJSON()
|
||||
nb := new(bytes.Buffer)
|
||||
die(json.Indent(nb, b, "", " "))
|
||||
return codejen.NewFile(filepath.FromSlash("docs/sources/developers/plugins/plugin.schema.json"), nb.Bytes(), j), nil
|
||||
}
|
||||
|
||||
func elsedie[T any](t T, err error) func(msg string) T {
|
||||
if err != nil {
|
||||
return func(msg string) T {
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", msg, err)
|
||||
os.Exit(1)
|
||||
return t
|
||||
}
|
||||
}
|
||||
return func(msg string) T {
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
func die(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err, "\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package pluginmeta
|
||||
package plugindef
|
||||
|
||||
import (
|
||||
"strings"
|
||||
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
thema.#Lineage
|
||||
name: "pluginmeta"
|
||||
name: "plugindef"
|
||||
seqs: [
|
||||
{
|
||||
schemas: [
|
||||
@ -16,7 +16,7 @@ seqs: [
|
||||
// grafana.com, then the plugin id has to follow the naming
|
||||
// conventions.
|
||||
id: string & strings.MinRunes(1)
|
||||
id: =~"^([0-9a-z]+\\-([0-9a-z]+\\-)?(\(strings.Join([for t in _types {t}], "|"))))|(alertGroups|alertlist|annolist|barchart|bargauge|candlestick|canvas|dashlist|debug|gauge|geomap|gettingstarted|graph|heatmap|heatmap-old|histogram|icon|live|logs|news|nodeGraph|piechart|pluginlist|stat|state-timeline|status-history|table|table-old|text|timeseries|traces|welcome|xychart|alertmanager|cloudwatch|dashboard|elasticsearch|grafana|grafana-azure-monitor-datasource|graphite|influxdb|jaeger|loki|mixed|mssql|mysql|opentsdb|postgres|prometheus|stackdriver|tempo|testdata|zipkin|phlare|parca)$"
|
||||
id: =~"^([0-9a-z]+\\-([0-9a-z]+\\-)?(\(strings.Join([ for t in _types {t}], "|"))))|(alertGroups|alertlist|annolist|barchart|bargauge|candlestick|canvas|dashlist|debug|gauge|geomap|gettingstarted|graph|heatmap|heatmap-old|histogram|icon|live|logs|news|nodeGraph|piechart|pluginlist|stat|state-timeline|status-history|table|table-old|text|timeseries|traces|welcome|xychart|alertmanager|cloudwatch|dashboard|elasticsearch|grafana|grafana-azure-monitor-datasource|graphite|influxdb|jaeger|loki|mixed|mssql|mysql|opentsdb|postgres|prometheus|stackdriver|tempo|testdata|zipkin|phlare|parca)$"
|
||||
|
||||
// The set of all plugin types. This hidden field exists solely
|
||||
// so that the set can be string-interpolated into other fields.
|
||||
@ -132,7 +132,7 @@ seqs: [
|
||||
autoEnabled?: bool
|
||||
|
||||
// Optional list of RBAC RoleRegistrations.
|
||||
// Describes and organizes the default permissions associated with any of the Grafana basic roles,
|
||||
// Describes and organizes the default permissions associated with any of the Grafana basic roles,
|
||||
// which characterizes what viewers, editors, admins, or grafana admins can do on the plugin.
|
||||
// The Admin basic role inherits its default permissions from the Editor basic role which in turn
|
||||
// inherits them from the Viewer basic role.
|
||||
@ -167,7 +167,7 @@ seqs: [
|
||||
// scope.
|
||||
// Example: action: 'test-app.schedules:read', scope: 'test-app.schedules:*'
|
||||
#Permission: {
|
||||
action: string,
|
||||
action: string
|
||||
scope?: string
|
||||
}
|
||||
|
38
pkg/plugins/plugindef/plugindef.go
Normal file
38
pkg/plugins/plugindef/plugindef.go
Normal file
@ -0,0 +1,38 @@
|
||||
package plugindef
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"cuelang.org/go/cue/build"
|
||||
"github.com/grafana/grafana/pkg/cuectx"
|
||||
"github.com/grafana/thema"
|
||||
)
|
||||
|
||||
//go:generate go run gen.go
|
||||
|
||||
// PkgName is the name of the CUE package that Grafana will load when looking
|
||||
// for kind declarations by a Grafana plugin.
|
||||
const PkgName = "grafanaplugin"
|
||||
|
||||
func loadInstanceForplugindef() (*build.Instance, error) {
|
||||
return cuectx.LoadGrafanaInstance("pkg/plugins/plugindef", "", nil)
|
||||
}
|
||||
|
||||
var linonce sync.Once
|
||||
var pdlin thema.ConvergentLineage[*PluginDef]
|
||||
var pdlinerr error
|
||||
|
||||
// Lineage returns the [thema.ConvergentLineage] for plugindef, the canonical
|
||||
// specification for Grafana plugin.json files.
|
||||
//
|
||||
// Unless a custom thema.Runtime is specifically needed, prefer calling this with
|
||||
// nil, as a cached lineage will be returned.
|
||||
func Lineage(rt *thema.Runtime, opts ...thema.BindOption) (thema.ConvergentLineage[*PluginDef], error) {
|
||||
if len(opts) == 0 && (rt == nil || rt == cuectx.GrafanaThemaRuntime()) {
|
||||
linonce.Do(func() {
|
||||
pdlin, pdlinerr = doLineage(rt)
|
||||
})
|
||||
return pdlin, pdlinerr
|
||||
}
|
||||
return doLineage(rt, opts...)
|
||||
}
|
85
pkg/plugins/plugindef/plugindef_bindings_gen.go
Normal file
85
pkg/plugins/plugindef/plugindef_bindings_gen.go
Normal file
@ -0,0 +1,85 @@
|
||||
// THIS FILE IS GENERATED. EDITING IS FUTILE.
|
||||
//
|
||||
// Generated by:
|
||||
// pkg/plugins/plugindef/gen.go
|
||||
// Using jennies:
|
||||
// PluginGoBindings
|
||||
//
|
||||
// Run 'make gen-cue' from repository root to regenerate.
|
||||
|
||||
package plugindef
|
||||
|
||||
import (
|
||||
"cuelang.org/go/cue/build"
|
||||
"github.com/grafana/thema"
|
||||
)
|
||||
|
||||
// doLineage returns a [thema.ConvergentLineage] for the 'plugindef' Thema lineage.
|
||||
//
|
||||
// The lineage is the canonical specification of plugindef. It contains all
|
||||
// schema versions that have ever existed for plugindef, and the lenses that
|
||||
// allow valid instances of one schema in the lineage to be translated to
|
||||
// another schema in the lineage.
|
||||
//
|
||||
// As a [thema.ConvergentLineage], the returned lineage has one primary schema, 0.0,
|
||||
// which is [thema.AssignableTo] [*PluginDef], the lineage's parameterized type.
|
||||
//
|
||||
// This function will return an error if the [Thema invariants] are not met by
|
||||
// the underlying lineage declaration in CUE, or if [*PluginDef] is not
|
||||
// [thema.AssignableTo] the 0.0 schema.
|
||||
//
|
||||
// [Thema's general invariants]: https://github.com/grafana/thema/blob/main/docs/invariants.md
|
||||
func doLineage(rt *thema.Runtime, opts ...thema.BindOption) (thema.ConvergentLineage[*PluginDef], error) {
|
||||
lin, err := baseLineage(rt, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sch := thema.SchemaP(lin, thema.SV(0, 0))
|
||||
typ := new(PluginDef)
|
||||
tsch, err := thema.BindType(sch, typ)
|
||||
if err != nil {
|
||||
// This will error out if the 0.0 schema isn't assignable to
|
||||
// *PluginDef. If Thema also generates that type, this should be unreachable,
|
||||
// barring a critical bug in Thema's Go generator.
|
||||
return nil, err
|
||||
}
|
||||
return tsch.ConvergentLineage(), nil
|
||||
}
|
||||
|
||||
func baseLineage(rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
|
||||
// First, we must get the bytes of the .cue file(s) in which the "plugindef" lineage
|
||||
// is declared, and load them into a
|
||||
// "cuelang.org/go/cue/build".Instance.
|
||||
//
|
||||
// For most Thema-based development workflows, these bytes should come from an embed.FS.
|
||||
// This ensures Go is always compiled with the current state of the .cue files.
|
||||
var inst *build.Instance
|
||||
var err error
|
||||
|
||||
// loadInstanceForplugindef must be manually implemented in another file in this
|
||||
// Go package.
|
||||
inst, err = loadInstanceForplugindef()
|
||||
if err != nil {
|
||||
// Errors at this point indicate a problem with basic loading of .cue file bytes,
|
||||
// which typically means the code generator was misconfigured and a path input
|
||||
// is incorrect.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw := rt.Context().BuildInstance(inst)
|
||||
|
||||
// An error returned from thema.BindLineage indicates one of the following:
|
||||
// - The parsed path does not exist in the loaded CUE file (["github.com/grafana/thema/errors".ErrValueNotExist])
|
||||
// - The value at the parsed path exists, but does not appear to be a Thema
|
||||
// lineage (["github.com/grafana/thema/errors".ErrValueNotALineage])
|
||||
// - The value at the parsed path exists and is a lineage (["github.com/grafana/thema/errors".ErrInvalidLineage]),
|
||||
// but is invalid due to the violation of some general Thema invariant -
|
||||
// for example, declared schemas don't follow backwards compatibility rules,
|
||||
// lenses are incomplete.
|
||||
return thema.BindLineage(raw, rt)
|
||||
}
|
||||
|
||||
// type guards
|
||||
var _ thema.ConvergentLineageFactory[*PluginDef] = doLineage
|
||||
var _ thema.LineageFactory = baseLineage
|
@ -1,21 +1,13 @@
|
||||
// This file is autogenerated. DO NOT EDIT.
|
||||
// THIS FILE IS GENERATED. EDITING IS FUTILE.
|
||||
//
|
||||
// Generated by pkg/framework/coremodel/gen.go
|
||||
// Generated by:
|
||||
// pkg/plugins/plugindef/gen.go
|
||||
// Using jennies:
|
||||
// PluginGoTypes
|
||||
//
|
||||
// Derived from the Thema lineage declared in pkg/coremodel/pluginmeta/coremodel.cue
|
||||
//
|
||||
// Run `make gen-cue` from repository root to regenerate.
|
||||
// Run 'make gen-cue' from repository root to regenerate.
|
||||
|
||||
package pluginmeta
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/grafana/grafana/pkg/cuectx"
|
||||
"github.com/grafana/grafana/pkg/framework/coremodel"
|
||||
"github.com/grafana/thema"
|
||||
)
|
||||
package plugindef
|
||||
|
||||
// Defines values for Category.
|
||||
const (
|
||||
@ -117,11 +109,8 @@ const (
|
||||
RoleRegistrationGrantsViewer RoleRegistrationGrants = "Viewer"
|
||||
)
|
||||
|
||||
// Model is the Go representation of a pluginmeta.
|
||||
//
|
||||
// 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 Model struct {
|
||||
// PluginDef defines model for plugindef.
|
||||
type PluginDef struct {
|
||||
// For data source plugins, if the plugin supports alerting.
|
||||
Alerting *bool `json:"alerting,omitempty"`
|
||||
|
||||
@ -308,30 +297,18 @@ type Model struct {
|
||||
}
|
||||
|
||||
// Plugin category used on the Add data source page.
|
||||
//
|
||||
// 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 Category string
|
||||
|
||||
// type indicates which type of Grafana plugin this is, of the defined
|
||||
// set of Grafana plugin types.
|
||||
//
|
||||
// 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 Type string
|
||||
|
||||
// BasicRole is a Grafana basic role, which can be 'Viewer', 'Editor', 'Admin' or 'Grafana Admin'.
|
||||
// With RBAC, the Admin basic role inherits its default permissions from the Editor basic role which
|
||||
// in turn inherits them from the Viewer basic role.
|
||||
//
|
||||
// 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 BasicRole string
|
||||
|
||||
// BuildInfo is the Go representation of a pluginmeta.BuildInfo.
|
||||
//
|
||||
// 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.
|
||||
// BuildInfo defines model for plugindef.BuildInfo.
|
||||
type BuildInfo struct {
|
||||
// Git branch the plugin was built from.
|
||||
Branch *string `json:"branch,omitempty"`
|
||||
@ -348,10 +325,7 @@ type BuildInfo struct {
|
||||
Time *int64 `json:"time,omitempty"`
|
||||
}
|
||||
|
||||
// Dependencies is the Go representation of a pluginmeta.Dependencies.
|
||||
//
|
||||
// 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.
|
||||
// Dependencies defines model for plugindef.Dependencies.
|
||||
type Dependencies struct {
|
||||
// Required Grafana version for this plugin. Validated using
|
||||
// https://github.com/npm/node-semver.
|
||||
@ -369,9 +343,6 @@ type Dependencies struct {
|
||||
// Dependency describes another plugin on which a plugin depends.
|
||||
// The id refers to the plugin package identifier, as given on
|
||||
// the grafana.com plugin marketplace.
|
||||
//
|
||||
// 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 Dependency struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@ -379,26 +350,17 @@ type Dependency struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// DependencyType is the Go representation of a Dependency.Type.
|
||||
//
|
||||
// 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.
|
||||
// DependencyType defines model for Dependency.Type.
|
||||
type DependencyType string
|
||||
|
||||
// Header describes an HTTP header that is forwarded with a proxied request for
|
||||
// a plugin route.
|
||||
//
|
||||
// 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 Header struct {
|
||||
Content string `json:"content"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// A resource to be included in a plugin.
|
||||
//
|
||||
// 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 Include struct {
|
||||
// Add the include to the side menu.
|
||||
AddToNav *bool `json:"addToNav,omitempty"`
|
||||
@ -424,23 +386,14 @@ type Include struct {
|
||||
Uid *string `json:"uid,omitempty"`
|
||||
}
|
||||
|
||||
// IncludeRole is the Go representation of a Include.Role.
|
||||
//
|
||||
// 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.
|
||||
// IncludeRole defines model for Include.Role.
|
||||
type IncludeRole string
|
||||
|
||||
// IncludeType is the Go representation of a Include.Type.
|
||||
//
|
||||
// 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.
|
||||
// IncludeType defines model for Include.Type.
|
||||
type IncludeType string
|
||||
|
||||
// Metadata about a Grafana plugin. Some fields are used on the plugins
|
||||
// page in Grafana and others on grafana.com, if the plugin is published.
|
||||
//
|
||||
// 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 Info struct {
|
||||
// Information about the plugin author.
|
||||
Author *struct {
|
||||
@ -497,9 +450,6 @@ type Info struct {
|
||||
|
||||
// TODO docs
|
||||
// TODO should this really be separate from TokenAuth?
|
||||
//
|
||||
// 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 JWTTokenAuth struct {
|
||||
// Parameters for the JWT token authentication request.
|
||||
Params map[string]interface{} `json:"params"`
|
||||
@ -515,26 +465,17 @@ type JWTTokenAuth struct {
|
||||
// Permission describes an RBAC permission on the plugin. A permission has an action and an option
|
||||
// scope.
|
||||
// Example: action: 'test-app.schedules:read', scope: 'test-app.schedules:*'
|
||||
//
|
||||
// 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 Permission struct {
|
||||
Action string `json:"action"`
|
||||
Scope *string `json:"scope,omitempty"`
|
||||
}
|
||||
|
||||
// ReleaseState indicates release maturity state of a plugin.
|
||||
//
|
||||
// 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 ReleaseState string
|
||||
|
||||
// Role describes an RBAC role which allows grouping multiple related permissions on the plugin,
|
||||
// each of which has an action and an optional scope.
|
||||
// Example: the role 'Schedules Reader' bundles permissions to view all schedules of the plugin.
|
||||
//
|
||||
// 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 Role struct {
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
@ -549,9 +490,6 @@ type Role struct {
|
||||
// will get them by default.
|
||||
// Example: the role 'Schedules Reader' bundles permissions to view all schedules of the plugin
|
||||
// which will be granted to Admins by default.
|
||||
//
|
||||
// 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 RoleRegistration struct {
|
||||
// Default assignment of the role to Grafana basic roles (Viewer, Editor, Admin, Grafana Admin)
|
||||
// The Admin basic role inherits its default permissions from the Editor basic role which in turn
|
||||
@ -569,19 +507,13 @@ type RoleRegistration struct {
|
||||
} `json:"role"`
|
||||
}
|
||||
|
||||
// RoleRegistrationGrants is the Go representation of a RoleRegistration.Grants.
|
||||
//
|
||||
// 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.
|
||||
// RoleRegistrationGrants defines model for RoleRegistration.Grants.
|
||||
type RoleRegistrationGrants string
|
||||
|
||||
// A proxy route used in datasource plugins for plugin authentication
|
||||
// and adding headers to HTTP requests made by the plugin.
|
||||
// For more information, refer to [Authentication for data source
|
||||
// plugins](https://grafana.com/docs/grafana/latest/developers/plugins/authentication/).
|
||||
//
|
||||
// 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 Route struct {
|
||||
// For data source plugins. Route headers set the body content and
|
||||
// length to the proxied request.
|
||||
@ -616,9 +548,6 @@ type Route struct {
|
||||
}
|
||||
|
||||
// TODO docs
|
||||
//
|
||||
// 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 TokenAuth struct {
|
||||
// Parameters for the token authentication request.
|
||||
Params map[string]interface{} `json:"params"`
|
||||
@ -633,70 +562,7 @@ type TokenAuth struct {
|
||||
|
||||
// URLParam describes query string parameters for
|
||||
// a url in a plugin route
|
||||
//
|
||||
// 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 URLParam struct {
|
||||
Content string `json:"content"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
//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(0, 0)
|
||||
|
||||
// Lineage returns the Thema lineage representing a Grafana pluginmeta.
|
||||
//
|
||||
// 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(rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) {
|
||||
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "pluginmeta"), cueFS, rt, opts...)
|
||||
}
|
||||
|
||||
var _ thema.LineageFactory = Lineage
|
||||
var _ coremodel.Interface = &Coremodel{}
|
||||
|
||||
// Coremodel contains the foundational schema declaration for pluginmetas.
|
||||
// It implements coremodel.Interface.
|
||||
type Coremodel struct {
|
||||
lin thema.Lineage
|
||||
}
|
||||
|
||||
// Lineage returns the canonical pluginmeta Lineage.
|
||||
func (c *Coremodel) Lineage() thema.Lineage {
|
||||
return c.lin
|
||||
}
|
||||
|
||||
// CurrentSchema returns the current (latest) pluginmeta 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 pluginmeta 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(rt *thema.Runtime) (*Coremodel, error) {
|
||||
lin, err := Lineage(rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Coremodel{
|
||||
lin: lin,
|
||||
}, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user