Schemas: Replace registry generation and github workflow (#83490)

* Create small registries for core and composable kinds

* Update workflow with new registries

* Fix imports in plugin schemas and deleted old registry generation files

* Remove verification and maturity

* Modify registries and add missing composable information to make schemas in kind-registry work

* Add missing aliases

* Remove unused templates

* Remove kinds verification

* Format generated code

* Add gen header

* Delete unused code and clean path in composable template

* Delete kind-registry loader

* Delete unused code

* Update License link

* Update codeowners path

* Sort imports

* More cleanup

* Remove verify-kinds.yml from codeowners

* Fix lint

* Update composable_kidns

* Fix cue extension

* Restore verify-kinds to avoid to push outdated kind's registry

* Fix composable format

* Restore code owners for verify-kinds

* Remove verify check
This commit is contained in:
Selene 2024-03-13 17:05:21 +01:00 committed by GitHub
parent fd9031ca37
commit 5c7849417b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 1049 additions and 1710 deletions

2
.github/CODEOWNERS vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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