Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion cmd/grafana-app-sdk/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,8 @@ func projectAddComponent(cmd *cobra.Command, args []string) error {
where <components> are one or more of:
backend
frontend
operator`)
operator
grafanaApp`)
os.Exit(1)
}

Expand Down Expand Up @@ -482,6 +483,17 @@ func projectAddComponent(cmd *cobra.Command, args []string) error {
fmt.Printf("%s\n", err.Error())
os.Exit(1)
}
case "grafanaApp":
switch format {
case FormatCUE:
err = addComponentGrafanaApp(path, generator.(*codegen.Generator[codegen.Kind]), selectors, kindGrouping == kindGroupingKind)
default:
return fmt.Errorf("unknown kind format '%s'", format)
}
if err != nil {
fmt.Printf("%s\n", err.Error())
os.Exit(1)
}
default:
return fmt.Errorf("unknown component %s", component)
}
Expand Down Expand Up @@ -545,6 +557,47 @@ func addComponentOperator[G anyGenerator](projectRootPath string, generator G, s
return nil
}

//
// Grafana App
//

func addComponentGrafanaApp[G anyGenerator](projectRootPath string, generator G, selectors []string, groupKinds bool) error {
// Get the repo from the go.mod file
repo, err := getGoModule(filepath.Join(projectRootPath, "go.mod"))
if err != nil {
return err
}
var files codejen.Files
switch cast := any(generator).(type) {
case *codegen.Generator[codegen.Kind]:
appFiles, err := cast.Generate(cuekind.GrafanaAppGenerator(repo, ".", "pkg/apis", groupKinds), selectors...)
if err != nil {
return err
}
files = append(files, appFiles...)
default:
return fmt.Errorf("unknown generator type: %T", cast)
}
if err = checkAndMakePath("pkg"); err != nil {
return err
}
for _, f := range files {
err = writeFile(filepath.Join(projectRootPath, f.RelativePath), f.Data)
if err != nil {
return err
}
}
dockerfile, err := templates.ReadFile("templates/operator_Dockerfile.tmpl")
if err != nil {
return err
}
err = writeFile(filepath.Join(projectRootPath, "cmd", "operator", "Dockerfile"), dockerfile)
if err != nil {
return err
}
return nil
}

//
// Backend plugin
//
Expand Down
17 changes: 17 additions & 0 deletions codegen/cuekind/generators.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,23 @@ func AppGenerator(projectRepo, codegenPath string, groupKinds bool) *codejen.Jen
return g
}

func GrafanaAppGenerator(projectRepo, codegenPath, apisPath string, groupKinds bool) *codejen.JennyList[codegen.Kind] {
parts := strings.Split(projectRepo, "/")
if len(parts) == 0 {
parts = []string{""}
}
g := codejen.JennyListWithNamer[codegen.Kind](namerFunc)
g.Append(
jennies.WatcherJenny(projectRepo, codegenPath, !groupKinds),
&jennies.GrafanaAppGenerator{
ProjectRepo: projectRepo,
ProjectName: parts[len(parts)-1],
APIsPath: apisPath,
},
)
return g
}

func PostResourceGenerationGenerator(projectRepo, goGenPath string, groupKinds bool) *codejen.JennyList[codegen.Kind] {
g := codejen.JennyListWithNamer[codegen.Kind](namerFunc)
g.Append(&jennies.OpenAPI{
Expand Down
2 changes: 1 addition & 1 deletion codegen/jennies/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (a *AppGenerator) Generate(kinds ...codegen.Kind) (*codejen.File, error) {
}

b := bytes.Buffer{}
err := templates.WriteAppGoFile(tmd, &b)
err := templates.WriteAppGoFile(tmd, &b, templates.TemplateApp)
if err != nil {
return nil, err
}
Expand Down
101 changes: 101 additions & 0 deletions codegen/jennies/grafana-app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package jennies

import (
"bytes"
"go/format"
"path"

"github.com/grafana/codejen"

"github.com/grafana/grafana-app-sdk/codegen"
"github.com/grafana/grafana-app-sdk/codegen/templates"
)

type GrafanaAppGenerator struct {
ProjectRepo string
ProjectName string
APIsPath string
}

func (*GrafanaAppGenerator) JennyName() string {
return "GrafanaApp"
}

func (a *GrafanaAppGenerator) Generate(kinds ...codegen.Kind) (*codejen.File, error) {
tmd := templates.AppMetadata{
Repo: a.ProjectRepo,
ProjectName: a.ProjectName,
APIsPath: a.APIsPath,
PackageName: "app",
WatcherPackage: "watchers",
Resources: make([]templates.AppMetadataKind, 0),
}

for _, kind := range kinds {
vers := make([]string, len(kind.Versions()))
for i, ver := range kind.Versions() {
vers[i] = ver.Version
}
tmd.Resources = append(tmd.Resources, templates.AppMetadataKind{
KindProperties: kind.Properties(),
Versions: vers,
})
}

b := bytes.Buffer{}
err := templates.WriteAppGoFile(tmd, &b, templates.TemplateGrafanaApp)
if err != nil {
return nil, err
}
formatted, err := format.Source(b.Bytes())
if err != nil {
return nil, err
}
// TODO: do inside codegen_path/package_name (apps/playlist)
return codejen.NewFile(path.Join(tmd.CodegenPath, "pkg/app/app.go"), formatted, a), nil
}

type GrafanaAppReconcilerGenerator struct {
ProjectRepo string
ProjectName string
APIsPath string
}

func (*GrafanaAppReconcilerGenerator) JennyName() string {
return "GrafanaApp"
}

func (a *GrafanaAppReconcilerGenerator) Generate(kinds ...codegen.Kind) (*codejen.File, error) {
tmd := templates.AppMetadata{
Repo: a.ProjectRepo,
ProjectName: a.ProjectName,
APIsPath: a.APIsPath,
PackageName: "app",
ReconcilerPackage: "reconcilers",
Resources: make([]templates.AppMetadataKind, 0),
}

for _, kind := range kinds {
vers := make([]string, len(kind.Versions()))
for i, ver := range kind.Versions() {
vers[i] = ver.Version
}
tmd.Resources = append(tmd.Resources, templates.AppMetadataKind{
KindProperties: kind.Properties(),
Versions: vers,
})
}

b := bytes.Buffer{}
err := templates.WriteAppGoFile(tmd, &b, templates.TemplateGrafanaAppReconciler)
if err != nil {
return nil, err
}
formatted, err := format.Source(b.Bytes())
if err != nil {
return nil, err
}
// TODO: do inside codegen_path/package_name (apps/playlist)
// pkg/reconcilers/reconciler_<kind>.go
return codejen.NewFile(path.Join(tmd.CodegenPath, "pkg/reconcilers/reconciler.go"), formatted, a), nil
}
1 change: 1 addition & 0 deletions codegen/jennies/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ func buildManifestData(m codegen.AppManifest) (*app.ManifestData, error) {
mver := app.ManifestKindVersion{
Name: version.Version,
}

if len(version.Mutation.Operations) > 0 {
operations, err := sanitizeAdmissionOperations(version.Mutation.Operations)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion codegen/jennies/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (w *watcherJenny) Generate(kind codegen.Kind) (*codejen.File, error) {
Version: ver,
KindPackage: GetGeneratedPath(w.groupByKind, kind, ver),
KindsAreGrouped: !w.groupByKind,
}, &b)
}, &b, templates.TemplateWatcher)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion codegen/templates/app/app.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ func New(cfg app.Config) (app.App, error) {
// Validate the capabilities against the provided manifest to make sure there isn't a mismatch
err = a.ValidateManifest(cfg.ManifestData)
return a, err
}
}
89 changes: 89 additions & 0 deletions codegen/templates/app/grafana-app-reconciler.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package {{.PackageName}}

import (
"context"
"fmt"

"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/resource"
"go.opentelemetry.io/otel"

{{ if ne .Version "" }}{{.MachineName}} "{{.Repo}}/{{.CodegenPath}}{{ if not .KindsAreGrouped }}/resource{{end}}/{{.KindPackage}}"
{{ else }}"{{.Repo}}/{{.CodegenPath}}/resource/{{.MachineName}}"{{ end }}
)

var _ operator.ResourceWatcher = &{{.Kind}}Watcher{}

type {{.Kind}}Watcher struct {}

func New{{.Kind}}Watcher() (*{{.Kind}}Watcher, error) {
return &{{.Kind}}Watcher{}, nil
}

// Add handles add events for {{.MachineName}}.{{.Kind}} resources.
func (s *{{.Kind}}Watcher) Add(ctx context.Context, rObj resource.Object) error {
ctx, span := otel.GetTracerProvider().Tracer("watcher").Start(ctx, "watcher-add")
defer span.End()
object, ok := rObj.(*{{.MachineName}}.{{.Kind}})
if !ok {
return fmt.Errorf("provided object is not of type *{{.MachineName}}.{{.Kind}} (name=%s, namespace=%s, kind=%s)",
rObj.GetStaticMetadata().Name, rObj.GetStaticMetadata().Namespace, rObj.GetStaticMetadata().Kind)
}

// TODO
logging.FromContext(ctx).Debug("Added resource", "name", object.GetStaticMetadata().Identifier().Name)
return nil
}

// Update handles update events for {{.MachineName}}.{{.Kind}} resources.
func (s *{{.Kind}}Watcher) Update(ctx context.Context, rOld resource.Object, rNew resource.Object) error {
ctx, span := otel.GetTracerProvider().Tracer("watcher").Start(ctx, "watcher-update")
defer span.End()
oldObject, ok := rOld.(*{{.MachineName}}.{{.Kind}})
if !ok {
return fmt.Errorf("provided object is not of type *{{.MachineName}}.{{.Kind}} (name=%s, namespace=%s, kind=%s)",
rOld.GetStaticMetadata().Name, rOld.GetStaticMetadata().Namespace, rOld.GetStaticMetadata().Kind)
}

_, ok = rNew.(*{{.MachineName}}.{{.Kind}})
if !ok {
return fmt.Errorf("provided object is not of type *{{.MachineName}}.{{.Kind}} (name=%s, namespace=%s, kind=%s)",
rNew.GetStaticMetadata().Name, rNew.GetStaticMetadata().Namespace, rNew.GetStaticMetadata().Kind)
}

// TODO
logging.FromContext(ctx).Debug("Updated resource", "name", oldObject.GetStaticMetadata().Identifier().Name)
return nil
}

// Delete handles delete events for {{.MachineName}}.{{.Kind}} resources.
func (s *{{.Kind}}Watcher) Delete(ctx context.Context, rObj resource.Object) error {
ctx, span := otel.GetTracerProvider().Tracer("watcher").Start(ctx, "watcher-delete")
defer span.End()
object, ok := rObj.(*{{.MachineName}}.{{.Kind}})
if !ok {
return fmt.Errorf("provided object is not of type *{{.MachineName}}.{{.Kind}} (name=%s, namespace=%s, kind=%s)",
rObj.GetStaticMetadata().Name, rObj.GetStaticMetadata().Namespace, rObj.GetStaticMetadata().Kind)
}

// TODO
logging.FromContext(ctx).Debug("Deleted resource", "name", object.GetStaticMetadata().Identifier().Name)
return nil
}

// Sync is not a standard resource.Watcher function, but is used when wrapping this watcher in an operator.OpinionatedWatcher.
// It handles resources which MAY have been updated during an outage period where the watcher was not able to consume events.
func (s *{{.Kind}}Watcher) Sync(ctx context.Context, rObj resource.Object) error {
ctx, span := otel.GetTracerProvider().Tracer("watcher").Start(ctx, "watcher-sync")
defer span.End()
object, ok := rObj.(*{{.MachineName}}.{{.Kind}})
if !ok {
return fmt.Errorf("provided object is not of type *{{.MachineName}}.{{.Kind}} (name=%s, namespace=%s, kind=%s)",
rObj.GetStaticMetadata().Name, rObj.GetStaticMetadata().Namespace, rObj.GetStaticMetadata().Kind)
}

// TODO
logging.FromContext(ctx).Debug("Possible resource update", "name", object.GetStaticMetadata().Identifier().Name)
return nil
}
Loading
Loading