230 lines
5.6 KiB
Go
230 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"cuelang.org/go/cue"
|
|
cueformat "cuelang.org/go/cue/format"
|
|
"github.com/grafana/codejen"
|
|
"github.com/grafana/grafana/pkg/registry/schemas"
|
|
)
|
|
|
|
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).
|
|
// 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() {
|
|
// File generation
|
|
jfs := codejen.NewFS()
|
|
outputPath := filepath.Join(".github", "workflows", "scripts", "kinds")
|
|
|
|
corekinds, err := schemas.GetCoreKinds()
|
|
die(err)
|
|
|
|
composableKinds, err := schemas.GetComposableKinds()
|
|
die(err)
|
|
|
|
coreJennies := codejen.JennyList[schemas.CoreKind]{}
|
|
coreJennies.Append(
|
|
CoreKindRegistryJenny(outputPath),
|
|
)
|
|
corefs, err := coreJennies.GenerateFS(corekinds...)
|
|
die(err)
|
|
die(jfs.Merge(corefs))
|
|
|
|
composableJennies := codejen.JennyList[schemas.ComposableKind]{}
|
|
composableJennies.Append(
|
|
ComposableKindRegistryJenny(outputPath),
|
|
)
|
|
composablefs, err := composableJennies.GenerateFS(composableKinds...)
|
|
die(err)
|
|
die(jfs.Merge(composablefs))
|
|
|
|
if err = jfs.Write(context.Background(), ""); err != nil {
|
|
die(fmt.Errorf("error while writing generated code to disk:\n%s", err))
|
|
}
|
|
|
|
if err := copyCueSchemas("packages/grafana-schema/src/common", filepath.Join(outputPath, "next")); err != nil {
|
|
die(fmt.Errorf("error while copying the grafana-schema/common package:\n%s", err))
|
|
}
|
|
}
|
|
|
|
func copyCueSchemas(fromDir string, toDir string) error {
|
|
baseTargetDir := filepath.Base(fromDir)
|
|
|
|
return filepath.Walk(fromDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
targetPath := filepath.Join(
|
|
toDir,
|
|
baseTargetDir,
|
|
strings.TrimPrefix(path, fromDir),
|
|
)
|
|
|
|
if info.IsDir() {
|
|
return ensureDirectoryExists(targetPath, info.Mode())
|
|
}
|
|
|
|
if !strings.HasSuffix(path, ".cue") {
|
|
return nil
|
|
}
|
|
|
|
return copyFile(path, targetPath, info.Mode())
|
|
})
|
|
}
|
|
|
|
func copyFile(from string, to string, mode os.FileMode) error {
|
|
input, err := os.ReadFile(from)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(to, input, mode)
|
|
}
|
|
|
|
func ensureDirectoryExists(directory string, mode os.FileMode) error {
|
|
_, err := os.Stat(directory)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
if err = os.Mkdir(directory, mode); err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.Chmod(directory, mode)
|
|
}
|
|
|
|
func die(errs ...error) {
|
|
if len(errs) > 0 && errs[0] != nil {
|
|
for _, err := range errs {
|
|
fmt.Fprint(os.Stderr, err, "\n")
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
}
|
|
}
|
|
|
|
type kindregjenny struct {
|
|
path string
|
|
}
|
|
|
|
func (j *kindregjenny) JennyName() string {
|
|
return "KindRegistryJenny"
|
|
}
|
|
|
|
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", kind.Name, kind.Name+".cue")
|
|
return codejen.NewFile(path, newKindBytes, j), nil
|
|
}
|
|
|
|
// ComposableKindRegistryJenny generates kind files into the "next" folder of the local kind registry.
|
|
func ComposableKindRegistryJenny(path string) codejen.OneToOne[schemas.ComposableKind] {
|
|
return &ckrJenny{
|
|
path: path,
|
|
}
|
|
}
|
|
|
|
type ckrJenny struct {
|
|
path string
|
|
}
|
|
|
|
func (j *ckrJenny) JennyName() string {
|
|
return "ComposableKindRegistryJenny"
|
|
}
|
|
|
|
func (j *ckrJenny) Generate(k schemas.ComposableKind) (*codejen.File, error) {
|
|
name := strings.ToLower(fmt.Sprintf("%s/%s", k.Name, k.Filename))
|
|
|
|
v := fixComposableKindFormat(k)
|
|
|
|
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), 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)
|
|
}
|
|
|
|
func fixComposableKindFormat(schema schemas.ComposableKind) cue.Value {
|
|
variant := "PanelCfg"
|
|
if schema.CueFile.LookupPath(cue.ParsePath("composableKinds.DataQuery")).Exists() {
|
|
variant = "DataQuery"
|
|
}
|
|
|
|
newCue := schema.CueFile.Context().CompileString(
|
|
fmt.Sprintf("schemaInterface: %q\n", variant) +
|
|
fmt.Sprintf("name: %q + %q\n\n", UpperCamelCase(schema.Name), variant) +
|
|
"lineage: _",
|
|
)
|
|
|
|
lineagePath := cue.MakePath(cue.Str("composableKinds"), cue.Str(variant), cue.Str("lineage"))
|
|
return newCue.FillPath(cue.MakePath(cue.Str("lineage")), schema.CueFile.LookupPath(lineagePath))
|
|
}
|
|
|
|
func UpperCamelCase(s string) string {
|
|
s = LowerCamelCase(s)
|
|
|
|
// Uppercase the first letter
|
|
if len(s) > 0 {
|
|
s = strings.ToUpper(s[:1]) + s[1:]
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func LowerCamelCase(s string) string {
|
|
// Replace all non-alphanumeric characters by spaces
|
|
s = nonAlphaNumRegex.ReplaceAllString(s, " ")
|
|
|
|
// Title case s
|
|
s = cases.Title(language.AmericanEnglish, cases.NoLower).String(s)
|
|
|
|
// Remove all spaces
|
|
s = strings.ReplaceAll(s, " ", "")
|
|
|
|
// Lowercase the first letter
|
|
if len(s) > 0 {
|
|
s = strings.ToLower(s[:1]) + s[1:]
|
|
}
|
|
|
|
return s
|
|
}
|