diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2edf210b7e1..cae0f3454d1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -630,7 +630,7 @@ embed.go @grafana/grafana-as-code /pkg/registry/apis/ @grafana/grafana-app-platform-squad /pkg/codegen/ @grafana/grafana-as-code /pkg/kinds/*/*_gen.go @grafana/grafana-as-code -/pkg/registry/corekind/ @grafana/grafana-as-code +/pkg/registry/schemas/ @grafana/grafana-as-code /public/app/plugins/*gen.go @grafana/grafana-as-code /cue.mod/ @grafana/grafana-as-code diff --git a/.github/workflows/scripts/kinds/verify-kinds.go b/.github/workflows/scripts/kinds/verify-kinds.go index 640110edd67..ab60a90bd3e 100644 --- a/.github/workflows/scripts/kinds/verify-kinds.go +++ b/.github/workflows/scripts/kinds/verify-kinds.go @@ -1,116 +1,53 @@ package main import ( - "archive/zip" "context" "errors" "fmt" - "io" - "net/http" + "golang.org/x/text/cases" + "golang.org/x/text/language" "os" "path/filepath" "regexp" - "strconv" "strings" - "testing/fstest" "cuelang.org/go/cue" cueformat "cuelang.org/go/cue/format" - "github.com/google/go-github/github" "github.com/grafana/codejen" - "github.com/grafana/grafana/pkg/codegen" - "github.com/grafana/grafana/pkg/cuectx" - "github.com/grafana/grafana/pkg/plugins/pfs" - "github.com/grafana/grafana/pkg/plugins/pfs/corelist" - "github.com/grafana/grafana/pkg/registry/corekind" - "github.com/grafana/kindsys" - "github.com/grafana/thema" - "golang.org/x/oauth2" + "github.com/grafana/grafana/pkg/registry/schemas" ) -const ( - GITHUB_OWNER = "grafana" - GITHUB_REPO = "kind-registry" -) +var nonAlphaNumRegex = regexp.MustCompile("[^a-zA-Z0-9 ]+") // main This script verifies that stable kinds are not updated once published (new schemas // can be added but existing ones cannot be updated). -// If the env variable CODEGEN_VERIFY is not present, this also generates kind files into a -// local "next" folder, ready to be published in the kind-registry repo. +// It generates kind files into a local "next" folder, ready to be published in the kind-registry repo. // If kind names are given as parameters, the script will make the above actions only for the // given kinds. func main() { - var corek []kindsys.Kind - var compok []kindsys.Composable - - kindRegistry, err := NewKindRegistry() - defer kindRegistry.cleanUp() - if err != nil { - die(err) - } - - // Search for the latest version directory present in the kind-registry repo - latestRegistryDir, err := kindRegistry.findLatestDir() - if err != nil { - die(fmt.Errorf("failed to get latest directory for published kinds: %s", err)) - } - - errs := make([]error, 0) - - // Kind verification - for _, kind := range corekind.NewBase(nil).All() { - name := kind.Props().Common().MachineName - err := verifyKind(kindRegistry, kind, name, "core", latestRegistryDir) - if err != nil { - errs = append(errs, err) - continue - } - - corek = append(corek, kind) - } - - for _, pp := range corelist.New(nil) { - for _, kind := range pp.ComposableKinds { - si, err := kindsys.FindSchemaInterface(kind.Def().Properties.SchemaInterface) - if err != nil { - errs = append(errs, err) - continue - } - - name := strings.ToLower(fmt.Sprintf("%s/%s", strings.TrimSuffix(kind.Lineage().Name(), si.Name()), si.Name())) - err = verifyKind(kindRegistry, kind, name, "composable", latestRegistryDir) - if err != nil { - errs = append(errs, err) - continue - } - - compok = append(compok, kind) - } - } - - die(errs...) - - if _, set := os.LookupEnv("CODEGEN_VERIFY"); set { - os.Exit(0) - } - // File generation jfs := codejen.NewFS() outputPath := filepath.Join(".github", "workflows", "scripts", "kinds") - coreJennies := codejen.JennyList[kindsys.Kind]{} + corekinds, err := schemas.GetCoreKinds() + die(err) + + composableKinds, err := schemas.GetComposableKinds() + die(err) + + coreJennies := codejen.JennyList[schemas.CoreKind]{} coreJennies.Append( - KindRegistryJenny(outputPath), + CoreKindRegistryJenny(outputPath), ) - corefs, err := coreJennies.GenerateFS(corek...) + corefs, err := coreJennies.GenerateFS(corekinds...) die(err) die(jfs.Merge(corefs)) - composableJennies := codejen.JennyList[kindsys.Composable]{} + composableJennies := codejen.JennyList[schemas.ComposableKind]{} composableJennies.Append( ComposableKindRegistryJenny(outputPath), ) - composablefs, err := composableJennies.GenerateFS(compok...) + composablefs, err := composableJennies.GenerateFS(composableKinds...) die(err) die(jfs.Merge(composablefs)) @@ -180,101 +117,8 @@ func die(errs ...error) { } } -// verifyKind verifies that stable kinds are not updated once published (new schemas -// can be added but existing ones cannot be updated) -func verifyKind(registry *kindRegistry, kind kindsys.Kind, name string, category string, latestRegistryDir string) error { - oldKindString, err := registry.getPublishedKind(name, category, latestRegistryDir) - if err != nil { - return err - } - - var oldKind kindsys.Kind - if oldKindString != "" { - switch category { - case "core": - oldKind, err = loadCoreKind(name, oldKindString) - case "composable": - oldKind, err = loadComposableKind(name, oldKindString) - default: - return fmt.Errorf("kind can only be core or composable") - } - } - if err != nil { - return err - } - - // Kind is new - no need to compare it - if oldKind == nil { - return nil - } - - // Check that maturity isn't downgraded - if kind.Maturity().Less(oldKind.Maturity()) { - return fmt.Errorf("kind maturity can't be downgraded once a kind is published") - } - - if oldKind.Maturity().Less(kindsys.MaturityStable) { - return nil - } - - // Check that old schemas do not contain updates - err = thema.IsAppendOnly(oldKind.Lineage(), kind.Lineage()) - if err != nil { - return fmt.Errorf("existing schemas in lineage %s cannot be modified: %w", name, err) - } - - return nil -} - -func isLess(v1 []uint64, v2 []uint64) bool { - if len(v1) == 1 || len(v2) == 1 { - return v1[0] < v2[0] - } - - return v1[0] < v2[0] || (v1[0] == v2[0] && isLess(v1[2:], v2[2:])) -} - -func loadCoreKind(name string, kind string) (kindsys.Kind, error) { - fs := fstest.MapFS{ - fmt.Sprintf("%s.cue", name): &fstest.MapFile{ - Data: []byte(kind), - }, - } - - rt := cuectx.GrafanaThemaRuntime() - - def, err := cuectx.LoadCoreKindDef(fmt.Sprintf("%s.cue", name), rt.Context(), fs) - if err != nil { - return nil, fmt.Errorf("%s is not a valid kind: %w", name, err) - } - - return kindsys.BindCore(rt, def) -} - -func loadComposableKind(name string, kind string) (kindsys.Kind, error) { - parts := strings.Split(name, "/") - if len(parts) > 1 { - name = parts[1] - } - - fs := fstest.MapFS{ - fmt.Sprintf("%s.cue", name): &fstest.MapFile{ - Data: []byte(kind), - }, - } - - rt := cuectx.GrafanaThemaRuntime() - - def, err := pfs.LoadComposableKindDef(fs, rt, fmt.Sprintf("%s.cue", name)) - if err != nil { - return nil, fmt.Errorf("%s is not a valid kind: %w", name, err) - } - - return kindsys.BindComposable(rt, def) -} - -// KindRegistryJenny generates kind files into the "next" folder of the local kind registry. -func KindRegistryJenny(path string) codegen.OneToOne { +// CoreKindRegistryJenny generates kind files into the "next" folder of the local kind registry. +func CoreKindRegistryJenny(path string) codejen.OneToOne[schemas.CoreKind] { return &kindregjenny{ path: path, } @@ -288,35 +132,18 @@ func (j *kindregjenny) JennyName() string { return "KindRegistryJenny" } -func (j *kindregjenny) Generate(kind kindsys.Kind) (*codejen.File, error) { - name := kind.Props().Common().MachineName - core, ok := kind.(kindsys.Core) - if !ok { - return nil, fmt.Errorf("kind sent to KindRegistryJenny must be a core kind") - } - - newKindBytes, err := kindToBytes(core.Def().V) +func (j *kindregjenny) Generate(kind schemas.CoreKind) (*codejen.File, error) { + newKindBytes, err := kindToBytes(kind.CueFile) if err != nil { return nil, err } - path := filepath.Join(j.path, "next", "core", name, name+".cue") + path := filepath.Join(j.path, "next", "core", kind.Name, kind.Name+".cue") return codejen.NewFile(path, newKindBytes, j), nil } -// kindToBytes converts a kind cue value to a .cue file content -func kindToBytes(kind cue.Value) ([]byte, error) { - node := kind.Syntax( - cue.All(), - cue.Schema(), - cue.Docs(true), - ) - - return cueformat.Node(node) -} - // ComposableKindRegistryJenny generates kind files into the "next" folder of the local kind registry. -func ComposableKindRegistryJenny(path string) codejen.OneToOne[kindsys.Composable] { +func ComposableKindRegistryJenny(path string) codejen.OneToOne[schemas.ComposableKind] { return &ckrJenny{ path: path, } @@ -330,149 +157,73 @@ func (j *ckrJenny) JennyName() string { return "ComposableKindRegistryJenny" } -func (j *ckrJenny) Generate(k kindsys.Composable) (*codejen.File, error) { - si, err := kindsys.FindSchemaInterface(k.Def().Properties.SchemaInterface) - if err != nil { - panic(err) - } +func (j *ckrJenny) Generate(k schemas.ComposableKind) (*codejen.File, error) { + name := strings.ToLower(fmt.Sprintf("%s/%s", k.Name, k.Filename)) - name := strings.ToLower(fmt.Sprintf("%s/%s", strings.TrimSuffix(k.Lineage().Name(), si.Name()), si.Name())) + v := fixComposableKindFormat(k) - newKindBytes, err := kindToBytes(k.Def().V) + newKindBytes, err := kindToBytes(v) if err != nil { return nil, err } newKindBytes = []byte(fmt.Sprintf("package grafanaplugin\n\n%s", newKindBytes)) - return codejen.NewFile(filepath.Join(j.path, "next", "composable", name+".cue"), newKindBytes, j), nil + return codejen.NewFile(filepath.Join(j.path, "next", "composable", name), newKindBytes, j), nil } -type kindRegistry struct { - zipDir string - zipFile *zip.ReadCloser +// kindToBytes converts a kind cue value to a .cue file content +func kindToBytes(kind cue.Value) ([]byte, error) { + node := kind.Syntax( + cue.All(), + cue.Schema(), + cue.Docs(true), + ) + + return cueformat.Node(node) } -// NewKindRegistry downloads the archive of the kind-registry GH repository and open it -func NewKindRegistry() (*kindRegistry, error) { - ctx := context.Background() - tc := oauth2.NewClient(ctx, nil) - client := github.NewClient(tc) - - // Create a temporary file to store the downloaded archive - file, err := os.CreateTemp("", "*.zip") - if err != nil { - return nil, fmt.Errorf("failed to create temporary file: %w", err) - } - defer file.Close() - - // Get the repository archive URL - archiveURL, _, err := client.Repositories.GetArchiveLink(ctx, GITHUB_OWNER, GITHUB_REPO, github.Zipball, &github.RepositoryContentGetOptions{}) - if err != nil { - return nil, fmt.Errorf("failed to get archive URL: %w", err) +func fixComposableKindFormat(schema schemas.ComposableKind) cue.Value { + variant := "PanelCfg" + if schema.CueFile.LookupPath(cue.ParsePath("composableKinds.DataQuery")).Exists() { + variant = "DataQuery" } - // Download the archive file - httpClient := http.DefaultClient - resp, err := httpClient.Get(archiveURL.String()) - if err != nil { - return nil, fmt.Errorf("failed to download archive: %w", err) - } - defer resp.Body.Close() + newCue := schema.CueFile.Context().CompileString( + fmt.Sprintf("schemaInterface: %q\n", variant) + + fmt.Sprintf("name: %q + %q\n\n", UpperCamelCase(schema.Name), variant) + + "lineage: _", + ) - // Save the downloaded archive to the temporary file - _, err = io.Copy(file, resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to save archive: %w", err) - } - - // Open the zip file for reading - zipDir := file.Name() - zipFile, err := zip.OpenReader(zipDir) - if err != nil { - return nil, fmt.Errorf("failed to open zip file %s: %w", zipDir, err) - } - - return &kindRegistry{ - zipDir: zipDir, - zipFile: zipFile, - }, nil + lineagePath := cue.MakePath(cue.Str("composableKinds"), cue.Str(variant), cue.Str("lineage")) + return newCue.FillPath(cue.MakePath(cue.Str("lineage")), schema.CueFile.LookupPath(lineagePath)) } -// cleanUp removes the archive from the temporary files and closes the zip reader -func (registry *kindRegistry) cleanUp() { - if registry.zipDir != "" { - err := os.Remove(registry.zipDir) - if err != nil { - fmt.Fprint(os.Stderr, fmt.Errorf("failed to remove zip archive: %w", err)) - } +func UpperCamelCase(s string) string { + s = LowerCamelCase(s) + + // Uppercase the first letter + if len(s) > 0 { + s = strings.ToUpper(s[:1]) + s[1:] } - if registry.zipFile != nil { - err := registry.zipFile.Close() - if err != nil { - fmt.Fprint(os.Stderr, fmt.Errorf("failed to close zip file reader: %w", err)) - } - } + return s } -// findLatestDir get the latest version directory published in the kind registry -func (registry *kindRegistry) findLatestDir() (string, error) { - re := regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)`) - latestVersion := []uint64{0, 0, 0} - latestDir := "" +func LowerCamelCase(s string) string { + // Replace all non-alphanumeric characters by spaces + s = nonAlphaNumRegex.ReplaceAllString(s, " ") - for _, file := range registry.zipFile.File { - if !file.FileInfo().IsDir() { - continue - } + // Title case s + s = cases.Title(language.AmericanEnglish, cases.NoLower).String(s) - parts := re.FindStringSubmatch(file.Name) - if parts == nil || len(parts) < 4 { - continue - } + // Remove all spaces + s = strings.ReplaceAll(s, " ", "") - version := make([]uint64, len(parts)-1) - for i := 1; i < len(parts); i++ { - version[i-1], _ = strconv.ParseUint(parts[i], 10, 32) - } - - if isLess(latestVersion, version) { - latestVersion = version - latestDir = file.Name - } + // Lowercase the first letter + if len(s) > 0 { + s = strings.ToLower(s[:1]) + s[1:] } - return latestDir, nil -} - -// getPublishedKind retrieves the latest published kind from the kind registry -func (registry *kindRegistry) getPublishedKind(name string, category string, latestRegistryDir string) (string, error) { - if latestRegistryDir == "" { - return "", nil - } - - var cueFilePath string - switch category { - case "core": - cueFilePath = fmt.Sprintf("%s/%s.cue", name, name) - case "composable": - cueFilePath = fmt.Sprintf("%s.cue", name) - default: - return "", fmt.Errorf("kind can only be core or composable") - } - - kindPath := filepath.Join(latestRegistryDir, category, cueFilePath) - file, err := registry.zipFile.Open(kindPath) - if err != nil { - return "", fmt.Errorf("failed to open file: %w", err) - } - defer file.Close() - - data, err := io.ReadAll(file) - if err != nil { - return "", fmt.Errorf("failed to read file: %w", err) - } - - return string(data), nil + return s } diff --git a/.github/workflows/verify-kinds.yml b/.github/workflows/verify-kinds.yml index 007abd0b9a2..e9acdccfe0c 100644 --- a/.github/workflows/verify-kinds.yml +++ b/.github/workflows/verify-kinds.yml @@ -4,7 +4,7 @@ on: pull_request: branches: [ main ] paths: - - '**/*.cue' + - '**/*.cue' jobs: main: @@ -23,5 +23,4 @@ jobs: - name: "Verify kinds" run: go run .github/workflows/scripts/kinds/verify-kinds.go env: - CODEGEN_VERIFY: 1 GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/LICENSING.md b/LICENSING.md index f8bfef7f945..97f2c53d8e7 100644 --- a/LICENSING.md +++ b/LICENSING.md @@ -18,7 +18,7 @@ packaging/ kinds/ pkg/kinds/ pkg/kindsys/ -pkg/registry/corekind/ +pkg/registry/schemas/ grafana-mixin/ public/app/plugins/datasource/tempo public/app/features/explore/TraceView/components diff --git a/kinds/gen.go b/kinds/gen.go index 4dc69e32799..32b7be8eba6 100644 --- a/kinds/gen.go +++ b/kinds/gen.go @@ -15,7 +15,6 @@ import ( "sort" "strings" - "cuelang.org/go/cue" "cuelang.org/go/cue/errors" "github.com/grafana/codejen" "github.com/grafana/cuetsy" @@ -40,8 +39,6 @@ func main() { // All the jennies that comprise the core kinds generator pipeline coreKindsGen.Append( &codegen.GoSpecJenny{}, - codegen.CoreKindJenny(cuectx.GoCoreKindParentPath, nil), - codegen.BaseCoreRegistryJenny(filepath.Join("pkg", "registry", "corekind"), cuectx.GoCoreKindParentPath), codegen.LatestMajorsOrXJenny(cuectx.TSCoreKindParentPath), codegen.TSVeneerIndexJenny(filepath.Join("packages", "grafana-schema", "src")), ) @@ -93,12 +90,12 @@ func main() { } // Merging k8 resources - k8Resources, err := genK8Resources(kinddirs) + rawResources, err := genRawResources(kinddirs) if err != nil { die(err) } - if err = jfs.Merge(k8Resources); err != nil { + if err = jfs.Merge(rawResources); err != nil { die(err) } @@ -189,12 +186,16 @@ func die(err error) { os.Exit(1) } -func genK8Resources(dirs []os.DirEntry) (*codejen.FS, error) { - jenny := codejen.JennyListWithNamer[[]cue.Value](func(_ []cue.Value) string { - return "K8Resources" +// Resource generation without using Thema +func genRawResources(dirs []os.DirEntry) (*codejen.FS, error) { + jenny := codejen.JennyListWithNamer[[]codegen.CueSchema](func(_ []codegen.CueSchema) string { + return "RawResources" }) - jenny.Append(&codegen.K8ResourcesJenny{}) + jenny.Append( + &codegen.K8ResourcesJenny{}, + &codegen.CoreRegistryJenny{}, + ) header := codegen.SlashHeaderMapper("kinds/gen.go") jenny.AddPostprocessors(header) @@ -202,9 +203,9 @@ func genK8Resources(dirs []os.DirEntry) (*codejen.FS, error) { return jenny.GenerateFS(loadCueFiles(dirs)) } -func loadCueFiles(dirs []os.DirEntry) []cue.Value { +func loadCueFiles(dirs []os.DirEntry) []codegen.CueSchema { ctx := cuectx.GrafanaCUEContext() - values := make([]cue.Value, 0) + values := make([]codegen.CueSchema, 0) for _, dir := range dirs { if !dir.IsDir() { continue @@ -224,7 +225,12 @@ func loadCueFiles(dirs []os.DirEntry) []cue.Value { os.Exit(1) } - values = append(values, ctx.CompileBytes(cueFile)) + sch := codegen.CueSchema{ + FilePath: "./" + filepath.Join(cuectx.CoreDefParentPath, entry), + CueFile: ctx.CompileBytes(cueFile), + } + + values = append(values, sch) } return values diff --git a/pkg/codegen/generators.go b/pkg/codegen/generators.go index 50b29ffbae6..6aadfd45ae8 100644 --- a/pkg/codegen/generators.go +++ b/pkg/codegen/generators.go @@ -5,6 +5,7 @@ import ( "fmt" "path/filepath" + "cuelang.org/go/cue" "github.com/grafana/codejen" "github.com/grafana/kindsys" "github.com/grafana/thema" @@ -59,3 +60,8 @@ type SchemaForGen struct { // Whether the schema is grouped. See https://github.com/grafana/thema/issues/62 IsGroup bool } + +type CueSchema struct { + CueFile cue.Value + FilePath string +} diff --git a/pkg/codegen/jenny_basecorereg.go b/pkg/codegen/jenny_basecorereg.go deleted file mode 100644 index 1a19a9aca8c..00000000000 --- a/pkg/codegen/jenny_basecorereg.go +++ /dev/null @@ -1,64 +0,0 @@ -package codegen - -import ( - "bytes" - "fmt" - "path/filepath" - - "github.com/grafana/codejen" - "github.com/grafana/kindsys" -) - -// BaseCoreRegistryJenny generates a static registry for core kinds that -// only initializes their [kindsys.Kind]. No slot kinds are composed. -// -// Path should be the relative path to the directory that will contain the -// generated registry. kindrelroot should be the repo-root-relative path to the -// parent directory to all directories that contain generated kind bindings -// (e.g. pkg/kind). -func BaseCoreRegistryJenny(path, kindrelroot string) ManyToOne { - return &genBaseRegistry{ - path: path, - kindrelroot: kindrelroot, - } -} - -type genBaseRegistry struct { - path string - kindrelroot string -} - -func (gen *genBaseRegistry) JennyName() string { - return "BaseCoreRegistryJenny" -} - -func (gen *genBaseRegistry) Generate(kinds ...kindsys.Kind) (*codejen.File, error) { - cores := make([]kindsys.Core, 0, len(kinds)) - for _, d := range kinds { - if corekind, is := d.(kindsys.Core); is { - cores = append(cores, corekind) - } - } - if len(cores) == 0 { - return nil, nil - } - - buf := new(bytes.Buffer) - if err := tmpls.Lookup("kind_registry.tmpl").Execute(buf, tvars_kind_registry{ - PackageName: filepath.Base(gen.path), - KindPackagePrefix: filepath.ToSlash(filepath.Join("github.com/grafana/grafana", gen.kindrelroot)), - Kinds: cores, - }); err != nil { - return nil, fmt.Errorf("failed executing kind registry template: %w", err) - } - - b, err := postprocessGoFile(genGoFile{ - path: gen.path, - in: buf.Bytes(), - }) - if err != nil { - return nil, err - } - - return codejen.NewFile(filepath.Join(gen.path, "base_gen.go"), b, gen), nil -} diff --git a/pkg/codegen/jenny_core_registry.go b/pkg/codegen/jenny_core_registry.go new file mode 100644 index 00000000000..2db4702e96d --- /dev/null +++ b/pkg/codegen/jenny_core_registry.go @@ -0,0 +1,62 @@ +package codegen + +import ( + "bytes" + "fmt" + "go/format" + "path/filepath" + "strings" + + "cuelang.org/go/cue" + "github.com/grafana/codejen" +) + +var registryPath = filepath.Join("pkg", "registry", "schemas") + +// CoreRegistryJenny generates a registry with all core kinds. +type CoreRegistryJenny struct { +} + +func (jenny *CoreRegistryJenny) JennyName() string { + return "CoreRegistryJenny" +} + +func (jenny *CoreRegistryJenny) Generate(cueFiles []CueSchema) (codejen.Files, error) { + schemas := make([]Schema, len(cueFiles)) + for i, v := range cueFiles { + name, err := getSchemaName(v.CueFile) + if err != nil { + return nil, err + } + + schemas[i] = Schema{ + Name: name, + FilePath: v.FilePath, + } + } + + buf := new(bytes.Buffer) + if err := tmpls.Lookup("core_registry.tmpl").Execute(buf, tvars_registry{ + Schemas: schemas, + }); err != nil { + return nil, fmt.Errorf("failed executing kind registry template: %w", err) + } + + b, err := format.Source(buf.Bytes()) + if err != nil { + return nil, err + } + + file := codejen.NewFile(filepath.Join(registryPath, "core_kind.go"), b, jenny) + return codejen.Files{*file}, nil +} + +func getSchemaName(v cue.Value) (string, error) { + name, err := getPackageName(v) + if err != nil { + return "", err + } + + name = strings.Replace(name, "-", "_", -1) + return strings.ToLower(name), nil +} diff --git a/pkg/codegen/jenny_corekind.go b/pkg/codegen/jenny_corekind.go deleted file mode 100644 index b7deb36cfb5..00000000000 --- a/pkg/codegen/jenny_corekind.go +++ /dev/null @@ -1,72 +0,0 @@ -package codegen - -import ( - "bytes" - "fmt" - "path/filepath" - - "github.com/grafana/codejen" - "github.com/grafana/kindsys" -) - -// CoreKindJenny generates the implementation of [kindsys.Core] for the provided -// kind declaration. -// -// gokindsdir should be the relative path to the parent directory that contains -// all generated kinds. -// -// This generator only has output for core structured kinds. -func CoreKindJenny(gokindsdir string, cfg *CoreKindJennyConfig) OneToOne { - if cfg == nil { - cfg = new(CoreKindJennyConfig) - } - if cfg.GenDirName == nil { - cfg.GenDirName = func(def kindsys.Kind) string { - return def.Props().Common().MachineName - } - } - - return &coreKindJenny{ - gokindsdir: gokindsdir, - cfg: cfg, - } -} - -// CoreKindJennyConfig holds configuration options for [CoreKindJenny]. -type CoreKindJennyConfig struct { - // GenDirName returns the name of the directory in which the file should be - // generated. Defaults to DefForGen.Lineage().Name() if nil. - GenDirName func(kindsys.Kind) string -} - -type coreKindJenny struct { - gokindsdir string - cfg *CoreKindJennyConfig -} - -var _ OneToOne = &coreKindJenny{} - -func (gen *coreKindJenny) JennyName() string { - return "CoreKindJenny" -} - -func (gen *coreKindJenny) Generate(kind kindsys.Kind) (*codejen.File, error) { - if _, is := kind.(kindsys.Core); !is { - return nil, nil - } - - path := filepath.Join(gen.gokindsdir, gen.cfg.GenDirName(kind), kind.Props().Common().MachineName+"_kind_gen.go") - buf := new(bytes.Buffer) - if err := tmpls.Lookup("kind_core.tmpl").Execute(buf, kind); err != nil { - return nil, fmt.Errorf("failed executing kind_core template for %s: %w", path, err) - } - b, err := postprocessGoFile(genGoFile{ - path: path, - in: buf.Bytes(), - }) - if err != nil { - return nil, err - } - - return codejen.NewFile(path, b, gen), nil -} diff --git a/pkg/codegen/jenny_k8_resources.go b/pkg/codegen/jenny_k8_resources.go index 56ca123af73..547ca2bcc71 100644 --- a/pkg/codegen/jenny_k8_resources.go +++ b/pkg/codegen/jenny_k8_resources.go @@ -18,15 +18,15 @@ func (jenny *K8ResourcesJenny) JennyName() string { return "K8ResourcesJenny" } -func (jenny *K8ResourcesJenny) Generate(cueFiles []cue.Value) (codejen.Files, error) { +func (jenny *K8ResourcesJenny) Generate(cueFiles []CueSchema) (codejen.Files, error) { files := make(codejen.Files, 0) for _, val := range cueFiles { - pkg, err := getPackageName(val) + pkg, err := getPackageName(val.CueFile) if err != nil { return nil, err } - resource, err := jenny.genResource(pkg, val) + resource, err := jenny.genResource(pkg, val.CueFile) if err != nil { return nil, err } diff --git a/pkg/codegen/tmpl.go b/pkg/codegen/tmpl.go index d329e990f68..8f8a179cb32 100644 --- a/pkg/codegen/tmpl.go +++ b/pkg/codegen/tmpl.go @@ -7,7 +7,6 @@ import ( "time" "github.com/grafana/codejen" - "github.com/grafana/kindsys" ) // All the parsed templates in the tmpl subdirectory @@ -33,11 +32,7 @@ type ( From string Leader string } - tvars_kind_registry struct { - PackageName string - KindPackagePrefix string - Kinds []kindsys.Core - } + tvars_resource struct { PackageName string KindName string @@ -51,4 +46,13 @@ type ( tvars_status struct { PackageName string } + + tvars_registry struct { + Schemas []Schema + } + + Schema struct { + Name string + FilePath string + } ) diff --git a/pkg/codegen/tmpl/core_registry.tmpl b/pkg/codegen/tmpl/core_registry.tmpl new file mode 100644 index 00000000000..8fc8bb9b092 --- /dev/null +++ b/pkg/codegen/tmpl/core_registry.tmpl @@ -0,0 +1,46 @@ +package schemas + +import ( + "os" + "path/filepath" + "runtime" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" +) + +type CoreKind struct { + Name string + CueFile cue.Value +} + +func GetCoreKinds() ([]CoreKind, error) { + ctx := cuecontext.New() + kinds := make([]CoreKind, 0) + + _, caller, _, _ := runtime.Caller(0) + root := filepath.Join(caller, "../../../..") + + {{- range .Schemas }} + + {{ .Name }}Cue, err := loadCueFile(ctx, filepath.Join(root, "{{ .FilePath }}")) + if err != nil { + return nil, err + } + kinds = append(kinds, CoreKind{ + Name: "{{ .Name }}", + CueFile: {{ .Name }}Cue, + }) + {{- end }} + + return kinds, nil +} + +func loadCueFile(ctx *cue.Context, path string) (cue.Value, error) { + cueFile, err := os.ReadFile(path) + if err != nil { + return cue.Value{}, err + } + + return ctx.CompileBytes(cueFile), nil +} diff --git a/pkg/codegen/tmpl/kind_core.tmpl b/pkg/codegen/tmpl/kind_core.tmpl deleted file mode 100644 index a2db88dffaf..00000000000 --- a/pkg/codegen/tmpl/kind_core.tmpl +++ /dev/null @@ -1,70 +0,0 @@ -package {{ .Props.MachineName }} - -import ( - "github.com/grafana/kindsys" - "github.com/grafana/thema" - "github.com/grafana/thema/vmux" - - "github.com/grafana/grafana/pkg/cuectx" -) - -// rootrel is the relative path from the grafana repository root to the -// directory containing the .cue files in which this kind is defined. Necessary -// for runtime errors related to the definition and/or lineage to provide -// a real path to the correct .cue file. -const rootrel string = "kinds/{{ .Props.MachineName }}" - -// TODO standard generated docs -type Kind struct { - kindsys.Core - lin thema.ConvergentLineage[*Resource] - jcodec vmux.Codec - valmux vmux.ValueMux[*Resource] -} - -// type guard - ensure generated Kind type satisfies the kindsys.Core interface -var _ kindsys.Core = &Kind{} - -// TODO standard generated docs -func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { - def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) - if err != nil { - return nil, err - } - - k := &Kind{} - k.Core, err = kindsys.BindCore(rt, def, opts...) - if err != nil { - return nil, err - } - // Get the thema.Schema that the meta says is in the current version (which - // codegen ensures is always the latest) - cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) - tsch, err := thema.BindType(cursch, &Resource{}) - if err != nil { - // Should be unreachable, modulo bugs in the Thema->Go code generator - return nil, err - } - - k.jcodec = vmux.NewJSONCodec("{{ .Props.MachineName }}.json") - k.lin = tsch.ConvergentLineage() - k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) - return k, nil -} - -// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) -// to the the {{ .Props.Name }} [Resource] type generated from the current schema, v{{ .Props.CurrentVersion }}. -func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { - return k.lin -} - -// JSONValueMux is a version multiplexer that maps a []byte containing JSON data -// at any schematized dashboard version to an instance of {{ .Props.Name }} [Resource]. -// -// Validation and translation errors emitted from this func will identify the -// input bytes as "dashboard.json". -// -// This is a thin wrapper around Thema's [vmux.ValueMux]. -func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { - return k.valmux(b) -} diff --git a/pkg/codegen/tmpl/kind_registry.tmpl b/pkg/codegen/tmpl/kind_registry.tmpl deleted file mode 100644 index 59cf5fbc0ae..00000000000 --- a/pkg/codegen/tmpl/kind_registry.tmpl +++ /dev/null @@ -1,61 +0,0 @@ -package {{ .PackageName }} - -import ( - "fmt" - "sync" - - {{range .Kinds }} - "{{ $.KindPackagePrefix }}/{{ .Props.MachineName }}"{{end}} - "github.com/grafana/grafana/pkg/cuectx" - "github.com/grafana/kindsys" - "github.com/grafana/thema" -) - -// Base is a registry of all Grafana core kinds. It is designed for use both inside -// of Grafana itself, and for import by external Go programs wanting to work with Grafana's -// kind system. -// -// The registry provides two modes for accessing core kinds: -// * Per-kind methods, which return the kind-specific type, e.g. Dashboard() returns [dashboard.Dashboard]. -// * All(), which returns a slice of [kindsys.Core]. -// -// Prefer the individual named methods for use cases where the particular kind(s) that -// are needed are known to the caller. For example, a dashboard linter can know that it -// specifically wants the dashboard kind. -// -// Prefer All() when performing operations generically across all kinds. For example, -// a generic HTTP middleware for validating request bodies expected to contain some -// kind-schematized type. -type Base struct { - all []kindsys.Core - {{- range .Kinds }} - {{ .Props.MachineName }} *{{ .Props.MachineName }}.Kind{{end}} -} - -// type guards -var ( -{{- range .Kinds }} - _ kindsys.Core = &{{ .Props.MachineName }}.Kind{}{{end}} -) - -{{range .Kinds }} -// {{ .Props.Name }} returns the [kindsys.Interface] implementation for the {{ .Props.MachineName }} kind. -func (b *Base) {{ .Props.Name }}() *{{ .Props.MachineName }}.Kind { - return b.{{ .Props.MachineName }} -} -{{end}} - -func doNewBase(rt *thema.Runtime) *Base { - var err error - reg := &Base{} - -{{range .Kinds }} - reg.{{ .Props.MachineName }}, err = {{ .Props.MachineName }}.NewKind(rt) - if err != nil { - panic(fmt.Sprintf("error while initializing the {{ .Props.MachineName }} Kind: %s", err)) - } - reg.all = append(reg.all, reg.{{ .Props.MachineName }}) -{{end}} - - return reg -} diff --git a/pkg/codegen/util_go.go b/pkg/codegen/util_go.go index 2cd172b5d66..faecaae57c7 100644 --- a/pkg/codegen/util_go.go +++ b/pkg/codegen/util_go.go @@ -1,74 +1,14 @@ package codegen import ( - "bytes" "fmt" - "go/format" - "go/parser" - "go/token" - "os" - "path/filepath" "regexp" "strings" "github.com/dave/dst" - "github.com/dave/dst/decorator" "github.com/dave/dst/dstutil" - "golang.org/x/tools/imports" ) -type genGoFile struct { - path string - walker dstutil.ApplyFunc - in []byte -} - -func postprocessGoFile(cfg genGoFile) ([]byte, error) { - fname := filepath.Base(cfg.path) - buf := new(bytes.Buffer) - fset := token.NewFileSet() - gf, err := decorator.ParseFile(fset, fname, string(cfg.in), parser.ParseComments) - if err != nil { - return nil, fmt.Errorf("error parsing generated file: %w", err) - } - - if cfg.walker != nil { - dstutil.Apply(gf, cfg.walker, nil) - - err = format.Node(buf, fset, gf) - if err != nil { - return nil, fmt.Errorf("error formatting Go AST: %w", err) - } - } else { - buf = bytes.NewBuffer(cfg.in) - } - - byt, err := imports.Process(fname, buf.Bytes(), nil) - if err != nil { - return nil, fmt.Errorf("goimports processing failed: %w", err) - } - - // Compare imports before and after; warn about performance if some were added - gfa, _ := parser.ParseFile(fset, fname, string(byt), parser.ParseComments) - imap := make(map[string]bool) - for _, im := range gf.Imports { - imap[im.Path.Value] = true - } - var added []string - for _, im := range gfa.Imports { - if !imap[im.Path.Value] { - added = append(added, im.Path.Value) - } - } - - if len(added) != 0 { - // TODO improve the guidance in this error if/when we better abstract over imports to generate - fmt.Fprintf(os.Stderr, "The following imports were added by goimports while generating %s: \n\t%s\nRelying on goimports to find imports significantly slows down code generation. Consider adding these to the relevant template.\n", cfg.path, strings.Join(added, "\n\t")) - } - - return byt, nil -} - type prefixmod struct { prefix string replace string diff --git a/pkg/kinds/accesspolicy/accesspolicy_kind_gen.go b/pkg/kinds/accesspolicy/accesspolicy_kind_gen.go deleted file mode 100644 index 200ad2653d0..00000000000 --- a/pkg/kinds/accesspolicy/accesspolicy_kind_gen.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// CoreKindJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -package accesspolicy - -import ( - "github.com/grafana/kindsys" - "github.com/grafana/thema" - "github.com/grafana/thema/vmux" - - "github.com/grafana/grafana/pkg/cuectx" -) - -// rootrel is the relative path from the grafana repository root to the -// directory containing the .cue files in which this kind is defined. Necessary -// for runtime errors related to the definition and/or lineage to provide -// a real path to the correct .cue file. -const rootrel string = "kinds/accesspolicy" - -// TODO standard generated docs -type Kind struct { - kindsys.Core - lin thema.ConvergentLineage[*Resource] - jcodec vmux.Codec - valmux vmux.ValueMux[*Resource] -} - -// type guard - ensure generated Kind type satisfies the kindsys.Core interface -var _ kindsys.Core = &Kind{} - -// TODO standard generated docs -func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { - def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) - if err != nil { - return nil, err - } - - k := &Kind{} - k.Core, err = kindsys.BindCore(rt, def, opts...) - if err != nil { - return nil, err - } - // Get the thema.Schema that the meta says is in the current version (which - // codegen ensures is always the latest) - cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) - tsch, err := thema.BindType(cursch, &Resource{}) - if err != nil { - // Should be unreachable, modulo bugs in the Thema->Go code generator - return nil, err - } - - k.jcodec = vmux.NewJSONCodec("accesspolicy.json") - k.lin = tsch.ConvergentLineage() - k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) - return k, nil -} - -// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) -// to the the AccessPolicy [Resource] type generated from the current schema, v0.0. -func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { - return k.lin -} - -// JSONValueMux is a version multiplexer that maps a []byte containing JSON data -// at any schematized dashboard version to an instance of AccessPolicy [Resource]. -// -// Validation and translation errors emitted from this func will identify the -// input bytes as "dashboard.json". -// -// This is a thin wrapper around Thema's [vmux.ValueMux]. -func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { - return k.valmux(b) -} diff --git a/pkg/kinds/dashboard/dashboard_kind_gen.go b/pkg/kinds/dashboard/dashboard_kind_gen.go deleted file mode 100644 index 4fe311a23a4..00000000000 --- a/pkg/kinds/dashboard/dashboard_kind_gen.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// CoreKindJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -package dashboard - -import ( - "github.com/grafana/kindsys" - "github.com/grafana/thema" - "github.com/grafana/thema/vmux" - - "github.com/grafana/grafana/pkg/cuectx" -) - -// rootrel is the relative path from the grafana repository root to the -// directory containing the .cue files in which this kind is defined. Necessary -// for runtime errors related to the definition and/or lineage to provide -// a real path to the correct .cue file. -const rootrel string = "kinds/dashboard" - -// TODO standard generated docs -type Kind struct { - kindsys.Core - lin thema.ConvergentLineage[*Resource] - jcodec vmux.Codec - valmux vmux.ValueMux[*Resource] -} - -// type guard - ensure generated Kind type satisfies the kindsys.Core interface -var _ kindsys.Core = &Kind{} - -// TODO standard generated docs -func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { - def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) - if err != nil { - return nil, err - } - - k := &Kind{} - k.Core, err = kindsys.BindCore(rt, def, opts...) - if err != nil { - return nil, err - } - // Get the thema.Schema that the meta says is in the current version (which - // codegen ensures is always the latest) - cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) - tsch, err := thema.BindType(cursch, &Resource{}) - if err != nil { - // Should be unreachable, modulo bugs in the Thema->Go code generator - return nil, err - } - - k.jcodec = vmux.NewJSONCodec("dashboard.json") - k.lin = tsch.ConvergentLineage() - k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) - return k, nil -} - -// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) -// to the the Dashboard [Resource] type generated from the current schema, v0.0. -func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { - return k.lin -} - -// JSONValueMux is a version multiplexer that maps a []byte containing JSON data -// at any schematized dashboard version to an instance of Dashboard [Resource]. -// -// Validation and translation errors emitted from this func will identify the -// input bytes as "dashboard.json". -// -// This is a thin wrapper around Thema's [vmux.ValueMux]. -func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { - return k.valmux(b) -} diff --git a/pkg/kinds/librarypanel/librarypanel_kind_gen.go b/pkg/kinds/librarypanel/librarypanel_kind_gen.go deleted file mode 100644 index 246c8869bc1..00000000000 --- a/pkg/kinds/librarypanel/librarypanel_kind_gen.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// CoreKindJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -package librarypanel - -import ( - "github.com/grafana/kindsys" - "github.com/grafana/thema" - "github.com/grafana/thema/vmux" - - "github.com/grafana/grafana/pkg/cuectx" -) - -// rootrel is the relative path from the grafana repository root to the -// directory containing the .cue files in which this kind is defined. Necessary -// for runtime errors related to the definition and/or lineage to provide -// a real path to the correct .cue file. -const rootrel string = "kinds/librarypanel" - -// TODO standard generated docs -type Kind struct { - kindsys.Core - lin thema.ConvergentLineage[*Resource] - jcodec vmux.Codec - valmux vmux.ValueMux[*Resource] -} - -// type guard - ensure generated Kind type satisfies the kindsys.Core interface -var _ kindsys.Core = &Kind{} - -// TODO standard generated docs -func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { - def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) - if err != nil { - return nil, err - } - - k := &Kind{} - k.Core, err = kindsys.BindCore(rt, def, opts...) - if err != nil { - return nil, err - } - // Get the thema.Schema that the meta says is in the current version (which - // codegen ensures is always the latest) - cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) - tsch, err := thema.BindType(cursch, &Resource{}) - if err != nil { - // Should be unreachable, modulo bugs in the Thema->Go code generator - return nil, err - } - - k.jcodec = vmux.NewJSONCodec("librarypanel.json") - k.lin = tsch.ConvergentLineage() - k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) - return k, nil -} - -// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) -// to the the LibraryPanel [Resource] type generated from the current schema, v0.0. -func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { - return k.lin -} - -// JSONValueMux is a version multiplexer that maps a []byte containing JSON data -// at any schematized dashboard version to an instance of LibraryPanel [Resource]. -// -// Validation and translation errors emitted from this func will identify the -// input bytes as "dashboard.json". -// -// This is a thin wrapper around Thema's [vmux.ValueMux]. -func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { - return k.valmux(b) -} diff --git a/pkg/kinds/preferences/preferences_kind_gen.go b/pkg/kinds/preferences/preferences_kind_gen.go deleted file mode 100644 index 9efc26fba77..00000000000 --- a/pkg/kinds/preferences/preferences_kind_gen.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// CoreKindJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -package preferences - -import ( - "github.com/grafana/kindsys" - "github.com/grafana/thema" - "github.com/grafana/thema/vmux" - - "github.com/grafana/grafana/pkg/cuectx" -) - -// rootrel is the relative path from the grafana repository root to the -// directory containing the .cue files in which this kind is defined. Necessary -// for runtime errors related to the definition and/or lineage to provide -// a real path to the correct .cue file. -const rootrel string = "kinds/preferences" - -// TODO standard generated docs -type Kind struct { - kindsys.Core - lin thema.ConvergentLineage[*Resource] - jcodec vmux.Codec - valmux vmux.ValueMux[*Resource] -} - -// type guard - ensure generated Kind type satisfies the kindsys.Core interface -var _ kindsys.Core = &Kind{} - -// TODO standard generated docs -func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { - def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) - if err != nil { - return nil, err - } - - k := &Kind{} - k.Core, err = kindsys.BindCore(rt, def, opts...) - if err != nil { - return nil, err - } - // Get the thema.Schema that the meta says is in the current version (which - // codegen ensures is always the latest) - cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) - tsch, err := thema.BindType(cursch, &Resource{}) - if err != nil { - // Should be unreachable, modulo bugs in the Thema->Go code generator - return nil, err - } - - k.jcodec = vmux.NewJSONCodec("preferences.json") - k.lin = tsch.ConvergentLineage() - k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) - return k, nil -} - -// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) -// to the the Preferences [Resource] type generated from the current schema, v0.0. -func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { - return k.lin -} - -// JSONValueMux is a version multiplexer that maps a []byte containing JSON data -// at any schematized dashboard version to an instance of Preferences [Resource]. -// -// Validation and translation errors emitted from this func will identify the -// input bytes as "dashboard.json". -// -// This is a thin wrapper around Thema's [vmux.ValueMux]. -func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { - return k.valmux(b) -} diff --git a/pkg/kinds/publicdashboard/publicdashboard_kind_gen.go b/pkg/kinds/publicdashboard/publicdashboard_kind_gen.go deleted file mode 100644 index f82475779e8..00000000000 --- a/pkg/kinds/publicdashboard/publicdashboard_kind_gen.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// CoreKindJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -package publicdashboard - -import ( - "github.com/grafana/kindsys" - "github.com/grafana/thema" - "github.com/grafana/thema/vmux" - - "github.com/grafana/grafana/pkg/cuectx" -) - -// rootrel is the relative path from the grafana repository root to the -// directory containing the .cue files in which this kind is defined. Necessary -// for runtime errors related to the definition and/or lineage to provide -// a real path to the correct .cue file. -const rootrel string = "kinds/publicdashboard" - -// TODO standard generated docs -type Kind struct { - kindsys.Core - lin thema.ConvergentLineage[*Resource] - jcodec vmux.Codec - valmux vmux.ValueMux[*Resource] -} - -// type guard - ensure generated Kind type satisfies the kindsys.Core interface -var _ kindsys.Core = &Kind{} - -// TODO standard generated docs -func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { - def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) - if err != nil { - return nil, err - } - - k := &Kind{} - k.Core, err = kindsys.BindCore(rt, def, opts...) - if err != nil { - return nil, err - } - // Get the thema.Schema that the meta says is in the current version (which - // codegen ensures is always the latest) - cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) - tsch, err := thema.BindType(cursch, &Resource{}) - if err != nil { - // Should be unreachable, modulo bugs in the Thema->Go code generator - return nil, err - } - - k.jcodec = vmux.NewJSONCodec("publicdashboard.json") - k.lin = tsch.ConvergentLineage() - k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) - return k, nil -} - -// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) -// to the the PublicDashboard [Resource] type generated from the current schema, v0.0. -func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { - return k.lin -} - -// JSONValueMux is a version multiplexer that maps a []byte containing JSON data -// at any schematized dashboard version to an instance of PublicDashboard [Resource]. -// -// Validation and translation errors emitted from this func will identify the -// input bytes as "dashboard.json". -// -// This is a thin wrapper around Thema's [vmux.ValueMux]. -func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { - return k.valmux(b) -} diff --git a/pkg/kinds/role/role_kind_gen.go b/pkg/kinds/role/role_kind_gen.go deleted file mode 100644 index cc297cb0807..00000000000 --- a/pkg/kinds/role/role_kind_gen.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// CoreKindJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -package role - -import ( - "github.com/grafana/kindsys" - "github.com/grafana/thema" - "github.com/grafana/thema/vmux" - - "github.com/grafana/grafana/pkg/cuectx" -) - -// rootrel is the relative path from the grafana repository root to the -// directory containing the .cue files in which this kind is defined. Necessary -// for runtime errors related to the definition and/or lineage to provide -// a real path to the correct .cue file. -const rootrel string = "kinds/role" - -// TODO standard generated docs -type Kind struct { - kindsys.Core - lin thema.ConvergentLineage[*Resource] - jcodec vmux.Codec - valmux vmux.ValueMux[*Resource] -} - -// type guard - ensure generated Kind type satisfies the kindsys.Core interface -var _ kindsys.Core = &Kind{} - -// TODO standard generated docs -func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { - def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) - if err != nil { - return nil, err - } - - k := &Kind{} - k.Core, err = kindsys.BindCore(rt, def, opts...) - if err != nil { - return nil, err - } - // Get the thema.Schema that the meta says is in the current version (which - // codegen ensures is always the latest) - cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) - tsch, err := thema.BindType(cursch, &Resource{}) - if err != nil { - // Should be unreachable, modulo bugs in the Thema->Go code generator - return nil, err - } - - k.jcodec = vmux.NewJSONCodec("role.json") - k.lin = tsch.ConvergentLineage() - k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) - return k, nil -} - -// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) -// to the the Role [Resource] type generated from the current schema, v0.0. -func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { - return k.lin -} - -// JSONValueMux is a version multiplexer that maps a []byte containing JSON data -// at any schematized dashboard version to an instance of Role [Resource]. -// -// Validation and translation errors emitted from this func will identify the -// input bytes as "dashboard.json". -// -// This is a thin wrapper around Thema's [vmux.ValueMux]. -func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { - return k.valmux(b) -} diff --git a/pkg/kinds/rolebinding/rolebinding_kind_gen.go b/pkg/kinds/rolebinding/rolebinding_kind_gen.go deleted file mode 100644 index 603b1d96747..00000000000 --- a/pkg/kinds/rolebinding/rolebinding_kind_gen.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// CoreKindJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -package rolebinding - -import ( - "github.com/grafana/kindsys" - "github.com/grafana/thema" - "github.com/grafana/thema/vmux" - - "github.com/grafana/grafana/pkg/cuectx" -) - -// rootrel is the relative path from the grafana repository root to the -// directory containing the .cue files in which this kind is defined. Necessary -// for runtime errors related to the definition and/or lineage to provide -// a real path to the correct .cue file. -const rootrel string = "kinds/rolebinding" - -// TODO standard generated docs -type Kind struct { - kindsys.Core - lin thema.ConvergentLineage[*Resource] - jcodec vmux.Codec - valmux vmux.ValueMux[*Resource] -} - -// type guard - ensure generated Kind type satisfies the kindsys.Core interface -var _ kindsys.Core = &Kind{} - -// TODO standard generated docs -func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { - def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) - if err != nil { - return nil, err - } - - k := &Kind{} - k.Core, err = kindsys.BindCore(rt, def, opts...) - if err != nil { - return nil, err - } - // Get the thema.Schema that the meta says is in the current version (which - // codegen ensures is always the latest) - cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) - tsch, err := thema.BindType(cursch, &Resource{}) - if err != nil { - // Should be unreachable, modulo bugs in the Thema->Go code generator - return nil, err - } - - k.jcodec = vmux.NewJSONCodec("rolebinding.json") - k.lin = tsch.ConvergentLineage() - k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) - return k, nil -} - -// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) -// to the the RoleBinding [Resource] type generated from the current schema, v0.0. -func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { - return k.lin -} - -// JSONValueMux is a version multiplexer that maps a []byte containing JSON data -// at any schematized dashboard version to an instance of RoleBinding [Resource]. -// -// Validation and translation errors emitted from this func will identify the -// input bytes as "dashboard.json". -// -// This is a thin wrapper around Thema's [vmux.ValueMux]. -func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { - return k.valmux(b) -} diff --git a/pkg/kinds/team/team_kind_gen.go b/pkg/kinds/team/team_kind_gen.go deleted file mode 100644 index 8fc6e091565..00000000000 --- a/pkg/kinds/team/team_kind_gen.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// CoreKindJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -package team - -import ( - "github.com/grafana/kindsys" - "github.com/grafana/thema" - "github.com/grafana/thema/vmux" - - "github.com/grafana/grafana/pkg/cuectx" -) - -// rootrel is the relative path from the grafana repository root to the -// directory containing the .cue files in which this kind is defined. Necessary -// for runtime errors related to the definition and/or lineage to provide -// a real path to the correct .cue file. -const rootrel string = "kinds/team" - -// TODO standard generated docs -type Kind struct { - kindsys.Core - lin thema.ConvergentLineage[*Resource] - jcodec vmux.Codec - valmux vmux.ValueMux[*Resource] -} - -// type guard - ensure generated Kind type satisfies the kindsys.Core interface -var _ kindsys.Core = &Kind{} - -// TODO standard generated docs -func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { - def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) - if err != nil { - return nil, err - } - - k := &Kind{} - k.Core, err = kindsys.BindCore(rt, def, opts...) - if err != nil { - return nil, err - } - // Get the thema.Schema that the meta says is in the current version (which - // codegen ensures is always the latest) - cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) - tsch, err := thema.BindType(cursch, &Resource{}) - if err != nil { - // Should be unreachable, modulo bugs in the Thema->Go code generator - return nil, err - } - - k.jcodec = vmux.NewJSONCodec("team.json") - k.lin = tsch.ConvergentLineage() - k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) - return k, nil -} - -// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) -// to the the Team [Resource] type generated from the current schema, v0.0. -func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { - return k.lin -} - -// JSONValueMux is a version multiplexer that maps a []byte containing JSON data -// at any schematized dashboard version to an instance of Team [Resource]. -// -// Validation and translation errors emitted from this func will identify the -// input bytes as "dashboard.json". -// -// This is a thin wrapper around Thema's [vmux.ValueMux]. -func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { - return k.valmux(b) -} diff --git a/pkg/plugins/codegen/jenny_plugin_registry.go b/pkg/plugins/codegen/jenny_plugin_registry.go new file mode 100644 index 00000000000..e28b3222ee0 --- /dev/null +++ b/pkg/plugins/codegen/jenny_plugin_registry.go @@ -0,0 +1,74 @@ +package codegen + +import ( + "bytes" + "fmt" + "go/format" + "path/filepath" + "strings" + + "github.com/grafana/codejen" +) + +var registryPath = filepath.Join("pkg", "registry", "schemas") + +var renamedPlugins = map[string]string{ + "cloud-monitoring": "googlecloudmonitoring", + "grafana-pyroscope-datasource": "grafanapyroscope", + "annolist": "annotationslist", + "grafanatestdatadatasource": "testdata", + "dashlist": "dashboardlist", +} + +type PluginRegistryJenny struct { +} + +func (jenny *PluginRegistryJenny) JennyName() string { + return "PluginRegistryJenny" +} + +func (jenny *PluginRegistryJenny) Generate(files []string) (*codejen.File, error) { + if len(files) == 0 { + return nil, nil + } + schemas := make([]Schema, len(files)) + for i, file := range files { + name, err := getSchemaName(file) + if err != nil { + return nil, fmt.Errorf("unable to find schema name: %s", err) + } + + schemas[i] = Schema{ + Name: name, + Filename: filepath.Base(file), + FilePath: file, + } + } + + buf := new(bytes.Buffer) + if err := tmpls.Lookup("composable_registry.tmpl").Execute(buf, tmpl_vars_plugin_registry{ + Schemas: schemas, + }); err != nil { + return nil, fmt.Errorf("failed executing kind registry template: %w", err) + } + + b, err := format.Source(buf.Bytes()) + if err != nil { + return nil, err + } + + return codejen.NewFile(filepath.Join(registryPath, "composable_kind.go"), b, jenny), nil +} + +func getSchemaName(path string) (string, error) { + parts := strings.Split(path, "/") + if len(parts) < 2 { + return "", fmt.Errorf("path should contain more than 2 elements") + } + folderName := parts[len(parts)-2] + if renamed, ok := renamedPlugins[folderName]; ok { + folderName = renamed + } + folderName = strings.ReplaceAll(folderName, "-", "") + return strings.ToLower(folderName), nil +} diff --git a/pkg/plugins/codegen/jenny_plugintreelist.go b/pkg/plugins/codegen/jenny_plugintreelist.go deleted file mode 100644 index fe737720434..00000000000 --- a/pkg/plugins/codegen/jenny_plugintreelist.go +++ /dev/null @@ -1,100 +0,0 @@ -package codegen - -import ( - "bytes" - "fmt" - "path" - "path/filepath" - "strings" - - "github.com/grafana/codejen" - "github.com/grafana/grafana/pkg/plugins/pfs" -) - -const prefix = "github.com/grafana/grafana/public/app/plugins" - -// PluginTreeListJenny creates a [codejen.ManyToOne] that produces Go code -// for loading a [pfs.PluginList] given [*kindsys.PluginDecl] as inputs. -func PluginTreeListJenny() codejen.ManyToOne[*pfs.PluginDecl] { - outputFile := filepath.Join("pkg", "plugins", "pfs", "corelist", "corelist_load_gen.go") - - return &ptlJenny{ - outputFile: outputFile, - plugins: make(map[string]bool, 0), - } -} - -type ptlJenny struct { - outputFile string - plugins map[string]bool -} - -func (j *ptlJenny) JennyName() string { - return "PluginTreeListJenny" -} - -func (j *ptlJenny) Generate(decls ...*pfs.PluginDecl) (*codejen.File, error) { - buf := new(bytes.Buffer) - vars := templateVars_plugin_registry{ - Plugins: make([]struct { - PkgName, Path, ImportPath string - NoAlias bool - }, 0, len(decls)), - } - - type tpl struct { - PkgName, Path, ImportPath string - NoAlias bool - } - - for _, decl := range decls { - meta := decl.PluginMeta - - if _, exists := j.plugins[meta.Id]; exists { - continue - } - - pluginId := j.sanitizePluginId(meta.Id) - vars.Plugins = append(vars.Plugins, tpl{ - PkgName: pluginId, - NoAlias: pluginId != filepath.Base(decl.PluginPath), - ImportPath: filepath.ToSlash(filepath.Join(prefix, decl.PluginPath)), - Path: path.Join(append(strings.Split(prefix, "/")[3:], decl.PluginPath)...), - }) - - j.plugins[meta.Id] = true - } - - if err := tmpls.Lookup("plugin_registry.tmpl").Execute(buf, vars); err != nil { - return nil, fmt.Errorf("failed executing plugin registry template: %w", err) - } - - byt, err := postprocessGoFile(genGoFile{ - path: j.outputFile, - in: buf.Bytes(), - }) - if err != nil { - return nil, fmt.Errorf("error postprocessing plugin registry: %w", err) - } - - return codejen.NewFile(j.outputFile, byt, j), nil -} - -func (j *ptlJenny) sanitizePluginId(s string) string { - return strings.Map(func(r rune) rune { - switch { - case r >= 'a' && r <= 'z': - fallthrough - case r >= 'A' && r <= 'Z': - fallthrough - case r >= '0' && r <= '9': - fallthrough - case r == '_': - return r - case r == '-': - return '_' - default: - return -1 - } - }, s) -} diff --git a/pkg/plugins/codegen/tmpl.go b/pkg/plugins/codegen/tmpl.go index 71bd93cc1b8..729072a440f 100644 --- a/pkg/plugins/codegen/tmpl.go +++ b/pkg/plugins/codegen/tmpl.go @@ -22,12 +22,13 @@ var tmplFS embed.FS // The following group of types, beginning with templateVars_*, all contain the set // of variables expected by the corresponding named template file under tmpl/ type ( - templateVars_plugin_registry struct { - Plugins []struct { - PkgName string - Path string - ImportPath string - NoAlias bool - } + tmpl_vars_plugin_registry struct { + Schemas []Schema + } + + Schema struct { + Name string + Filename string + FilePath string } ) diff --git a/pkg/plugins/codegen/tmpl/composable_registry.tmpl b/pkg/plugins/codegen/tmpl/composable_registry.tmpl new file mode 100644 index 00000000000..5ad6416a41a --- /dev/null +++ b/pkg/plugins/codegen/tmpl/composable_registry.tmpl @@ -0,0 +1,132 @@ +package schemas + +import ( + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "runtime" + "testing/fstest" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" + "cuelang.org/go/cue/load" +) + +var cueImportsPath = filepath.Join("packages", "grafana-schema", "src", "common") +var importPath = "github.com/grafana/grafana/packages/grafana-schema/src/common" + +type ComposableKind struct { + Name string + Filename string + CueFile cue.Value +} + +func GetComposableKinds() ([]ComposableKind, error) { + kinds := make([]ComposableKind, 0) + + _, caller, _, _ := runtime.Caller(0) + root := filepath.Join(caller, "../../../..") + + {{- range .Schemas }} + + {{ .Name }}Cue, err := loadCueFileWithCommon(root, filepath.Join(root, "{{ .FilePath }}")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "{{ .Name }}", + Filename: "{{ .Filename }}", + CueFile: {{ .Name }}Cue, + }) + {{- end }} + + return kinds, nil +} + +func loadCueFileWithCommon(root string, entrypoint string) (cue.Value, error) { + commonFS, err := mockCommonFS(root) + if err != nil { + fmt.Printf("cannot load common cue files: %s\n", err) + return cue.Value{}, err + } + + overlay, err := buildOverlay(commonFS) + if err != nil { + fmt.Printf("Cannot build overlay: %s\n", err) + return cue.Value{}, err + } + + bis := load.Instances([]string{entrypoint}, &load.Config{ + ModuleRoot: "/", + Overlay: overlay, + }) + + values, err := cuecontext.New().BuildInstances(bis) + if err != nil { + fmt.Printf("Cannot build instance: %s\n", err) + return cue.Value{}, err + } + + return values[0], nil +} + +func mockCommonFS(root string) (fs.FS, error) { + path := filepath.Join(root, cueImportsPath) + dir, err := os.ReadDir(path) + if err != nil { + return nil, fmt.Errorf("cannot open common cue files directory: %s", err) + } + + prefix := "cue.mod/pkg/" + importPath + + commonFS := fstest.MapFS{} + for _, d := range dir { + if d.IsDir() { + continue + } + + readPath := filepath.Join(path, d.Name()) + b, err := os.ReadFile(filepath.Clean(readPath)) + if err != nil { + return nil, err + } + + commonFS[filepath.Join(prefix, d.Name())] = &fstest.MapFile{Data: b} + } + + return commonFS, nil +} + +// It loads common cue files into the schema to be able to make import works +func buildOverlay(commonFS fs.FS) (map[string]load.Source, error) { + overlay := make(map[string]load.Source) + + err := fs.WalkDir(commonFS, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + return nil + } + + f, err := commonFS.Open(path) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + + b, err := io.ReadAll(f) + if err != nil { + return err + } + + overlay[filepath.Join("/", path)] = load.FromBytes(b) + + return nil + }) + + return overlay, err +} diff --git a/pkg/plugins/codegen/tmpl/plugin_registry.tmpl b/pkg/plugins/codegen/tmpl/plugin_registry.tmpl deleted file mode 100644 index 26ef440e02b..00000000000 --- a/pkg/plugins/codegen/tmpl/plugin_registry.tmpl +++ /dev/null @@ -1,30 +0,0 @@ -package corelist - -import ( - "fmt" - "io/fs" - "sync" - "github.com/grafana/grafana" - "github.com/grafana/grafana/pkg/plugins/pfs" - "github.com/grafana/thema" -) - -func parsePluginOrPanic(path string, pkgname string, rt *thema.Runtime) pfs.ParsedPlugin { - sub, err := fs.Sub(grafana.CueSchemaFS, path) - if err != nil { - panic("could not create fs sub to " + path) - } - pp, err := pfs.ParsePluginFS(sub, rt) - if err != nil { - panic(fmt.Sprintf("error parsing plugin metadata for %s: %s", pkgname, err)) - } - return pp -} - -func corePlugins(rt *thema.Runtime) []pfs.ParsedPlugin{ - return []pfs.ParsedPlugin{ - {{- range .Plugins }} - parsePluginOrPanic("{{ .Path }}", "{{ .PkgName }}", rt), - {{- end }} - } -} diff --git a/pkg/plugins/codegen/util_go.go b/pkg/plugins/codegen/util_go.go deleted file mode 100644 index a9caf75ea2a..00000000000 --- a/pkg/plugins/codegen/util_go.go +++ /dev/null @@ -1,68 +0,0 @@ -package codegen - -import ( - "bytes" - "fmt" - "go/format" - "go/parser" - "go/token" - "os" - "path/filepath" - "strings" - - "github.com/dave/dst/decorator" - "github.com/dave/dst/dstutil" - "golang.org/x/tools/imports" -) - -type genGoFile struct { - path string - walker dstutil.ApplyFunc - in []byte -} - -func postprocessGoFile(cfg genGoFile) ([]byte, error) { - fname := filepath.Base(cfg.path) - buf := new(bytes.Buffer) - fset := token.NewFileSet() - gf, err := decorator.ParseFile(fset, fname, string(cfg.in), parser.ParseComments) - if err != nil { - return nil, fmt.Errorf("error parsing generated file: %w", err) - } - - if cfg.walker != nil { - dstutil.Apply(gf, cfg.walker, nil) - - err = format.Node(buf, fset, gf) - if err != nil { - return nil, fmt.Errorf("error formatting Go AST: %w", err) - } - } else { - buf = bytes.NewBuffer(cfg.in) - } - - byt, err := imports.Process(fname, buf.Bytes(), nil) - if err != nil { - return nil, fmt.Errorf("goimports processing failed: %w", err) - } - - // Compare imports before and after; warn about performance if some were added - gfa, _ := parser.ParseFile(fset, fname, string(byt), parser.ParseComments) - imap := make(map[string]bool) - for _, im := range gf.Imports { - imap[im.Path.Value] = true - } - var added []string - for _, im := range gfa.Imports { - if !imap[im.Path.Value] { - added = append(added, im.Path.Value) - } - } - - if len(added) != 0 { - // TODO improve the guidance in this error if/when we better abstract over imports to generate - fmt.Fprintf(os.Stderr, "The following imports were added by goimports while generating %s: \n\t%s\nRelying on goimports to find imports significantly slows down code generation. Consider adding these to the relevant template.\n", cfg.path, strings.Join(added, "\n\t")) - } - - return byt, nil -} diff --git a/pkg/registry/corekind/base.go b/pkg/registry/corekind/base.go deleted file mode 100644 index 148bbb72f40..00000000000 --- a/pkg/registry/corekind/base.go +++ /dev/null @@ -1,42 +0,0 @@ -package corekind - -import ( - "sync" - - "github.com/grafana/kindsys" - "github.com/grafana/thema" - - "github.com/grafana/grafana/pkg/cuectx" -) - -var ( - baseOnce sync.Once - defaultBase *Base -) - -// NewBase provides a registry of all core raw and structured kinds, without any -// composition of slot kinds. -// -// All calling code within grafana/grafana is expected to use Grafana's -// singleton [thema.Runtime], returned from [cuectx.GrafanaThemaRuntime]. If nil -// is passed, the singleton will be used. -func NewBase(rt *thema.Runtime) *Base { - allrt := cuectx.GrafanaThemaRuntime() - if rt == nil || rt == allrt { - baseOnce.Do(func() { - defaultBase = doNewBase(allrt) - }) - return defaultBase - } - - return doNewBase(rt) -} - -// All returns a slice of [kindsys.Core] containing all core Grafana kinds. -// -// The returned slice is sorted lexicographically by kind machine name. -func (b *Base) All() []kindsys.Core { - ret := make([]kindsys.Core, len(b.all)) - copy(ret, b.all) - return ret -} diff --git a/pkg/registry/corekind/base_gen.go b/pkg/registry/corekind/base_gen.go deleted file mode 100644 index 658078498b5..00000000000 --- a/pkg/registry/corekind/base_gen.go +++ /dev/null @@ -1,159 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// BaseCoreRegistryJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -package corekind - -import ( - "fmt" - - "github.com/grafana/grafana/pkg/kinds/accesspolicy" - "github.com/grafana/grafana/pkg/kinds/dashboard" - "github.com/grafana/grafana/pkg/kinds/librarypanel" - "github.com/grafana/grafana/pkg/kinds/preferences" - "github.com/grafana/grafana/pkg/kinds/publicdashboard" - "github.com/grafana/grafana/pkg/kinds/role" - "github.com/grafana/grafana/pkg/kinds/rolebinding" - "github.com/grafana/grafana/pkg/kinds/team" - "github.com/grafana/kindsys" - "github.com/grafana/thema" -) - -// Base is a registry of all Grafana core kinds. It is designed for use both inside -// of Grafana itself, and for import by external Go programs wanting to work with Grafana's -// kind system. -// -// The registry provides two modes for accessing core kinds: -// - Per-kind methods, which return the kind-specific type, e.g. Dashboard() returns [dashboard.Dashboard]. -// - All(), which returns a slice of [kindsys.Core]. -// -// Prefer the individual named methods for use cases where the particular kind(s) that -// are needed are known to the caller. For example, a dashboard linter can know that it -// specifically wants the dashboard kind. -// -// Prefer All() when performing operations generically across all kinds. For example, -// a generic HTTP middleware for validating request bodies expected to contain some -// kind-schematized type. -type Base struct { - all []kindsys.Core - accesspolicy *accesspolicy.Kind - dashboard *dashboard.Kind - librarypanel *librarypanel.Kind - preferences *preferences.Kind - publicdashboard *publicdashboard.Kind - role *role.Kind - rolebinding *rolebinding.Kind - team *team.Kind -} - -// type guards -var ( - _ kindsys.Core = &accesspolicy.Kind{} - _ kindsys.Core = &dashboard.Kind{} - _ kindsys.Core = &librarypanel.Kind{} - _ kindsys.Core = &preferences.Kind{} - _ kindsys.Core = &publicdashboard.Kind{} - _ kindsys.Core = &role.Kind{} - _ kindsys.Core = &rolebinding.Kind{} - _ kindsys.Core = &team.Kind{} -) - -// AccessPolicy returns the [kindsys.Interface] implementation for the accesspolicy kind. -func (b *Base) AccessPolicy() *accesspolicy.Kind { - return b.accesspolicy -} - -// Dashboard returns the [kindsys.Interface] implementation for the dashboard kind. -func (b *Base) Dashboard() *dashboard.Kind { - return b.dashboard -} - -// LibraryPanel returns the [kindsys.Interface] implementation for the librarypanel kind. -func (b *Base) LibraryPanel() *librarypanel.Kind { - return b.librarypanel -} - -// Preferences returns the [kindsys.Interface] implementation for the preferences kind. -func (b *Base) Preferences() *preferences.Kind { - return b.preferences -} - -// PublicDashboard returns the [kindsys.Interface] implementation for the publicdashboard kind. -func (b *Base) PublicDashboard() *publicdashboard.Kind { - return b.publicdashboard -} - -// Role returns the [kindsys.Interface] implementation for the role kind. -func (b *Base) Role() *role.Kind { - return b.role -} - -// RoleBinding returns the [kindsys.Interface] implementation for the rolebinding kind. -func (b *Base) RoleBinding() *rolebinding.Kind { - return b.rolebinding -} - -// Team returns the [kindsys.Interface] implementation for the team kind. -func (b *Base) Team() *team.Kind { - return b.team -} - -func doNewBase(rt *thema.Runtime) *Base { - var err error - reg := &Base{} - - reg.accesspolicy, err = accesspolicy.NewKind(rt) - if err != nil { - panic(fmt.Sprintf("error while initializing the accesspolicy Kind: %s", err)) - } - reg.all = append(reg.all, reg.accesspolicy) - - reg.dashboard, err = dashboard.NewKind(rt) - if err != nil { - panic(fmt.Sprintf("error while initializing the dashboard Kind: %s", err)) - } - reg.all = append(reg.all, reg.dashboard) - - reg.librarypanel, err = librarypanel.NewKind(rt) - if err != nil { - panic(fmt.Sprintf("error while initializing the librarypanel Kind: %s", err)) - } - reg.all = append(reg.all, reg.librarypanel) - - reg.preferences, err = preferences.NewKind(rt) - if err != nil { - panic(fmt.Sprintf("error while initializing the preferences Kind: %s", err)) - } - reg.all = append(reg.all, reg.preferences) - - reg.publicdashboard, err = publicdashboard.NewKind(rt) - if err != nil { - panic(fmt.Sprintf("error while initializing the publicdashboard Kind: %s", err)) - } - reg.all = append(reg.all, reg.publicdashboard) - - reg.role, err = role.NewKind(rt) - if err != nil { - panic(fmt.Sprintf("error while initializing the role Kind: %s", err)) - } - reg.all = append(reg.all, reg.role) - - reg.rolebinding, err = rolebinding.NewKind(rt) - if err != nil { - panic(fmt.Sprintf("error while initializing the rolebinding Kind: %s", err)) - } - reg.all = append(reg.all, reg.rolebinding) - - reg.team, err = team.NewKind(rt) - if err != nil { - panic(fmt.Sprintf("error while initializing the team Kind: %s", err)) - } - reg.all = append(reg.all, reg.team) - - return reg -} diff --git a/pkg/registry/schemas/composable_kind.go b/pkg/registry/schemas/composable_kind.go new file mode 100644 index 00000000000..f091fff8e47 --- /dev/null +++ b/pkg/registry/schemas/composable_kind.go @@ -0,0 +1,468 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// public/app/plugins/gen.go +// Using jennies: +// PluginRegistryJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package schemas + +import ( + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "runtime" + "testing/fstest" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" + "cuelang.org/go/cue/load" +) + +var cueImportsPath = filepath.Join("packages", "grafana-schema", "src", "common") +var importPath = "github.com/grafana/grafana/packages/grafana-schema/src/common" + +type ComposableKind struct { + Name string + Filename string + CueFile cue.Value +} + +func GetComposableKinds() ([]ComposableKind, error) { + kinds := make([]ComposableKind, 0) + + _, caller, _, _ := runtime.Caller(0) + root := filepath.Join(caller, "../../../..") + + azuremonitorCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/datasource/azuremonitor/dataquery.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "azuremonitor", + Filename: "dataquery.cue", + CueFile: azuremonitorCue, + }) + + googlecloudmonitoringCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/datasource/cloud-monitoring/dataquery.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "googlecloudmonitoring", + Filename: "dataquery.cue", + CueFile: googlecloudmonitoringCue, + }) + + cloudwatchCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/datasource/cloudwatch/dataquery.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "cloudwatch", + Filename: "dataquery.cue", + CueFile: cloudwatchCue, + }) + + elasticsearchCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/datasource/elasticsearch/dataquery.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "elasticsearch", + Filename: "dataquery.cue", + CueFile: elasticsearchCue, + }) + + grafanapyroscopeCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/datasource/grafana-pyroscope-datasource/dataquery.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "grafanapyroscope", + Filename: "dataquery.cue", + CueFile: grafanapyroscopeCue, + }) + + grafanatestdatadatasourceCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/datasource/grafana-testdata-datasource/dataquery.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "grafanatestdatadatasource", + Filename: "dataquery.cue", + CueFile: grafanatestdatadatasourceCue, + }) + + lokiCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/datasource/loki/dataquery.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "loki", + Filename: "dataquery.cue", + CueFile: lokiCue, + }) + + parcaCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/datasource/parca/dataquery.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "parca", + Filename: "dataquery.cue", + CueFile: parcaCue, + }) + + tempoCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/datasource/tempo/dataquery.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "tempo", + Filename: "dataquery.cue", + CueFile: tempoCue, + }) + + alertgroupsCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/alertGroups/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "alertgroups", + Filename: "panelcfg.cue", + CueFile: alertgroupsCue, + }) + + annotationslistCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/annolist/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "annotationslist", + Filename: "panelcfg.cue", + CueFile: annotationslistCue, + }) + + barchartCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/barchart/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "barchart", + Filename: "panelcfg.cue", + CueFile: barchartCue, + }) + + bargaugeCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/bargauge/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "bargauge", + Filename: "panelcfg.cue", + CueFile: bargaugeCue, + }) + + candlestickCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/candlestick/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "candlestick", + Filename: "panelcfg.cue", + CueFile: candlestickCue, + }) + + canvasCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/canvas/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "canvas", + Filename: "panelcfg.cue", + CueFile: canvasCue, + }) + + dashboardlistCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/dashlist/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "dashboardlist", + Filename: "panelcfg.cue", + CueFile: dashboardlistCue, + }) + + datagridCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/datagrid/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "datagrid", + Filename: "panelcfg.cue", + CueFile: datagridCue, + }) + + debugCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/debug/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "debug", + Filename: "panelcfg.cue", + CueFile: debugCue, + }) + + gaugeCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/gauge/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "gauge", + Filename: "panelcfg.cue", + CueFile: gaugeCue, + }) + + geomapCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/geomap/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "geomap", + Filename: "panelcfg.cue", + CueFile: geomapCue, + }) + + heatmapCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/heatmap/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "heatmap", + Filename: "panelcfg.cue", + CueFile: heatmapCue, + }) + + histogramCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/histogram/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "histogram", + Filename: "panelcfg.cue", + CueFile: histogramCue, + }) + + logsCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/logs/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "logs", + Filename: "panelcfg.cue", + CueFile: logsCue, + }) + + newsCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/news/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "news", + Filename: "panelcfg.cue", + CueFile: newsCue, + }) + + nodegraphCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/nodeGraph/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "nodegraph", + Filename: "panelcfg.cue", + CueFile: nodegraphCue, + }) + + piechartCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/piechart/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "piechart", + Filename: "panelcfg.cue", + CueFile: piechartCue, + }) + + statCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/stat/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "stat", + Filename: "panelcfg.cue", + CueFile: statCue, + }) + + statetimelineCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/state-timeline/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "statetimeline", + Filename: "panelcfg.cue", + CueFile: statetimelineCue, + }) + + statushistoryCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/status-history/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "statushistory", + Filename: "panelcfg.cue", + CueFile: statushistoryCue, + }) + + tableCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/table/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "table", + Filename: "panelcfg.cue", + CueFile: tableCue, + }) + + textCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/text/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "text", + Filename: "panelcfg.cue", + CueFile: textCue, + }) + + timeseriesCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/timeseries/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "timeseries", + Filename: "panelcfg.cue", + CueFile: timeseriesCue, + }) + + trendCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/trend/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "trend", + Filename: "panelcfg.cue", + CueFile: trendCue, + }) + + xychartCue, err := loadCueFileWithCommon(root, filepath.Join(root, "./public/app/plugins/panel/xychart/panelcfg.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, ComposableKind{ + Name: "xychart", + Filename: "panelcfg.cue", + CueFile: xychartCue, + }) + + return kinds, nil +} + +func loadCueFileWithCommon(root string, entrypoint string) (cue.Value, error) { + commonFS, err := mockCommonFS(root) + if err != nil { + fmt.Printf("cannot load common cue files: %s\n", err) + return cue.Value{}, err + } + + overlay, err := buildOverlay(commonFS) + if err != nil { + fmt.Printf("Cannot build overlay: %s\n", err) + return cue.Value{}, err + } + + bis := load.Instances([]string{entrypoint}, &load.Config{ + ModuleRoot: "/", + Overlay: overlay, + }) + + values, err := cuecontext.New().BuildInstances(bis) + if err != nil { + fmt.Printf("Cannot build instance: %s\n", err) + return cue.Value{}, err + } + + return values[0], nil +} + +func mockCommonFS(root string) (fs.FS, error) { + path := filepath.Join(root, cueImportsPath) + dir, err := os.ReadDir(path) + if err != nil { + return nil, fmt.Errorf("cannot open common cue files directory: %s", err) + } + + prefix := "cue.mod/pkg/" + importPath + + commonFS := fstest.MapFS{} + for _, d := range dir { + if d.IsDir() { + continue + } + + readPath := filepath.Join(path, d.Name()) + b, err := os.ReadFile(filepath.Clean(readPath)) + if err != nil { + return nil, err + } + + commonFS[filepath.Join(prefix, d.Name())] = &fstest.MapFile{Data: b} + } + + return commonFS, nil +} + +// It loads common cue files into the schema to be able to make import works +func buildOverlay(commonFS fs.FS) (map[string]load.Source, error) { + overlay := make(map[string]load.Source) + + err := fs.WalkDir(commonFS, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + return nil + } + + f, err := commonFS.Open(path) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + + b, err := io.ReadAll(f) + if err != nil { + return err + } + + overlay[filepath.Join("/", path)] = load.FromBytes(b) + + return nil + }) + + return overlay, err +} diff --git a/pkg/registry/schemas/core_kind.go b/pkg/registry/schemas/core_kind.go new file mode 100644 index 00000000000..596eeedf043 --- /dev/null +++ b/pkg/registry/schemas/core_kind.go @@ -0,0 +1,115 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CoreRegistryJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package schemas + +import ( + "os" + "path/filepath" + "runtime" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" +) + +type CoreKind struct { + Name string + CueFile cue.Value +} + +func GetCoreKinds() ([]CoreKind, error) { + ctx := cuecontext.New() + kinds := make([]CoreKind, 0) + + _, caller, _, _ := runtime.Caller(0) + root := filepath.Join(caller, "../../../..") + + accesspolicyCue, err := loadCueFile(ctx, filepath.Join(root, "./kinds/accesspolicy/access_policy_kind.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, CoreKind{ + Name: "accesspolicy", + CueFile: accesspolicyCue, + }) + + dashboardCue, err := loadCueFile(ctx, filepath.Join(root, "./kinds/dashboard/dashboard_kind.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, CoreKind{ + Name: "dashboard", + CueFile: dashboardCue, + }) + + librarypanelCue, err := loadCueFile(ctx, filepath.Join(root, "./kinds/librarypanel/librarypanel_kind.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, CoreKind{ + Name: "librarypanel", + CueFile: librarypanelCue, + }) + + preferencesCue, err := loadCueFile(ctx, filepath.Join(root, "./kinds/preferences/preferences_kind.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, CoreKind{ + Name: "preferences", + CueFile: preferencesCue, + }) + + publicdashboardCue, err := loadCueFile(ctx, filepath.Join(root, "./kinds/publicdashboard/public_dashboard_kind.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, CoreKind{ + Name: "publicdashboard", + CueFile: publicdashboardCue, + }) + + roleCue, err := loadCueFile(ctx, filepath.Join(root, "./kinds/role/role_kind.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, CoreKind{ + Name: "role", + CueFile: roleCue, + }) + + rolebindingCue, err := loadCueFile(ctx, filepath.Join(root, "./kinds/rolebinding/role_binding_kind.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, CoreKind{ + Name: "rolebinding", + CueFile: rolebindingCue, + }) + + teamCue, err := loadCueFile(ctx, filepath.Join(root, "./kinds/team/team_kind.cue")) + if err != nil { + return nil, err + } + kinds = append(kinds, CoreKind{ + Name: "team", + CueFile: teamCue, + }) + + return kinds, nil +} + +func loadCueFile(ctx *cue.Context, path string) (cue.Value, error) { + cueFile, err := os.ReadFile(path) + if err != nil { + return cue.Value{}, err + } + + return ctx.CompileBytes(cueFile), nil +} diff --git a/public/app/plugins/gen.go b/public/app/plugins/gen.go index 264152e442d..693a8af0fa2 100644 --- a/public/app/plugins/gen.go +++ b/public/app/plugins/gen.go @@ -8,11 +8,6 @@ package main import ( "context" "fmt" - "log" - "os" - "path/filepath" - "strings" - "github.com/grafana/codejen" corecodegen "github.com/grafana/grafana/pkg/codegen" "github.com/grafana/grafana/pkg/cuectx" @@ -20,6 +15,11 @@ import ( "github.com/grafana/grafana/pkg/plugins/pfs" "github.com/grafana/kindsys" "github.com/grafana/thema" + "io/fs" + "log" + "os" + "path/filepath" + "strings" ) var skipPlugins = map[string]bool{ @@ -47,7 +47,6 @@ func main() { }) pluginKindGen.Append( - codegen.PluginTreeListJenny(), codegen.PluginGoTypesJenny("pkg/tsdb"), codegen.PluginTSTypesJenny("public/app/plugins"), ) @@ -70,6 +69,15 @@ func main() { log.Fatalln(fmt.Errorf("error writing files to disk: %s", err)) } + rawResources, err := genRawResources() + if err != nil { + log.Fatalln(fmt.Errorf("error generating raw plugin resources: %s", err)) + } + + if err := jfs.Merge(rawResources); err != nil { + log.Fatalln(fmt.Errorf("Unable to merge raw resources: %s", err)) + } + if _, set := os.LookupEnv("CODEGEN_VERIFY"); set { if err = jfs.Verify(context.Background(), groot); err != nil { log.Fatal(fmt.Errorf("generated code is out of sync with inputs:\n%s\nrun `make gen-cue` to regenerate", err)) @@ -105,3 +113,28 @@ func splitSchiffer(names []string) codejen.FileMapper { return f, nil } } + +func genRawResources() (*codejen.FS, error) { + jennies := codejen.JennyListWithNamer(func(d []string) string { + return "PluginsRawResources" + }) + jennies.Append(&codegen.PluginRegistryJenny{}) + + schemas := make([]string, 0) + filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error { + if d.IsDir() { + return nil + } + + if !strings.HasSuffix(d.Name(), ".cue") { + return nil + } + + schemas = append(schemas, "./"+filepath.Join("public", "app", "plugins", path)) + return nil + }) + + jennies.AddPostprocessors(corecodegen.SlashHeaderMapper("public/app/plugins/gen.go")) + + return jennies.GenerateFS(schemas) +}