diff --git a/cmd/grafana-app-sdk/generate.go b/cmd/grafana-app-sdk/generate.go index f98a3c3ba..33f17a269 100644 --- a/cmd/grafana-app-sdk/generate.go +++ b/cmd/grafana-app-sdk/generate.go @@ -239,12 +239,6 @@ func generateCmdFunc(cmd *cobra.Command, _ []string) error { //nolint:funlen,goconst func generateKindsCue(parser *cuekind.Parser, cfg *config.Config) (codejen.Files, error) { - // Slightly hacky multiple generators as an intermediary while we move to a better system. - // Both still source from a Manifest, but generatorForKinds supplies []Kind to jennies, vs AppManifest - generatorForKinds, err := codegen.NewGenerator(parser.KindParser()) - if err != nil { - return nil, err - } generatorForManifest, err := codegen.NewGenerator(parser.ManifestParser()) if err != nil { return nil, err @@ -264,14 +258,14 @@ func generateKindsCue(parser *cuekind.Parser, cfg *config.Config) (codejen.Files } // Resource - resourceFiles, err := generatorForKinds.Generate(cuekind.ResourceGenerator(goModule, goModGenPath, cfg.GroupKinds()), cfg.ManifestSelectors...) + resourceFiles, err := generatorForManifest.Generate(cuekind.ResourceGenerator(goModule, goModGenPath, cfg.GroupKinds()), cfg.ManifestSelectors...) if err != nil { return nil, err } for i, f := range resourceFiles { resourceFiles[i].RelativePath = filepath.Join(cfg.Codegen.GoGenPath, f.RelativePath) } - tsResourceFiles, err := generatorForKinds.Generate(cuekind.TypeScriptResourceGenerator(), cfg.ManifestSelectors...) + tsResourceFiles, err := generatorForManifest.Generate(cuekind.TypeScriptResourceGenerator(), cfg.ManifestSelectors...) if err != nil { return nil, err } @@ -287,7 +281,7 @@ func generateKindsCue(parser *cuekind.Parser, cfg *config.Config) (codejen.Files if cfg.Definitions.Encoding == "yaml" { encFunc = yaml.Marshal } - crdFiles, err = generatorForKinds.Generate(cuekind.CRDGenerator(encFunc, cfg.Definitions.Encoding), cfg.ManifestSelectors...) + crdFiles, err = generatorForManifest.Generate(cuekind.CRDGenerator(encFunc, cfg.Definitions.Encoding), cfg.ManifestSelectors...) if err != nil { return nil, err } @@ -345,7 +339,7 @@ func postGenerateFilesCue(parser *cuekind.Parser, cfg *config.Config) (codejen.F if err != nil { return nil, err } - generator, err := codegen.NewGenerator[codegen.Kind](parser.KindParser()) + generator, err := codegen.NewGenerator[codegen.AppManifest](parser.ManifestParser()) if err != nil { return nil, err } diff --git a/cmd/grafana-app-sdk/project.go b/cmd/grafana-app-sdk/project.go index af92e5e14..4a42dd602 100644 --- a/cmd/grafana-app-sdk/project.go +++ b/cmd/grafana-app-sdk/project.go @@ -598,11 +598,11 @@ func projectAddComponent(cmd *cobra.Command, args []string) error { if err != nil { return err } - generator, err = codegen.NewGenerator(parser.KindParser()) + manifestParser = parser.ManifestParser() + generator, err = codegen.NewGenerator(manifestParser) if err != nil { return err } - manifestParser = parser.ManifestParser() default: return fmt.Errorf("unknown kind format '%s'", format) } @@ -622,7 +622,7 @@ func projectAddComponent(cmd *cobra.Command, args []string) error { case "backend": switch format { case FormatCUE: - err = addComponentBackend(path, generator.(*codegen.Generator[codegen.Kind]), cfg.ManifestSelectors, manifest.Properties().Group, cfg.Kinds.Grouping == config.KindGroupingGroup) + err = addComponentBackend(path, generator.(*codegen.Generator[codegen.AppManifest]), cfg.ManifestSelectors, manifest.Properties().Group, cfg.Kinds.Grouping == config.KindGroupingGroup) default: return fmt.Errorf("unknown kind format '%s'", format) } @@ -639,7 +639,7 @@ func projectAddComponent(cmd *cobra.Command, args []string) error { case "operator": switch format { case FormatCUE: - err = addComponentOperator(path, generator.(*codegen.Generator[codegen.Kind]), cfg.ManifestSelectors, cfg.Kinds.Grouping == config.KindGroupingGroup, !overwrite) + err = addComponentOperator(path, generator.(*codegen.Generator[codegen.AppManifest]), cfg.ManifestSelectors, cfg.Kinds.Grouping == config.KindGroupingGroup, !overwrite) default: return fmt.Errorf("unknown kind format '%s'", format) } @@ -659,7 +659,7 @@ func projectAddComponent(cmd *cobra.Command, args []string) error { } type anyGenerator interface { - *codegen.Generator[codegen.Kind] + *codegen.Generator[codegen.AppManifest] } //nolint:revive @@ -681,7 +681,7 @@ func addComponentOperator[G anyGenerator](projectRootPath string, generator G, s var files codejen.Files switch cast := any(generator).(type) { - case *codegen.Generator[codegen.Kind]: + case *codegen.Generator[codegen.AppManifest]: files, err = cast.Generate(cuekind.OperatorGenerator(repo, "pkg/generated", groupKinds), selectors...) if err != nil { return err @@ -776,7 +776,7 @@ func projectAddPluginAPI[G anyGenerator](generator G, repo, generatedAPIModelsPa var files codejen.Files var err error switch cast := any(generator).(type) { - case *codegen.Generator[codegen.Kind]: + case *codegen.Generator[codegen.AppManifest]: files, err = cast.Generate(cuekind.BackendPluginGenerator(repo, generatedAPIModelsPath, groupKinds), selectors...) if err != nil { return err diff --git a/cmd/grafana-app-sdk/project_local.go b/cmd/grafana-app-sdk/project_local.go index b40aad438..cf1618d72 100644 --- a/cmd/grafana-app-sdk/project_local.go +++ b/cmd/grafana-app-sdk/project_local.go @@ -275,7 +275,7 @@ func projectLocalEnvGenerate(cmd *cobra.Command, _ []string) error { if err != nil { return nil, err } - generator, err := codegen.NewGenerator(parser.KindParser()) + generator, err := codegen.NewGenerator(parser.ManifestParser()) if err != nil { return nil, err } diff --git a/cmd/grafana-app-sdk/templates/Makefile.tmpl b/cmd/grafana-app-sdk/templates/Makefile.tmpl index f207286b7..1e2001e41 100644 --- a/cmd/grafana-app-sdk/templates/Makefile.tmpl +++ b/cmd/grafana-app-sdk/templates/Makefile.tmpl @@ -51,7 +51,7 @@ endif .PHONY: build/operator build/operator: ifeq ("$(wildcard cmd/operator/Dockerfile)","cmd/operator/Dockerfile") - docker build -t $(OPERATOR_DOCKERIMAGE) -f cmd/operator/Dockerfile . + docker build -t $(OPERATOR_DOCKERIMAGE) -f cmd/operator/Dockerfile . else @echo "No cmd/operator/Dockerfile, skipping operator build" endif diff --git a/codegen/cuekind/generators.go b/codegen/cuekind/generators.go index d6ddefa6a..9111b8ba8 100644 --- a/codegen/cuekind/generators.go +++ b/codegen/cuekind/generators.go @@ -15,8 +15,8 @@ import ( ) // CRDGenerator returns a Generator which will create a CRD file -func CRDGenerator(outputEncoder jennies.CRDOutputEncoder, outputExtension string) *codejen.JennyList[codegen.Kind] { - g := codejen.JennyListWithNamer(namerFunc) +func CRDGenerator(outputEncoder jennies.CRDOutputEncoder, outputExtension string) *codejen.JennyList[codegen.AppManifest] { + g := codejen.JennyListWithNamer[codegen.AppManifest](namerFuncManifest) g.Append(jennies.CRDGenerator(outputEncoder, outputExtension)) return g } @@ -27,8 +27,8 @@ func CRDGenerator(outputEncoder jennies.CRDOutputEncoder, outputExtension string // If `groupKinds` is true, kinds within the same group will exist in the same package. // When combined with `versioned`, each version package will contain all kinds in the group // which have a schema for that version. -func ResourceGenerator(projectRepo, generatedAPIPath string, groupKinds bool) *codejen.JennyList[codegen.Kind] { - g := codejen.JennyListWithNamer(namerFunc) +func ResourceGenerator(projectRepo, generatedAPIPath string, groupKinds bool) *codejen.JennyList[codegen.AppManifest] { + g := codejen.JennyListWithNamer[codegen.AppManifest](namerFuncManifest) g.Append( &jennies.GoTypes{ Depth: 1, @@ -63,21 +63,21 @@ func ResourceGenerator(projectRepo, generatedAPIPath string, groupKinds bool) *c } // BackendPluginGenerator returns a Generator which will produce boilerplate backend plugin code -func BackendPluginGenerator(projectRepo, generatedAPIPath string, groupKinds bool) *codejen.JennyList[codegen.Kind] { +func BackendPluginGenerator(projectRepo, generatedAPIPath string, groupKinds bool) *codejen.JennyList[codegen.AppManifest] { pluginSecurePkgFiles, _ := templates.GetBackendPluginSecurePackageFiles() - g := codejen.JennyListWithNamer(namerFunc) + g := codejen.JennyListWithNamer(namerFuncManifest) g.Append( jennies.RouterHandlerCodeGenerator(projectRepo, generatedAPIPath, !groupKinds), - jennies.StaticManyToOneGenerator[codegen.Kind](codejen.File{ + jennies.StaticManyToOneGenerator[codegen.AppManifest](codejen.File{ RelativePath: "plugin/secure/data.go", Data: pluginSecurePkgFiles["data.go"], }), - jennies.StaticManyToOneGenerator[codegen.Kind](codejen.File{ + jennies.StaticManyToOneGenerator[codegen.AppManifest](codejen.File{ RelativePath: "plugin/secure/middleware.go", Data: pluginSecurePkgFiles["middleware.go"], }), - jennies.StaticManyToOneGenerator[codegen.Kind](codejen.File{ + jennies.StaticManyToOneGenerator[codegen.AppManifest](codejen.File{ RelativePath: "plugin/secure/retriever.go", Data: pluginSecurePkgFiles["retriever.go"], }), @@ -88,8 +88,8 @@ func BackendPluginGenerator(projectRepo, generatedAPIPath string, groupKinds boo } // TypeScriptResourceGenerator returns a Generator which generates TypeScript resource code. -func TypeScriptResourceGenerator() *codejen.JennyList[codegen.Kind] { - g := codejen.JennyListWithNamer(namerFunc) +func TypeScriptResourceGenerator() *codejen.JennyList[codegen.AppManifest] { + g := codejen.JennyListWithNamer[codegen.AppManifest](namerFuncManifest) g.Append(&jennies.TypeScriptTypes{ Depth: 1, }, &jennies.TypeScriptResourceTypes{}) @@ -98,8 +98,8 @@ func TypeScriptResourceGenerator() *codejen.JennyList[codegen.Kind] { // OperatorGenerator returns a Generator which will build out watcher boilerplate for each resource, // and a main func to run an operator for the watchers. -func OperatorGenerator(projectRepo, codegenPath string, groupKinds bool) *codejen.JennyList[codegen.Kind] { - g := codejen.JennyListWithNamer[codegen.Kind](namerFunc) +func OperatorGenerator(projectRepo, codegenPath string, groupKinds bool) *codejen.JennyList[codegen.AppManifest] { + g := codejen.JennyListWithNamer[codegen.AppManifest](namerFuncManifest) g.Append( &jennies.OperatorKubeConfigJenny{}, jennies.OperatorMainJenny(projectRepo, codegenPath, !groupKinds), @@ -108,12 +108,12 @@ func OperatorGenerator(projectRepo, codegenPath string, groupKinds bool) *codeje return g } -func AppGenerator(projectRepo, codegenPath string, manifestGoFilePath string, groupKinds bool) *codejen.JennyList[codegen.Kind] { +func AppGenerator(projectRepo, codegenPath string, manifestGoFilePath string, groupKinds bool) *codejen.JennyList[codegen.AppManifest] { parts := strings.Split(projectRepo, "/") if len(parts) == 0 { parts = []string{""} } - g := codejen.JennyListWithNamer[codegen.Kind](namerFunc) + g := codejen.JennyListWithNamer[codegen.AppManifest](namerFuncManifest) g.Append( jennies.WatcherJenny(projectRepo, codegenPath, !groupKinds), &jennies.AppGenerator{ @@ -127,8 +127,8 @@ func AppGenerator(projectRepo, codegenPath string, manifestGoFilePath string, gr return g } -func PostResourceGenerationGenerator(projectRepo, goGenPath string, groupKinds bool) *codejen.JennyList[codegen.Kind] { - g := codejen.JennyListWithNamer[codegen.Kind](namerFunc) +func PostResourceGenerationGenerator(projectRepo, goGenPath string, groupKinds bool) *codejen.JennyList[codegen.AppManifest] { + g := codejen.JennyListWithNamer[codegen.AppManifest](namerFuncManifest) g.Append(&jennies.OpenAPI{ GoModName: projectRepo, GoGenPath: goGenPath, @@ -184,13 +184,6 @@ func ManifestGoGenerator(pkg string, includeSchemas bool, projectRepo, goGenPath return g } -func namerFunc(k codegen.Kind) string { - if k == nil { - return "nil" - } - return k.Properties().Kind -} - func namerFuncManifest(m codegen.AppManifest) string { if m == nil { return "nil" diff --git a/codegen/cuekind/generators_test.go b/codegen/cuekind/generators_test.go index e0973d9f3..a0b099d93 100644 --- a/codegen/cuekind/generators_test.go +++ b/codegen/cuekind/generators_test.go @@ -23,7 +23,7 @@ func TestCRDGenerator(t *testing.T) { parser, err := NewParser(testingCue(t), true, false) require.NoError(t, err) - kinds, err := parser.KindParser().Parse("customManifest", "testManifest") + kinds, err := parser.ManifestParser().Parse("customManifest", "testManifest") require.NoError(t, err) t.Run("JSON", func(t *testing.T) { @@ -51,9 +51,9 @@ func TestResourceGenerator(t *testing.T) { parser, err := NewParser(testingCue(t), true, false) require.NoError(t, err) - kinds, err := parser.KindParser().Parse("customManifest") + kinds, err := parser.ManifestParser().Parse("customManifest") require.NoError(t, err) - sameGroupKinds, err := parser.KindParser().Parse("testManifest") + sameGroupKinds, err := parser.ManifestParser().Parse("testManifest") require.NoError(t, err) t.Run("group by kind", func(t *testing.T) { @@ -94,7 +94,7 @@ func TestTypeScriptResourceGenerator(t *testing.T) { require.NoError(t, err) t.Run("versioned", func(t *testing.T) { - kinds, err := parser.KindParser().Parse("customManifest") + kinds, err := parser.ManifestParser().Parse("customManifest") require.NoError(t, err) files, err := TypeScriptResourceGenerator().Generate(kinds...) require.NoError(t, err) diff --git a/codegen/cuekind/parser.go b/codegen/cuekind/parser.go index 960d5c8fc..f38fb3ca5 100644 --- a/codegen/cuekind/parser.go +++ b/codegen/cuekind/parser.go @@ -64,6 +64,7 @@ func (p *Parser) ManifestParser() codegen.Parser[codegen.AppManifest] { // KindParser returns a Parser that returns a list of codegen.Kind. // If useManifest is true, it will load kinds from a manifest provided by the selector(s) in Parse (or DefaultManifestSelector if no selectors are present), // rather than loading the selector(s) as kinds. +// Deprecated: Use ManifestParser instead, Kind is deprecated // //nolint:revive func (p *Parser) KindParser() codegen.Parser[codegen.Kind] { diff --git a/codegen/jennies/app.go b/codegen/jennies/app.go index 96c1e348e..3132fcf84 100644 --- a/codegen/jennies/app.go +++ b/codegen/jennies/app.go @@ -3,6 +3,8 @@ package jennies import ( "bytes" "go/format" + "slices" + "strings" "github.com/grafana/codejen" @@ -22,7 +24,7 @@ func (*AppGenerator) JennyName() string { return "App" } -func (a *AppGenerator) Generate(kinds ...codegen.Kind) (*codejen.File, error) { +func (a *AppGenerator) Generate(appManifest codegen.AppManifest) (*codejen.File, error) { tmd := templates.AppMetadata{ Repo: a.ProjectRepo, ProjectName: a.ProjectName, @@ -34,16 +36,30 @@ func (a *AppGenerator) Generate(kinds ...codegen.Kind) (*codejen.File, error) { ManifestPackagePath: a.ManifestPackagePath, } - for _, kind := range kinds { - vers := make([]string, len(kind.Versions())) - for i, ver := range kind.Versions() { - vers[i] = ver.Version + appMetadataByKind := make(map[string]templates.AppMetadataKind) + + for version, kind := range codegen.VersionedKinds(appManifest) { + meta, ok := appMetadataByKind[kind.Kind] + if !ok { + meta = templates.AppMetadataKind{ + KindProperties: versionedKindToKindProperties(kind, appManifest), + Versions: make([]string, 0), + } + } + meta.Versions = append(meta.Versions, version.Name()) + if version.Name() == appManifest.Properties().PreferredVersion { + meta.KindProperties = versionedKindToKindProperties(kind, appManifest) } - tmd.Resources = append(tmd.Resources, templates.AppMetadataKind{ - KindProperties: kind.Properties(), - Versions: vers, - }) + appMetadataByKind[kind.Kind] = meta + } + + for _, meta := range appMetadataByKind { + tmd.Resources = append(tmd.Resources, meta) } + // Sort for deterministic output + slices.SortFunc(tmd.Resources, func(a, b templates.AppMetadataKind) int { + return strings.Compare(a.Kind, b.Kind) + }) b := bytes.Buffer{} err := templates.WriteAppGoFile(tmd, &b) diff --git a/codegen/jennies/backendplugin.go b/codegen/jennies/backendplugin.go index cb123888d..85223234c 100644 --- a/codegen/jennies/backendplugin.go +++ b/codegen/jennies/backendplugin.go @@ -11,8 +11,8 @@ import ( "github.com/grafana/grafana-app-sdk/codegen/templates" ) -// BackendPluginMainGenerator returns a many-to-one jenny which generates the `main.go` file needed to run the backend plugin. -func BackendPluginMainGenerator(projectRepo, apiCodegenPath string, groupByKind bool) codejen.ManyToOne[codegen.Kind] { +// BackendPluginMainGenerator returns a one-to-one jenny which generates the `main.go` file needed to run the backend plugin. +func BackendPluginMainGenerator(projectRepo, apiCodegenPath string, groupByKind bool) codejen.OneToOne[codegen.AppManifest] { return &backendPluginMainGenerator{ projectRepo: projectRepo, apiCodegenPath: apiCodegenPath, @@ -26,7 +26,7 @@ type backendPluginMainGenerator struct { groupByKind bool } -func (m *backendPluginMainGenerator) Generate(decls ...codegen.Kind) (*codejen.File, error) { +func (m *backendPluginMainGenerator) Generate(appManifest codegen.AppManifest) (*codejen.File, error) { tmd := templates.BackendPluginRouterTemplateMetadata{ Repo: m.projectRepo, APICodegenPath: m.apiCodegenPath, @@ -35,10 +35,10 @@ func (m *backendPluginMainGenerator) Generate(decls ...codegen.Kind) (*codejen.F KindsAreGrouped: !m.groupByKind, } - for _, decl := range decls { - tmd.Resources = append(tmd.Resources, decl.Properties()) - if decl.Properties().Group != "" { - tmd.PluginID = strings.Split(decl.Properties().Group, ".")[0] + for _, kind := range codegen.PreferredVersionKinds(appManifest) { + tmd.Resources = append(tmd.Resources, versionedKindToKindProperties(kind, appManifest)) + if appManifest.Properties().FullGroup != "" { + tmd.PluginID = strings.Split(appManifest.Properties().FullGroup, ".")[0] } } diff --git a/codegen/jennies/codec.go b/codegen/jennies/codec.go index cbec6a33d..1dbceba36 100644 --- a/codegen/jennies/codec.go +++ b/codegen/jennies/codec.go @@ -31,29 +31,31 @@ func (*CodecGenerator) JennyName() string { // Generate creates one or more codec go files for the provided Kind // nolint:dupl -func (c *CodecGenerator) Generate(kind codegen.Kind) (codejen.Files, error) { - meta := kind.Properties() +func (c *CodecGenerator) Generate(appManifest codegen.AppManifest) (codejen.Files, error) { + files := make(codejen.Files, 0) + for version, kind := range codegen.VersionedKinds(appManifest) { + if c.OnlyUseCurrentVersion && appManifest.Properties().PreferredVersion != version.Name() { + continue + } - if meta.Scope != string(resource.NamespacedScope) && meta.Scope != string(resource.ClusterScope) { - return nil, fmt.Errorf("scope '%s' is invalid, must be one of: '%s', '%s'", - meta.Scope, resource.ClusterScope, resource.NamespacedScope) - } + if kind.Scope != string(resource.NamespacedScope) && kind.Scope != string(resource.ClusterScope) { + return nil, fmt.Errorf("scope '%s' is invalid, must be one of: '%s', '%s'", + kind.Scope, resource.ClusterScope, resource.NamespacedScope) + } - prefix := "" - if !c.GroupByKind { - prefix = exportField(kind.Name()) - } + prefix := "" + if !c.GroupByKind { + prefix = exportField(kind.Kind) + } - files := make(codejen.Files, 0) - if c.OnlyUseCurrentVersion { b := bytes.Buffer{} err := templates.WriteCodec(templates.SchemaMetadata{ - Package: meta.MachineName, - Group: meta.Group, - Version: meta.Current, - Kind: meta.Kind, - Plural: meta.PluralMachineName, - Scope: meta.Scope, + Package: ToPackageName(version.Name()), + Group: appManifest.Properties().Group, + Version: version.Name(), + Kind: kind.Kind, + Plural: kind.PluralMachineName, + Scope: kind.Scope, FuncPrefix: prefix, }, &b) if err != nil { @@ -65,35 +67,9 @@ func (c *CodecGenerator) Generate(kind codegen.Kind) (codejen.Files, error) { } files = append(files, codejen.File{ Data: formatted, - RelativePath: fmt.Sprintf("%s/%s_codec_gen.go", meta.MachineName, meta.MachineName), + RelativePath: filepath.Join(GetGeneratedGoTypePath(c.GroupByKind, appManifest.Properties().Group, version.Name(), kind.MachineName), fmt.Sprintf("%s_codec_gen.go", kind.MachineName)), From: []codejen.NamedJenny{c}, }) - } else { - for _, ver := range kind.Versions() { - b := bytes.Buffer{} - err := templates.WriteCodec(templates.SchemaMetadata{ - Package: ToPackageName(ver.Version), - Group: meta.Group, - Version: ver.Version, - Kind: meta.Kind, - Plural: meta.PluralMachineName, - Scope: meta.Scope, - FuncPrefix: prefix, - }, &b) - if err != nil { - return nil, err - } - formatted, err := format.Source(b.Bytes()) - if err != nil { - return nil, err - } - files = append(files, codejen.File{ - Data: formatted, - RelativePath: filepath.Join(GetGeneratedPath(c.GroupByKind, kind, ver.Version), fmt.Sprintf("%s_codec_gen.go", meta.MachineName)), - From: []codejen.NamedJenny{c}, - }) - } } - return files, nil } diff --git a/codegen/jennies/constants.go b/codegen/jennies/constants.go index af2784915..873df0be9 100644 --- a/codegen/jennies/constants.go +++ b/codegen/jennies/constants.go @@ -28,17 +28,15 @@ type constantsFileParams struct { path string } -func (c *Constants) Generate(kinds ...codegen.Kind) (codejen.Files, error) { +func (c *Constants) Generate(appManifest codegen.AppManifest) (codejen.Files, error) { m := make(map[string]constantsFileParams) - for _, k := range kinds { - for _, v := range k.Versions() { - path := GetGeneratedPath(c.GroupByKind, k, v.Version) - if _, ok := m[path]; !ok { - m[path] = constantsFileParams{ - group: k.Properties().Group, - version: v.Version, - path: filepath.Join(path, "constants.go"), - } + for v, k := range codegen.VersionedKinds(appManifest) { + path := GetGeneratedGoTypePath(c.GroupByKind, appManifest.Properties().Group, v.Name(), k.MachineName) + if _, ok := m[path]; !ok { + m[path] = constantsFileParams{ + group: appManifest.Properties().FullGroup, + version: v.Name(), + path: filepath.Join(path, "constants.go"), } } } diff --git a/codegen/jennies/crd.go b/codegen/jennies/crd.go index 532076327..6bc9f3b72 100644 --- a/codegen/jennies/crd.go +++ b/codegen/jennies/crd.go @@ -21,7 +21,7 @@ import ( // CRDOutputEncoder is a function which marshals an object into a desired output format type CRDOutputEncoder func(any) ([]byte, error) -func CRDGenerator(encoder CRDOutputEncoder, extension string) codejen.OneToOne[codegen.Kind] { +func CRDGenerator(encoder CRDOutputEncoder, extension string) codejen.OneToMany[codegen.AppManifest] { return &crdGenerator{ outputEncoder: encoder, outputExtension: extension, @@ -37,72 +37,102 @@ func (*crdGenerator) JennyName() string { return "CRD Generator" } -func (c *crdGenerator) Generate(kind codegen.Kind) (*codejen.File, error) { - props := kind.Properties() +type kindWithVersion struct { + version string + kind codegen.VersionedKind +} - resource := customResourceDefinition{ - APIVersion: "apiextensions.k8s.io/v1", - Kind: "CustomResourceDefinition", - Metadata: customResourceDefinitionMetadata{ - Name: fmt.Sprintf("%s.%s", props.PluralMachineName, props.Group), - }, - Spec: k8s.CustomResourceDefinitionSpec{ - Group: props.Group, - Scope: props.Scope, - Names: k8s.CustomResourceDefinitionSpecNames{ - Kind: props.Kind, - Plural: props.PluralMachineName, - }, - Versions: make([]k8s.CustomResourceDefinitionSpecVersion, 0), - }, +func (c *crdGenerator) Generate(appManifest codegen.AppManifest) (codejen.Files, error) { + files := make(codejen.Files, 0) + + // Need to group all versions of a kind together to make a CRD (each CRD contains schemas for all versions) + kinds := make(map[string][]kindWithVersion) + for version, kind := range codegen.VersionedKinds(appManifest) { + kv, ok := kinds[kind.Kind] + if !ok { + kv = make([]kindWithVersion, 0) + } + kv = append(kv, kindWithVersion{ + version: version.Name(), + kind: kind, + }) + kinds[kind.Kind] = kv } - if kind.Properties().Conversion && kind.Properties().ConversionWebhookProps.URL != "" { - webhookURL, err := url.Parse(kind.Properties().ConversionWebhookProps.URL) - if err != nil { - return nil, fmt.Errorf("invalid conversion webhook URL: %w", err) + for _, kv := range kinds { + // Edge case that should never happen, but just in case + if len(kv) == 0 { + continue } - resource.Spec.Conversion = &k8s.CustomResourceDefinitionSpecConversion{ - Strategy: "webhook", - Webhook: &k8s.CustomResourceDefinitionSpecConversionWebhook{ - ConversionReviewVersions: []string{"v1"}, - ClientConfig: k8s.CustomResourceDefinitionClientConfig{ - URL: webhookURL.String(), + resource := customResourceDefinition{ + APIVersion: "apiextensions.k8s.io/v1", + Kind: "CustomResourceDefinition", + Metadata: customResourceDefinitionMetadata{ + Name: fmt.Sprintf("%s.%s", kv[0].kind.PluralMachineName, appManifest.Properties().FullGroup), + }, + Spec: k8s.CustomResourceDefinitionSpec{ + Group: appManifest.Properties().FullGroup, + Scope: kv[0].kind.Scope, + Names: k8s.CustomResourceDefinitionSpecNames{ + Kind: kv[0].kind.Kind, + Plural: kv[0].kind.PluralMachineName, }, + Versions: make([]k8s.CustomResourceDefinitionSpecVersion, 0), }, } - } - for _, ver := range kind.Versions() { - v, err := KindVersionToCRDSpecVersion(ver, kind.Properties().Kind, ver.Version == kind.Properties().Current) - if err != nil { - return nil, err + if kv[0].kind.Conversion && kv[0].kind.ConversionWebhookProps.URL != "" { + webhookURL, err := url.Parse(kv[0].kind.ConversionWebhookProps.URL) + if err != nil { + return nil, fmt.Errorf("invalid conversion webhook URL: %w", err) + } + resource.Spec.Conversion = &k8s.CustomResourceDefinitionSpecConversion{ + Strategy: "webhook", + Webhook: &k8s.CustomResourceDefinitionSpecConversionWebhook{ + ConversionReviewVersions: []string{"v1"}, + ClientConfig: k8s.CustomResourceDefinitionClientConfig{ + URL: webhookURL.String(), + }, + }, + } } - // Check for edge case that results in CRDs that may not work with discovery, but should still be allowed to work. - // If there is only one version, storage must always be true. - if len(kind.Versions()) == 1 { - v.Storage = true + for _, vs := range kv { + v, err := KindVersionToCRDSpecVersion(vs.kind.Schema, vs.kind, vs.version, vs.version == appManifest.Properties().PreferredVersion) + if err != nil { + return nil, err + } + + // Check for edge case that results in CRDs that may not work with discovery, but should still be allowed to work. + // If there is only one version, storage must always be true. + if len(kv) == 1 { + v.Storage = true + } + resource.Spec.Versions = append(resource.Spec.Versions, v) } - resource.Spec.Versions = append(resource.Spec.Versions, v) - } - contents, err := c.outputEncoder(resource) - if err != nil { - return nil, err + contents, err := c.outputEncoder(resource) + if err != nil { + return nil, err + } + files = append(files, codejen.File{ + Data: contents, + RelativePath: fmt.Sprintf("%s.%s.%s", kv[0].kind.MachineName, appManifest.Properties().FullGroup, c.outputExtension), + From: []codejen.NamedJenny{c}, + }) } - return codejen.NewFile(fmt.Sprintf("%s.%s.%s", kind.Properties().MachineName, kind.Properties().Group, c.outputExtension), contents, c), nil + return files, nil } -func KindVersionToCRDSpecVersion(kv codegen.KindVersion, kindName string, stored bool) (k8s.CustomResourceDefinitionSpecVersion, error) { - props, err := cueToCRDOpenAPI(kv.Schema, kindName) +func KindVersionToCRDSpecVersion(schema cue.Value, kind codegen.VersionedKind, version string, stored bool) (k8s.CustomResourceDefinitionSpecVersion, error) { + props, err := cueToCRDOpenAPI(schema, kind.Kind) if err != nil { return k8s.CustomResourceDefinitionSpecVersion{}, err } def := k8s.CustomResourceDefinitionSpecVersion{ - Name: kv.Version, + Name: version, Served: true, Storage: stored, Schema: map[string]any{ @@ -116,9 +146,9 @@ func KindVersionToCRDSpecVersion(kv codegen.KindVersion, kindName string, stored }, Subresources: make(map[string]any), } - if len(kv.SelectableFields) > 0 { - sf := make([]k8s.CustomResourceDefinitionSelectableField, len(kv.SelectableFields)) - for i, field := range kv.SelectableFields { + if len(kind.SelectableFields) > 0 { + sf := make([]k8s.CustomResourceDefinitionSelectableField, len(kind.SelectableFields)) + for i, field := range kind.SelectableFields { field = strings.Trim(field, " ") if field == "" { continue @@ -133,9 +163,9 @@ func KindVersionToCRDSpecVersion(kv codegen.KindVersion, kindName string, stored def.SelectableFields = sf } - if len(kv.AdditionalPrinterColumns) > 0 { - apc := make([]k8s.CustomResourceDefinitionAdditionalPrinterColumn, len(kv.AdditionalPrinterColumns)) - for i, col := range kv.AdditionalPrinterColumns { + if len(kind.AdditionalPrinterColumns) > 0 { + apc := make([]k8s.CustomResourceDefinitionAdditionalPrinterColumn, len(kind.AdditionalPrinterColumns)) + for i, col := range kind.AdditionalPrinterColumns { apc[i] = k8s.CustomResourceDefinitionAdditionalPrinterColumn{ Name: col.Name, Type: col.Type, diff --git a/codegen/jennies/goclients.go b/codegen/jennies/goclients.go index 68b8b1fc5..bc5e66118 100644 --- a/codegen/jennies/goclients.go +++ b/codegen/jennies/goclients.go @@ -29,80 +29,78 @@ func (*ResourceClientJenny) JennyName() string { func (r *ResourceClientJenny) Generate(appManifest codegen.AppManifest) (codejen.Files, error) { files := make(codejen.Files, 0) - for _, version := range appManifest.Versions() { - for _, kind := range version.Kinds() { - if !kind.Codegen.Go.Enabled { + for version, kind := range codegen.VersionedKinds(appManifest) { + if !kind.Codegen.Go.Enabled { + continue + } + prefix := exportField(kind.Kind) + if r.GroupByKind { + prefix = "" + } + subresources := make([]templates.GoResourceClientSubresource, 0) + it, err := kind.Schema.Fields() + if err != nil { + return nil, err + } + for it.Next() { + sr := it.Selector().String() + if sr == "metadata" || sr == "spec" { //nolint:goconst continue } - prefix := exportField(kind.Kind) - if r.GroupByKind { - prefix = "" - } - subresources := make([]templates.GoResourceClientSubresource, 0) - it, err := kind.Schema.Fields() - if err != nil { - return nil, err - } - for it.Next() { - sr := it.Selector().String() - if sr == "metadata" || sr == "spec" { //nolint:goconst - continue - } - subresources = append(subresources, templates.GoResourceClientSubresource{ - FieldName: exportField(sr), - Subresource: sr, - }) - } - // Sort for consistent output in the template - slices.SortFunc(subresources, func(a, b templates.GoResourceClientSubresource) int { - return strings.Compare(a.FieldName, b.FieldName) + subresources = append(subresources, templates.GoResourceClientSubresource{ + FieldName: exportField(sr), + Subresource: sr, }) - md := templates.GoResourceClientMetadata{ - PackageName: ToPackageName(version.Name()), - KindName: exportField(kind.Kind), - KindPrefix: prefix, - Subresources: subresources, - CustomRoutes: make([]templates.GoResourceClientCustomRoute, 0), - } - for cpath, methods := range kind.Routes { - for method, route := range methods { - if route.Name == "" { - route.Name = defaultRouteName(method, cpath) - } - crmd, err := r.getCustomRouteInfo(route) - if err != nil { - return nil, err - } - crmd.Path = cpath - crmd.Method = method - md.CustomRoutes = append(md.CustomRoutes, crmd) + } + // Sort for consistent output in the template + slices.SortFunc(subresources, func(a, b templates.GoResourceClientSubresource) int { + return strings.Compare(a.FieldName, b.FieldName) + }) + md := templates.GoResourceClientMetadata{ + PackageName: ToPackageName(version.Name()), + KindName: exportField(kind.Kind), + KindPrefix: prefix, + Subresources: subresources, + CustomRoutes: make([]templates.GoResourceClientCustomRoute, 0), + } + for cpath, methods := range kind.Routes { + for method, route := range methods { + if route.Name == "" { + route.Name = defaultRouteName(method, cpath) + } + crmd, err := r.getCustomRouteInfo(route) + if err != nil { + return nil, err } + crmd.Path = cpath + crmd.Method = method + md.CustomRoutes = append(md.CustomRoutes, crmd) } - slices.SortFunc(md.CustomRoutes, func(a, b templates.GoResourceClientCustomRoute) int { - return strings.Compare(a.TypeName, b.TypeName) - }) + } + slices.SortFunc(md.CustomRoutes, func(a, b templates.GoResourceClientCustomRoute) int { + return strings.Compare(a.TypeName, b.TypeName) + }) - b := bytes.Buffer{} - err = templates.WriteGoResourceClient(md, &b) - if err != nil { - return nil, err - } - formatted, err := format.Source(b.Bytes()) - if err != nil { - return nil, err - } - formatted, err = imports.Process("", formatted, &imports.Options{ - Comments: true, - }) - if err != nil { - return nil, err - } - files = append(files, codejen.File{ - RelativePath: filepath.Join(getGeneratedPathForKind(r.GroupByKind, appManifest.Properties().Group, kind, version.Name()), fmt.Sprintf("%s_client_gen.go", kind.MachineName)), - Data: formatted, - From: []codejen.NamedJenny{r}, - }) + b := bytes.Buffer{} + err = templates.WriteGoResourceClient(md, &b) + if err != nil { + return nil, err + } + formatted, err := format.Source(b.Bytes()) + if err != nil { + return nil, err + } + formatted, err = imports.Process("", formatted, &imports.Options{ + Comments: true, + }) + if err != nil { + return nil, err } + files = append(files, codejen.File{ + RelativePath: filepath.Join(getGeneratedPathForKind(r.GroupByKind, appManifest.Properties().Group, kind, version.Name()), fmt.Sprintf("%s_client_gen.go", kind.MachineName)), + Data: formatted, + From: []codejen.NamedJenny{r}, + }) } return files, nil } diff --git a/codegen/jennies/gotypes.go b/codegen/jennies/gotypes.go index 4237b4a12..ae1594d31 100644 --- a/codegen/jennies/gotypes.go +++ b/codegen/jennies/gotypes.go @@ -17,7 +17,7 @@ import ( const GoTypesMaxDepth = 5 -// GoTypes is a Jenny for turning a codegen.Kind into go types according to its codegen settings. +// GoTypes is a Jenny for turning a codegen.AppManifest into go types according to its codegen settings. type GoTypes struct { // GenerateOnlyCurrent should be set to true if you only want to generate code for the kind.Properties().Current version. // This will affect the package and path(s) of the generated file(s). @@ -84,82 +84,87 @@ func (*GoTypes) JennyName() string { return "GoTypes" } -func (g *GoTypes) Generate(kind codegen.Kind) (codejen.Files, error) { - if g.GenerateOnlyCurrent { - ver := kind.Version(kind.Properties().Current) - if ver == nil { - return nil, fmt.Errorf("version '%s' of kind '%s' does not exist", kind.Properties().Current, kind.Name()) +func (g *GoTypes) Generate(appManifest codegen.AppManifest) (codejen.Files, error) { + files := make(codejen.Files, 0) + for version, kind := range codegen.VersionedKinds(appManifest) { + if g.GenerateOnlyCurrent && appManifest.Properties().PreferredVersion != version.Name() { + continue } - return g.generateFiles(kind, ver, kind.Name(), kind.Properties().MachineName, kind.Properties().MachineName, kind.Properties().MachineName) - } - files := make(codejen.Files, 0) - versions := kind.Versions() - for i := range len(versions) { - ver := versions[i] - if !ver.Codegen.Go.Enabled { + genCfg := goTypesGenerateFilesConfig{ + VersionName: version.Name(), + KindName: kind.Kind, + MachineName: kind.MachineName, + Group: appManifest.Properties().Group, + PackageName: ToPackageName(version.Name()), + PathPrefix: GetGeneratedGoTypePath(g.GroupByKind, appManifest.Properties().Group, version.Name(), kind.MachineName), + ExcludeFields: g.ExcludeFields, + } + if g.Depth > 0 { + if !g.GroupByKind { + genCfg.NamePrefix = exportField(kind.Kind) + } + generated, err := g.generateFilesAtDepth(kind.Schema, kind.Schema.Path(), 0, genCfg) + if err != nil { + return nil, err + } + files = append(files, generated...) continue } - generated, err := g.generateFiles(kind, &ver, kind.Name(), kind.Properties().MachineName, ToPackageName(ver.Version), GetGeneratedPath(g.GroupByKind, kind, ver.Version)) + codegenPipeline := cog.TypesFromSchema(). + CUEValue(genCfg.PackageName, kind.Schema, cog.ForceEnvelope(kind.Kind)). + Golang(cog.GoConfig{ + AnyAsInterface: g.AnyAsInterface, + }) + + if g.AddKubernetesCodegen { + codegenPipeline = codegenPipeline.SchemaTransformations(cog.AppendCommentToObjects("+k8s:openapi-gen=true")) + } + + generated, err := codegenPipeline.Run(context.Background()) if err != nil { return nil, err } - files = append(files, generated...) - } - return files, nil -} + if len(generated) != 1 { + return nil, fmt.Errorf("expected one file to be generated, got %d", len(generated)) + } -func (g *GoTypes) generateFiles(kind codegen.Kind, version *codegen.KindVersion, name string, machineName string, packageName string, pathPrefix string) (codejen.Files, error) { - if g.Depth > 0 { - namePrefix := "" - if !g.GroupByKind { - namePrefix = exportField(name) + formatted, err := format.Source(generated[0].Data) + if err != nil { + return nil, err } - return g.generateFilesAtDepth(version.Schema, kind, version, 0, machineName, packageName, pathPrefix, namePrefix, g.ExcludeFields) - } - codegenPipeline := cog.TypesFromSchema(). - CUEValue(packageName, version.Schema, cog.ForceEnvelope(name)). - Golang(cog.GoConfig{ - AnyAsInterface: g.AnyAsInterface, + files = append(files, codejen.File{ + Data: formatted, + RelativePath: fmt.Sprintf(path.Join(genCfg.PathPrefix, "%s_gen.go"), strings.ToLower(genCfg.MachineName)), + From: []codejen.NamedJenny{g}, }) - - if g.AddKubernetesCodegen { - codegenPipeline = codegenPipeline.SchemaTransformations(cog.AppendCommentToObjects("+k8s:openapi-gen=true")) - } - - files, err := codegenPipeline.Run(context.Background()) - if err != nil { - return nil, err - } - - if len(files) != 1 { - return nil, fmt.Errorf("expected one file to be generated, got %d", len(files)) - } - - formatted, err := format.Source(files[0].Data) - if err != nil { - return nil, err } + return files, nil +} - return codejen.Files{codejen.File{ - Data: formatted, - RelativePath: fmt.Sprintf(path.Join(pathPrefix, "%s_gen.go"), strings.ToLower(machineName)), - From: []codejen.NamedJenny{g}, - }}, nil +type goTypesGenerateFilesConfig struct { + VersionName string + KindName string + MachineName string + PackageName string + PathPrefix string + NamePrefix string + Group string + ExcludeFields []string } //nolint:goconst -func (g *GoTypes) generateFilesAtDepth(v cue.Value, kind codegen.Kind, kv *codegen.KindVersion, currDepth int, machineName string, packageName string, pathPrefix string, namePrefix string, excludeFields []string) (codejen.Files, error) { +func (g *GoTypes) generateFilesAtDepth(v cue.Value, schemaPath cue.Path, currDepth int, cfg goTypesGenerateFilesConfig) (codejen.Files, error) { if currDepth == g.Depth { fieldName := make([]string, 0) - for _, s := range TrimPathPrefix(v.Path(), kv.Schema.Path()).Selectors() { + for _, s := range TrimPathPrefix(v.Path(), schemaPath).Selectors() { fieldName = append(fieldName, s.String()) } exclude := false - for _, s := range excludeFields { + for _, s := range cfg.ExcludeFields { // Check if the exclude name matches either the final element of the path, or the joined path if len(fieldName) > 0 && strings.EqualFold(fieldName[len(fieldName)-1], s) { exclude = true @@ -177,23 +182,19 @@ func (g *GoTypes) generateFilesAtDepth(v cue.Value, kind codegen.Kind, kv *codeg var namerFunc func(string) string if g.OpenAPINamer != nil { namerFunc = func(name string) string { - grp := kind.Properties().ManifestGroup - if grp == "" { - grp = kind.Properties().Group - } return g.OpenAPINamer(OpenAPINamerInfo{ TypeName: name, - ShortGroup: grp, - Version: kv.Version, - Kind: kind.Name(), + ShortGroup: cfg.Group, + Version: cfg.VersionName, + Kind: cfg.KindName, }) } } goBytes, err := GoTypesFromCUE(v, CUEGoConfig{ - PackageName: packageName, + PackageName: cfg.PackageName, Name: exportField(strings.Join(fieldName, "")), - NamePrefix: namePrefix, + NamePrefix: cfg.NamePrefix, AddKubernetesOpenAPIGenComment: g.AddKubernetesCodegen && (len(fieldName) != 1 || fieldName[0] != "metadata"), AnyAsInterface: g.AnyAsInterface, }, len(v.Path().Selectors())-(g.Depth-g.NamingDepth), namerFunc) @@ -203,7 +204,7 @@ func (g *GoTypes) generateFilesAtDepth(v cue.Value, kind codegen.Kind, kv *codeg return codejen.Files{codejen.File{ Data: goBytes, - RelativePath: fmt.Sprintf(path.Join(pathPrefix, "%s_%s_gen.go"), strings.ToLower(machineName), strings.Join(fieldName, "_")), + RelativePath: fmt.Sprintf(path.Join(cfg.PathPrefix, "%s_%s_gen.go"), strings.ToLower(cfg.MachineName), strings.Join(fieldName, "_")), From: []codejen.NamedJenny{g}, }}, nil } @@ -215,7 +216,7 @@ func (g *GoTypes) generateFilesAtDepth(v cue.Value, kind codegen.Kind, kv *codeg files := make(codejen.Files, 0) for it.Next() { - f, err := g.generateFilesAtDepth(it.Value(), kind, kv, currDepth+1, machineName, packageName, pathPrefix, namePrefix, excludeFields) + f, err := g.generateFilesAtDepth(it.Value(), schemaPath, currDepth+1, cfg) if err != nil { return nil, err } diff --git a/codegen/jennies/openapi.go b/codegen/jennies/openapi.go index c34b9d9d0..92447c9c6 100644 --- a/codegen/jennies/openapi.go +++ b/codegen/jennies/openapi.go @@ -35,44 +35,41 @@ func (*OpenAPI) JennyName() string { return "OpenAPI" } -func (o *OpenAPI) Generate(kinds ...codegen.Kind) (codejen.Files, error) { +func (o *OpenAPI) Generate(appManifest codegen.AppManifest) (codejen.Files, error) { fs := codejen.NewFS() // Group kinds by package name if o.GroupByKind { - for _, k := range kinds { - versions := k.Versions() - for i := 0; i < len(versions); i++ { - ver := versions[i] - if !ver.Codegen.Go.Enabled { - continue - } + for v, k := range codegen.VersionedKinds(appManifest) { + if !k.Codegen.Go.Enabled { + continue + } - relativePkg := filepath.Join(o.GoGenPath, GetGeneratedPath(o.GroupByKind, k, ver.Version)) - err := gengo.Execute(generators.NameSystems(), - generators.DefaultNameSystem(), - o.getTargetsFunc(relativePkg, fs), - gengo.StdBuildTag, - []string{filepath.Join(o.GoModName, filepath.ToSlash(relativePkg))}, - ) - if err != nil { - return nil, err - } + relativePkg := filepath.Join(o.GoGenPath, GetGeneratedGoTypePath(o.GroupByKind, appManifest.Properties().Group, v.Name(), k.MachineName)) + err := gengo.Execute(generators.NameSystems(), + generators.DefaultNameSystem(), + o.getTargetsFunc(relativePkg, fs), + gengo.StdBuildTag, + []string{filepath.Join(o.GoModName, filepath.ToSlash(relativePkg))}, + ) + if err != nil { + return nil, err } } } else { gvs := make(map[schema.GroupVersion]struct{}) - for _, k := range kinds { - for _, v := range k.Versions() { - if !v.Codegen.Go.Enabled { - continue - } - grp := k.Properties().ManifestGroup - if grp == "" { - grp = k.Properties().Group + for _, v := range appManifest.Versions() { + codegenEnabled := false + for _, k := range v.Kinds() { + if k.Codegen.Go.Enabled { + codegenEnabled = true + break } - gvs[schema.GroupVersion{Group: grp, Version: v.Version}] = struct{}{} } + if !codegenEnabled { + continue + } + gvs[schema.GroupVersion{Group: appManifest.Properties().Group, Version: v.Name()}] = struct{}{} } for gv := range gvs { relativePkg := filepath.Join(o.GoGenPath, ToPackageName(strings.ToLower(gv.Group)), ToPackageName(gv.Version)) diff --git a/codegen/jennies/operator.go b/codegen/jennies/operator.go index 5887f07aa..2ca96f71f 100644 --- a/codegen/jennies/operator.go +++ b/codegen/jennies/operator.go @@ -12,7 +12,7 @@ import ( "github.com/grafana/grafana-app-sdk/codegen/templates" ) -func WatcherJenny(projectRepo, codegenPath string, groupByKind bool) codejen.OneToOne[codegen.Kind] { +func WatcherJenny(projectRepo, codegenPath string, groupByKind bool) codejen.OneToMany[codegen.AppManifest] { return &watcherJenny{ projectRepo: projectRepo, codegenPath: codegenPath, @@ -30,32 +30,39 @@ func (*watcherJenny) JennyName() string { return "Watcher" } -func (w *watcherJenny) Generate(kind codegen.Kind) (*codejen.File, error) { - if !kind.Version(kind.Properties().Current).Codegen.Go.Enabled { - return nil, nil +func (w *watcherJenny) Generate(appManifest codegen.AppManifest) (codejen.Files, error) { + files := make(codejen.Files, 0) + for version, kind := range codegen.PreferredVersionKinds(appManifest) { + if !kind.Codegen.Go.Enabled { + continue + } + props := versionedKindToKindProperties(kind, appManifest) + b := bytes.Buffer{} + err := templates.WriteWatcher(templates.WatcherMetadata{ + KindProperties: props, + PackageName: "watchers", + Repo: w.projectRepo, + CodegenPath: w.codegenPath, + Version: version.Name(), + KindPackage: GetGeneratedGoTypePath(w.groupByKind, appManifest.Properties().Group, version.Name(), kind.MachineName), + KindsAreGrouped: !w.groupByKind, + KindPackageAlias: fmt.Sprintf("%s%s", kind.MachineName, version.Name()), + }, &b) + if err != nil { + return nil, err + } + formatted, err := format.Source(b.Bytes()) + if err != nil { + return nil, err + } + files = append(files, codejen.File{ + RelativePath: fmt.Sprintf("pkg/watchers/watcher_%s.go", props.MachineName), + Data: formatted, + From: []codejen.NamedJenny{w}, + }) } - ver := kind.Properties().Current - props := kind.Properties() - b := bytes.Buffer{} - err := templates.WriteWatcher(templates.WatcherMetadata{ - KindProperties: props, - PackageName: "watchers", - Repo: w.projectRepo, - CodegenPath: w.codegenPath, - Version: ver, - KindPackage: GetGeneratedPath(w.groupByKind, kind, ver), - KindsAreGrouped: !w.groupByKind, - KindPackageAlias: fmt.Sprintf("%s%s", kind.Properties().MachineName, kind.Properties().Current), - }, &b) - if err != nil { - return nil, err - } - formatted, err := format.Source(b.Bytes()) - if err != nil { - return nil, err - } - return codejen.NewFile(fmt.Sprintf("pkg/watchers/watcher_%s.go", props.MachineName), formatted, w), nil + return files, nil } type OperatorKubeConfigJenny struct { @@ -65,7 +72,7 @@ func (*OperatorKubeConfigJenny) JennyName() string { return "OperatorKubeConfig" } -func (o *OperatorKubeConfigJenny) Generate(_ ...codegen.Kind) (*codejen.File, error) { +func (o *OperatorKubeConfigJenny) Generate(_ codegen.AppManifest) (*codejen.File, error) { b := bytes.Buffer{} err := templates.WriteOperatorKubeConfig(&b) if err != nil { @@ -85,7 +92,7 @@ func (*OperatorConfigJenny) JennyName() string { return "OperatorConfig" } -func (o *OperatorConfigJenny) Generate(_ ...codegen.Kind) (*codejen.File, error) { +func (o *OperatorConfigJenny) Generate(_ codegen.AppManifest) (*codejen.File, error) { // TODO: combine this with kubeconfig? b := bytes.Buffer{} err := templates.WriteOperatorConfig(&b) @@ -99,7 +106,7 @@ func (o *OperatorConfigJenny) Generate(_ ...codegen.Kind) (*codejen.File, error) return codejen.NewFile("cmd/operator/config.go", formatted, o), nil } -func OperatorMainJenny(projectRepo, codegenPath string, groupByKind bool) codejen.ManyToOne[codegen.Kind] { +func OperatorMainJenny(projectRepo, codegenPath string, groupByKind bool) codejen.OneToOne[codegen.AppManifest] { parts := strings.Split(projectRepo, "/") if len(parts) == 0 { parts = []string{""} @@ -123,21 +130,16 @@ func (*operatorMainJenny) JennyName() string { return "OperatorMain" } -func (o *operatorMainJenny) Generate(kinds ...codegen.Kind) (*codejen.File, error) { +func (o *operatorMainJenny) Generate(_ codegen.AppManifest) (*codejen.File, error) { tmd := templates.OperatorMainMetadata{ Repo: o.projectRepo, ProjectName: o.projectName, CodegenPath: o.codegenPath, PackageName: "main", WatcherPackage: "watchers", - Resources: make([]codegen.KindProperties, 0), KindsAreGrouped: !o.groupByKind, } - for _, kind := range kinds { - tmd.Resources = append(tmd.Resources, kind.Properties()) - } - b := bytes.Buffer{} err := templates.WriteOperatorMain(tmd, &b) if err != nil { diff --git a/codegen/jennies/resourceobject.go b/codegen/jennies/resourceobject.go index 9ed551147..51e0848f8 100644 --- a/codegen/jennies/resourceobject.go +++ b/codegen/jennies/resourceobject.go @@ -70,30 +70,24 @@ func (*ResourceObjectGenerator) JennyName() string { return "ResourceObjectGenerator" } -func (r *ResourceObjectGenerator) Generate(kind codegen.Kind) (codejen.Files, error) { +func (r *ResourceObjectGenerator) Generate(appManifest codegen.AppManifest) (codejen.Files, error) { files := make(codejen.Files, 0) - allVersions := kind.Versions() - for i := range len(allVersions) { - ver := allVersions[i] + for version, kind := range codegen.VersionedKinds(appManifest) { openAPIName := "" if r.OpenAPINamer != nil { - grp := kind.Properties().ManifestGroup - if grp == "" { - grp = kind.Properties().Group - } openAPIName = r.OpenAPINamer(OpenAPINamerInfo{ - TypeName: kind.Name(), - ShortGroup: grp, - Version: ver.Version, - Kind: kind.Name(), + TypeName: kind.Kind, + ShortGroup: appManifest.Properties().Group, + Version: version.Name(), + Kind: kind.Kind, }) } - b, err := r.generateObjectFile(kind, &ver, ToPackageName(ver.Version), openAPIName) + b, err := r.generateObjectFile(kind, ToPackageName(version.Name()), openAPIName) if err != nil { return nil, err } files = append(files, codejen.File{ - RelativePath: filepath.Join(GetGeneratedPath(r.GroupByKind, kind, ver.Version), fmt.Sprintf("%s_object_gen.go", strings.ToLower(kind.Properties().MachineName))), + RelativePath: filepath.Join(GetGeneratedGoTypePath(r.GroupByKind, appManifest.Properties().Group, version.Name(), kind.MachineName), fmt.Sprintf("%s_object_gen.go", strings.ToLower(kind.MachineName))), Data: b, From: []codejen.NamedJenny{r}, }) @@ -101,9 +95,9 @@ func (r *ResourceObjectGenerator) Generate(kind codegen.Kind) (codejen.Files, er return files, nil } -func (r *ResourceObjectGenerator) generateObjectFile(kind codegen.Kind, version *codegen.KindVersion, pkg string, openAPIName string) ([]byte, error) { +func (r *ResourceObjectGenerator) generateObjectFile(kind codegen.VersionedKind, pkg string, openAPIName string) ([]byte, error) { customMetadataFields := make([]templates.ObjectMetadataField, 0) - mdv := version.Schema.LookupPath(cue.MakePath(cue.Str("metadata"))) + mdv := kind.Schema.LookupPath(cue.MakePath(cue.Str("metadata"))) if mdv.Exists() { mit, err := mdv.Fields() if err != nil { @@ -137,12 +131,11 @@ func (r *ResourceObjectGenerator) generateObjectFile(kind codegen.Kind, version typePrefix := "" if r.SubresourceTypesArePrefixed { - typePrefix = exportField(kind.Name()) + typePrefix = exportField(kind.Kind) } - meta := kind.Properties() md := templates.ResourceObjectTemplateMetadata{ Package: pkg, - TypeName: meta.Kind, + TypeName: kind.Kind, SpecTypeName: typePrefix + "Spec", ObjectTypeName: "Object", // Package is the machine name of the object, so this makes it machinename.Object ObjectShortName: "o", @@ -150,7 +143,7 @@ func (r *ResourceObjectGenerator) generateObjectFile(kind codegen.Kind, version CustomMetadataFields: customMetadataFields, OpenAPIModelName: openAPIName, } - it, err := version.Schema.Fields() + it, err := kind.Schema.Fields() if err != nil { return nil, err } diff --git a/codegen/jennies/routercode.go b/codegen/jennies/routercode.go index fb03c674e..176dc83aa 100644 --- a/codegen/jennies/routercode.go +++ b/codegen/jennies/routercode.go @@ -11,7 +11,7 @@ import ( "github.com/grafana/grafana-app-sdk/codegen/templates" ) -func RouterCodeGenerator(projectRepo string) codejen.ManyToOne[codegen.Kind] { +func RouterCodeGenerator(projectRepo string) codejen.OneToOne[codegen.AppManifest] { return &routerCodeGenerator{ projectRepo: projectRepo, } @@ -22,15 +22,15 @@ type routerCodeGenerator struct { groupByKind bool } -func (r *routerCodeGenerator) Generate(decls ...codegen.Kind) (*codejen.File, error) { +func (r *routerCodeGenerator) Generate(appManifest codegen.AppManifest) (*codejen.File, error) { tmd := templates.BackendPluginRouterTemplateMetadata{ Repo: r.projectRepo, Resources: make([]codegen.KindProperties, 0), KindsAreGrouped: !r.groupByKind, } - for _, decl := range decls { - tmd.Resources = append(tmd.Resources, decl.Properties()) + for _, kind := range codegen.PreferredVersionKinds(appManifest) { + tmd.Resources = append(tmd.Resources, versionedKindToKindProperties(kind, appManifest)) } b := bytes.Buffer{} @@ -49,7 +49,7 @@ func (*routerCodeGenerator) JennyName() string { return "routerCodeGenerator" } -func RouterHandlerCodeGenerator(projectRepo, apiCodegenPath string, groupByKind bool) codejen.OneToOne[codegen.Kind] { +func RouterHandlerCodeGenerator(projectRepo, apiCodegenPath string, groupByKind bool) codejen.OneToMany[codegen.AppManifest] { return &routerHandlerCodeGenerator{ projectRepo: projectRepo, apiCodegenPath: apiCodegenPath, @@ -63,29 +63,34 @@ type routerHandlerCodeGenerator struct { groupByKind bool } -func (h *routerHandlerCodeGenerator) Generate(decl codegen.Kind) (*codejen.File, error) { - meta := decl.Properties() - - ver := ToPackageName(decl.Properties().Current) - b := bytes.Buffer{} - err := templates.WriteBackendPluginHandler(templates.BackendPluginHandlerTemplateMetadata{ - KindProperties: meta, - Repo: h.projectRepo, - APICodegenPath: h.apiCodegenPath, - TypeName: exportField(decl.Properties().Kind), - IsResource: true, - Version: ver, - KindPackage: GetGeneratedPath(h.groupByKind, decl, ver), - KindsAreGrouped: !h.groupByKind, - }, &b) - if err != nil { - return nil, err - } - formatted, err := format.Source(b.Bytes()) - if err != nil { - return nil, err +func (h *routerHandlerCodeGenerator) Generate(appManifest codegen.AppManifest) (codejen.Files, error) { + files := make(codejen.Files, 0) + for version, kind := range codegen.PreferredVersionKinds(appManifest) { + b := bytes.Buffer{} + err := templates.WriteBackendPluginHandler(templates.BackendPluginHandlerTemplateMetadata{ + KindProperties: versionedKindToKindProperties(kind, appManifest), + Repo: h.projectRepo, + APICodegenPath: h.apiCodegenPath, + TypeName: exportField(kind.Kind), + IsResource: true, + Version: version.Name(), + KindPackage: GetGeneratedGoTypePath(h.groupByKind, appManifest.Properties().Group, version.Name(), kind.MachineName), + KindsAreGrouped: !h.groupByKind, + }, &b) + if err != nil { + return nil, err + } + formatted, err := format.Source(b.Bytes()) + if err != nil { + return nil, err + } + files = append(files, codejen.File{ + RelativePath: fmt.Sprintf("plugin/handler_%s.go", kind.MachineName), + Data: formatted, + From: []codejen.NamedJenny{h}, + }) } - return codejen.NewFile(fmt.Sprintf("plugin/handler_%s.go", meta.MachineName), formatted, h), nil + return files, nil } func (*routerHandlerCodeGenerator) JennyName() string { diff --git a/codegen/jennies/schema.go b/codegen/jennies/schema.go index 73289b538..0e7b60988 100644 --- a/codegen/jennies/schema.go +++ b/codegen/jennies/schema.go @@ -29,34 +29,29 @@ func (*SchemaGenerator) JennyName() string { } // Generate creates one or more schema go files for the provided Kind -// nolint:dupl -func (s *SchemaGenerator) Generate(kind codegen.Kind) (codejen.Files, error) { - meta := kind.Properties() - - if meta.Scope != string(resource.NamespacedScope) && meta.Scope != string(resource.ClusterScope) { - return nil, fmt.Errorf("scope '%s' is invalid, must be one of: '%s', '%s'", - meta.Scope, resource.ClusterScope, resource.NamespacedScope) - } - - prefix := "" - if !s.GroupByKind { - prefix = exportField(kind.Name()) - } - +func (s *SchemaGenerator) Generate(appManifest codegen.AppManifest) (codejen.Files, error) { files := make(codejen.Files, 0) - for _, ver := range kind.Versions() { - sf, err := s.getSelectableFields(&ver) + for version, kind := range codegen.VersionedKinds(appManifest) { + if kind.Scope != string(resource.NamespacedScope) && kind.Scope != string(resource.ClusterScope) { + return nil, fmt.Errorf("%s/%s: scope '%s' is invalid, must be one of: '%s', '%s'", + version.Name(), kind.Kind, kind.Scope, resource.ClusterScope, resource.NamespacedScope) + } + prefix := "" + if !s.GroupByKind { + prefix = exportField(kind.Kind) + } + sf, err := s.getSelectableFields(&kind) if err != nil { return nil, err } b := bytes.Buffer{} err = templates.WriteSchema(templates.SchemaMetadata{ - Package: ToPackageName(ver.Version), - Group: meta.Group, - Version: ver.Version, - Kind: meta.Kind, - Plural: meta.PluralMachineName, - Scope: meta.Scope, + Package: ToPackageName(version.Name()), + Group: appManifest.Properties().FullGroup, + Version: version.Name(), + Kind: kind.Kind, + Plural: kind.PluralMachineName, + Scope: kind.Scope, SelectableFields: sf, FuncPrefix: prefix, }, &b) @@ -69,21 +64,20 @@ func (s *SchemaGenerator) Generate(kind codegen.Kind) (codejen.Files, error) { } files = append(files, codejen.File{ Data: formatted, - RelativePath: filepath.Join(GetGeneratedPath(s.GroupByKind, kind, ver.Version), fmt.Sprintf("%s_schema_gen.go", meta.MachineName)), + RelativePath: filepath.Join(GetGeneratedGoTypePath(s.GroupByKind, appManifest.Properties().Group, version.Name(), kind.MachineName), fmt.Sprintf("%s_schema_gen.go", kind.MachineName)), From: []codejen.NamedJenny{s}, }) } - return files, nil } -func (*SchemaGenerator) getSelectableFields(ver *codegen.KindVersion) ([]templates.SchemaMetadataSelectableField, error) { +func (*SchemaGenerator) getSelectableFields(kind *codegen.VersionedKind) ([]templates.SchemaMetadataSelectableField, error) { fields := make([]templates.SchemaMetadataSelectableField, 0) - if len(ver.SelectableFields) == 0 { + if len(kind.SelectableFields) == 0 { return fields, nil } // Check each field in the CUE (TODO: make this OpenAPI instead?) to check if the field is optional - for _, s := range ver.SelectableFields { + for _, s := range kind.SelectableFields { fieldPath := s if len(s) > 1 && s[0] == '.' { fieldPath = s[1:] @@ -98,7 +92,7 @@ func (*SchemaGenerator) getSelectableFields(ver *codegen.KindVersion) ([]templat for _, p := range parts { path = append(path, cue.Str(p)) } - if val := ver.Schema.LookupPath(cue.MakePath(path...).Optional()); val.Err() == nil { + if val := kind.Schema.LookupPath(cue.MakePath(path...).Optional()); val.Err() == nil { var lookup cue.Value var optional bool @@ -122,7 +116,7 @@ func (*SchemaGenerator) getSelectableFields(ver *codegen.KindVersion) ([]templat Field: s, Optional: optional, Type: typeStr, - OptionalFieldsInPath: getOptionalFieldsInPath(ver.Schema, fieldPath), + OptionalFieldsInPath: getOptionalFieldsInPath(kind.Schema, fieldPath), }) } } diff --git a/codegen/jennies/typescript.go b/codegen/jennies/typescript.go index 1ede1c186..2af55c870 100644 --- a/codegen/jennies/typescript.go +++ b/codegen/jennies/typescript.go @@ -21,38 +21,34 @@ type TypeScriptResourceTypes struct { func (*TypeScriptResourceTypes) JennyName() string { return "TypeScriptResourceTypes" } -func (t *TypeScriptResourceTypes) Generate(kind codegen.Kind) (codejen.Files, error) { +func (t *TypeScriptResourceTypes) Generate(appManifest codegen.AppManifest) (codejen.Files, error) { files := make(codejen.Files, 0) if t.GenerateOnlyCurrent { - ver := kind.Version(kind.Properties().Current) - if ver == nil { - return nil, fmt.Errorf("no version for %s", kind.Properties().Current) - } - if !ver.Codegen.TS.Enabled { - return nil, nil - } - b, err := t.generateObjectFile(kind, ver, strings.ToLower(kind.Properties().MachineName)+"_") - if err != nil { - return nil, err + for _, kind := range codegen.PreferredVersionKinds(appManifest) { + if !kind.Codegen.TS.Enabled { + return nil, nil + } + b, err := t.generateObjectFile(&kind, strings.ToLower(kind.MachineName)+"_") + if err != nil { + return nil, err + } + files = append(files, codejen.File{ + RelativePath: fmt.Sprintf("%s/%s_object_gen.ts", kind.MachineName, kind.MachineName), + Data: b, + From: []codejen.NamedJenny{t}, + }) } - files = append(files, codejen.File{ - RelativePath: fmt.Sprintf("%s/%s_object_gen.ts", kind.Properties().MachineName, kind.Properties().MachineName), - Data: b, - From: []codejen.NamedJenny{t}, - }) } else { - allVersions := kind.Versions() - for i := 0; i < len(allVersions); i++ { - ver := allVersions[i] - if !ver.Codegen.TS.Enabled { + for version, kind := range codegen.VersionedKinds(appManifest) { + if !kind.Codegen.TS.Enabled { continue } - b, err := t.generateObjectFile(kind, &ver, "") + b, err := t.generateObjectFile(&kind, "") if err != nil { return nil, err } files = append(files, codejen.File{ - RelativePath: fmt.Sprintf("%s/%s/%s_object_gen.ts", kind.Properties().MachineName, ver.Version, kind.Properties().MachineName), + RelativePath: fmt.Sprintf("%s/%s/%s_object_gen.ts", kind.MachineName, version.Name(), kind.MachineName), Data: b, From: []codejen.NamedJenny{t}, }) @@ -61,14 +57,14 @@ func (t *TypeScriptResourceTypes) Generate(kind codegen.Kind) (codejen.Files, er return files, nil } -func (*TypeScriptResourceTypes) generateObjectFile(kind codegen.Kind, version *codegen.KindVersion, tsTypePrefix string) ([]byte, error) { +func (*TypeScriptResourceTypes) generateObjectFile(vk *codegen.VersionedKind, tsTypePrefix string) ([]byte, error) { metadata := templates.ResourceTSTemplateMetadata{ - TypeName: exportField(kind.Name()), + TypeName: exportField(vk.Kind), Subresources: make([]templates.SubresourceMetadata, 0), FilePrefix: tsTypePrefix, } - it, err := version.Schema.Fields() + it, err := vk.Schema.Fields() if err != nil { return nil, err } @@ -114,51 +110,50 @@ type TypeScriptTypes struct { NamingDepth int } -var _ codejen.OneToMany[codegen.Kind] = &TypeScriptTypes{} +var _ codejen.OneToMany[codegen.AppManifest] = &TypeScriptTypes{} func (TypeScriptTypes) JennyName() string { return "TypeScriptTypes" } -func (j TypeScriptTypes) Generate(kind codegen.Kind) (codejen.Files, error) { +func (j TypeScriptTypes) Generate(appManifest codegen.AppManifest) (codejen.Files, error) { + files := make(codejen.Files, 0) if j.GenerateOnlyCurrent { - ver := kind.Version(kind.Properties().Current) - if ver == nil { - return nil, fmt.Errorf("version '%s' of kind '%s' does not exist", kind.Properties().Current, kind.Name()) - } - if !ver.Codegen.TS.Enabled { - return nil, nil - } - - return j.generateFiles(ver, kind.Name(), "", strings.ToLower(kind.Properties().MachineName)+"_") - } + for version, kind := range codegen.PreferredVersionKinds(appManifest) { + if !kind.Codegen.TS.Enabled { + return nil, nil + } - files := make(codejen.Files, 0) - // For each version, check if we need to codegen - allVersions := kind.Versions() - for i := 0; i < len(allVersions); i++ { - v := allVersions[i] - if !v.Codegen.TS.Enabled { - continue + generated, err := j.generateFiles(version.Name(), &kind, kind.Kind, "", strings.ToLower(kind.MachineName)+"_") + if err != nil { + return nil, err + } + files = append(files, generated...) } + } else { + for version, kind := range codegen.VersionedKinds(appManifest) { + if !kind.Codegen.TS.Enabled { + continue + } - generated, err := j.generateFiles(&v, kind.Name(), fmt.Sprintf("%s/%s", kind.Properties().MachineName, v.Version), "") - if err != nil { - return nil, err + generated, err := j.generateFiles(version.Name(), &kind, kind.Kind, fmt.Sprintf("%s/%s", kind.MachineName, version.Name()), "") + if err != nil { + return nil, err + } + files = append(files, generated...) } - files = append(files, generated...) } return files, nil } -func (j TypeScriptTypes) generateFiles(version *codegen.KindVersion, name, pathPrefix, prefix string) (codejen.Files, error) { +func (j TypeScriptTypes) generateFiles(version string, kind *codegen.VersionedKind, name, pathPrefix, prefix string) (codejen.Files, error) { if j.Depth > 0 { - return j.generateFilesAtDepth(version.Schema, version, 0, pathPrefix, prefix) + return j.generateFilesAtDepth(kind.Schema, version, kind, 0, pathPrefix, prefix) } - tsBytes, err := generateTypescriptBytes(version.Schema, ToPackageName(version.Version), exportField(sanitizeLabelString(name)), cog.TypescriptConfig{ - ImportsMap: version.Codegen.TS.Config.ImportsMap, - EnumsAsUnionTypes: version.Codegen.TS.Config.EnumsAsUnionTypes, + tsBytes, err := generateTypescriptBytes(kind.Schema, ToPackageName(version), exportField(sanitizeLabelString(name)), cog.TypescriptConfig{ + ImportsMap: kind.Codegen.TS.Config.ImportsMap, + EnumsAsUnionTypes: kind.Codegen.TS.Config.EnumsAsUnionTypes, }) if err != nil { return nil, err @@ -170,15 +165,15 @@ func (j TypeScriptTypes) generateFiles(version *codegen.KindVersion, name, pathP }}, nil } -func (j TypeScriptTypes) generateFilesAtDepth(v cue.Value, kv *codegen.KindVersion, currDepth int, pathPrefix string, prefix string) (codejen.Files, error) { +func (j TypeScriptTypes) generateFilesAtDepth(v cue.Value, version string, vk *codegen.VersionedKind, currDepth int, pathPrefix string, prefix string) (codejen.Files, error) { if currDepth == j.Depth { fieldName := make([]string, 0) - for _, s := range TrimPathPrefix(v.Path(), kv.Schema.Path()).Selectors() { + for _, s := range TrimPathPrefix(v.Path(), vk.Schema.Path()).Selectors() { fieldName = append(fieldName, s.String()) } - tsBytes, err := generateTypescriptBytes(v, ToPackageName(kv.Version), exportField(strings.Join(fieldName, "")), cog.TypescriptConfig{ - ImportsMap: kv.Codegen.TS.Config.ImportsMap, - EnumsAsUnionTypes: kv.Codegen.TS.Config.EnumsAsUnionTypes, + tsBytes, err := generateTypescriptBytes(v, ToPackageName(version), exportField(strings.Join(fieldName, "")), cog.TypescriptConfig{ + ImportsMap: vk.Codegen.TS.Config.ImportsMap, + EnumsAsUnionTypes: vk.Codegen.TS.Config.EnumsAsUnionTypes, }) if err != nil { return nil, err @@ -197,7 +192,7 @@ func (j TypeScriptTypes) generateFilesAtDepth(v cue.Value, kv *codegen.KindVersi files := make(codejen.Files, 0) for it.Next() { - f, err := j.generateFilesAtDepth(it.Value(), kv, currDepth+1, pathPrefix, prefix) + f, err := j.generateFilesAtDepth(it.Value(), version, vk, currDepth+1, pathPrefix, prefix) if err != nil { return nil, err } diff --git a/codegen/jennies/util.go b/codegen/jennies/util.go index aef9d5402..5311c6e31 100644 --- a/codegen/jennies/util.go +++ b/codegen/jennies/util.go @@ -17,6 +17,7 @@ func ToPackageName(input string) string { // generated code should be grouped by kind or by GroupVersion. // When groupByKind is true, the path will be /. // When groupByKind is false, the path will be /. +// Deprecated: Use GetGeneratedGoTypePath instead. // //nolint:revive func GetGeneratedPath(groupByKind bool, kind codegen.Kind, version string) string { @@ -52,3 +53,21 @@ func GetGeneratedGoTypePath(groupByKind bool, shortGroup string, version string, } return filepath.Join(ToPackageName(shortGroup), ToPackageName(version)) } + +func versionedKindToKindProperties(kind codegen.VersionedKind, appManifest codegen.AppManifest) codegen.KindProperties { + return codegen.KindProperties{ + Kind: kind.Kind, + Group: appManifest.Properties().FullGroup, + ManifestGroup: appManifest.Properties().Group, + MachineName: kind.MachineName, + PluralMachineName: kind.PluralMachineName, + PluralName: kind.PluralName, + Current: appManifest.Properties().PreferredVersion, + Scope: kind.Scope, + Validation: kind.Validation, + Mutation: kind.Mutation, + Conversion: kind.Conversion, + ConversionWebhookProps: kind.ConversionWebhookProps, + Codegen: kind.Codegen, + } +} diff --git a/codegen/kind.go b/codegen/kind.go index 4e71c9274..feca07008 100644 --- a/codegen/kind.go +++ b/codegen/kind.go @@ -7,6 +7,7 @@ import ( // Kind is a common interface declaration for code generation. // Any type parser should be able to parse a kind into this definition to supply // to various common Jennies in the codegen package. +// Deprecated: use AppManifest instead type Kind interface { Name() string Properties() KindProperties diff --git a/codegen/manifest.go b/codegen/manifest.go index 5710e7034..529713449 100644 --- a/codegen/manifest.go +++ b/codegen/manifest.go @@ -1,6 +1,10 @@ package codegen -import "cuelang.org/go/cue" +import ( + "iter" + + "cuelang.org/go/cue" +) type AppManifest interface { Name() string @@ -186,3 +190,50 @@ type VersionedKind struct { Schema cue.Value `json:"schema"` // TODO: this should eventually be OpenAPI/JSONSchema (ast or bytes?) Routes map[string]map[string]CustomRoute `json:"routes"` } + +// VersionedKinds returns a sequence of all VersionedKinds in version order. +// It can be used with the range operator to make the operation: +// +// for _, version := range m.Versions() { +// for _, kind := range version.Kinds() { +// ... +// } +// } +// +// simplified to: +// +// for version, kind := range m.VersionedKinds() { +// ... +// } +func VersionedKinds(manifest AppManifest) iter.Seq2[Version, VersionedKind] { + return func(yield func(Version, VersionedKind) bool) { + for _, version := range manifest.Versions() { + for _, kind := range version.Kinds() { + if !yield(version, kind) { + return + } + } + } + } +} + +// PreferredVersionKinds returns a sequence of all VersionedKinds for the preferred version. +// It can be used with the range operator to make the operation: +// +// for version, kind := range m.PreferredVersionKinds() { +// ... +// } +func PreferredVersionKinds(manifest AppManifest) iter.Seq2[Version, VersionedKind] { + return func(yield func(Version, VersionedKind) bool) { + for _, version := range manifest.Versions() { + if version.Name() != manifest.Properties().PreferredVersion { + continue + } + for _, kind := range version.Kinds() { + if !yield(version, kind) { + return + } + } + } + } +} diff --git a/codegen/templates/templates.go b/codegen/templates/templates.go index ad51a782e..b32ba760c 100644 --- a/codegen/templates/templates.go +++ b/codegen/templates/templates.go @@ -371,12 +371,6 @@ type OperatorMainMetadata struct { CodegenPath string WatcherPackage string KindsAreGrouped bool - Resources []codegen.KindProperties -} - -type extendedOperatorMainMetadata struct { - OperatorMainMetadata - GVToKind map[schema.GroupVersion][]codegen.KindProperties } func (OperatorMainMetadata) ToPackageName(input string) string { @@ -392,20 +386,7 @@ func (OperatorMainMetadata) GroupToPackageName(input string) string { } func WriteOperatorMain(metadata OperatorMainMetadata, out io.Writer) error { - md := extendedOperatorMainMetadata{ - OperatorMainMetadata: metadata, - GVToKind: make(map[schema.GroupVersion][]codegen.KindProperties), - } - for _, k := range md.Resources { - gv := schema.GroupVersion{Group: k.Group, Version: k.Current} - l, ok := md.GVToKind[gv] - if !ok { - l = make([]codegen.KindProperties, 0) - } - l = append(l, k) - md.GVToKind[gv] = l - } - return templateOperatorMain.Execute(out, md) + return templateOperatorMain.Execute(out, metadata) } func WriteOperatorConfig(out io.Writer) error {