From 1181141b401e8763485c89f415ec8f8563467843 Mon Sep 17 00:00:00 2001 From: Selene Date: Thu, 7 Mar 2024 11:09:19 +0100 Subject: [PATCH] Schemas: Refactor plugin's metadata (#83696) * Remove kinds verification for kind-registry * Read plugin's json information with json library instead of use thema binding * Remove grafanaplugin unification * Don't use kindsys for extract the slot name * Fix IsGroup * Remove all plugindef generation * Refactor schema interfaces * Pushed this change from a different branch by mistake... * Create small plugin definition structure adding additional information for plugins registration * Add some validation checks * Delete unused code * Fix imports lint --- Makefile | 1 - embed.go | 2 +- pkg/api/dtos/plugins.go | 4 +- pkg/api/plugins.go | 4 +- pkg/api/plugins_test.go | 6 +- pkg/plugins/auth/models.go | 4 +- pkg/plugins/codegen/jenny_plugingotypes.go | 4 +- pkg/plugins/codegen/jenny_pluginseachmajor.go | 4 +- pkg/plugins/codegen/jenny_plugintstypes.go | 2 +- pkg/plugins/manager/fakes/fakes.go | 4 +- pkg/plugins/pfs/decl.go | 20 +- pkg/plugins/pfs/decl_parser.go | 16 +- pkg/plugins/pfs/errors.go | 10 - pkg/plugins/pfs/grafanaplugin.cue | 31 -- pkg/plugins/pfs/pfs.go | 151 +++--- pkg/plugins/pfs/plugin.go | 4 +- pkg/plugins/pfs/plugindef_types.go | 42 ++ pkg/plugins/plugindef/gen.go | 130 ----- pkg/plugins/plugindef/pascal_test.go | 43 -- pkg/plugins/plugindef/plugindef.cue | 428 ---------------- pkg/plugins/plugindef/plugindef.go | 73 --- .../plugindef/plugindef_bindings_gen.go | 84 --- pkg/plugins/plugindef/plugindef_types_gen.go | 484 ------------------ pkg/plugins/plugins.go | 4 +- .../pluginsintegration/loader/loader_test.go | 6 +- .../pluginsintegration/pipeline/steps.go | 4 +- .../pluginconfig/envvars_test.go | 4 +- .../serviceregistration.go | 8 +- public/app/plugins/gen.go | 5 +- 29 files changed, 181 insertions(+), 1401 deletions(-) delete mode 100644 pkg/plugins/pfs/grafanaplugin.cue create mode 100644 pkg/plugins/pfs/plugindef_types.go delete mode 100644 pkg/plugins/plugindef/gen.go delete mode 100644 pkg/plugins/plugindef/pascal_test.go delete mode 100644 pkg/plugins/plugindef/plugindef.cue delete mode 100644 pkg/plugins/plugindef/plugindef.go delete mode 100644 pkg/plugins/plugindef/plugindef_bindings_gen.go delete mode 100644 pkg/plugins/plugindef/plugindef_types_gen.go diff --git a/Makefile b/Makefile index 896493bc40e..f6f07671f02 100644 --- a/Makefile +++ b/Makefile @@ -103,7 +103,6 @@ openapi3-gen: swagger-gen ## Generates OpenApi 3 specs from the Swagger 2 alread ##@ 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 ./public/app/plugins/gen.go diff --git a/embed.go b/embed.go index 570415e0034..e022a032c45 100644 --- a/embed.go +++ b/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/common/*.cue public/app/plugins/*/*/*.cue public/app/plugins/*/*/plugin.json pkg/plugins/*/*.cue +//go:embed cue.mod/module.cue kinds/*.cue kinds/*/*.cue packages/grafana-schema/src/common/*.cue public/app/plugins/*/*/*.cue public/app/plugins/*/*/plugin.json var CueSchemaFS embed.FS diff --git a/pkg/api/dtos/plugins.go b/pkg/api/dtos/plugins.go index 0f50b6cbd7c..7d768e059e4 100644 --- a/pkg/api/dtos/plugins.go +++ b/pkg/api/dtos/plugins.go @@ -2,7 +2,7 @@ package dtos import ( "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/plugins/plugindef" + "github.com/grafana/grafana/pkg/plugins/pfs" "github.com/grafana/grafana/pkg/services/accesscontrol" ) @@ -48,7 +48,7 @@ type PluginListItem struct { SignatureOrg string `json:"signatureOrg"` AccessControl accesscontrol.Metadata `json:"accessControl,omitempty"` AngularDetected bool `json:"angularDetected"` - IAM *plugindef.IAM `json:"iam,omitempty"` + IAM *pfs.IAM `json:"iam,omitempty"` } type PluginList []PluginListItem diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index 55a610891ee..59209f44910 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -21,7 +21,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/plugins/plugindef" + "github.com/grafana/grafana/pkg/plugins/pfs" "github.com/grafana/grafana/pkg/plugins/repo" ac "github.com/grafana/grafana/pkg/services/accesscontrol" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" @@ -552,7 +552,7 @@ func (hs *HTTPServer) hasPluginRequestedPermissions(c *contextmodel.ReqContext, } // evalAllPermissions generates an evaluator with all permissions from the input slice -func evalAllPermissions(ps []plugindef.Permission) ac.Evaluator { +func evalAllPermissions(ps []pfs.Permission) ac.Evaluator { res := []ac.Evaluator{} for _, p := range ps { if p.Scope != nil { diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go index 4d6841bc281..9fdcf916ebc 100644 --- a/pkg/api/plugins_test.go +++ b/pkg/api/plugins_test.go @@ -27,7 +27,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/grafana/grafana/pkg/plugins/manager/filestore" "github.com/grafana/grafana/pkg/plugins/manager/registry" - "github.com/grafana/grafana/pkg/plugins/plugindef" + "github.com/grafana/grafana/pkg/plugins/pfs" "github.com/grafana/grafana/pkg/plugins/pluginscdn" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" @@ -658,8 +658,8 @@ func TestHTTPServer_hasPluginRequestedPermissions(t *testing.T) { pluginReg := pluginstore.Plugin{ JSONData: plugins.JSONData{ ID: "grafana-test-app", - IAM: &plugindef.IAM{ - Permissions: []plugindef.Permission{{Action: ac.ActionUsersRead, Scope: newStr(ac.ScopeUsersAll)}, {Action: ac.ActionUsersCreate}}, + IAM: &pfs.IAM{ + Permissions: []pfs.Permission{{Action: ac.ActionUsersRead, Scope: newStr(ac.ScopeUsersAll)}, {Action: ac.ActionUsersCreate}}, }, }, } diff --git a/pkg/plugins/auth/models.go b/pkg/plugins/auth/models.go index b236353116f..93b97f0394c 100644 --- a/pkg/plugins/auth/models.go +++ b/pkg/plugins/auth/models.go @@ -3,7 +3,7 @@ package auth import ( "context" - "github.com/grafana/grafana/pkg/plugins/plugindef" + "github.com/grafana/grafana/pkg/plugins/pfs" ) type ExternalService struct { @@ -14,6 +14,6 @@ type ExternalService struct { type ExternalServiceRegistry interface { HasExternalService(ctx context.Context, pluginID string) (bool, error) - RegisterExternalService(ctx context.Context, pluginID string, pType plugindef.Type, svc *plugindef.IAM) (*ExternalService, error) + RegisterExternalService(ctx context.Context, pluginID string, pType pfs.Type, svc *pfs.IAM) (*ExternalService, error) RemoveExternalService(ctx context.Context, pluginID string) error } diff --git a/pkg/plugins/codegen/jenny_plugingotypes.go b/pkg/plugins/codegen/jenny_plugingotypes.go index 9547e2d09f0..16062b60e2a 100644 --- a/pkg/plugins/codegen/jenny_plugingotypes.go +++ b/pkg/plugins/codegen/jenny_plugingotypes.go @@ -35,10 +35,10 @@ func (j *pgoJenny) Generate(decl *pfs.PluginDecl) (*codejen.File, error) { return nil, nil } - slotname := strings.ToLower(decl.SchemaInterface.Name()) + slotname := strings.ToLower(decl.SchemaInterface.Name) byt, err := gocode.GenerateTypesOpenAPI(decl.Lineage.Latest(), &gocode.TypeConfigOpenAPI{ Config: &openapi.Config{ - Group: decl.SchemaInterface.IsGroup(), + Group: decl.SchemaInterface.IsGroup, Config: &copenapi.Config{ MaxCycleDepth: 10, }, diff --git a/pkg/plugins/codegen/jenny_pluginseachmajor.go b/pkg/plugins/codegen/jenny_pluginseachmajor.go index 6d3e6d26821..e90a11a984b 100644 --- a/pkg/plugins/codegen/jenny_pluginseachmajor.go +++ b/pkg/plugins/codegen/jenny_pluginseachmajor.go @@ -42,8 +42,8 @@ func (j *pleJenny) Generate(decl *pfs.PluginDecl) (codejen.Files, error) { } version := "export const pluginVersion = \"%s\";" - if decl.PluginMeta.Info.Version != nil { - version = fmt.Sprintf(version, *decl.PluginMeta.Info.Version) + if decl.PluginMeta.Version != nil { + version = fmt.Sprintf(version, *decl.PluginMeta.Version) } else { version = fmt.Sprintf(version, getGrafanaVersion()) } diff --git a/pkg/plugins/codegen/jenny_plugintstypes.go b/pkg/plugins/codegen/jenny_plugintstypes.go index 1bedbaea727..23c00a5ca55 100644 --- a/pkg/plugins/codegen/jenny_plugintstypes.go +++ b/pkg/plugins/codegen/jenny_plugintstypes.go @@ -51,7 +51,7 @@ func (j *ptsJenny) Generate(decl *pfs.PluginDecl) (*codejen.File, error) { Data: string(jf.Data), }) - path := filepath.Join(j.root, decl.PluginPath, fmt.Sprintf("%s.gen.ts", strings.ToLower(decl.SchemaInterface.Name()))) + path := filepath.Join(j.root, decl.PluginPath, fmt.Sprintf("%s.gen.ts", strings.ToLower(decl.SchemaInterface.Name))) data := []byte(tsf.String()) data = data[:len(data)-1] // remove the additional line break added by the inner jenny diff --git a/pkg/plugins/manager/fakes/fakes.go b/pkg/plugins/manager/fakes/fakes.go index 17e39fe86ee..361d2d90d23 100644 --- a/pkg/plugins/manager/fakes/fakes.go +++ b/pkg/plugins/manager/fakes/fakes.go @@ -13,7 +13,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/auth" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/log" - "github.com/grafana/grafana/pkg/plugins/plugindef" + "github.com/grafana/grafana/pkg/plugins/pfs" "github.com/grafana/grafana/pkg/plugins/repo" "github.com/grafana/grafana/pkg/plugins/storage" ) @@ -456,7 +456,7 @@ func (f *FakeAuthService) HasExternalService(ctx context.Context, pluginID strin return f.Result != nil, nil } -func (f *FakeAuthService) RegisterExternalService(ctx context.Context, pluginID string, pType plugindef.Type, svc *plugindef.IAM) (*auth.ExternalService, error) { +func (f *FakeAuthService) RegisterExternalService(ctx context.Context, pluginID string, pType pfs.Type, svc *pfs.IAM) (*auth.ExternalService, error) { return f.Result, nil } diff --git a/pkg/plugins/pfs/decl.go b/pkg/plugins/pfs/decl.go index 37a3916d904..406275d3c85 100644 --- a/pkg/plugins/pfs/decl.go +++ b/pkg/plugins/pfs/decl.go @@ -4,20 +4,30 @@ import ( "cuelang.org/go/cue/ast" "github.com/grafana/kindsys" "github.com/grafana/thema" - - "github.com/grafana/grafana/pkg/plugins/plugindef" ) type PluginDecl struct { - SchemaInterface *kindsys.SchemaInterface + SchemaInterface *SchemaInterface Lineage thema.Lineage Imports []*ast.ImportSpec PluginPath string - PluginMeta plugindef.PluginDef + PluginMeta Metadata KindDecl kindsys.Def[kindsys.ComposableProperties] } -func EmptyPluginDecl(path string, meta plugindef.PluginDef) *PluginDecl { +type SchemaInterface struct { + Name string + IsGroup bool +} + +type Metadata struct { + Id string + Name string + Backend *bool + Version *string +} + +func EmptyPluginDecl(path string, meta Metadata) *PluginDecl { return &PluginDecl{ PluginPath: path, PluginMeta: meta, diff --git a/pkg/plugins/pfs/decl_parser.go b/pkg/plugins/pfs/decl_parser.go index 04833a425d5..4e909b16ab0 100644 --- a/pkg/plugins/pfs/decl_parser.go +++ b/pkg/plugins/pfs/decl_parser.go @@ -6,7 +6,6 @@ import ( "path/filepath" "sort" - "github.com/grafana/kindsys" "github.com/grafana/thema" ) @@ -15,6 +14,18 @@ type declParser struct { skip map[string]bool } +// Extracted from kindsys repository +var schemaInterfaces = map[string]*SchemaInterface{ + "PanelCfg": { + Name: "PanelCfg", + IsGroup: true, + }, + "DataQuery": { + Name: "DataQuery", + IsGroup: false, + }, +} + func NewDeclParser(rt *thema.Runtime, skip map[string]bool) *declParser { return &declParser{ rt: rt, @@ -50,12 +61,11 @@ func (psr *declParser) Parse(root fs.FS) ([]*PluginDecl, error) { } for slotName, kind := range pp.ComposableKinds { - slot, err := kindsys.FindSchemaInterface(slotName) if err != nil { return nil, fmt.Errorf("parsing plugin failed for %s: %s", dir, err) } decls = append(decls, &PluginDecl{ - SchemaInterface: &slot, + SchemaInterface: schemaInterfaces[slotName], Lineage: kind.Lineage(), Imports: pp.CUEImports, PluginMeta: pp.Properties, diff --git a/pkg/plugins/pfs/errors.go b/pkg/plugins/pfs/errors.go index a65c588a0d7..6bbd1cc093c 100644 --- a/pkg/plugins/pfs/errors.go +++ b/pkg/plugins/pfs/errors.go @@ -11,16 +11,6 @@ var ErrNoRootFile = errors.New("no plugin.json at root of fs.fS") // ErrInvalidRootFile indicates that the root plugin.json file is invalid. var ErrInvalidRootFile = errors.New("plugin.json is invalid") -// ErrComposableNotExpected indicates that a plugin has a composable kind for a -// schema interface that is not expected, given the type of the plugin. (For -// example, a datasource plugin has a panelcfg composable kind) -var ErrComposableNotExpected = errors.New("plugin type should not produce composable kind for schema interface") - -// ErrExpectedComposable indicates that a plugin lacks a composable kind -// implementation for a schema interface that is expected for that plugin's -// type. (For example, a datasource plugin lacks a queries composable kind) -var ErrExpectedComposable = errors.New("plugin type should produce composable kind for schema interface") - // ErrInvalidGrafanaPluginInstance indicates a plugin's set of .cue // grafanaplugin package files are invalid with respect to the GrafanaPlugin // spec. diff --git a/pkg/plugins/pfs/grafanaplugin.cue b/pkg/plugins/pfs/grafanaplugin.cue deleted file mode 100644 index 6adb68555a1..00000000000 --- a/pkg/plugins/pfs/grafanaplugin.cue +++ /dev/null @@ -1,31 +0,0 @@ -package pfs - -import ( - "github.com/grafana/kindsys" -) - -// GrafanaPlugin specifies what plugins may declare in .cue files in a -// `grafanaplugin` CUE package in the plugin root directory (adjacent to plugin.json). -GrafanaPlugin: { - // id and pascalName are injected from plugin.json. Plugin authors can write - // values for them in .cue files, but the only valid values will be the ones - // given in plugin.json. - id: string - pascalName: string - - // A plugin defines its Composable kinds under this key. - // - // This struct is open for forwards compatibility - older versions of Grafana (or - // dependent tooling) should not break if new versions introduce additional schema interfaces. - composableKinds?: [Iface=string]: kindsys.Composable & { - name: pascalName + Iface - schemaInterface: Iface - lineage: name: pascalName + Iface - } - - // A plugin defines its Custom kinds under this key. - customKinds?: [Name=string]: kindsys.Custom & { - name: Name - } - ... -} diff --git a/pkg/plugins/pfs/pfs.go b/pkg/plugins/pfs/pfs.go index 73cb4a3f33c..b56f5fdf652 100644 --- a/pkg/plugins/pfs/pfs.go +++ b/pkg/plugins/pfs/pfs.go @@ -1,56 +1,29 @@ package pfs import ( + "encoding/json" "fmt" "io/fs" - "path/filepath" "sort" "strings" - "sync" "testing/fstest" "cuelang.org/go/cue" - "cuelang.org/go/cue/build" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" - "cuelang.org/go/cue/parser" "cuelang.org/go/cue/token" "github.com/grafana/kindsys" "github.com/grafana/thema" "github.com/grafana/thema/load" - "github.com/grafana/thema/vmux" "github.com/yalue/merged_fs" "github.com/grafana/grafana/pkg/cuectx" - "github.com/grafana/grafana/pkg/plugins/plugindef" ) // PackageName is the name of the CUE package that Grafana will load when // looking for a Grafana plugin's kind declarations. const PackageName = "grafanaplugin" -var onceGP sync.Once -var defaultGP cue.Value - -func doLoadGP(ctx *cue.Context) cue.Value { - v, err := cuectx.BuildGrafanaInstance(ctx, filepath.Join("pkg", "plugins", "pfs"), "pfs", nil) - if err != nil { - // should be unreachable - panic(err) - } - return v.LookupPath(cue.MakePath(cue.Str("GrafanaPlugin"))) -} - -func loadGP(ctx *cue.Context) cue.Value { - if ctx == nil || ctx == cuectx.GrafanaCUEContext() { - onceGP.Do(func() { - defaultGP = doLoadGP(ctx) - }) - return defaultGP - } - return doLoadGP(ctx) -} - // PermittedCUEImports returns the list of import paths that may be used in a // plugin's grafanaplugin cue package. var PermittedCUEImports = cuectx.PermittedCUEImports @@ -109,35 +82,14 @@ func ParsePluginFS(fsys fs.FS, rt *thema.Runtime) (ParsedPlugin, error) { rt = cuectx.GrafanaThemaRuntime() } - lin, err := plugindef.Lineage(rt) + metadata, err := getPluginMetadata(fsys) if err != nil { - panic(fmt.Sprintf("plugindef lineage is invalid or broken, needs dev attention: %s", err)) - } - ctx := rt.Context() - - b, err := fs.ReadFile(fsys, "plugin.json") - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - return ParsedPlugin{}, ErrNoRootFile - } - return ParsedPlugin{}, fmt.Errorf("error reading plugin.json: %w", err) + return ParsedPlugin{}, err } pp := ParsedPlugin{ ComposableKinds: make(map[string]kindsys.Composable), - // CustomKinds: make(map[string]kindsys.Custom), - } - - // 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) - pinst, _, err := vmux.NewTypedMux(lin.TypedSchema(), vmux.NewJSONCodec("plugin.json"))(b) - if err != nil { - return ParsedPlugin{}, errors.Wrap(errors.Promote(err, ""), ErrInvalidRootFile) - } - pp.Properties = *(pinst.ValueP()) - // FIXME remove this once it's being correctly populated coming out of lineage - if pp.Properties.PascalName == "" { - pp.Properties.PascalName = plugindef.DerivePascalName(pp.Properties) + Properties: metadata, } if cuefiles, err := fs.Glob(fsys, "*.cue"); err != nil { @@ -146,8 +98,6 @@ func ParsePluginFS(fsys fs.FS, rt *thema.Runtime) (ParsedPlugin, error) { return pp, nil } - gpv := loadGP(rt.Context()) - fsys, err = ensureCueMod(fsys, pp.Properties) if err != nil { return ParsedPlugin{}, fmt.Errorf("%s has invalid cue.mod: %w", pp.Properties.Id, err) @@ -161,11 +111,6 @@ func ParsePluginFS(fsys fs.FS, rt *thema.Runtime) (ParsedPlugin, error) { return ParsedPlugin{}, errors.Wrap(errors.Newf(token.NoPos, "%s did not load", pp.Properties.Id), err) } - f, _ := parser.ParseFile("plugin.json", fmt.Sprintf(`{ - "id": %q, - "pascalName": %q - }`, pp.Properties.Id, pp.Properties.PascalName)) - for _, f := range bi.Files { for _, im := range f.Imports { ip := strings.Trim(im.Path.Value, "\"") @@ -187,16 +132,7 @@ func ParsePluginFS(fsys fs.FS, rt *thema.Runtime) (ParsedPlugin, error) { panic("Refactor required - upstream CUE implementation changed, bi.Files is no longer populated") } - // Inject the JSON directly into the build so it gets loaded together - bi.BuildFiles = append(bi.BuildFiles, &build.File{ - Filename: "plugin.json", - Encoding: build.JSON, - Form: build.Data, - Source: b, - }) - bi.Files = append(bi.Files, f) - - gpi := ctx.BuildInstance(bi).Unify(gpv) + gpi := rt.Context().BuildInstance(bi) if gpi.Err() != nil { return ParsedPlugin{}, errors.Wrap(errors.Promote(ErrInvalidGrafanaPluginInstance, pp.Properties.Id), gpi.Err()) } @@ -207,6 +143,18 @@ func ParsePluginFS(fsys fs.FS, rt *thema.Runtime) (ParsedPlugin, error) { continue } + iv = iv.FillPath(cue.MakePath(cue.Str("schemaInterface")), si.Name()) + iv = iv.FillPath(cue.MakePath(cue.Str("name")), derivePascalName(pp.Properties.Id, pp.Properties.Name)+si.Name()) + lineageNamePath := iv.LookupPath(cue.MakePath(cue.Str("lineage"), cue.Str("name"))) + if !lineageNamePath.Exists() { + iv = iv.FillPath(cue.MakePath(cue.Str("lineage"), cue.Str("name")), derivePascalName(pp.Properties.Id, pp.Properties.Name)+si.Name()) + } + + validSchema := iv.LookupPath(cue.ParsePath("lineage.schemas[0].schema")) + if !validSchema.Exists() { + return ParsedPlugin{}, errors.Wrap(errors.Promote(ErrInvalidGrafanaPluginInstance, pp.Properties.Id), validSchema.Err()) + } + props, err := kindsys.ToKindProps[kindsys.ComposableProperties](iv) if err != nil { return ParsedPlugin{}, err @@ -222,7 +170,6 @@ func ParsePluginFS(fsys fs.FS, rt *thema.Runtime) (ParsedPlugin, error) { pp.ComposableKinds[si.Name()] = compo } - // TODO custom kinds return pp, nil } @@ -237,7 +184,7 @@ func ParsePluginFS(fsys fs.FS, rt *thema.Runtime) (ParsedPlugin, error) { func LoadComposableKindDef(fsys fs.FS, rt *thema.Runtime, defpath string) (kindsys.Def[kindsys.ComposableProperties], error) { pp := ParsedPlugin{ ComposableKinds: make(map[string]kindsys.Composable), - Properties: plugindef.PluginDef{ + Properties: Metadata{ Id: defpath, }, } @@ -269,13 +216,13 @@ func LoadComposableKindDef(fsys fs.FS, rt *thema.Runtime, defpath string) (kinds }, nil } -func ensureCueMod(fsys fs.FS, pdef plugindef.PluginDef) (fs.FS, error) { +func ensureCueMod(fsys fs.FS, metadata Metadata) (fs.FS, error) { if modf, err := fs.ReadFile(fsys, "cue.mod/module.cue"); err != nil { if !errors.Is(err, fs.ErrNotExist) { return nil, err } return merged_fs.NewMergedFS(fsys, fstest.MapFS{ - "cue.mod/module.cue": &fstest.MapFile{Data: []byte(fmt.Sprintf(`module: "grafana.com/grafana/plugins/%s"`, pdef.Id))}, + "cue.mod/module.cue": &fstest.MapFile{Data: []byte(fmt.Sprintf(`module: "grafana.com/grafana/plugins/%s"`, metadata.Id))}, }), nil } else if _, err := cuecontext.New().CompileBytes(modf).LookupPath(cue.MakePath(cue.Str("module"))).String(); err != nil { return nil, fmt.Errorf("error reading cue module name: %w", err) @@ -283,3 +230,61 @@ func ensureCueMod(fsys fs.FS, pdef plugindef.PluginDef) (fs.FS, error) { return fsys, nil } + +func getPluginMetadata(fsys fs.FS) (Metadata, error) { + b, err := fs.ReadFile(fsys, "plugin.json") + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return Metadata{}, ErrNoRootFile + } + return Metadata{}, fmt.Errorf("error reading plugin.json: %w", err) + } + + var metadata PluginDef + if err := json.Unmarshal(b, &metadata); err != nil { + return Metadata{}, fmt.Errorf("error unmarshalling plugin.json: %s", err) + } + + if err := metadata.Validate(); err != nil { + return Metadata{}, err + } + + return Metadata{ + Id: metadata.Id, + Name: metadata.Name, + Backend: metadata.Backend, + Version: metadata.Info.Version, + }, nil +} + +func derivePascalName(id string, name string) string { + sani := func(s string) string { + ret := strings.Title(strings.Map(func(r rune) rune { + switch { + case r >= 'a' && r <= 'z': + return r + case r >= 'A' && r <= 'Z': + return r + default: + return -1 + } + }, strings.Title(strings.Map(func(r rune) rune { + switch r { + case '-', '_': + return ' ' + default: + return r + } + }, s)))) + if len(ret) > 63 { + return ret[:63] + } + return ret + } + + fromname := sani(name) + if len(fromname) != 0 { + return fromname + } + return sani(strings.Split(id, "-")[1]) +} diff --git a/pkg/plugins/pfs/plugin.go b/pkg/plugins/pfs/plugin.go index e5b95dae398..2829a81046d 100644 --- a/pkg/plugins/pfs/plugin.go +++ b/pkg/plugins/pfs/plugin.go @@ -3,8 +3,6 @@ package pfs import ( "cuelang.org/go/cue/ast" "github.com/grafana/kindsys" - - "github.com/grafana/grafana/pkg/plugins/plugindef" ) // ParsedPlugin represents everything knowable about a single plugin from static @@ -14,7 +12,7 @@ import ( // struct returned from [ParsePluginFS]. type ParsedPlugin struct { // Properties contains the plugin's definition, as declared in plugin.json. - Properties plugindef.PluginDef + Properties Metadata // ComposableKinds is a map of all the composable kinds declared in this plugin. // Keys are the name of the [kindsys.SchemaInterface] implemented by the value. diff --git a/pkg/plugins/pfs/plugindef_types.go b/pkg/plugins/pfs/plugindef_types.go new file mode 100644 index 00000000000..841200428a0 --- /dev/null +++ b/pkg/plugins/pfs/plugindef_types.go @@ -0,0 +1,42 @@ +package pfs + +type Type string + +// Defines values for Type. +const ( + TypeApp Type = "app" + TypeDatasource Type = "datasource" + TypePanel Type = "panel" + TypeRenderer Type = "renderer" + TypeSecretsmanager Type = "secretsmanager" +) + +type PluginDef struct { + Id string + Name string + Backend *bool + Type Type + Info Info + IAM IAM +} + +type Info struct { + Version *string +} + +type IAM struct { + Permissions []Permission `json:"permissions,omitempty"` +} + +type Permission struct { + Action string `json:"action"` + Scope *string `json:"scope,omitempty"` +} + +func (pd PluginDef) Validate() error { + if pd.Id == "" || pd.Name == "" || pd.Type == "" { + return ErrInvalidRootFile + } + + return nil +} diff --git a/pkg/plugins/plugindef/gen.go b/pkg/plugins/plugindef/gen.go deleted file mode 100644 index 710a6734fa8..00000000000 --- a/pkg/plugins/plugindef/gen.go +++ /dev/null @@ -1,130 +0,0 @@ -//go:build ignore -// +build ignore - -package main - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - - "cuelang.org/go/cue/cuecontext" - "github.com/dave/dst" - "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" -) - -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) - } - - groot := filepath.Clean(filepath.Join(cwd, "../../..")) - - jfs := elsedie(jl.GenerateFS(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) { - f, err := codegen.GoTypesJenny{}.Generate(codegen.SchemaForGen{ - Name: "PluginDef", - Schema: lin.Latest(), - IsGroup: false, - }) - if f != nil { - f.RelativePath = filepath.Join(dirPlugindef, f.RelativePath) - } - return f, err -} - -// 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: dst.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) - } -} diff --git a/pkg/plugins/plugindef/pascal_test.go b/pkg/plugins/plugindef/pascal_test.go deleted file mode 100644 index 095384c5d03..00000000000 --- a/pkg/plugins/plugindef/pascal_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package plugindef - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestDerivePascal(t *testing.T) { - table := []struct { - id, name, out string - }{ - { - name: "-- Grafana --", - out: "Grafana", - }, - { - name: "A weird/Thing", - out: "AWeirdThing", - }, - { - name: "/", - out: "Empty", - }, - { - name: "some really Long thing WHY would38883 anyone do this i don't know but hey It seems like it this is just going on and", - out: "SomeReallyLongThingWHYWouldAnyoneDoThisIDonTKnowButHeyItSeemsLi", - }, - } - - for _, row := range table { - if row.id == "" { - row.id = "default-empty-panel" - } - - pd := PluginDef{ - Id: row.id, - Name: row.name, - } - - require.Equal(t, row.out, DerivePascalName(pd)) - } -} diff --git a/pkg/plugins/plugindef/plugindef.cue b/pkg/plugins/plugindef/plugindef.cue deleted file mode 100644 index 89255479fc9..00000000000 --- a/pkg/plugins/plugindef/plugindef.cue +++ /dev/null @@ -1,428 +0,0 @@ -package plugindef - -import ( - "regexp" - "strings" - - "github.com/grafana/thema" -) - -thema.#Lineage -name: "plugindef" -schemas: [{ - version: [0, 0] - schema: { - // Unique name of the plugin. If the plugin is published on - // 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|datagrid|gauge|geomap|gettingstarted|graph|heatmap|histogram|icon|live|logs|news|nodeGraph|piechart|pluginlist|stat|state-timeline|status-history|table|table-old|text|timeseries|trend|traces|welcome|xychart|alertmanager|cloudwatch|dashboard|elasticsearch|grafana|grafana-azure-monitor-datasource|stackdriver|graphite|influxdb|jaeger|loki|mixed|mssql|mysql|opentsdb|postgres|prometheus|stackdriver|tempo|grafana-testdata-datasource|zipkin|phlare|parca)$" - - // An alias is useful when migrating from one plugin id to another (rebranding etc) - // This should be used sparingly, and is currently only supported though a hardcoded checklist - aliasIDs?: [...string] - - // Human-readable name of the plugin that is shown to the user in - // the UI. - name: string - - // The set of all plugin types. This hidden field exists solely - // so that the set can be string-interpolated into other fields. - _types: ["app", "datasource", "panel", "renderer", "secretsmanager"] - - // type indicates which type of Grafana plugin this is, of the defined - // set of Grafana plugin types. - type: or(_types) - - // IncludeType is a string identifier of a plugin include type, which is - // a superset of plugin types. - #IncludeType: type | "dashboard" | "page" - - // Metadata about the plugin - info: #Info - - // 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. - #Info: { - // Information about the plugin author - author?: { - // Author's name - name?: string - - // Author's name - email?: string - - // Link to author's website - url?: string - } - - // Build information - build?: #BuildInfo - - // Description of plugin. Used on the plugins page in Grafana and - // for search on grafana.com. - description?: string - - // Array of plugin keywords. Used for search on grafana.com. - keywords: [...string] - // should be this, but CUE to openapi converter screws this up - // by inserting a non-concrete default. - // keywords: [string, ...string] - - // An array of link objects to be displayed on this plugin's - // project page in the form `{name: 'foo', url: - // 'http://example.com'}` - links?: [...{ - name?: string - url?: string - }] - - // SVG images that are used as plugin icons - logos?: { - // Link to the "small" version of the plugin logo, which must be - // an SVG image. "Large" and "small" logos can be the same image. - small: string - - // Link to the "large" version of the plugin logo, which must be - // an SVG image. "Large" and "small" logos can be the same image. - large: string - } - - // An array of screenshot objects in the form `{name: 'bar', path: - // 'img/screenshot.png'}` - screenshots?: [...{ - name?: string - path?: string - }] - - // Date when this plugin was built - updated?: =~"^(\\d{4}-\\d{2}-\\d{2}|\\%TODAY\\%)$" - - // Project version of this commit, e.g. `6.7.x` - version?: =~"^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)|(\\%VERSION\\%)$" - } - - #BuildInfo: { - // Time when the plugin was built, as a Unix timestamp - time?: int64 - repo?: string - - // Git branch the plugin was built from - branch?: string - - // Git hash of the commit the plugin was built from - hash?: string - number?: int64 - - // GitHub pull request the plugin was built from - pr?: int32 - } - - // Dependency information related to Grafana and other plugins - dependencies: #Dependencies - - #Dependencies: { - // (Deprecated) Required Grafana version for this plugin, e.g. - // `6.x.x 7.x.x` to denote plugin requires Grafana v6.x.x or - // v7.x.x. - grafanaVersion?: =~"^([0-9]+)(\\.[0-9x]+)?(\\.[0-9x])?$" - - // Required Grafana version for this plugin. Validated using - // https://github.com/npm/node-semver. - grafanaDependency?: =~"^(<=|>=|<|>|=|~|\\^)?([0-9]+)(\\.[0-9x\\*]+)(\\.[0-9x\\*]+)?(\\s(<=|>=|<|=>)?([0-9]+)(\\.[0-9x]+)(\\.[0-9x]+))?(\\-[0-9]+)?$" - - // An array of required plugins on which this plugin depends - plugins?: [...#Dependency] - } - - // 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. - #Dependency: { - id: =~"^[0-9a-z]+\\-([0-9a-z]+\\-)?(app|panel|datasource)$" - type: "app" | "datasource" | "panel" - name: string - version: string - ... - } - - // Schema definition for the plugin.json file. Used primarily for schema validation. - $schema?: string - - // For data source plugins, if the plugin supports alerting. Requires `backend` to be set to `true`. - alerting?: bool - - // For data source plugins, if the plugin supports annotation - // queries. - annotations?: bool - - // Set to true for app plugins that should be enabled and pinned to the navigation bar in all orgs. - autoEnabled?: bool - - // If the plugin has a backend component. - backend?: bool - - // [internal only] Indicates whether the plugin is developed and shipped as part - // of Grafana. Also known as a 'core plugin'. - builtIn: bool | *false - - // Plugin category used on the Add data source page. - category?: "tsdb" | "logging" | "cloud" | "tracing" | "profiling" | "sql" | "enterprise" | "iot" | "other" - - // Grafana Enterprise specific features. - enterpriseFeatures?: { - // Enable/Disable health diagnostics errors. Requires Grafana - // >=7.5.5. - healthDiagnosticsErrors?: bool | *false - ... - } - - // The first part of the file name of the backend component - // executable. There can be multiple executables built for - // different operating system and architecture. Grafana will - // check for executables named `_<$GOOS>_<.exe for Windows>`, e.g. `plugin_linux_amd64`. - // Combination of $GOOS and $GOARCH can be found here: - // https://golang.org/doc/install/source#environment. - executable?: string - - // [internal only] Excludes the plugin from listings in Grafana's UI. Only - // allowed for `builtIn` plugins. - hideFromList: bool | *false - - // Resources to include in plugin. - includes?: [...#Include] - - // A resource to be included in a plugin. - #Include: { - // Unique identifier of the included resource - uid?: string - type: #IncludeType - name?: string - - // (Legacy) The Angular component to use for a page. - component?: string - - // The minimum role a user must have to see this page in the navigation menu. - role?: "Admin" | "Editor" | "Viewer" - - // RBAC action the user must have to access the route - action?: string - - // Used for app plugins. - path?: string - - // Add the include to the navigation menu. - addToNav?: bool - - // Page or dashboard when user clicks the icon in the side menu. - defaultNav?: bool - - // Icon to use in the side menu. For information on available - // icon, refer to [Icons - // Overview](https://developers.grafana.com/ui/latest/index.html?path=/story/docs-overview-icon--icons-overview). - icon?: string - ... - } - - // For data source plugins, if the plugin supports logs. It may be used to filter logs only features. - logs?: bool - - // For data source plugins, if the plugin supports metric queries. - // Used to enable the plugin in the panel editor. - metrics?: bool - - // FIXME there appears to be a bug in thema that prevents this from working. Maybe it'd - // help to refer to it with an alias, but thema can't support using current list syntax. - // syntax (fixed by grafana/thema#82). Either way, for now, pascalName gets populated in Go. - let sani = (strings.ToTitle(regexp.ReplaceAllLiteral("[^a-zA-Z]+", name, ""))) - - // [internal only] The PascalCase name for the plugin. Used for creating machine-friendly - // identifiers, typically in code generation. - // - // If not provided, defaults to name, but title-cased and sanitized (only - // alphabetical characters allowed). - pascalName: string & =~"^([A-Z][a-zA-Z]{1,62})$" | *sani - - // Initialize plugin on startup. By default, the plugin - // initializes on first use. - preload?: bool - - // For data source plugins. There is a query options section in - // the plugin's query editor and these options can be turned on - // if needed. - queryOptions?: { - // For data source plugins. If the `max data points` option should - // be shown in the query options section in the query editor. - maxDataPoints?: bool - - // For data source plugins. If the `min interval` option should be - // shown in the query options section in the query editor. - minInterval?: bool - - // For data source plugins. If the `cache timeout` option should - // be shown in the query options section in the query editor. - cacheTimeout?: bool - } - - // Routes is a list of proxy routes, if any. For datasource plugins only. - routes?: [...#Route] - - // For panel plugins. Hides the query editor. - skipDataQuery?: bool - - // Marks a plugin as a pre-release. - state?: #ReleaseState - - // ReleaseState indicates release maturity state of a plugin. - #ReleaseState: "alpha" | "beta" | "deprecated" | *"stable" - - // For data source plugins, if the plugin supports streaming. Used in Explore to start live streaming. - streaming?: bool - - // For data source plugins, if the plugin supports tracing. Used for example to link logs (e.g. Loki logs) with tracing plugins. - tracing?: bool - - // Optional list of RBAC RoleRegistrations. - // 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. - roles?: [...#RoleRegistration] - - // RoleRegistration describes an RBAC role and its assignments to basic roles. - // It organizes related RBAC permissions on the plugin into a role and defines which basic roles - // 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. - #RoleRegistration: { - // RBAC role definition to bundle related RBAC permissions on the plugin. - role: #Role - - // 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 - // inherits them from the Viewer basic role. - grants: [...#BasicRole] - } - - // 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. - #Role: { - name: string - name: =~"^([A-Z][0-9A-Za-z ]+)$" - description: string - permissions: [...#Permission] - } - - // Permission describes an RBAC permission on the plugin. A permission has an action and an optional - // scope. - // Example: action: 'test-app.schedules:read', scope: 'test-app.schedules:*' - #Permission: { - action: string - scope?: 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. - #BasicRole: "Grafana Admin" | "Admin" | "Editor" | "Viewer" - - // Header describes an HTTP header that is forwarded with a proxied request for - // a plugin route. - #Header: { - name: string - content: string - } - - // URLParam describes query string parameters for - // a url in a plugin route - #URLParam: { - name: string - content: 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/developers/plugin-tools/create-a-plugin/extend-a-plugin/add-authentication-for-data-source-plugins). - #Route: { - // For data source plugins. The route path that is replaced by the - // route URL field when proxying the call. - path?: string - - // For data source plugins. Route method matches the HTTP verb - // like GET or POST. Multiple methods can be provided as a - // comma-separated list. - method?: string - - // For data source plugins. Route URL is where the request is - // proxied to. - url?: string - - urlParams?: [...#URLParam] - reqSignedIn?: bool - reqRole?: string - - // RBAC action the user must have to access the route. i.e. plugin-id.projects:read - reqAction?: string - - // For data source plugins. Route headers adds HTTP headers to the - // proxied request. - headers?: [...#Header] - - // For data source plugins. Route headers set the body content and - // length to the proxied request. - body?: { - ... - } - - // For data source plugins. Token authentication section used with - // an OAuth API. - tokenAuth?: #TokenAuth - - // For data source plugins. Token authentication section used with - // an JWT OAuth API. - jwtTokenAuth?: #JWTTokenAuth - } - - // TODO docs - #TokenAuth: { - // URL to fetch the authentication token. - url?: string - - // The list of scopes that your application should be granted - // access to. - scopes?: [...string] - - // Parameters for the token authentication request. - params: [string]: string - } - - // TODO docs - // TODO should this really be separate from TokenAuth? - #JWTTokenAuth: { - // URL to fetch the JWT token. - url: string - - // The list of scopes that your application should be granted - // access to. - scopes: [...string] - - // Parameters for the JWT token authentication request. - params: [string]: string - } - - // Identity and Access Management information. - // Allows the plugin to define the permissions it requires to have on Grafana. - iam: #IAM - - // IAM allows the plugin to get a service account with tailored permissions and a token - // (or to use the client_credentials grant if the token provider is the OAuth2 Server) - #IAM: { - // Permissions are the permissions that the external service needs its associated service account to have. - permissions?: [...#Permission] - } - } -}] -lenses: [] diff --git a/pkg/plugins/plugindef/plugindef.go b/pkg/plugins/plugindef/plugindef.go deleted file mode 100644 index f49a73637cf..00000000000 --- a/pkg/plugins/plugindef/plugindef.go +++ /dev/null @@ -1,73 +0,0 @@ -package plugindef - -import ( - "strings" - "sync" - - "cuelang.org/go/cue/build" - "github.com/grafana/grafana/pkg/cuectx" - "github.com/grafana/thema" -) - -//go:generate go run gen.go - -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...) -} - -// DerivePascalName derives a PascalCase name from a PluginDef. -// -// This function does not mutate the input PluginDef; as such, it ignores -// whether there exists any value for PluginDef.PascalName. -// -// FIXME this should be removable once CUE logic for it works/unmarshals correctly. -func DerivePascalName(pd PluginDef) string { - sani := func(s string) string { - ret := strings.Title(strings.Map(func(r rune) rune { - switch { - case r >= 'a' && r <= 'z': - return r - case r >= 'A' && r <= 'Z': - return r - default: - return -1 - } - }, strings.Title(strings.Map(func(r rune) rune { - switch r { - case '-', '_': - return ' ' - default: - return r - } - }, s)))) - if len(ret) > 63 { - return ret[:63] - } - return ret - } - - fromname := sani(pd.Name) - if len(fromname) != 0 { - return fromname - } - return sani(strings.Split(pd.Id, "-")[1]) -} diff --git a/pkg/plugins/plugindef/plugindef_bindings_gen.go b/pkg/plugins/plugindef/plugindef_bindings_gen.go deleted file mode 100644 index 3438f6896a3..00000000000 --- a/pkg/plugins/plugindef/plugindef_bindings_gen.go +++ /dev/null @@ -1,84 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// 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, opts...) -} - -// type guards -var _ thema.ConvergentLineageFactory[*PluginDef] = doLineage -var _ thema.LineageFactory = baseLineage diff --git a/pkg/plugins/plugindef/plugindef_types_gen.go b/pkg/plugins/plugindef/plugindef_types_gen.go deleted file mode 100644 index 1b5cb5e152d..00000000000 --- a/pkg/plugins/plugindef/plugindef_types_gen.go +++ /dev/null @@ -1,484 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// pkg/plugins/plugindef/gen.go -// Using jennies: -// GoTypesJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -package plugindef - -// Defines values for BasicRole. -const ( - BasicRoleAdmin BasicRole = "Admin" - BasicRoleEditor BasicRole = "Editor" - BasicRoleGrafanaAdmin BasicRole = "Grafana Admin" - BasicRoleViewer BasicRole = "Viewer" -) - -// Defines values for DependencyType. -const ( - DependencyTypeApp DependencyType = "app" - DependencyTypeDatasource DependencyType = "datasource" - DependencyTypePanel DependencyType = "panel" -) - -// Defines values for IncludeRole. -const ( - IncludeRoleAdmin IncludeRole = "Admin" - IncludeRoleEditor IncludeRole = "Editor" - IncludeRoleViewer IncludeRole = "Viewer" -) - -// Defines values for IncludeType. -const ( - IncludeTypeApp IncludeType = "app" - IncludeTypeDashboard IncludeType = "dashboard" - IncludeTypeDatasource IncludeType = "datasource" - IncludeTypePage IncludeType = "page" - IncludeTypePanel IncludeType = "panel" - IncludeTypeRenderer IncludeType = "renderer" - IncludeTypeSecretsmanager IncludeType = "secretsmanager" -) - -// Defines values for Category. -const ( - CategoryCloud Category = "cloud" - CategoryEnterprise Category = "enterprise" - CategoryIot Category = "iot" - CategoryLogging Category = "logging" - CategoryOther Category = "other" - CategoryProfiling Category = "profiling" - CategorySql Category = "sql" - CategoryTracing Category = "tracing" - CategoryTsdb Category = "tsdb" -) - -// Defines values for Type. -const ( - TypeApp Type = "app" - TypeDatasource Type = "datasource" - TypePanel Type = "panel" - TypeRenderer Type = "renderer" - TypeSecretsmanager Type = "secretsmanager" -) - -// Defines values for ReleaseState. -const ( - ReleaseStateAlpha ReleaseState = "alpha" - ReleaseStateBeta ReleaseState = "beta" - ReleaseStateDeprecated ReleaseState = "deprecated" - ReleaseStateStable ReleaseState = "stable" -) - -// 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. -type BasicRole string - -// BuildInfo defines model for BuildInfo. -type BuildInfo struct { - // Git branch the plugin was built from - Branch *string `json:"branch,omitempty"` - - // Git hash of the commit the plugin was built from - Hash *string `json:"hash,omitempty"` - Number *int64 `json:"number,omitempty"` - - // GitHub pull request the plugin was built from - Pr *int32 `json:"pr,omitempty"` - Repo *string `json:"repo,omitempty"` - - // Time when the plugin was built, as a Unix timestamp - Time *int64 `json:"time,omitempty"` -} - -// Dependencies defines model for Dependencies. -type Dependencies struct { - // Required Grafana version for this plugin. Validated using - // https://github.com/npm/node-semver. - GrafanaDependency *string `json:"grafanaDependency,omitempty"` - - // (Deprecated) Required Grafana version for this plugin, e.g. - // `6.x.x 7.x.x` to denote plugin requires Grafana v6.x.x or - // v7.x.x. - GrafanaVersion *string `json:"grafanaVersion,omitempty"` - - // An array of required plugins on which this plugin depends - Plugins []Dependency `json:"plugins,omitempty"` -} - -// 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. -type Dependency struct { - Id string `json:"id"` - Name string `json:"name"` - Type DependencyType `json:"type"` - Version string `json:"version"` -} - -// 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. -type Header struct { - Content string `json:"content"` - Name string `json:"name"` -} - -// IAM allows the plugin to get a service account with tailored permissions and a token -// (or to use the client_credentials grant if the token provider is the OAuth2 Server) -type IAM struct { - // Permissions are the permissions that the external service needs its associated service account to have. - Permissions []Permission `json:"permissions,omitempty"` -} - -// A resource to be included in a plugin. -type Include struct { - // RBAC action the user must have to access the route - Action *string `json:"action,omitempty"` - - // Add the include to the navigation menu. - AddToNav *bool `json:"addToNav,omitempty"` - - // (Legacy) The Angular component to use for a page. - Component *string `json:"component,omitempty"` - - // Page or dashboard when user clicks the icon in the side menu. - DefaultNav *bool `json:"defaultNav,omitempty"` - - // Icon to use in the side menu. For information on available - // icon, refer to [Icons - // Overview](https://developers.grafana.com/ui/latest/index.html?path=/story/docs-overview-icon--icons-overview). - Icon *string `json:"icon,omitempty"` - Name *string `json:"name,omitempty"` - - // Used for app plugins. - Path *string `json:"path,omitempty"` - - // The minimum role a user must have to see this page in the navigation menu. - Role *IncludeRole `json:"role,omitempty"` - - // IncludeType is a string identifier of a plugin include type, which is - // a superset of plugin types. - Type IncludeType `json:"type"` - - // Unique identifier of the included resource - Uid *string `json:"uid,omitempty"` -} - -// The minimum role a user must have to see this page in the navigation menu. -type IncludeRole string - -// IncludeType is a string identifier of a plugin include type, which is -// a superset of plugin types. -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. -type Info struct { - // Information about the plugin author - Author *struct { - // Author's name - Email *string `json:"email,omitempty"` - - // Author's name - Name *string `json:"name,omitempty"` - - // Link to author's website - Url *string `json:"url,omitempty"` - } `json:"author,omitempty"` - Build *BuildInfo `json:"build,omitempty"` - - // Description of plugin. Used on the plugins page in Grafana and - // for search on grafana.com. - Description *string `json:"description,omitempty"` - - // Array of plugin keywords. Used for search on grafana.com. - Keywords []string `json:"keywords"` - - // An array of link objects to be displayed on this plugin's - // project page in the form `{name: 'foo', url: - // 'http://example.com'}` - Links []struct { - Name *string `json:"name,omitempty"` - Url *string `json:"url,omitempty"` - } `json:"links,omitempty"` - - // SVG images that are used as plugin icons - Logos *struct { - // Link to the "large" version of the plugin logo, which must be - // an SVG image. "Large" and "small" logos can be the same image. - Large string `json:"large"` - - // Link to the "small" version of the plugin logo, which must be - // an SVG image. "Large" and "small" logos can be the same image. - Small string `json:"small"` - } `json:"logos,omitempty"` - - // An array of screenshot objects in the form `{name: 'bar', path: - // 'img/screenshot.png'}` - Screenshots []struct { - Name *string `json:"name,omitempty"` - Path *string `json:"path,omitempty"` - } `json:"screenshots,omitempty"` - - // Date when this plugin was built - Updated *string `json:"updated,omitempty"` - - // Project version of this commit, e.g. `6.7.x` - Version *string `json:"version,omitempty"` -} - -// TODO docs -// TODO should this really be separate from TokenAuth? -type JWTTokenAuth struct { - // Parameters for the JWT token authentication request. - Params map[string]string `json:"params"` - - // The list of scopes that your application should be granted - // access to. - Scopes []string `json:"scopes"` - - // URL to fetch the JWT token. - Url string `json:"url"` -} - -// Permission describes an RBAC permission on the plugin. A permission has an action and an optional -// scope. -// Example: action: 'test-app.schedules:read', scope: 'test-app.schedules:*' -type Permission struct { - Action string `json:"action"` - Scope *string `json:"scope,omitempty"` -} - -// PluginDef defines model for PluginDef. -type PluginDef struct { - // Schema definition for the plugin.json file. Used primarily for schema validation. - Schema *string `json:"$schema,omitempty"` - - // For data source plugins, if the plugin supports alerting. Requires `backend` to be set to `true`. - Alerting *bool `json:"alerting,omitempty"` - - // An alias is useful when migrating from one plugin id to another (rebranding etc) - // This should be used sparingly, and is currently only supported though a hardcoded checklist - AliasIDs []string `json:"aliasIDs,omitempty"` - - // For data source plugins, if the plugin supports annotation - // queries. - Annotations *bool `json:"annotations,omitempty"` - - // Set to true for app plugins that should be enabled and pinned to the navigation bar in all orgs. - AutoEnabled *bool `json:"autoEnabled,omitempty"` - - // If the plugin has a backend component. - Backend *bool `json:"backend,omitempty"` - - // [internal only] Indicates whether the plugin is developed and shipped as part - // of Grafana. Also known as a 'core plugin'. - BuiltIn bool `json:"builtIn"` - - // Plugin category used on the Add data source page. - Category *Category `json:"category,omitempty"` - Dependencies Dependencies `json:"dependencies"` - - // Grafana Enterprise specific features. - EnterpriseFeatures *struct { - // Enable/Disable health diagnostics errors. Requires Grafana - // >=7.5.5. - HealthDiagnosticsErrors *bool `json:"healthDiagnosticsErrors,omitempty"` - } `json:"enterpriseFeatures,omitempty"` - - // The first part of the file name of the backend component - // executable. There can be multiple executables built for - // different operating system and architecture. Grafana will - // check for executables named `_<$GOOS>_<.exe for Windows>`, e.g. `plugin_linux_amd64`. - // Combination of $GOOS and $GOARCH can be found here: - // https://golang.org/doc/install/source#environment. - Executable *string `json:"executable,omitempty"` - - // [internal only] Excludes the plugin from listings in Grafana's UI. Only - // allowed for `builtIn` plugins. - HideFromList bool `json:"hideFromList"` - - // IAM allows the plugin to get a service account with tailored permissions and a token - // (or to use the client_credentials grant if the token provider is the OAuth2 Server) - Iam IAM `json:"iam"` - - // Unique name of the plugin. If the plugin is published on - // grafana.com, then the plugin `id` has to follow the naming - // conventions. - Id string `json:"id"` - - // Resources to include in plugin. - Includes []Include `json:"includes,omitempty"` - - // 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. - Info Info `json:"info"` - - // For data source plugins, if the plugin supports logs. It may be used to filter logs only features. - Logs *bool `json:"logs,omitempty"` - - // For data source plugins, if the plugin supports metric queries. - // Used to enable the plugin in the panel editor. - Metrics *bool `json:"metrics,omitempty"` - - // Human-readable name of the plugin that is shown to the user in - // the UI. - Name string `json:"name"` - - // [internal only] The PascalCase name for the plugin. Used for creating machine-friendly - // identifiers, typically in code generation. - // - // If not provided, defaults to name, but title-cased and sanitized (only - // alphabetical characters allowed). - PascalName string `json:"pascalName"` - - // Initialize plugin on startup. By default, the plugin - // initializes on first use. - Preload *bool `json:"preload,omitempty"` - - // For data source plugins. There is a query options section in - // the plugin's query editor and these options can be turned on - // if needed. - QueryOptions *struct { - // For data source plugins. If the `cache timeout` option should - // be shown in the query options section in the query editor. - CacheTimeout *bool `json:"cacheTimeout,omitempty"` - - // For data source plugins. If the `max data points` option should - // be shown in the query options section in the query editor. - MaxDataPoints *bool `json:"maxDataPoints,omitempty"` - - // For data source plugins. If the `min interval` option should be - // shown in the query options section in the query editor. - MinInterval *bool `json:"minInterval,omitempty"` - } `json:"queryOptions,omitempty"` - - // Optional list of RBAC RoleRegistrations. - // 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. - Roles []RoleRegistration `json:"roles,omitempty"` - - // Routes is a list of proxy routes, if any. For datasource plugins only. - Routes []Route `json:"routes,omitempty"` - - // For panel plugins. Hides the query editor. - SkipDataQuery *bool `json:"skipDataQuery,omitempty"` - - // ReleaseState indicates release maturity state of a plugin. - State *ReleaseState `json:"state,omitempty"` - - // For data source plugins, if the plugin supports streaming. Used in Explore to start live streaming. - Streaming *bool `json:"streaming,omitempty"` - - // For data source plugins, if the plugin supports tracing. Used for example to link logs (e.g. Loki logs) with tracing plugins. - Tracing *bool `json:"tracing,omitempty"` - - // type indicates which type of Grafana plugin this is, of the defined - // set of Grafana plugin types. - Type Type `json:"type"` -} - -// Plugin category used on the Add data source page. -type Category string - -// Type type indicates which type of Grafana plugin this is, of the defined -// set of Grafana plugin types. -type Type string - -// ReleaseState indicates release maturity state of a plugin. -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. -type Role struct { - Description string `json:"description"` - Name string `json:"name"` - Permissions []Permission `json:"permissions"` -} - -// RoleRegistration describes an RBAC role and its assignments to basic roles. -// It organizes related RBAC permissions on the plugin into a role and defines which basic roles -// 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. -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 - // inherits them from the Viewer basic role. - Grants []BasicRole `json:"grants"` - - // 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. - Role Role `json:"role"` -} - -// 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/developers/plugin-tools/create-a-plugin/extend-a-plugin/add-authentication-for-data-source-plugins). -type Route struct { - // For data source plugins. Route headers set the body content and - // length to the proxied request. - Body map[string]any `json:"body,omitempty"` - - // For data source plugins. Route headers adds HTTP headers to the - // proxied request. - Headers []Header `json:"headers,omitempty"` - - // TODO docs - // TODO should this really be separate from TokenAuth? - JwtTokenAuth *JWTTokenAuth `json:"jwtTokenAuth,omitempty"` - - // For data source plugins. Route method matches the HTTP verb - // like GET or POST. Multiple methods can be provided as a - // comma-separated list. - Method *string `json:"method,omitempty"` - - // For data source plugins. The route path that is replaced by the - // route URL field when proxying the call. - Path *string `json:"path,omitempty"` - - // RBAC action the user must have to access the route. i.e. plugin-id.projects:read - ReqAction *string `json:"reqAction,omitempty"` - ReqRole *string `json:"reqRole,omitempty"` - ReqSignedIn *bool `json:"reqSignedIn,omitempty"` - - // TODO docs - TokenAuth *TokenAuth `json:"tokenAuth,omitempty"` - - // For data source plugins. Route URL is where the request is - // proxied to. - Url *string `json:"url,omitempty"` - UrlParams []URLParam `json:"urlParams,omitempty"` -} - -// TODO docs -type TokenAuth struct { - // Parameters for the token authentication request. - Params map[string]string `json:"params"` - - // The list of scopes that your application should be granted - // access to. - Scopes []string `json:"scopes,omitempty"` - - // URL to fetch the authentication token. - Url *string `json:"url,omitempty"` -} - -// URLParam describes query string parameters for -// a url in a plugin route -type URLParam struct { - Content string `json:"content"` - Name string `json:"name"` -} diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 3e98bf13445..15e43ad07d9 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -19,7 +19,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" "github.com/grafana/grafana/pkg/plugins/log" - "github.com/grafana/grafana/pkg/plugins/plugindef" + "github.com/grafana/grafana/pkg/plugins/pfs" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/util" ) @@ -118,7 +118,7 @@ type JSONData struct { Executable string `json:"executable,omitempty"` // App Service Auth Registration - IAM *plugindef.IAM `json:"iam,omitempty"` + IAM *pfs.IAM `json:"iam,omitempty"` } func ReadPluginJSON(reader io.Reader) (JSONData, error) { diff --git a/pkg/services/pluginsintegration/loader/loader_test.go b/pkg/services/pluginsintegration/loader/loader_test.go index 0b8976f45cf..c2327558f82 100644 --- a/pkg/services/pluginsintegration/loader/loader_test.go +++ b/pkg/services/pluginsintegration/loader/loader_test.go @@ -24,7 +24,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/manager/registry" "github.com/grafana/grafana/pkg/plugins/manager/signature" "github.com/grafana/grafana/pkg/plugins/manager/sources" - "github.com/grafana/grafana/pkg/plugins/plugindef" + "github.com/grafana/grafana/pkg/plugins/pfs" "github.com/grafana/grafana/pkg/plugins/pluginscdn" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/org" @@ -541,8 +541,8 @@ func TestLoader_Load_ExternalRegistration(t *testing.T) { GrafanaVersion: "*", Plugins: []plugins.Dependency{}, }, - IAM: &plugindef.IAM{ - Permissions: []plugindef.Permission{ + IAM: &pfs.IAM{ + Permissions: []pfs.Permission{ { Action: "read", Scope: stringPtr("datasource"), diff --git a/pkg/services/pluginsintegration/pipeline/steps.go b/pkg/services/pluginsintegration/pipeline/steps.go index d5408d02246..ea798c2dd2a 100644 --- a/pkg/services/pluginsintegration/pipeline/steps.go +++ b/pkg/services/pluginsintegration/pipeline/steps.go @@ -14,7 +14,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/manager/pipeline/validation" "github.com/grafana/grafana/pkg/plugins/manager/registry" "github.com/grafana/grafana/pkg/plugins/manager/signature" - "github.com/grafana/grafana/pkg/plugins/plugindef" + "github.com/grafana/grafana/pkg/plugins/pfs" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs" ) @@ -42,7 +42,7 @@ func newExternalServiceRegistration(cfg *config.PluginManagementCfg, serviceRegi // Register registers the external service with the external service registry, if the feature is enabled. func (r *ExternalServiceRegistration) Register(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) { if p.IAM != nil { - s, err := r.externalServiceRegistry.RegisterExternalService(ctx, p.ID, plugindef.Type(p.Type), p.IAM) + s, err := r.externalServiceRegistry.RegisterExternalService(ctx, p.ID, pfs.Type(p.Type), p.IAM) if err != nil { r.log.Error("Could not register an external service. Initialization skipped", "pluginId", p.ID, "error", err) return nil, err diff --git a/pkg/services/pluginsintegration/pluginconfig/envvars_test.go b/pkg/services/pluginsintegration/pluginconfig/envvars_test.go index a3bf9025df0..1a967b8a14e 100644 --- a/pkg/services/pluginsintegration/pluginconfig/envvars_test.go +++ b/pkg/services/pluginsintegration/pluginconfig/envvars_test.go @@ -17,7 +17,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/config" "github.com/grafana/grafana/pkg/plugins/envvars" "github.com/grafana/grafana/pkg/plugins/manager/fakes" - "github.com/grafana/grafana/pkg/plugins/plugindef" + "github.com/grafana/grafana/pkg/plugins/pfs" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" ) @@ -590,7 +590,7 @@ func TestPluginEnvVarsProvider_authEnvVars(t *testing.T) { p := &plugins.Plugin{ JSONData: plugins.JSONData{ ID: "test", - IAM: &plugindef.IAM{}, + IAM: &pfs.IAM{}, }, ExternalService: &auth.ExternalService{ ClientID: "clientID", diff --git a/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go b/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go index e5b7c19d6f0..0f7ad32115a 100644 --- a/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go +++ b/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go @@ -7,7 +7,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/auth" "github.com/grafana/grafana/pkg/plugins/config" "github.com/grafana/grafana/pkg/plugins/log" - "github.com/grafana/grafana/pkg/plugins/plugindef" + "github.com/grafana/grafana/pkg/plugins/pfs" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/extsvcauth" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -41,7 +41,7 @@ func (s *Service) HasExternalService(ctx context.Context, pluginID string) (bool } // RegisterExternalService is a simplified wrapper around SaveExternalService for the plugin use case. -func (s *Service) RegisterExternalService(ctx context.Context, pluginID string, pType plugindef.Type, svc *plugindef.IAM) (*auth.ExternalService, error) { +func (s *Service) RegisterExternalService(ctx context.Context, pluginID string, pType pfs.Type, svc *pfs.IAM) (*auth.ExternalService, error) { if !s.featureEnabled { s.log.Warn("Skipping External Service Registration. The feature is behind a feature toggle and needs to be enabled.") return nil, nil @@ -50,7 +50,7 @@ func (s *Service) RegisterExternalService(ctx context.Context, pluginID string, // Datasource plugins can only be enabled enabled := true // App plugins can be disabled - if pType == plugindef.TypeApp { + if pType == pfs.TypeApp { settings, err := s.settingsSvc.GetPluginSettingByPluginID(ctx, &pluginsettings.GetByPluginIDArgs{PluginID: pluginID}) if err != nil && !errors.Is(err, pluginsettings.ErrPluginSettingNotFound) { return nil, err @@ -86,7 +86,7 @@ func (s *Service) RegisterExternalService(ctx context.Context, pluginID string, PrivateKey: privateKey}, nil } -func toAccessControlPermissions(ps []plugindef.Permission) []accesscontrol.Permission { +func toAccessControlPermissions(ps []pfs.Permission) []accesscontrol.Permission { res := make([]accesscontrol.Permission, 0, len(ps)) for _, p := range ps { scope := "" diff --git a/public/app/plugins/gen.go b/public/app/plugins/gen.go index 558e503245b..75975955253 100644 --- a/public/app/plugins/gen.go +++ b/public/app/plugins/gen.go @@ -14,12 +14,11 @@ import ( "strings" "github.com/grafana/codejen" - "github.com/grafana/kindsys" - corecodegen "github.com/grafana/grafana/pkg/codegen" "github.com/grafana/grafana/pkg/cuectx" "github.com/grafana/grafana/pkg/plugins/codegen" "github.com/grafana/grafana/pkg/plugins/pfs" + "github.com/grafana/kindsys" "github.com/grafana/thema" ) @@ -86,7 +85,7 @@ func adaptToPipeline(j codejen.OneToOne[corecodegen.SchemaForGen]) codejen.OneTo return corecodegen.SchemaForGen{ Name: strings.ReplaceAll(pd.PluginMeta.Name, " ", ""), Schema: pd.Lineage.Latest(), - IsGroup: pd.SchemaInterface.IsGroup(), + IsGroup: pd.SchemaInterface.IsGroup, } }) }